diff --git a/.backportrc.json b/.backportrc.json index b0595644da22..931ea6dbfe3a 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -4,6 +4,7 @@ "targetBranchChoices": [ "main", "8.x", + "8.17", "8.16", "8.15", "8.14", @@ -55,7 +56,7 @@ ], "branchLabelMapping": { "^v9.0.0$": "main", - "^v8.17.0$": "8.x", + "^v8.18.0$": "8.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 89550c59e6bb..955354e2acb5 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -100,7 +100,9 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/sources/indices/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/configs/serverless.config.ts diff --git a/.buildkite/ftr_security_stateful_configs.yml b/.buildkite/ftr_security_stateful_configs.yml index d3aadb1b7491..f7caacba05e1 100644 --- a/.buildkite/ftr_security_stateful_configs.yml +++ b/.buildkite/ftr_security_stateful_configs.yml @@ -80,13 +80,9 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/ess.config.ts - - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/ess.config.ts - - x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts - - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/ess.config.ts - - x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/sources/indices/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/configs/ess.config.ts diff --git a/.buildkite/package-lock.json b/.buildkite/package-lock.json index 92231d27ea50..ffec5b75fca6 100644 --- a/.buildkite/package-lock.json +++ b/.buildkite/package-lock.json @@ -22,7 +22,7 @@ "@types/mocha": "^10.0.1", "@types/node": "^15.12.2", "chai": "^4.3.10", - "mocha": "^10.3.0", + "mocha": "^10.8.2", "nock": "^12.0.2", "ts-node": "^10.9.2", "typescript": "^5.1.6" @@ -280,6 +280,15 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -536,12 +545,12 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -590,9 +599,9 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -1042,9 +1051,9 @@ } }, "node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1053,31 +1062,31 @@ } }, "node_modules/mocha": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", - "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -1087,33 +1096,6 @@ "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -1130,9 +1112,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node_modules/nock": { @@ -1567,9 +1549,9 @@ } }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "node_modules/wrap-ansi": { @@ -1622,9 +1604,9 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "engines": { "node": ">=10" @@ -1893,6 +1875,12 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2090,12 +2078,12 @@ "dev": true }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "decamelize": { @@ -2124,9 +2112,9 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true }, "dir-glob": { @@ -2438,62 +2426,41 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "requires": { "brace-expansion": "^2.0.1" } }, "mocha": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", - "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", - "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", "serialize-javascript": "^6.0.2", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "dependencies": { "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -2506,9 +2473,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "nock": { @@ -2784,9 +2751,9 @@ } }, "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "wrap-ansi": { @@ -2827,9 +2794,9 @@ } }, "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, "yargs-unparser": { diff --git a/.buildkite/package.json b/.buildkite/package.json index 158a55c777e6..5d5293971833 100644 --- a/.buildkite/package.json +++ b/.buildkite/package.json @@ -24,7 +24,7 @@ "@types/mocha": "^10.0.1", "@types/node": "^15.12.2", "chai": "^4.3.10", - "mocha": "^10.3.0", + "mocha": "^10.8.2", "nock": "^12.0.2", "ts-node": "^10.9.2", "typescript": "^5.1.6" diff --git a/.buildkite/pipeline-resource-definitions/kibana-console-definitions-sync.yml b/.buildkite/pipeline-resource-definitions/kibana-console-definitions-sync.yml new file mode 100644 index 000000000000..a228823202c0 --- /dev/null +++ b/.buildkite/pipeline-resource-definitions/kibana-console-definitions-sync.yml @@ -0,0 +1,54 @@ +# yaml-language-server: $schema=https://gist.githubusercontent.com/elasticmachine/988b80dae436cafea07d9a4a460a011d/raw/rre.schema.json +apiVersion: backstage.io/v1alpha1 +kind: Resource +metadata: + name: bk-kibana-console-definitions-sync + description: Opens a PR if anything changes in the console definitions in elasticsearch-definitions + links: + - url: 'https://buildkite.com/elastic/kibana-console-definitions-sync' + title: Pipeline link +spec: + type: buildkite-pipeline + owner: 'group:kibana-management' + system: buildkite + implementation: + apiVersion: buildkite.elastic.dev/v1 + kind: Pipeline + metadata: + name: kibana / Console definitions sync + description: Opens a PR if anything changes in the console definitions in elasticsearch-definitions + spec: + env: + SLACK_NOTIFICATIONS_CHANNEL: '#kibana-management' + ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' + allow_rebuilds: false + branch_configuration: main + default_branch: main + repository: elastic/kibana + pipeline_file: .buildkite/pipelines/console_definitions_sync.yml + provider_settings: + build_branches: false + build_pull_requests: false + publish_commit_status: false + trigger_mode: none + build_tags: false + prefix_pull_request_fork_branch_names: false + skip_pull_request_builds_for_existing_commits: true + teams: + kibana-management: + access_level: MANAGE_BUILD_AND_READ + kibana-operations: + access_level: MANAGE_BUILD_AND_READ + appex-qa: + access_level: MANAGE_BUILD_AND_READ + kibana-tech-leads: + access_level: MANAGE_BUILD_AND_READ + everyone: + access_level: BUILD_AND_READ + schedules: + Weekly build: + cronline: 0 0 * * 1 America/New_York + message: Weekly build + branch: main + tags: + - kibana diff --git a/.buildkite/pipeline-resource-definitions/kibana-es-snapshots.yml b/.buildkite/pipeline-resource-definitions/kibana-es-snapshots.yml index 652ce7b35c30..beccde5ab87f 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-es-snapshots.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-es-snapshots.yml @@ -22,7 +22,7 @@ spec: SLACK_NOTIFICATIONS_CHANNEL: '#kibana-operations-alerts' ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' allow_rebuilds: true - branch_configuration: main 8.x 8.16 8.15 7.17 + branch_configuration: main 8.x 8.17 8.16 8.15 7.17 default_branch: main repository: elastic/kibana pipeline_file: .buildkite/pipelines/es_snapshots/build.yml @@ -52,6 +52,10 @@ spec: cronline: 0 22 * * * America/New_York message: Daily build branch: '8.x' + Daily build (8.17): + cronline: 0 22 * * * America/New_York + message: Daily build + branch: '8.17' Daily build (8.16): cronline: 0 22 * * * America/New_York message: Daily build @@ -91,7 +95,7 @@ spec: SLACK_NOTIFICATIONS_CHANNEL: '#kibana-operations-alerts' ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' allow_rebuilds: true - branch_configuration: main 8.x 8.16 8.15 7.17 + branch_configuration: main 8.x 8.17 8.16 8.15 7.17 default_branch: main repository: elastic/kibana pipeline_file: .buildkite/pipelines/es_snapshots/promote.yml @@ -140,7 +144,7 @@ spec: ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' REPORT_FAILED_TESTS_TO_GITHUB: 'true' allow_rebuilds: true - branch_configuration: main 8.x 8.16 8.15 7.17 + branch_configuration: main 8.x 8.17 8.16 8.15 7.17 default_branch: main repository: elastic/kibana pipeline_file: .buildkite/pipelines/es_snapshots/verify.yml diff --git a/.buildkite/pipeline-resource-definitions/kibana-on-merge-unsupported-ftrs.yml b/.buildkite/pipeline-resource-definitions/kibana-on-merge-unsupported-ftrs.yml index b2ec63310cc6..8219f37d349f 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-on-merge-unsupported-ftrs.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-on-merge-unsupported-ftrs.yml @@ -22,7 +22,7 @@ spec: SLACK_NOTIFICATIONS_CHANNEL: '#kibana-unsupported-ftrs-alerts' ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' allow_rebuilds: true - branch_configuration: main 8.x 8.16 8.15 7.17 + branch_configuration: main 8.x 8.17 8.16 8.15 7.17 default_branch: main repository: elastic/kibana pipeline_file: .buildkite/pipelines/on_merge_unsupported_ftrs.yml diff --git a/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml b/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml index 5b71b58b8a00..aa56f4561fb6 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml @@ -25,7 +25,7 @@ spec: REPORT_FAILED_TESTS_TO_GITHUB: 'true' ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' allow_rebuilds: true - branch_configuration: main 8.x 8.16 8.15 7.17 + branch_configuration: main 8.x 8.17 8.16 8.15 7.17 default_branch: main repository: elastic/kibana pipeline_file: .buildkite/pipelines/on_merge.yml diff --git a/.buildkite/pipeline-resource-definitions/locations.yml b/.buildkite/pipeline-resource-definitions/locations.yml index c88e37490eb4..ca454f64c269 100644 --- a/.buildkite/pipeline-resource-definitions/locations.yml +++ b/.buildkite/pipeline-resource-definitions/locations.yml @@ -15,6 +15,7 @@ spec: - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-artifacts-trigger.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-chrome-forward-testing.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-codeql.yml + - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-console-definitions-sync.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-coverage-daily.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-deploy-project.yml - https://github.com/elastic/kibana/blob/main/.buildkite/pipeline-resource-definitions/kibana-es-forward-testing.yml diff --git a/.buildkite/pipelines/console_definitions_sync.yml b/.buildkite/pipelines/console_definitions_sync.yml new file mode 100644 index 000000000000..22d91eacbdbd --- /dev/null +++ b/.buildkite/pipelines/console_definitions_sync.yml @@ -0,0 +1,10 @@ +steps: + - command: .buildkite/scripts/steps/console_definitions_sync.sh + label: Console Definitions Sync + timeout_in_minutes: 10 + agents: + image: family/kibana-ubuntu-2004 + imageProject: elastic-images-prod + provider: gcp + machineType: n2-standard-2 + preemptible: true diff --git a/.buildkite/scripts/steps/console_definitions_sync.sh b/.buildkite/scripts/steps/console_definitions_sync.sh new file mode 100755 index 000000000000..7dc565e0b964 --- /dev/null +++ b/.buildkite/scripts/steps/console_definitions_sync.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +GIT_SCOPE="src/plugins/console/server/lib/spec_definitions" + +report_main_step () { + echo "--- $1" +} + +main () { + cd "$PARENT_DIR" + + report_main_step "Cloning repositories" + + rm -rf elasticsearch-specification + if ! git clone https://github.com/elastic/elasticsearch-specification --depth 1; then + echo "Error: Failed to clone the elasticsearch-specification repository." + exit 1 + fi + + report_main_step "Bootstrapping Kibana" + cd "$KIBANA_DIR" + .buildkite/scripts/bootstrap.sh + + report_main_step "Generating console definitions" + node scripts/generate_console_definitions.js --source "$PARENT_DIR/elasticsearch-specification" --emptyDest + + # Check if there are any differences + set +e + git diff --exit-code --quiet "$GIT_SCOPE" + if [ $? -eq 0 ]; then + echo "No differences found. Exiting.." + exit + fi + set -e + + report_main_step "Differences found. Checking for an existing pull request." + + KIBANA_MACHINE_USERNAME="kibanamachine" + git config --global user.name "$KIBANA_MACHINE_USERNAME" + git config --global user.email '42973632+kibanamachine@users.noreply.github.com' + + PR_TITLE='[Console] Update console definitions' + PR_BODY='This PR updates the console definitions to match the latest ones from the @elastic/elasticsearch-specification repo.' + + # Check if a PR already exists + pr_search_result=$(gh pr list --search "$PR_TITLE" --state open --author "$KIBANA_MACHINE_USERNAME" --limit 1 --json title -q ".[].title") + + if [ "$pr_search_result" == "$PR_TITLE" ]; then + echo "PR already exists. Exiting.." + exit + fi + + echo "No existing PR found. Proceeding.." + + # Commit diff + BRANCH_NAME="console_definitions_sync_$(date +%s)" + + git checkout -b "$BRANCH_NAME" + + git add $GIT_SCOPE + git commit -m "Update console definitions" + + report_main_step "Changes committed. Creating pull request." + + git push origin "$BRANCH_NAME" + + # Create PR + gh pr create --title "$PR_TITLE" --body "$PR_BODY" --base main --head "${BRANCH_NAME}" --label 'release_note:skip' --label 'Feature:Console' --label 'Team:Kibana Management' +} + +main diff --git a/.eslintrc.js b/.eslintrc.js index e2d02c33288a..7ff37b0c9fd9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -952,6 +952,7 @@ module.exports = { { files: [ 'x-pack/plugins/observability_solution/**/*.{ts,tsx}', + 'x-pack/plugins/{streams,streams_app}/**/*.{ts,tsx}', 'x-pack/packages/observability/**/*.{ts,tsx}', ], rules: { @@ -959,7 +960,7 @@ module.exports = { 'error', { additionalHooks: - '^(useAbortableAsync|useMemoWithAbortSignal|useFetcher|useProgressiveFetcher|useBreadcrumb|useAsync|useTimeRangeAsync|useAutoAbortedHttpClient)$', + '^(useAbortableAsync|useMemoWithAbortSignal|useFetcher|useProgressiveFetcher|useBreadcrumb|useAsync|useTimeRangeAsync|useAutoAbortedHttpClient|use.*Fetch)$', }, ], }, @@ -968,6 +969,7 @@ module.exports = { files: [ 'x-pack/plugins/aiops/**/*.tsx', 'x-pack/plugins/observability_solution/**/*.tsx', + 'x-pack/plugins/{streams,streams_app}/**/*.{ts,tsx}', 'src/plugins/ai_assistant_management/**/*.tsx', 'x-pack/packages/observability/**/*.{ts,tsx}', ], @@ -984,6 +986,7 @@ module.exports = { { files: [ 'x-pack/plugins/observability_solution/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)', + 'x-pack/plugins/{streams,streams_app}/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)', 'src/plugins/ai_assistant_management/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)', 'x-pack/packages/observability/logs_overview/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)', ], @@ -2015,6 +2018,15 @@ module.exports = { '@kbn/imports/no_group_crossing_imports': 'warn', }, }, + { + files: ['packages/kbn-dependency-usage/**/*.{ts,tsx}'], + rules: { + // disabling it since package is a CLI tool + 'no-console': 'off', + // disabling it since package is marked as module and it requires extension for files written + '@kbn/imports/uniform_imports': 'off', + }, + }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6a0a36feb9e2..0b1ff9850ad5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -143,6 +143,7 @@ packages/core/http/core-http-router-server-mocks @elastic/kibana-core packages/core/http/core-http-server @elastic/kibana-core packages/core/http/core-http-server-internal @elastic/kibana-core packages/core/http/core-http-server-mocks @elastic/kibana-core +packages/core/http/core-http-server-utils @elastic/kibana-core packages/core/i18n/core-i18n-browser @elastic/kibana-core packages/core/i18n/core-i18n-browser-internal @elastic/kibana-core packages/core/i18n/core-i18n-browser-mocks @elastic/kibana-core @@ -328,6 +329,7 @@ packages/kbn-data-service @elastic/kibana-visualizations @elastic/kibana-data-di packages/kbn-data-stream-adapter @elastic/security-threat-hunting packages/kbn-data-view-utils @elastic/kibana-data-discovery packages/kbn-datemath @elastic/kibana-data-discovery +packages/kbn-dependency-usage @elastic/kibana-security packages/kbn-dev-cli-errors @elastic/kibana-operations packages/kbn-dev-cli-runner @elastic/kibana-operations packages/kbn-dev-proc-runner @elastic/kibana-operations @@ -919,6 +921,7 @@ x-pack/plugins/observability_solution/apm_data_access @elastic/obs-knowledge-tea x-pack/plugins/observability_solution/apm/ftr_e2e @elastic/obs-ux-infra_services-team x-pack/plugins/observability_solution/dataset_quality @elastic/obs-ux-logs-team x-pack/plugins/observability_solution/entities_data_access @elastic/obs-entities +x-pack/plugins/observability_solution/entity_manager_app @elastic/obs-entities x-pack/plugins/observability_solution/exploratory_view @elastic/obs-ux-management-team x-pack/plugins/observability_solution/infra @elastic/obs-ux-logs-team @elastic/obs-ux-infra_services-team x-pack/plugins/observability_solution/inventory @elastic/obs-ux-infra_services-team @@ -960,6 +963,7 @@ x-pack/plugins/search_indices @elastic/search-kibana x-pack/plugins/search_inference_endpoints @elastic/search-kibana x-pack/plugins/search_notebooks @elastic/search-kibana x-pack/plugins/search_playground @elastic/search-kibana +x-pack/plugins/search_solution/search_navigation @elastic/search-kibana x-pack/plugins/searchprofiler @elastic/kibana-management x-pack/plugins/security @elastic/kibana-security x-pack/plugins/security_solution @elastic/security-solution @@ -974,6 +978,7 @@ x-pack/plugins/spaces @elastic/kibana-security x-pack/plugins/stack_alerts @elastic/response-ops x-pack/plugins/stack_connectors @elastic/response-ops x-pack/plugins/streams @simianhacker @flash1293 @dgieselaar +x-pack/plugins/streams_app @simianhacker @flash1293 @dgieselaar x-pack/plugins/task_manager @elastic/response-ops x-pack/plugins/telemetry_collection_xpack @elastic/kibana-core x-pack/plugins/threat_intelligence @elastic/security-threat-hunting-investigations @@ -1044,6 +1049,23 @@ x-pack/test_serverless/api_integration/test_suites/common/platform_security @ela # Data Discovery +/x-pack/test/functional/fixtures/kbn_archiver/kibana_scripted_fields_on_logstash.json @elastic/kibana-data-discovery # Assigned per only use: https://github.com/elastic/kibana/blob/main/x-pack/test/functional/apps/discover/async_scripted_fields.ts#L35 +/x-pack/test/functional/es_archives/getting_started/shakespeare @elastic/kibana-data-discovery +/x-pack/test/functional/fixtures/kbn_archiver/discover @elastic/kibana-data-discovery +/test/functional/fixtures/kbn_archiver/unmapped_fields.json @elastic/kibana-data-discovery # Assigned per only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/group7/_indexpattern_with_unmapped_fields.ts#L28 +/test/functional/fixtures/kbn_archiver/testlargestring.json @elastic/kibana-data-discovery # Assigned per only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/group5/_large_string.ts#L28 +/test/functional/fixtures/kbn_archiver/message_with_newline.json @elastic/kibana-data-discovery # Assigned per only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/classic/_doc_table_newline.ts#L26 +/test/functional/fixtures/kbn_archiver/invalid_scripted_field.json @elastic/kibana-data-discovery +/test/functional/fixtures/kbn_archiver/index_pattern_without_timefield.json @elastic/kibana-data-discovery +/test/functional/fixtures/kbn_archiver/discover @elastic/kibana-data-discovery +/test/functional/fixtures/kbn_archiver/discover.json @elastic/kibana-data-discovery +/test/functional/fixtures/kbn_archiver/date_nested.json @elastic/kibana-data-discovery +/test/functional/fixtures/kbn_archiver/date_* @elastic/kibana-data-discovery +/test/functional/fixtures/es_archiver/unmapped_fields @elastic/kibana-data-discovery # Assigned per the only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/group7/_indexpattern_with_unmapped_fields.ts#L26 +/test/functional/fixtures/es_archiver/message_with_newline @elastic/kibana-data-discovery # Assigned per the only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/classic/_doc_table_newline.ts#L24 +/test/functional/fixtures/es_archiver/hamlet @elastic/kibana-data-discovery # Assigned per the only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/discover/group5/_large_string.ts#L30 +/test/api_integration/fixtures/kbn_archiver/index_patterns @elastic/kibana-data-discovery +/test/api_integration/fixtures/es_archiver/index_patterns @elastic/kibana-data-discovery /test/functional/fixtures/es_archiver/alias @elastic/kibana-data-discovery /test/functional/page_objects/context_page.ts @elastic/kibana-data-discovery /test/functional/services/data_views.ts @elastic/kibana-data-discovery @@ -1116,6 +1138,16 @@ src/plugins/discover/public/context_awareness/profile_providers/security @elasti /x-pack/test/screenshot_creation @elastic/platform-docs # Visualizations +/x-pack/test/functional/fixtures/kbn_archiver/rollup @elastic/kibana-visualizations # Assigned per the only uses are in lens and tsvb tests +/x-pack/test/functional/fixtures/kbn_archiver/hybrid_dataview.json @elastic/kibana-visualizations # Assigned per only use: https://github.com/elastic/kibana/blob/main/x-pack/test/functional/apps/visualize/hybrid_visualization.ts#L20 +/x-pack/test/functional/es_archives/pre_calculated_histogram @elastic/kibana-visualizations # Assigned per usages +/x-pack/test/functional/es_archives/hybrid/rollup @elastic/kibana-visualizations @elastic/search-kibana # Assigned per usage +/x-pack/test/functional/es_archives/hybrid/logstash @elastic/kibana-visualizations # Assigned per only use: https://github.com/elastic/kibana/blob/main/x-pack/test/functional/apps/visualize/hybrid_visualization.ts#L22 +/x-pack/test/functional/es_archives/graph @elastic/kibana-visualizations +/x-pack/test/functional/es_archives/visualize @elastic/kibana-visualizations +/test/functional/fixtures/kbn_archiver/visualize.json @elastic/kibana-visualizations +/test/functional/fixtures/kbn_archiver/managed_content.json @elastic/kibana-visualizations # Assigned per only use: https://github.com/elastic/kibana/blob/main/x-pack/test/functional/apps/managed_content/managed_content.ts#L38 +/test/api_integration/fixtures/kbn_archiver/event_annotations/event_annotations.json @elastic/kibana-visualizations /test/functional/page_objects/unified_search_page.ts @elastic/kibana-visualizations # Assigned per https://github.com/elastic/kibana/pull/200132#discussion_r1847188168 /test/functional/apps/getting_started/*.ts @elastic/kibana-visualizations # Assigned per https://github.com/elastic/kibana/pull/199767#discussion_r1840485031 /x-pack/test/upgrade/apps/graph @elastic/kibana-visualizations @@ -1183,7 +1215,7 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /x-pack/test/saved_object_tagging/ @elastic/appex-sharedux ### Kibana React (to be deprecated) -/src/plugins/kibana_react/public/@elastic/appex-sharedux @elastic/kibana-presentation +/src/plugins/kibana_react/public/ @elastic/appex-sharedux @elastic/kibana-presentation ### Home Plugin and Packages /src/plugins/home/public @elastic/appex-sharedux @@ -1210,6 +1242,7 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /x-pack/test/observability_ai_assistant_api_integration @elastic/obs-ai-assistant /x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant /x-pack/test_serverless/**/test_suites/observability/ai_assistant @elastic/obs-ai-assistant +/x-pack/test/functional/es_archives/observability/ai_assistant @elastic/obs-ai-assistant # Infra Obs ## This plugin mostly contains the codebase for the infra services, but also includes some code for the Logs UI app. @@ -1278,12 +1311,14 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /x-pack/test/common/utils/synthtrace @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team # Assigned per https://github.com/elastic/kibana/blob/main/packages/kbn-apm-synthtrace/kibana.jsonc#L5 # Infra Monitoring tests +/x-pack/test/common/services/infra_synthtrace_kibana_client.ts @elastic/obs-ux-infra_services-team +/x-pack/test/common/services/infra_log_views.ts @elastic/obs-ux-infra_services-team # Assigned per https://github.com/elastic/kibana/pull/188204 /x-pack/test/api_integration/apis/infra @elastic/obs-ux-infra_services-team /x-pack/test/functional/apps/infra @elastic/obs-ux-infra_services-team /x-pack/test/functional/apps/infra/logs @elastic/obs-ux-logs-team # Observability UX management team -/x-pack/test/api_integration/services/data_view_api.ts @elastic/obs-ux-management-team +/test/api_integration/apis/suggestions @elastic/obs-ux-management-team # Assigned per https://github.com/elastic/kibana/pull/200950#discussion_r1853705079 /x-pack/test/api_integration/services/slo.ts @elastic/obs-ux-management-team /x-pack/test/functional/services/slo @elastic/obs-ux-management-team /x-pack/test/functional/apps/slo @elastic/obs-ux-management-team @@ -1316,6 +1351,7 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /x-pack/test/accessibility/apps/group3/stack_monitoring.ts @elastic/stack-monitoring # Fleet +/x-pack/test/functional/es_archives/fleet @elastic/fleet /x-pack/test/api_integration/services/fleet_and_agents.ts @elastic/fleet /x-pack/test/fleet_api_integration @elastic/fleet /x-pack/test/fleet_packages @elastic/fleet @@ -1352,6 +1388,8 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /x-pack/test_serverless/api_integration/test_suites/observability/synthetics @elastic/obs-ux-management-team # obs-ux-logs-team +/x-pack/test/functional/es_archives/observability_logs_explorer @elastic/obs-ux-logs-team +/x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality @elastic/obs-ux-logs-team /x-pack/test_serverless/functional/test_suites/observability/config.* @elastic/obs-ux-logs-team /x-pack/test_serverless/functional/test_suites/observability/landing_page.ts @elastic/obs-ux-logs-team /x-pack/test_serverless/functional/test_suites/observability/navigation.ts @elastic/obs-ux-logs-team @@ -1376,6 +1414,7 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /x-pack/test/api_integration/apis/logs_shared @elastic/obs-ux-logs-team # Observability-ui +/x-pack/test_serverless/functional/test_suites/observability @elastic/observability-ui /x-pack/test_serverless/api_integration/test_suites/observability/index.ts @elastic/observability-ui /x-pack/test/functional_solution_sidenav/tests/observability_sidenav.ts @elastic/observability-ui /x-pack/test/functional/page_objects/observability_page.ts @elastic/observability-ui @@ -1391,6 +1430,13 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql ### END Observability Plugins # Presentation +/test/functional/fixtures/kbn_archiver/legacy.json @elastic/kibana-presentation # Assigned per https://github.com/elastic/kibana/pull/200934#discussion_r1856407606 +/x-pack/test/functional/fixtures/kbn_archiver/maps.json @elastic/kibana-presentation +/x-pack/test/functional/fixtures/kbn_archiver/canvas @elastic/kibana-presentation +/x-pack/test/functional/es_archives/dashboard/async_search @elastic/kibana-presentation +/test/functional/fixtures/kbn_archiver/dashboard @elastic/kibana-presentation +/test/functional/fixtures/kbn_archiver/canvas @elastic/kibana-presentation +/test/api_integration/apis/dashboards @elastic/kibana-presentation /test/interpreter_functional/snapshots @elastic/kibana-presentation # Assigned per https://github.com/elastic/kibana/pull/54342 /test/functional/services/inspector.ts @elastic/kibana-presentation /x-pack/test/functional/services/canvas_element.ts @elastic/kibana-presentation @@ -1503,6 +1549,28 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /.eslintignore @elastic/kibana-operations # QA - Appex QA +/x-pack/test/functional/fixtures/package_registry_config.yml @elastic/appex-qa # No usages found +/x-pack/test/functional/fixtures/kbn_archiver/packaging.json @elastic/appex-qa # No usages found +/x-pack/test/functional/es_archives/filebeat @elastic/appex-qa +/x-pack/test/functional/es_archives/logstash_functional @elastic/appex-qa +/x-pack/test/functional/es_archives/event_log_legacy_ids @elastic/appex-qa +/x-pack/test/functional/es_archives/dlstest @elastic/appex-qa # No usages found +/x-pack/test/functional/es_archives/beats/list/data.json @elastic/appex-qa # No usages found +/test/functional/fixtures/kbn_archiver/stress_test.json @elastic/appex-qa +/test/functional/fixtures/kbn_archiver/many_fields_data_view.json @elastic/appex-qa +/test/functional/fixtures/kbn_archiver/long_window_logstash_index_pattern.json @elastic/appex-qa +/test/functional/fixtures/kbn_archiver/kibana_sample_data_logs_tsdb.json @elastic/appex-qa +/test/functional/fixtures/kbn_archiver/kibana_sample_data_logs_logsdb.json @elastic/appex-qa +/test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern.json @elastic/appex-qa +/test/functional/fixtures/es_archiver/stress_test @elastic/appex-qa +/test/functional/fixtures/es_archiver/many_fields @elastic/appex-qa +/test/functional/fixtures/es_archiver/logstash_functional @elastic/appex-qa +/test/functional/fixtures/es_archiver/long_window_logstash @elastic/appex-qa +/test/functional/fixtures/es_archiver/kibana_sample_data_logs_* @elastic/appex-qa +/test/functional/fixtures/es_archiver/kibana_sample_data_flights* @elastic/appex-qa +/test/functional/fixtures/es_archiver/getting_started/shakespeare @elastic/appex-qa +/test/api_integration/fixtures/es_archiver/elasticsearch @elastic/appex-qa +/x-pack/test/plugin_functional/services.ts @elastic/appex-qa /test/server_integration/services/index.js @elastic/appex-qa /x-pack/test/stack_functional_integration/configs/config.stack_functional_integration_base.js @elastic/appex-qa /x-pack/test/stack_functional_integration/configs/consume_state.js @elastic/appex-qa @@ -1627,6 +1695,16 @@ packages/kbn-monaco/src/esql @elastic/kibana-esql /x-pack/test/api_integration/deployment_agnostic/services/ @elastic/appex-qa # Core +/test/api_integration/fixtures/kbn_archiver/management/saved_objects/relationships.json @elastic/kibana-core @elastic/kibana-data-discovery +/x-pack/test/functional/es_archives/lists @elastic/kibana-core +/test/functional/fixtures/kbn_archiver/saved_search.json @elastic/kibana-core # Assigned per only use: https://github.com/elastic/kibana/blob/main/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts#L100 +/test/functional/fixtures/kbn_archiver/saved_objects_management/show_relationships.json @elastic/kibana-core # Assigned per only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/saved_objects_management/show_relationships.ts#L20 +/test/functional/fixtures/kbn_archiver/saved_objects_management/hidden_from_http_apis.json @elastic/kibana-core +/test/functional/fixtures/kbn_archiver/saved_objects_management/edit_saved_object.json @elastic/kibana-core # Assigned per only use: https://github.com/elastic/kibana/blob/main/test/functional/apps/saved_objects_management/inspect_saved_objects.ts#L40 +/test/functional/fixtures/es_archiver/saved_objects_management @elastic/kibana-core +/test/api_integration/fixtures/es_archiver/saved_objects @elastic/kibana-core +/test/api_integration/fixtures/kbn_archiver/saved_objects @elastic/kibana-core +/test/interpreter_functional @elastic/kibana-core # Assigned per https://github.com/elastic/kibana/blob/main/test/interpreter_functional/plugins/kbn_tp_run_pipeline/kibana.jsonc#L4 /test/api_integration/apis/general/*.js @elastic/kibana-core # Assigned per https://github.com/elastic/kibana/pull/199795/files/894a8ede3f9d0398c5af56bf5a82654a9bc0610b#r1846691639 /x-pack/test/plugin_api_integration/plugins/feature_usage_test @elastic/kibana-core /x-pack/test/functional/page_objects/navigational_search.ts @elastic/kibana-core @@ -1744,6 +1822,15 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib # Kibana Platform Security # security +/x-pack/test/functional/es_archives/security @elastic/kibana-security +/x-pack/test/functional/fixtures/kbn_archiver/spaces @elastic/kibana-security +/x-pack/test/functional/fixtures/kbn_archiver/security @elastic/kibana-security +/x-pack/test/ftr_apis/common/lib @elastic/kibana-security +/x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/space_1.json @elastic/kibana-security # Assigned per only use: https://github.com/elastic/kibana/blob/main/x-pack/test/ftr_apis/security_and_spaces/apis/test_utils.ts#L33 +/x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/default_space.json @elastic/kibana-security # Assigned per only use: https://github.com/elastic/kibana/blob/main/x-pack/test/ftr_apis/security_and_spaces/apis/test_utils.ts#L33 +/x-pack/test/api_integration/apis/cloud @elastic/kibana-security # Assigned per https://github.com/elastic/kibana/pull/198444 +/test/plugin_functional/snapshots/baseline/hardening/prototype.json @elastic/kibana-security # Assigned per https://github.com/elastic/kibana/pull/190716 +/test/functional/page_objects/login_page.ts @elastic/kibana-security /x-pack/test_serverless/functional/test_suites/observability/role_management @elastic/kibana-security /x-pack/test/functional/config_security_basic.ts @elastic/kibana-security /x-pack/test/functional/page_objects/user_profile_page.ts @elastic/kibana-security @@ -1802,6 +1889,9 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib #CC# /x-pack/plugins/security/ @elastic/kibana-security # Response Ops team +/x-pack/test/functional/es_archives/rule_registry @elastic/response-ops +/x-pack/test/functional/es_archives/event_log_multiple_indicies @elastic/response-ops +/x-pack/test/functional/es_archives/task_manager* @elastic/response-ops # Assigned per https://github.com/elastic/kibana/blob/assign-response-ops/x-pack/test/plugin_api_perf/plugins/task_manager_performance/kibana.jsonc#L4 /x-pack/test/plugin_api_perf @elastic/response-ops # Assigned per https://github.com/elastic/kibana/blob/assign-response-ops/x-pack/test/plugin_api_perf/plugins/task_manager_performance/kibana.jsonc#L4 /x-pack/test/functional/page_objects/maintenance_windows_page.ts @elastic/response-ops /x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/index.ts @elastic/response-ops @@ -1867,6 +1957,12 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib # Enterprise Search # search +/x-pack/test/functional/es_archives/data/search_sessions @elastic/search-kibana +/x-pack/test/common/services/search_secure.ts @elastic/search-kibana +/test/functional/page_objects/unified_search_page.ts @elastic/search-kibana +/test/functional/fixtures/kbn_archiver/ccs @elastic/search-kibana +/test/functional/fixtures/kbn_archiver/annotation_listing_page_search.json @elastic/search-kibana +/test/functional/fixtures/es_archiver/search/downsampled @elastic/search-kibana /x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts @elastic/search-kibana /x-pack/test/functional/services/search_sessions.ts @elastic/search-kibana /x-pack/test/functional/page_objects/search_* @elastic/search-kibana @@ -1887,6 +1983,9 @@ x-pack/test/api_integration/apis/management/index_management/inference_endpoints /x-pack/test/functional_search/ @elastic/search-kibana # Management Experience - Deployment Management +/test/functional/fixtures/kbn_archiver/management.json @elastic/kibana-management @elastic/kibana-data-discovery # Assigned per 2 uses: test/functional/apps/management/_import_objects.ts && test/functional/apps/management/data_views/_scripted_fields_filter.ts +/x-pack/test/functional/fixtures/kbn_archiver/home/feature_controls/security/security.json @elastic/kibana-management +/x-pack/test/functional/es_archives/upgrade_assistant @elastic/kibana-management /x-pack/test/functional/services/ace_editor.js @elastic/kibana-management /x-pack/test/functional/page_objects/remote_clusters_page.ts @elastic/kibana-management /x-pack/test/stack_functional_integration/apps/ccs @elastic/kibana-management @@ -2061,6 +2160,7 @@ x-pack/test/security_solution_api_integration/test_suites/sources @elastic/secur /x-pack/plugins/security_solution/server/lib/siem_migrations @elastic/security-threat-hunting /x-pack/plugins/security_solution/common/siem_migrations @elastic/security-threat-hunting +/x-pack/plugins/security_solution/public/siem_migrations @elastic/security-threat-hunting ## Security Solution Threat Hunting areas - Threat Hunting Investigations @@ -2302,7 +2402,8 @@ x-pack/plugins/security_solution/common/api/entity_analytics @elastic/security-e x-pack/test/security_solution_api_integration/test_suites/genai @elastic/security-generative-ai # Security Defend Workflows - OSQuery Ownership -x-pack/plugins/osquery @elastic/security-defend-workflows +/x-pack/test/osquery_cypress @elastic/security-defend-workflows +/x-pack/plugins/osquery @elastic/security-defend-workflows /x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions @elastic/security-defend-workflows /x-pack/plugins/security_solution/public/detection_engine/rule_response_actions @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions @elastic/security-defend-workflows @@ -2312,7 +2413,10 @@ x-pack/plugins/osquery @elastic/security-defend-workflows /x-pack/plugins/security_solution/public/cloud_defend @elastic/kibana-cloud-security-posture # Cloud Security Posture -x-pack/packages/kbn-cloud-security-posture @elastic/kibana-cloud-security-posture +/x-pack/test/functional/es_archives/kubernetes_security @elastic/kibana-cloud-security-posture +/x-pack/test/functional/es_archives/session_view @elastic/kibana-cloud-security-posture +/x-pack/test/session_view @elastic/kibana-cloud-security-posture # Assigned per https://github.com/elastic/kibana/blob/main/api_docs/session_view.mdx#L18 +/x-pack/packages/kbn-cloud-security-posture @elastic/kibana-cloud-security-posture /x-pack/test/kubernetes_security @elastic/kibana-cloud-security-posture /x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.* @elastic/kibana-cloud-security-posture /x-pack/plugins/security_solution/public/cloud_security_posture @elastic/kibana-cloud-security-posture diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8ed73aa521af..000a80d2b84f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -15,7 +15,7 @@ Reviewers should verify this PR satisfies this list as well. - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed -- [ ] The PR description includes the appropriate Release Notes section, and the correct `release_node:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) +- [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks diff --git a/.gitignore b/.gitignore index 9bda927a92b6..34ba130ee298 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ target *.iml *.log types.eslint.config.js +types.eslint.config.cjs __tmp__ # Ignore example plugin builds @@ -159,3 +160,4 @@ x-pack/test/security_solution_playwright/playwright/.cache/ x-pack/test/security_solution_playwright/.auth/ x-pack/test/security_solution_playwright/.env .codeql +.dependency-graph-log.json diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 32647a7594ff..3f07bf7bdcb1 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 55ae54fac12e..88238206f229 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index 724f4ce18842..c90f02417935 100644 --- a/api_docs/ai_assistant_management_selection.mdx +++ b/api_docs/ai_assistant_management_selection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementSelection title: "aiAssistantManagementSelection" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementSelection plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementSelection'] --- import aiAssistantManagementSelectionObj from './ai_assistant_management_selection.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 54b6e466f2d6..bcd45bce6e67 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 06e50627b8c0..eecd6de841e7 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index a288da356746..d6b2c432c467 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -695,7 +695,7 @@ "section": "def-common.TopNFunctions", "text": "TopNFunctions" }, - "; hostNames: string[]; } | undefined, ", + "; hostNames: string[]; containerIds: string[]; } | undefined, ", "APMRouteCreateOptions", ">; \"GET /internal/apm/services/{serviceName}/profiling/hosts/flamegraph\": ", { @@ -829,7 +829,7 @@ "section": "def-common.BaseFlameGraph", "text": "BaseFlameGraph" }, - "; hostNames: string[]; } | undefined, ", + "; hostNames: string[]; containerIds: string[]; } | undefined, ", "APMRouteCreateOptions", ">; \"GET /internal/apm/services/{serviceName}/profiling/functions\": ", { diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 82f8a33b120e..9eef86e08c26 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.devdocs.json b/api_docs/apm_data_access.devdocs.json index be97ce96ca40..6d0f3b4532dc 100644 --- a/api_docs/apm_data_access.devdocs.json +++ b/api_docs/apm_data_access.devdocs.json @@ -441,78 +441,6 @@ ], "functions": [], "interfaces": [ - { - "parentPluginId": "apmDataAccess", - "id": "def-server.ApmDataAccessPrivilegesCheck", - "type": "Interface", - "tags": [], - "label": "ApmDataAccessPrivilegesCheck", - "description": [], - "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/check_privileges.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "apmDataAccess", - "id": "def-server.ApmDataAccessPrivilegesCheck.request", - "type": "Object", - "tags": [], - "label": "request", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.KibanaRequest", - "text": "KibanaRequest" - }, - "" - ], - "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/check_privileges.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "apmDataAccess", - "id": "def-server.ApmDataAccessPrivilegesCheck.security", - "type": "Object", - "tags": [], - "label": "security", - "description": [], - "signature": [ - { - "pluginId": "@kbn/security-plugin-types-server", - "scope": "server", - "docId": "kibKbnSecurityPluginTypesServerPluginApi", - "section": "def-server.SecurityPluginStart", - "text": "SecurityPluginStart" - }, - " | undefined" - ], - "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/check_privileges.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "apmDataAccess", - "id": "def-server.ApmDataAccessPrivilegesCheck.getApmIndices", - "type": "Function", - "tags": [], - "label": "getApmIndices", - "description": [], - "signature": [ - "() => Promise>" - ], - "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/check_privileges.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - } - ], - "initialIsOpen": false - }, { "parentPluginId": "apmDataAccess", "id": "def-server.ApmDataAccessServicesParams", @@ -2365,42 +2293,12 @@ "label": "getApmIndices", "description": [], "signature": [ - "(soClient: ", - { - "pluginId": "@kbn/core-saved-objects-api-server", - "scope": "server", - "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-server.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - }, - ") => Promise>" + "() => Promise>" ], "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "apmDataAccess", - "id": "def-server.ApmDataAccessPluginSetup.getApmIndices.$1", - "type": "Object", - "tags": [], - "label": "soClient", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-saved-objects-api-server", - "scope": "server", - "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-server.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - } - ], - "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], + "children": [], "returnComment": [] }, { @@ -2504,56 +2402,7 @@ "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "apmDataAccess", - "id": "def-server.ApmDataAccessPluginStart.hasPrivileges", - "type": "Function", - "tags": [], - "label": "hasPrivileges", - "description": [], - "signature": [ - "(params: Pick<", - { - "pluginId": "apmDataAccess", - "scope": "server", - "docId": "kibApmDataAccessPluginApi", - "section": "def-server.ApmDataAccessPrivilegesCheck", - "text": "ApmDataAccessPrivilegesCheck" - }, - ", \"request\">) => Promise" - ], - "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "apmDataAccess", - "id": "def-server.ApmDataAccessPluginStart.hasPrivileges.$1", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "Pick<", - { - "pluginId": "apmDataAccess", - "scope": "server", - "docId": "kibApmDataAccessPluginApi", - "section": "def-server.ApmDataAccessPrivilegesCheck", - "text": "ApmDataAccessPrivilegesCheck" - }, - ", \"request\">" - ], - "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - } - ], + "children": [], "lifecycle": "start", "initialIsOpen": true } diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index 9c55ce854584..b02711293b20 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 93 | 0 | 93 | 3 | +| 86 | 0 | 86 | 3 | ## Server diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 004cd65fd7ed..a1eca8cf25e6 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index f39a876132c2..ce14885554f3 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index f2ccd2185a8e..dbc5d9250be3 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 3b805ccad472..edf89128a592 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.devdocs.json b/api_docs/charts.devdocs.json index ae166771b391..4fdf943a868f 100644 --- a/api_docs/charts.devdocs.json +++ b/api_docs/charts.devdocs.json @@ -2219,22 +2219,6 @@ "deprecated": false, "trackAdoption": false, "children": [ - { - "parentPluginId": "charts", - "id": "def-public.ChartsPluginSetup.legacyColors", - "type": "Object", - "tags": [], - "label": "legacyColors", - "description": [], - "signature": [ - "{ readonly seedColors: string[]; readonly mappedColors: ", - "MappedColors", - "; createColorLookupFunction: (arrayOfStringsOrNumbers?: (string | number)[] | undefined, colorMapping?: Partial>) => (value: string | number) => any; }" - ], - "path": "src/plugins/charts/public/plugin.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "charts", "id": "def-public.ChartsPluginSetup.theme", diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 8148e88dd7a8..962acd458c4e 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 268 | 2 | 253 | 10 | +| 267 | 2 | 252 | 9 | ## Client diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index d7b93545bdfc..19cc7327b4ae 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 18d375f25a9e..fef5a02c211d 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 705c3dd7cd4d..0eb94ddf351b 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 915caecb9299..c6f808809f4d 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 47abb21ae411..063f052714ec 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index 694d6d2c8839..af321e0558b6 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 62f764811805..10201057da0d 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 53d90c254170..b9a9aa508b0b 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 47004e43c1f6..ac9b56f9ed8a 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 1f41e84ce804..60e078923532 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 032baaa85063..01dd0d4c5071 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_quality.mdx b/api_docs/data_quality.mdx index 5ee3c6116ca2..7b2c07657929 100644 --- a/api_docs/data_quality.mdx +++ b/api_docs/data_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataQuality title: "dataQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the dataQuality plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataQuality'] --- import dataQualityObj from './data_quality.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 35adf90f9975..113c8c9356fb 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index e03ef5b0fada..57cd0c01eab7 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_usage.devdocs.json b/api_docs/data_usage.devdocs.json index 934e8ba7e64a..fa0dd73eb8c2 100644 --- a/api_docs/data_usage.devdocs.json +++ b/api_docs/data_usage.devdocs.json @@ -88,7 +88,7 @@ "signature": [ "\"/api/data_usage/\"" ], - "path": "x-pack/plugins/data_usage/common/index.ts", + "path": "x-pack/plugins/data_usage/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -100,7 +100,7 @@ "tags": [], "label": "DATA_USAGE_DATA_STREAMS_API_ROUTE", "description": [], - "path": "x-pack/plugins/data_usage/common/index.ts", + "path": "x-pack/plugins/data_usage/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -112,34 +112,37 @@ "tags": [], "label": "DATA_USAGE_METRICS_API_ROUTE", "description": [], - "path": "x-pack/plugins/data_usage/common/index.ts", + "path": "x-pack/plugins/data_usage/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "dataUsage", - "id": "def-common.PLUGIN_ID", - "type": "string", + "id": "def-common.DEFAULT_SELECTED_OPTIONS", + "type": "number", "tags": [], - "label": "PLUGIN_ID", + "label": "DEFAULT_SELECTED_OPTIONS", "description": [], "signature": [ - "\"data_usage\"" + "50" ], - "path": "x-pack/plugins/data_usage/common/index.ts", + "path": "x-pack/plugins/data_usage/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "dataUsage", - "id": "def-common.PLUGIN_NAME", + "id": "def-common.PLUGIN_ID", "type": "string", "tags": [], - "label": "PLUGIN_NAME", + "label": "PLUGIN_ID", "description": [], - "path": "x-pack/plugins/data_usage/common/index.ts", + "signature": [ + "\"data_usage\"" + ], + "path": "x-pack/plugins/data_usage/common/constants.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/data_usage.mdx b/api_docs/data_usage.mdx index 51670016cf1e..5632d8c4e7d3 100644 --- a/api_docs/data_usage.mdx +++ b/api_docs/data_usage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataUsage title: "dataUsage" image: https://source.unsplash.com/400x175/?github description: API docs for the dataUsage plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataUsage'] --- import dataUsageObj from './data_usage.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 5380853b6d7e..e2cc2fc3ff03 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 5777bc672632..81ae08552215 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 400e35abbd89..c6f99faaab2b 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index 956b32de3213..2db8e5ec9476 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -14050,10 +14050,6 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/public/components/datasource/datasource_component.js" }, - { - "plugin": "logsShared", - "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts" - }, { "plugin": "fleet", "path": "x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/query_bar.tsx" @@ -14206,10 +14202,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts" - }, { "plugin": "timelines", "path": "x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts" @@ -26666,6 +26658,20 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "dataViews", + "id": "def-common.HasEsDataFailureReason", + "type": "Enum", + "tags": [], + "label": "HasEsDataFailureReason", + "description": [ + "\nValid `failureReason` attribute values for `has_es_data` API error responses" + ], + "path": "src/plugins/data_views/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "misc": [ diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index b1facd7339da..3840c930093d 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1224 | 0 | 443 | 4 | +| 1225 | 0 | 443 | 4 | ## Client diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index ea8d3782354a..2a3a70d6c5cb 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.devdocs.json b/api_docs/dataset_quality.devdocs.json index 0f42873f1c07..f6d641e42811 100644 --- a/api_docs/dataset_quality.devdocs.json +++ b/api_docs/dataset_quality.devdocs.json @@ -561,7 +561,7 @@ "section": "def-common.ServerRouteCreateOptions", "text": "ServerRouteCreateOptions" }, - "> ? TRouteParamsRT extends ", + " | undefined> ? TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -912,15 +912,7 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - " ? TReturnType extends ", + " ? TReturnType extends ", { "pluginId": "@kbn/core-http-server", "scope": "server", diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index b3c87427e71d..27735e46c00c 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 28b8e5a3b749..c3582dbe17f1 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -17,12 +17,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Referencing plugin(s) | Remove By | | ---------------|-----------|-----------| | | ml, stackAlerts | - | -| | data, @kbn/search-errors, savedObjectsManagement, unifiedSearch, @kbn/unified-field-list, controls, lens, @kbn/lens-embeddable-utils, triggersActionsUi, dataVisualizer, canvas, logsShared, fleet, ml, @kbn/ml-data-view-utils, enterpriseSearch, graph, visTypeTimeseries, exploratoryView, stackAlerts, securitySolution, timelines, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, eventAnnotationListing, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega | - | +| | data, @kbn/search-errors, savedObjectsManagement, unifiedSearch, @kbn/unified-field-list, controls, lens, @kbn/lens-embeddable-utils, triggersActionsUi, dataVisualizer, canvas, fleet, ml, @kbn/ml-data-view-utils, enterpriseSearch, graph, visTypeTimeseries, exploratoryView, stackAlerts, securitySolution, timelines, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, eventAnnotationListing, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega | - | | | ml, securitySolution | - | | | actions, savedObjectsTagging, ml, enterpriseSearch | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core, visualizations, aiops, dataVisualizer, ml, dashboardEnhanced, graph, lens, securitySolution, eventAnnotation, @kbn/core-saved-objects-browser-mocks | - | | | @kbn/core, embeddable, savedObjects, visualizations, canvas, graph, ml | - | -| | @kbn/core-saved-objects-base-server-internal, @kbn/core-saved-objects-migration-server-internal, @kbn/core-saved-objects-server-internal, @kbn/core-ui-settings-server-internal, @kbn/core-usage-data-server-internal, taskManager, dataViews, spaces, share, actions, data, alerting, dashboard, @kbn/core-saved-objects-migration-server-mocks, lens, cases, savedSearch, canvas, fleet, cloudSecurityPosture, ml, logsShared, graph, lists, maps, infra, visualizations, apmDataAccess, securitySolution, apm, slo, synthetics, uptime, eventAnnotation, links, savedObjectsManagement, @kbn/core-test-helpers-so-type-serializer, @kbn/core-saved-objects-api-server-internal | - | +| | @kbn/core-saved-objects-base-server-internal, @kbn/core-saved-objects-migration-server-internal, @kbn/core-saved-objects-server-internal, @kbn/core-ui-settings-server-internal, @kbn/core-usage-data-server-internal, taskManager, dataViews, spaces, share, actions, data, alerting, dashboard, @kbn/core-saved-objects-migration-server-mocks, lens, cases, savedSearch, canvas, fleet, cloudSecurityPosture, ml, graph, lists, maps, infra, visualizations, apmDataAccess, securitySolution, apm, slo, synthetics, uptime, eventAnnotation, links, savedObjectsManagement, @kbn/core-test-helpers-so-type-serializer, @kbn/core-saved-objects-api-server-internal | - | | | stackAlerts, alerting, securitySolution, inputControlVis | - | | | graph, stackAlerts, inputControlVis, securitySolution | - | | | dataVisualizer, stackAlerts, expressionPartitionVis | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 3bcbefc68608..be7588db27dd 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -574,7 +574,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts#:~:text=context), [embeddable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts#:~:text=context), [escount.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts#:~:text=context), [esdocs.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts#:~:text=context), [filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/common/functions/filters.ts#:~:text=context), [neq.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/common/neq.ts#:~:text=context), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts#:~:text=context) | - | | | [canvas_workpad_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts#:~:text=ResolvedSimpleSavedObject), [canvas_workpad_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts#:~:text=ResolvedSimpleSavedObject), [canvas_workpad_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts#:~:text=ResolvedSimpleSavedObject), [canvas_workpad_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts#:~:text=ResolvedSimpleSavedObject) | - | | | [workpad_route_context.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/workpad_route_context.ts#:~:text=migrationVersion) | - | -| | [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/custom_elements/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/custom_elements/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/custom_elements/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/custom_elements/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/find.ts#:~:text=SavedObjectAttributes) | - | +| | [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/custom_elements/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/custom_elements/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/find.ts#:~:text=SavedObjectAttributes), [has_workpads.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts#:~:text=SavedObjectAttributes), [has_workpads.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/custom_elements/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/custom_elements/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/find.ts#:~:text=SavedObjectAttributes), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/workpad/find.ts#:~:text=SavedObjectAttributes)+ 2 more | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/shareable_runtime/types.ts#:~:text=SavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/shareable_runtime/types.ts#:~:text=SavedObject), [canvas_workpad_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts#:~:text=SavedObject), [canvas_workpad_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/canvas_workpad_service.ts#:~:text=SavedObject), [use_upload_workpad.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts#:~:text=SavedObject), [use_upload_workpad.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/components/home/hooks/use_upload_workpad.ts#:~:text=SavedObject) | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/shareable_runtime/types.ts#:~:text=SavedObjectAttributes), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/shareable_runtime/types.ts#:~:text=SavedObjectAttributes) | - | | | [saved_lens.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts#:~:text=SavedObjectReference), [saved_lens.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_lens.ts#:~:text=SavedObjectReference), [saved_map.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts#:~:text=SavedObjectReference), [saved_map.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts#:~:text=SavedObjectReference), [saved_search.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.ts#:~:text=SavedObjectReference), [saved_search.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_search.ts#:~:text=SavedObjectReference), [saved_visualization.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts#:~:text=SavedObjectReference), [saved_visualization.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_visualization.ts#:~:text=SavedObjectReference), [embeddable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts#:~:text=SavedObjectReference), [embeddable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts#:~:text=SavedObjectReference) | - | @@ -1061,15 +1061,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] -## logsShared - -| Deprecated API | Reference location(s) | Remove By | -| ---------------|-----------|-----------| -| | [resolved_log_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts#:~:text=title) | - | -| | [log_view_saved_object.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts#:~:text=migrations) | - | - - - ## logstash | Deprecated API | Reference location(s) | Remove By | @@ -1337,7 +1328,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | -| | [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title), [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title), [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts#:~:text=title) | - | +| | [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title), [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title), [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title) | - | | | [fleet_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts#:~:text=policy_id), [fleet_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts#:~:text=policy_id), [endpoint_metadata_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts#:~:text=policy_id), [endpoint_package_policies.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/endpoint_package_policies.test.ts#:~:text=policy_id), [index.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts#:~:text=policy_id) | - | | | [fleet_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts#:~:text=policy_id), [fleet_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts#:~:text=policy_id), [endpoint_metadata_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts#:~:text=policy_id), [endpoint_package_policies.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/endpoint_package_policies.test.ts#:~:text=policy_id), [index.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts#:~:text=policy_id) | - | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 2f91715f99c6..58fe13364fbb 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index f52099ac7c33..82e50bc91427 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index c40aeb488b5d..339ab780f9c6 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index b7fbdcf6917a..e565b8349add 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/discover_shared.mdx b/api_docs/discover_shared.mdx index 3eb139a2302e..3446bd7676e0 100644 --- a/api_docs/discover_shared.mdx +++ b/api_docs/discover_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverShared title: "discoverShared" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverShared plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverShared'] --- import discoverSharedObj from './discover_shared.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index ec57939cc4d8..d95004e7f4ac 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index 8ba04a058336..54a208c08fb0 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 3fd50116d172..3f410c13fc6e 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 0a177f286ca2..5be5ced4c35a 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 43742400420b..ffcb619986b5 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index eb632fa8aa9f..cce28be65864 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/entities_data_access.mdx b/api_docs/entities_data_access.mdx index 5705a4753af7..82e53658be8c 100644 --- a/api_docs/entities_data_access.mdx +++ b/api_docs/entities_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/entitiesDataAccess title: "entitiesDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the entitiesDataAccess plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entitiesDataAccess'] --- import entitiesDataAccessObj from './entities_data_access.devdocs.json'; diff --git a/api_docs/entity_manager.devdocs.json b/api_docs/entity_manager.devdocs.json index a3f5fff48eb9..7895ae318fb6 100644 --- a/api_docs/entity_manager.devdocs.json +++ b/api_docs/entity_manager.devdocs.json @@ -2,6 +2,993 @@ "id": "entityManager", "client": { "classes": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient", + "type": "Class", + "tags": [], + "label": "EntityClient", + "description": [], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.repositoryClient", + "type": "Function", + "tags": [], + "label": "repositoryClient", + "description": [], + "signature": [ + "(endpoint: TEndpoint, ...args: MaybeOptionalArgs<", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ClientRequestParamsOf", + "text": "ClientRequestParamsOf" + }, + "<{ \"POST /internal/entities/v2/_search/preview\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/v2/_search/preview\", Zod.ZodObject<{ body: Zod.ZodObject<{ sources: Zod.ZodArray>; index_patterns: Zod.ZodArray; identity_fields: Zod.ZodArray; metadata_fields: Zod.ZodArray; filters: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }, { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }>, \"many\">; start: Zod.ZodEffects>, string, string | undefined>; end: Zod.ZodEffects>, string, string | undefined>; limit: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { start: string; end: string; sources: { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }[]; limit: number; }, { sources: { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }[]; start?: string | undefined; end?: string | undefined; limit?: number | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { body: { start: string; end: string; sources: { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }[]; limit: number; }; }, { body: { sources: { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }[]; start?: string | undefined; end?: string | undefined; limit?: number | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + "<{ entities: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.EntityV2", + "text": "EntityV2" + }, + "[]; }>, undefined>; \"POST /internal/entities/v2/_search\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/v2/_search\", Zod.ZodObject<{ body: Zod.ZodObject<{ type: Zod.ZodString; metadata_fields: Zod.ZodDefault>>; filters: Zod.ZodDefault>>; start: Zod.ZodEffects>, string, string | undefined>; end: Zod.ZodEffects>, string, string | undefined>; limit: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { type: string; start: string; end: string; filters: string[]; limit: number; metadata_fields: string[]; }, { type: string; start?: string | undefined; end?: string | undefined; filters?: string[] | undefined; limit?: number | undefined; metadata_fields?: string[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { body: { type: string; start: string; end: string; filters: string[]; limit: number; metadata_fields: string[]; }; }, { body: { type: string; start?: string | undefined; end?: string | undefined; filters?: string[] | undefined; limit?: number | undefined; metadata_fields?: string[] | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"PATCH /internal/entities/definition/{id}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"PATCH /internal/entities/definition/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; body: Zod.ZodObject; filter: Zod.ZodOptional>; version: Zod.ZodOptional>; name: Zod.ZodOptional; description: Zod.ZodOptional>; metrics: Zod.ZodOptional; field: Zod.ZodString; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; }, { name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; }>, Zod.ZodObject<{ name: Zod.ZodString; aggregation: Zod.ZodLiteral<\"doc_count\">; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; aggregation: \"doc_count\"; filter?: string | undefined; }, { name: string; aggregation: \"doc_count\"; filter?: string | undefined; }>, Zod.ZodObject<{ name: Zod.ZodString; aggregation: Zod.ZodLiteral<\"percentile\">; field: Zod.ZodString; percentile: Zod.ZodNumber; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; }, { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; }>]>, \"many\">; equation: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }, { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }>, \"many\">>>; indexPatterns: Zod.ZodOptional>; metadata: Zod.ZodOptional; aggregation: Zod.ZodDefault; limit: Zod.ZodDefault; lookbackPeriod: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; }, { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; }>, Zod.ZodObject<{ type: Zod.ZodLiteral<\"top_value\">; sort: Zod.ZodRecord, Zod.ZodLiteral<\"desc\">]>>; lookbackPeriod: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }, { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }>]>>>; }, \"strip\", Zod.ZodTypeAny, { source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; destination?: string | undefined; }, { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, Zod.ZodEffects]>, { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; }, string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; }, string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, \"many\">>>; identityFields: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { field: string; optional: false; }, { field: string; optional: false; }>, Zod.ZodEffects]>, \"many\">>; displayNameTemplate: Zod.ZodOptional; staticFields: Zod.ZodOptional>>; latest: Zod.ZodOptional>; settings: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { lookbackPeriod: string; timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; }, { timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; }>>; installedComponents: Zod.ZodOptional, Zod.ZodLiteral<\"ingest_pipeline\">, Zod.ZodLiteral<\"template\">]>; id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }, { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }>, \"many\">>>; }, { latest: Zod.ZodOptional; lookbackPeriod: Zod.ZodOptional>>; settings: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }>>>; }, \"strip\", Zod.ZodTypeAny, { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; }, { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; }>>; version: Zod.ZodEffects; }>, \"strip\", Zod.ZodTypeAny, { version: string; type?: string | undefined; filter?: string | undefined; name?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; indexPatterns?: string[] | undefined; metadata?: ({ destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; })[] | undefined; identityFields?: ({ field: string; optional: false; } | { field: string; optional: boolean; })[] | undefined; displayNameTemplate?: string | undefined; staticFields?: Record | undefined; latest?: { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; } | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }, { version: string; type?: string | undefined; filter?: string | undefined; name?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; indexPatterns?: string[] | undefined; metadata?: (string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; })[] | undefined; identityFields?: (string | { field: string; optional: false; })[] | undefined; displayNameTemplate?: string | undefined; staticFields?: Record | undefined; latest?: { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; } | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; body: { version: string; type?: string | undefined; filter?: string | undefined; name?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; indexPatterns?: string[] | undefined; metadata?: ({ destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; })[] | undefined; identityFields?: ({ field: string; optional: false; } | { field: string; optional: boolean; })[] | undefined; displayNameTemplate?: string | undefined; staticFields?: Record | undefined; latest?: { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; } | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }; }, { path: { id: string; }; body: { version: string; type?: string | undefined; filter?: string | undefined; name?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; indexPatterns?: string[] | undefined; metadata?: (string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; })[] | undefined; identityFields?: (string | { field: string; optional: false; })[] | undefined; displayNameTemplate?: string | undefined; staticFields?: Record | undefined; latest?: { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; } | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"POST /internal/entities/definition/{id}/_reset\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/definition/{id}/_reset\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; }, { path: { id: string; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"GET /internal/entities/definition/{id?}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /internal/entities/definition/{id?}\", Zod.ZodObject<{ query: Zod.ZodObject<{ page: Zod.ZodOptional; perPage: Zod.ZodOptional; includeState: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { includeState: boolean; page?: number | undefined; perPage?: number | undefined; }, { page?: number | undefined; perPage?: number | undefined; includeState?: boolean | \"true\" | \"false\" | undefined; }>; path: Zod.ZodObject<{ id: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { id?: string | undefined; }, { id?: string | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { includeState: boolean; page?: number | undefined; perPage?: number | undefined; }; path: { id?: string | undefined; }; }, { query: { page?: number | undefined; perPage?: number | undefined; includeState?: boolean | \"true\" | \"false\" | undefined; }; path: { id?: string | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"DELETE /internal/entities/definition/{id}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"DELETE /internal/entities/definition/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; query: Zod.ZodObject<{ deleteData: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { deleteData: boolean; }, { deleteData?: boolean | \"true\" | \"false\" | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { deleteData: boolean; }; path: { id: string; }; }, { query: { deleteData?: boolean | \"true\" | \"false\" | undefined; }; path: { id: string; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"POST /internal/entities/definition\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/definition\", Zod.ZodObject<{ query: Zod.ZodObject<{ installOnly: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { installOnly: boolean; }, { installOnly?: boolean | \"true\" | \"false\" | undefined; }>; body: Zod.ZodObject<{ id: Zod.ZodString; version: Zod.ZodEffects; name: Zod.ZodString; description: Zod.ZodOptional; type: Zod.ZodString; filter: Zod.ZodOptional; indexPatterns: Zod.ZodArray; identityFields: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { field: string; optional: false; }, { field: string; optional: false; }>, Zod.ZodEffects]>, \"many\">; displayNameTemplate: Zod.ZodString; metadata: Zod.ZodOptional; aggregation: Zod.ZodDefault; limit: Zod.ZodDefault; lookbackPeriod: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; }, { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; }>, Zod.ZodObject<{ type: Zod.ZodLiteral<\"top_value\">; sort: Zod.ZodRecord, Zod.ZodLiteral<\"desc\">]>>; lookbackPeriod: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }, { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }>]>>>; }, \"strip\", Zod.ZodTypeAny, { source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; destination?: string | undefined; }, { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, Zod.ZodEffects]>, { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; }, string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; }, string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, \"many\">>; metrics: Zod.ZodOptional; field: Zod.ZodString; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; }, { name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; }>, Zod.ZodObject<{ name: Zod.ZodString; aggregation: Zod.ZodLiteral<\"doc_count\">; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; aggregation: \"doc_count\"; filter?: string | undefined; }, { name: string; aggregation: \"doc_count\"; filter?: string | undefined; }>, Zod.ZodObject<{ name: Zod.ZodString; aggregation: Zod.ZodLiteral<\"percentile\">; field: Zod.ZodString; percentile: Zod.ZodNumber; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; }, { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; }>]>, \"many\">; equation: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }, { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }>, \"many\">>; staticFields: Zod.ZodOptional>; managed: Zod.ZodDefault>; latest: Zod.ZodObject<{ timestampField: Zod.ZodString; lookbackPeriod: Zod.ZodDefault>; settings: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { lookbackPeriod: string; timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; }, { timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; }>; installStatus: Zod.ZodOptional, Zod.ZodLiteral<\"upgrading\">, Zod.ZodLiteral<\"installed\">, Zod.ZodLiteral<\"failed\">]>>; installStartedAt: Zod.ZodOptional; installedComponents: Zod.ZodOptional, Zod.ZodLiteral<\"ingest_pipeline\">, Zod.ZodLiteral<\"template\">]>; id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }, { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }>, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { id: string; type: string; version: string; name: string; managed: boolean; indexPatterns: string[]; identityFields: ({ field: string; optional: false; } | { field: string; optional: boolean; })[]; displayNameTemplate: string; latest: { lookbackPeriod: string; timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; }; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; metadata?: ({ destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; })[] | undefined; staticFields?: Record | undefined; installStatus?: \"failed\" | \"installing\" | \"upgrading\" | \"installed\" | undefined; installStartedAt?: string | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }, { id: string; type: string; version: string; name: string; indexPatterns: string[]; identityFields: (string | { field: string; optional: false; })[]; displayNameTemplate: string; latest: { timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; }; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; managed?: boolean | undefined; metadata?: (string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; })[] | undefined; staticFields?: Record | undefined; installStatus?: \"failed\" | \"installing\" | \"upgrading\" | \"installed\" | undefined; installStartedAt?: string | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { installOnly: boolean; }; body: { id: string; type: string; version: string; name: string; managed: boolean; indexPatterns: string[]; identityFields: ({ field: string; optional: false; } | { field: string; optional: boolean; })[]; displayNameTemplate: string; latest: { lookbackPeriod: string; timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; }; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; metadata?: ({ destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; })[] | undefined; staticFields?: Record | undefined; installStatus?: \"failed\" | \"installing\" | \"upgrading\" | \"installed\" | undefined; installStartedAt?: string | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }; }, { query: { installOnly?: boolean | \"true\" | \"false\" | undefined; }; body: { id: string; type: string; version: string; name: string; indexPatterns: string[]; identityFields: (string | { field: string; optional: false; })[]; displayNameTemplate: string; latest: { timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; }; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; managed?: boolean | undefined; metadata?: (string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; })[] | undefined; staticFields?: Record | undefined; installStatus?: \"failed\" | \"installing\" | \"upgrading\" | \"installed\" | undefined; installStartedAt?: string | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"DELETE /internal/entities/managed/enablement\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"DELETE /internal/entities/managed/enablement\", Zod.ZodObject<{ query: Zod.ZodObject<{ deleteData: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { deleteData: boolean; }, { deleteData?: boolean | \"true\" | \"false\" | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { deleteData: boolean; }; }, { query: { deleteData?: boolean | \"true\" | \"false\" | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"PUT /internal/entities/managed/enablement\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"PUT /internal/entities/managed/enablement\", Zod.ZodObject<{ query: Zod.ZodObject<{ installOnly: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { installOnly: boolean; }, { installOnly?: boolean | \"true\" | \"false\" | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { installOnly: boolean; }; }, { query: { installOnly?: boolean | \"true\" | \"false\" | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"GET /internal/entities/managed/enablement\": { endpoint: \"GET /internal/entities/managed/enablement\"; handler: ServerRouteHandler<", + "EntityManagerRouteHandlerResources", + ", undefined, ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ">; security?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" + }, + " | undefined; }; }, TEndpoint> & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">) => Promise<", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ReturnOf", + "text": "ReturnOf" + }, + "<{ \"POST /internal/entities/v2/_search/preview\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/v2/_search/preview\", Zod.ZodObject<{ body: Zod.ZodObject<{ sources: Zod.ZodArray>; index_patterns: Zod.ZodArray; identity_fields: Zod.ZodArray; metadata_fields: Zod.ZodArray; filters: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }, { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }>, \"many\">; start: Zod.ZodEffects>, string, string | undefined>; end: Zod.ZodEffects>, string, string | undefined>; limit: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { start: string; end: string; sources: { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }[]; limit: number; }, { sources: { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }[]; start?: string | undefined; end?: string | undefined; limit?: number | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { body: { start: string; end: string; sources: { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }[]; limit: number; }; }, { body: { sources: { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }[]; start?: string | undefined; end?: string | undefined; limit?: number | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + "<{ entities: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.EntityV2", + "text": "EntityV2" + }, + "[]; }>, undefined>; \"POST /internal/entities/v2/_search\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/v2/_search\", Zod.ZodObject<{ body: Zod.ZodObject<{ type: Zod.ZodString; metadata_fields: Zod.ZodDefault>>; filters: Zod.ZodDefault>>; start: Zod.ZodEffects>, string, string | undefined>; end: Zod.ZodEffects>, string, string | undefined>; limit: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { type: string; start: string; end: string; filters: string[]; limit: number; metadata_fields: string[]; }, { type: string; start?: string | undefined; end?: string | undefined; filters?: string[] | undefined; limit?: number | undefined; metadata_fields?: string[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { body: { type: string; start: string; end: string; filters: string[]; limit: number; metadata_fields: string[]; }; }, { body: { type: string; start?: string | undefined; end?: string | undefined; filters?: string[] | undefined; limit?: number | undefined; metadata_fields?: string[] | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"PATCH /internal/entities/definition/{id}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"PATCH /internal/entities/definition/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; body: Zod.ZodObject; filter: Zod.ZodOptional>; version: Zod.ZodOptional>; name: Zod.ZodOptional; description: Zod.ZodOptional>; metrics: Zod.ZodOptional; field: Zod.ZodString; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; }, { name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; }>, Zod.ZodObject<{ name: Zod.ZodString; aggregation: Zod.ZodLiteral<\"doc_count\">; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; aggregation: \"doc_count\"; filter?: string | undefined; }, { name: string; aggregation: \"doc_count\"; filter?: string | undefined; }>, Zod.ZodObject<{ name: Zod.ZodString; aggregation: Zod.ZodLiteral<\"percentile\">; field: Zod.ZodString; percentile: Zod.ZodNumber; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; }, { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; }>]>, \"many\">; equation: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }, { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }>, \"many\">>>; indexPatterns: Zod.ZodOptional>; metadata: Zod.ZodOptional; aggregation: Zod.ZodDefault; limit: Zod.ZodDefault; lookbackPeriod: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; }, { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; }>, Zod.ZodObject<{ type: Zod.ZodLiteral<\"top_value\">; sort: Zod.ZodRecord, Zod.ZodLiteral<\"desc\">]>>; lookbackPeriod: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }, { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }>]>>>; }, \"strip\", Zod.ZodTypeAny, { source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; destination?: string | undefined; }, { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, Zod.ZodEffects]>, { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; }, string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; }, string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, \"many\">>>; identityFields: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { field: string; optional: false; }, { field: string; optional: false; }>, Zod.ZodEffects]>, \"many\">>; displayNameTemplate: Zod.ZodOptional; staticFields: Zod.ZodOptional>>; latest: Zod.ZodOptional>; settings: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { lookbackPeriod: string; timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; }, { timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; }>>; installedComponents: Zod.ZodOptional, Zod.ZodLiteral<\"ingest_pipeline\">, Zod.ZodLiteral<\"template\">]>; id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }, { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }>, \"many\">>>; }, { latest: Zod.ZodOptional; lookbackPeriod: Zod.ZodOptional>>; settings: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }>>>; }, \"strip\", Zod.ZodTypeAny, { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; }, { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; }>>; version: Zod.ZodEffects; }>, \"strip\", Zod.ZodTypeAny, { version: string; type?: string | undefined; filter?: string | undefined; name?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; indexPatterns?: string[] | undefined; metadata?: ({ destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; })[] | undefined; identityFields?: ({ field: string; optional: false; } | { field: string; optional: boolean; })[] | undefined; displayNameTemplate?: string | undefined; staticFields?: Record | undefined; latest?: { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; } | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }, { version: string; type?: string | undefined; filter?: string | undefined; name?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; indexPatterns?: string[] | undefined; metadata?: (string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; })[] | undefined; identityFields?: (string | { field: string; optional: false; })[] | undefined; displayNameTemplate?: string | undefined; staticFields?: Record | undefined; latest?: { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; } | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; body: { version: string; type?: string | undefined; filter?: string | undefined; name?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; indexPatterns?: string[] | undefined; metadata?: ({ destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; })[] | undefined; identityFields?: ({ field: string; optional: false; } | { field: string; optional: boolean; })[] | undefined; displayNameTemplate?: string | undefined; staticFields?: Record | undefined; latest?: { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; } | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }; }, { path: { id: string; }; body: { version: string; type?: string | undefined; filter?: string | undefined; name?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; indexPatterns?: string[] | undefined; metadata?: (string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; })[] | undefined; identityFields?: (string | { field: string; optional: false; })[] | undefined; displayNameTemplate?: string | undefined; staticFields?: Record | undefined; latest?: { settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; timestampField?: string | undefined; } | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"POST /internal/entities/definition/{id}/_reset\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/definition/{id}/_reset\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; }, { path: { id: string; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"GET /internal/entities/definition/{id?}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /internal/entities/definition/{id?}\", Zod.ZodObject<{ query: Zod.ZodObject<{ page: Zod.ZodOptional; perPage: Zod.ZodOptional; includeState: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { includeState: boolean; page?: number | undefined; perPage?: number | undefined; }, { page?: number | undefined; perPage?: number | undefined; includeState?: boolean | \"true\" | \"false\" | undefined; }>; path: Zod.ZodObject<{ id: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { id?: string | undefined; }, { id?: string | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { includeState: boolean; page?: number | undefined; perPage?: number | undefined; }; path: { id?: string | undefined; }; }, { query: { page?: number | undefined; perPage?: number | undefined; includeState?: boolean | \"true\" | \"false\" | undefined; }; path: { id?: string | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"DELETE /internal/entities/definition/{id}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"DELETE /internal/entities/definition/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; query: Zod.ZodObject<{ deleteData: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { deleteData: boolean; }, { deleteData?: boolean | \"true\" | \"false\" | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { deleteData: boolean; }; path: { id: string; }; }, { query: { deleteData?: boolean | \"true\" | \"false\" | undefined; }; path: { id: string; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"POST /internal/entities/definition\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/definition\", Zod.ZodObject<{ query: Zod.ZodObject<{ installOnly: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { installOnly: boolean; }, { installOnly?: boolean | \"true\" | \"false\" | undefined; }>; body: Zod.ZodObject<{ id: Zod.ZodString; version: Zod.ZodEffects; name: Zod.ZodString; description: Zod.ZodOptional; type: Zod.ZodString; filter: Zod.ZodOptional; indexPatterns: Zod.ZodArray; identityFields: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { field: string; optional: false; }, { field: string; optional: false; }>, Zod.ZodEffects]>, \"many\">; displayNameTemplate: Zod.ZodString; metadata: Zod.ZodOptional; aggregation: Zod.ZodDefault; limit: Zod.ZodDefault; lookbackPeriod: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; }, { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; }>, Zod.ZodObject<{ type: Zod.ZodLiteral<\"top_value\">; sort: Zod.ZodRecord, Zod.ZodLiteral<\"desc\">]>>; lookbackPeriod: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }, { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }>]>>>; }, \"strip\", Zod.ZodTypeAny, { source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; destination?: string | undefined; }, { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, Zod.ZodEffects]>, { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; }, string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; }, string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; }>, \"many\">>; metrics: Zod.ZodOptional; field: Zod.ZodString; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; }, { name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; }>, Zod.ZodObject<{ name: Zod.ZodString; aggregation: Zod.ZodLiteral<\"doc_count\">; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; aggregation: \"doc_count\"; filter?: string | undefined; }, { name: string; aggregation: \"doc_count\"; filter?: string | undefined; }>, Zod.ZodObject<{ name: Zod.ZodString; aggregation: Zod.ZodLiteral<\"percentile\">; field: Zod.ZodString; percentile: Zod.ZodNumber; filter: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; }, { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; }>]>, \"many\">; equation: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }, { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }>, \"many\">>; staticFields: Zod.ZodOptional>; managed: Zod.ZodDefault>; latest: Zod.ZodObject<{ timestampField: Zod.ZodString; lookbackPeriod: Zod.ZodDefault>; settings: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }, { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { lookbackPeriod: string; timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; }, { timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; }>; installStatus: Zod.ZodOptional, Zod.ZodLiteral<\"upgrading\">, Zod.ZodLiteral<\"installed\">, Zod.ZodLiteral<\"failed\">]>>; installStartedAt: Zod.ZodOptional; installedComponents: Zod.ZodOptional, Zod.ZodLiteral<\"ingest_pipeline\">, Zod.ZodLiteral<\"template\">]>; id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }, { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }>, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { id: string; type: string; version: string; name: string; managed: boolean; indexPatterns: string[]; identityFields: ({ field: string; optional: false; } | { field: string; optional: boolean; })[]; displayNameTemplate: string; latest: { lookbackPeriod: string; timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; }; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; metadata?: ({ destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; })[] | undefined; staticFields?: Record | undefined; installStatus?: \"failed\" | \"installing\" | \"upgrading\" | \"installed\" | undefined; installStartedAt?: string | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }, { id: string; type: string; version: string; name: string; indexPatterns: string[]; identityFields: (string | { field: string; optional: false; })[]; displayNameTemplate: string; latest: { timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; }; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; managed?: boolean | undefined; metadata?: (string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; })[] | undefined; staticFields?: Record | undefined; installStatus?: \"failed\" | \"installing\" | \"upgrading\" | \"installed\" | undefined; installStartedAt?: string | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { installOnly: boolean; }; body: { id: string; type: string; version: string; name: string; managed: boolean; indexPatterns: string[]; identityFields: ({ field: string; optional: false; } | { field: string; optional: boolean; })[]; displayNameTemplate: string; latest: { lookbackPeriod: string; timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; }; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; metadata?: ({ destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; }; } | { destination: string; source: string; aggregation: { type: \"terms\"; limit: number; lookbackPeriod: undefined; }; })[] | undefined; staticFields?: Record | undefined; installStatus?: \"failed\" | \"installing\" | \"upgrading\" | \"installed\" | undefined; installStartedAt?: string | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }; }, { query: { installOnly?: boolean | \"true\" | \"false\" | undefined; }; body: { id: string; type: string; version: string; name: string; indexPatterns: string[]; identityFields: (string | { field: string; optional: false; })[]; displayNameTemplate: string; latest: { timestampField: string; settings?: { frequency?: string | undefined; syncField?: string | undefined; syncDelay?: string | undefined; } | undefined; lookbackPeriod?: string | undefined; }; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.BasicAggregations", + "text": "BasicAggregations" + }, + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; managed?: boolean | undefined; metadata?: (string | { source: string; destination?: string | undefined; aggregation?: { type: \"terms\"; limit?: number | undefined; lookbackPeriod?: string | undefined; } | { type: \"top_value\"; sort: Record; lookbackPeriod?: string | undefined; } | undefined; })[] | undefined; staticFields?: Record | undefined; installStatus?: \"failed\" | \"installing\" | \"upgrading\" | \"installed\" | undefined; installStartedAt?: string | undefined; installedComponents?: { id: string; type: \"transform\" | \"template\" | \"ingest_pipeline\"; }[] | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"DELETE /internal/entities/managed/enablement\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"DELETE /internal/entities/managed/enablement\", Zod.ZodObject<{ query: Zod.ZodObject<{ deleteData: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { deleteData: boolean; }, { deleteData?: boolean | \"true\" | \"false\" | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { deleteData: boolean; }; }, { query: { deleteData?: boolean | \"true\" | \"false\" | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"PUT /internal/entities/managed/enablement\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"PUT /internal/entities/managed/enablement\", Zod.ZodObject<{ query: Zod.ZodObject<{ installOnly: Zod.ZodDefault, Zod.ZodBoolean]>, boolean, boolean | \"true\" | \"false\">>>; }, \"strip\", Zod.ZodTypeAny, { installOnly: boolean; }, { installOnly?: boolean | \"true\" | \"false\" | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { query: { installOnly: boolean; }; }, { query: { installOnly?: boolean | \"true\" | \"false\" | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"GET /internal/entities/managed/enablement\": { endpoint: \"GET /internal/entities/managed/enablement\"; handler: ServerRouteHandler<", + "EntityManagerRouteHandlerResources", + ", undefined, ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ">; security?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" + }, + " | undefined; }; }, TEndpoint>>" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.repositoryClient.$1", + "type": "Uncategorized", + "tags": [], + "label": "endpoint", + "description": [], + "signature": [ + "TEndpoint" + ], + "path": "packages/kbn-server-route-repository-utils/src/typings.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.repositoryClient.$2", + "type": "Uncategorized", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "RequiredKeys", + "<", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ClientRequestParamsOf", + "text": "ClientRequestParamsOf" + }, + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + "> extends never ? [] | [", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ClientRequestParamsOf", + "text": "ClientRequestParamsOf" + }, + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + "] : [", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ClientRequestParamsOf", + "text": "ClientRequestParamsOf" + }, + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + "]" + ], + "path": "packages/kbn-server-route-repository-utils/src/typings.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.Unnamed.$1", + "type": "CompoundType", + "tags": [], + "label": "core", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-lifecycle-browser", + "scope": "public", + "docId": "kibKbnCoreLifecycleBrowserPluginApi", + "section": "def-public.CoreStart", + "text": "CoreStart" + }, + " | ", + { + "pluginId": "@kbn/core-lifecycle-browser", + "scope": "public", + "docId": "kibKbnCoreLifecycleBrowserPluginApi", + "section": "def-public.CoreSetup", + "text": "CoreSetup" + }, + "" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.isManagedEntityDiscoveryEnabled", + "type": "Function", + "tags": [], + "label": "isManagedEntityDiscoveryEnabled", + "description": [], + "signature": [ + "() => Promise<{ enabled: boolean; reason: string; }>" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.enableManagedEntityDiscovery", + "type": "Function", + "tags": [], + "label": "enableManagedEntityDiscovery", + "description": [], + "signature": [ + "(query?: { installOnly?: boolean | \"true\" | \"false\" | undefined; } | undefined) => Promise<{ success: boolean; reason: string; message: string; }>" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.enableManagedEntityDiscovery.$1", + "type": "Object", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "{ installOnly?: boolean | \"true\" | \"false\" | undefined; } | undefined" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.disableManagedEntityDiscovery", + "type": "Function", + "tags": [], + "label": "disableManagedEntityDiscovery", + "description": [], + "signature": [ + "(query?: { deleteData?: boolean | \"true\" | \"false\" | undefined; } | undefined) => Promise<{ success: boolean; reason: string; message: string; }>" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.disableManagedEntityDiscovery.$1", + "type": "Object", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "{ deleteData?: boolean | \"true\" | \"false\" | undefined; } | undefined" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.asKqlFilter", + "type": "Function", + "tags": [], + "label": "asKqlFilter", + "description": [], + "signature": [ + "(entityInstance: { entity: Pick<{ id: string; type: string; schema_version: string; identity_fields: string | string[]; display_name: string; definition_version: string; definition_id: string; last_seen_timestamp: string; metrics?: Record | undefined; }, \"identity_fields\">; } & Required) => string" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.asKqlFilter.$1", + "type": "CompoundType", + "tags": [], + "label": "entityInstance", + "description": [], + "signature": [ + "{ entity: Pick<{ id: string; type: string; schema_version: string; identity_fields: string | string[]; display_name: string; definition_version: string; definition_id: string; last_seen_timestamp: string; metrics?: Record | undefined; }, \"identity_fields\">; } & Required" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.getIdentityFieldsValue", + "type": "Function", + "tags": [], + "label": "getIdentityFieldsValue", + "description": [], + "signature": [ + "(entityInstance: { entity: Pick<{ id: string; type: string; schema_version: string; identity_fields: string | string[]; display_name: string; definition_version: string; definition_id: string; last_seen_timestamp: string; metrics?: Record | undefined; }, \"identity_fields\">; } & Required) => Record" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityClient.getIdentityFieldsValue.$1", + "type": "CompoundType", + "tags": [], + "label": "entityInstance", + "description": [], + "signature": [ + "{ entity: Pick<{ id: string; type: string; schema_version: string; identity_fields: string | string[]; display_name: string; definition_version: string; definition_id: string; last_seen_timestamp: string; metrics?: Record | undefined; }, \"identity_fields\">; } & Required" + ], + "path": "x-pack/plugins/entity_manager/public/lib/entity_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "entityManager", "id": "def-public.EntityManagerUnauthorizedError", @@ -174,7 +1161,13 @@ "label": "entityClient", "description": [], "signature": [ - "EntityClient" + { + "pluginId": "entityManager", + "scope": "public", + "docId": "kibEntityManagerPluginApi", + "section": "def-public.EntityClient", + "text": "EntityClient" + } ], "path": "x-pack/plugins/entity_manager/public/types.ts", "deprecated": false, @@ -203,7 +1196,13 @@ "label": "entityClient", "description": [], "signature": [ - "EntityClient" + { + "pluginId": "entityManager", + "scope": "public", + "docId": "kibEntityManagerPluginApi", + "section": "def-public.EntityClient", + "text": "EntityClient" + } ], "path": "x-pack/plugins/entity_manager/public/types.ts", "deprecated": false, @@ -243,7 +1242,51 @@ "label": "EntityManagerRouteRepository", "description": [], "signature": [ - "{ \"PATCH /internal/entities/definition/{id}\": ", + "{ \"POST /internal/entities/v2/_search/preview\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/v2/_search/preview\", Zod.ZodObject<{ body: Zod.ZodObject<{ sources: Zod.ZodArray>; index_patterns: Zod.ZodArray; identity_fields: Zod.ZodArray; metadata_fields: Zod.ZodArray; filters: Zod.ZodArray; }, \"strip\", Zod.ZodTypeAny, { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }, { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }>, \"many\">; start: Zod.ZodEffects>, string, string | undefined>; end: Zod.ZodEffects>, string, string | undefined>; limit: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { start: string; end: string; sources: { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }[]; limit: number; }, { sources: { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }[]; start?: string | undefined; end?: string | undefined; limit?: number | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { body: { start: string; end: string; sources: { type: string; filters: string[]; timestamp_field: string; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; }[]; limit: number; }; }, { body: { sources: { type: string; filters: string[]; metadata_fields: string[]; index_patterns: string[]; identity_fields: string[]; timestamp_field?: string | undefined; }[]; start?: string | undefined; end?: string | undefined; limit?: number | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + "<{ entities: ", + { + "pluginId": "@kbn/entities-schema", + "scope": "common", + "docId": "kibKbnEntitiesSchemaPluginApi", + "section": "def-common.EntityV2", + "text": "EntityV2" + }, + "[]; }>, undefined>; \"POST /internal/entities/v2/_search\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/entities/v2/_search\", Zod.ZodObject<{ body: Zod.ZodObject<{ type: Zod.ZodString; metadata_fields: Zod.ZodDefault>>; filters: Zod.ZodDefault>>; start: Zod.ZodEffects>, string, string | undefined>; end: Zod.ZodEffects>, string, string | undefined>; limit: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { type: string; start: string; end: string; filters: string[]; limit: number; metadata_fields: string[]; }, { type: string; start?: string | undefined; end?: string | undefined; filters?: string[] | undefined; limit?: number | undefined; metadata_fields?: string[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { body: { type: string; start: string; end: string; filters: string[]; limit: number; metadata_fields: string[]; }; }, { body: { type: string; start?: string | undefined; end?: string | undefined; filters?: string[] | undefined; limit?: number | undefined; metadata_fields?: string[] | undefined; }; }>, ", + "EntityManagerRouteHandlerResources", + ", ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ", undefined>; \"PATCH /internal/entities/definition/{id}\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -333,15 +1376,7 @@ "section": "def-server.IKibanaResponse", "text": "IKibanaResponse" }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"POST /internal/entities/definition/{id}/_reset\": ", + ", undefined>; \"POST /internal/entities/definition/{id}/_reset\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -359,15 +1394,7 @@ "section": "def-server.IKibanaResponse", "text": "IKibanaResponse" }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"GET /internal/entities/definition/{id?}\": ", + ", undefined>; \"GET /internal/entities/definition/{id?}\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -385,15 +1412,7 @@ "section": "def-server.IKibanaResponse", "text": "IKibanaResponse" }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"DELETE /internal/entities/definition/{id}\": ", + ", undefined>; \"DELETE /internal/entities/definition/{id}\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -411,15 +1430,7 @@ "section": "def-server.IKibanaResponse", "text": "IKibanaResponse" }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"POST /internal/entities/definition\": ", + ", undefined>; \"POST /internal/entities/definition\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -509,15 +1520,7 @@ "section": "def-server.IKibanaResponse", "text": "IKibanaResponse" }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"DELETE /internal/entities/managed/enablement\": ", + ", undefined>; \"DELETE /internal/entities/managed/enablement\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -535,15 +1538,7 @@ "section": "def-server.IKibanaResponse", "text": "IKibanaResponse" }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"PUT /internal/entities/managed/enablement\": ", + ", undefined>; \"PUT /internal/entities/managed/enablement\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -561,25 +1556,9 @@ "section": "def-server.IKibanaResponse", "text": "IKibanaResponse" }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"GET /internal/entities/managed/enablement\": ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.ServerRoute", - "text": "ServerRoute" - }, - "<\"GET /internal/entities/managed/enablement\", undefined, ", + ", undefined>; \"GET /internal/entities/managed/enablement\": { endpoint: \"GET /internal/entities/managed/enablement\"; handler: ServerRouteHandler<", "EntityManagerRouteHandlerResources", - ", ", + ", undefined, ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -587,15 +1566,15 @@ "section": "def-server.IKibanaResponse", "text": "IKibanaResponse" }, - ", ", + ">; security?: ", { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" }, - ">; }" + " | undefined; }; }" ], "path": "x-pack/plugins/entity_manager/server/routes/index.ts", "deprecated": false, diff --git a/api_docs/entity_manager.mdx b/api_docs/entity_manager.mdx index ec318d2f8873..11ded400d81b 100644 --- a/api_docs/entity_manager.mdx +++ b/api_docs/entity_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/entityManager title: "entityManager" image: https://source.unsplash.com/400x175/?github description: API docs for the entityManager plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entityManager'] --- import entityManagerObj from './entity_manager.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entiti | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 20 | 0 | 20 | 3 | +| 35 | 0 | 35 | 2 | ## Client diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 7e0bbc6cc5d6..12256929bb52 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/esql.mdx b/api_docs/esql.mdx index a93d10e86866..75071d6f0336 100644 --- a/api_docs/esql.mdx +++ b/api_docs/esql.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esql title: "esql" image: https://source.unsplash.com/400x175/?github description: API docs for the esql plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esql'] --- import esqlObj from './esql.devdocs.json'; diff --git a/api_docs/esql_data_grid.mdx b/api_docs/esql_data_grid.mdx index fda8e6227e50..b6d6fc96cb2a 100644 --- a/api_docs/esql_data_grid.mdx +++ b/api_docs/esql_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esqlDataGrid title: "esqlDataGrid" image: https://source.unsplash.com/400x175/?github description: API docs for the esqlDataGrid plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esqlDataGrid'] --- import esqlDataGridObj from './esql_data_grid.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index cbdb922eec54..66adba7501c4 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx index f746c2c26af3..82ff1773c913 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] --- import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index ca02f9a5cb40..2cfb9a186546 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.devdocs.json b/api_docs/exploratory_view.devdocs.json index 5738ed0fdc2a..1e5af66252fb 100644 --- a/api_docs/exploratory_view.devdocs.json +++ b/api_docs/exploratory_view.devdocs.json @@ -802,6 +802,21 @@ "path": "x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "exploratoryView", + "id": "def-public.ExploratoryEmbeddableProps.dslFilters", + "type": "Array", + "tags": [], + "label": "dslFilters", + "description": [], + "signature": [ + "QueryDslQueryContainer", + "[] | undefined" + ], + "path": "x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index eac05d6de634..1ebb14f900e7 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 126 | 0 | 126 | 12 | +| 127 | 0 | 127 | 12 | ## Client diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 6a5fbd3403e3..b69ddb7e91ef 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 7ed076a8170d..383497073288 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 3d56b0ce024b..80e5b8321c25 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index 086410a49c2c..ea30bfd79a70 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index abdc1f0b6570..6481f46f9c1d 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 5a41a189a140..47af3b8bd93e 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 952bcccd5d9c..3bb05c0bb32e 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index 77eb892788db..81cdc668f7df 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 1ce3d9d45342..ba0c4c3566ba 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index ef05d569a34c..113d89ae9357 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 392e5f380eee..07ae5d1173a1 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 1b7eb81ed5ed..d4f92e3f290e 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 99a97700ca55..fc1a34d457a4 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index b242d12db535..ce44d06397ca 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index daf871dead94..1136571cf2fb 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 5b2cbdb30d66..c11d90b1bb6f 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/fields_metadata.mdx b/api_docs/fields_metadata.mdx index 6d06268b084d..866ece3fdf83 100644 --- a/api_docs/fields_metadata.mdx +++ b/api_docs/fields_metadata.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldsMetadata title: "fieldsMetadata" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldsMetadata plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldsMetadata'] --- import fieldsMetadataObj from './fields_metadata.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index feea806dea20..a49cc04b0f06 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 557b44e15e1e..b132ec182e28 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 6a5505b11107..bf58550b3858 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 9555696dadcb..6af93b912833 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -20134,6 +20134,57 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "fleet", + "id": "def-server.FleetStartContract.createOutputClient", + "type": "Function", + "tags": [], + "label": "createOutputClient", + "description": [ + "\nCreate a Fleet Output Client instance" + ], + "signature": [ + "(request: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + ") => Promise<", + "OutputClientInterface", + ">" + ], + "path": "x-pack/plugins/fleet/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.FleetStartContract.createOutputClient.$1", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + "" + ], + "path": "x-pack/plugins/fleet/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "lifecycle": "start", @@ -22472,7 +22523,7 @@ "signature": [ "{ monitoring: ", "FullAgentPolicyMonitoring", - "; download: { sourceURI: string; }; features: Record; protection?: { enabled: boolean; uninstall_token_hash: string; signing_key: string; } | undefined; } | undefined" + "; download: { sourceURI: string; }; features: Record; protection?: { enabled: boolean; uninstall_token_hash: string; signing_key: string; } | undefined; logging?: { level?: string | undefined; to_files?: boolean | undefined; files?: { rotateeverybytes?: number | undefined; keepfiles?: number | undefined; interval?: string | undefined; } | undefined; } | undefined; limits?: { go_max_procs?: number | undefined; } | undefined; } | undefined" ], "path": "x-pack/plugins/fleet/common/types/models/agent_policy.ts", "deprecated": false, diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 8c05bab735e1..a2dd317945c5 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1428 | 5 | 1303 | 81 | +| 1430 | 5 | 1304 | 82 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 1f94964f70a5..684d940f8a9d 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 84ef5b75f978..761c1e912530 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 6f6a5590e194..144234325c96 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index 32cb892e161c..dd6def7a1cf2 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index e6247ff39b1f..b05f09e3f517 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 3b4cbec9c3e6..a8cf21f9398e 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/inference.devdocs.json b/api_docs/inference.devdocs.json index d9b4b8cc287d..340b5e87d7f0 100644 --- a/api_docs/inference.devdocs.json +++ b/api_docs/inference.devdocs.json @@ -259,14 +259,202 @@ } ], "interfaces": [ + { + "parentPluginId": "inference", + "id": "def-server.BoundInferenceClient", + "type": "Interface", + "tags": [], + "label": "BoundInferenceClient", + "description": [ + "\nA version of the {@link InferenceClient} that is pre-bound to a set of parameters." + ], + "path": "x-pack/plugins/inference/server/inference_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "inference", + "id": "def-server.BoundInferenceClient.chatComplete", + "type": "Function", + "tags": [], + "label": "chatComplete", + "description": [ + "\n`chatComplete` requests the LLM to generate a response to\na prompt or conversation, which might be plain text\nor a tool call, or a combination of both." + ], + "signature": [ + " = ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ToolOptions", + "text": "ToolOptions" + }, + ", TStream extends boolean = false>(options: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.UnboundChatCompleteOptions", + "text": "UnboundChatCompleteOptions" + }, + ") => ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ChatCompleteCompositeResponse", + "text": "ChatCompleteCompositeResponse" + }, + "" + ], + "path": "x-pack/plugins/inference/server/inference_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "inference", + "id": "def-server.BoundInferenceClient.chatComplete.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ [P in \"system\" | \"stream\" | \"messages\" | Exclude]: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ChatCompleteOptions", + "text": "ChatCompleteOptions" + }, + "[P]; }" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "inference", + "id": "def-server.BoundInferenceClient.output", + "type": "Function", + "tags": [], + "label": "output", + "description": [ + "\n`output` asks the LLM to generate a structured (JSON)\nresponse based on a schema and a prompt or conversation." + ], + "signature": [ + "(options: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.UnboundOutputOptions", + "text": "UnboundOutputOptions" + }, + ") => ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.OutputCompositeResponse", + "text": "OutputCompositeResponse" + }, + "" + ], + "path": "x-pack/plugins/inference/server/inference_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "inference", + "id": "def-server.BoundInferenceClient.output.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ id: TId; input: string; schema?: TOutputSchema | undefined; system?: string | undefined; stream?: TStream | undefined; previousMessages?: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.Message", + "text": "Message" + }, + "[] | undefined; }" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "inference", + "id": "def-server.BoundInferenceClient.getConnectorById", + "type": "Function", + "tags": [], + "label": "getConnectorById", + "description": [ + "\n`getConnectorById` returns an inference connector by id.\nNon-inference connectors will throw an error." + ], + "signature": [ + "(id: string) => Promise<", + "InferenceConnector", + ">" + ], + "path": "x-pack/plugins/inference/server/inference_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "inference", + "id": "def-server.BoundInferenceClient.getConnectorById.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/inference/server/inference_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "inference", "id": "def-server.InferenceClient", "type": "Interface", "tags": [], "label": "InferenceClient", - "description": [], - "path": "x-pack/plugins/inference/server/types.ts", + "description": [ + "\nAn inference client, scoped to a request, that can be used to interact with LLMs." + ], + "path": "x-pack/plugins/inference/server/inference_client/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -314,7 +502,7 @@ }, "" ], - "path": "x-pack/plugins/inference/server/types.ts", + "path": "x-pack/plugins/inference/server/inference_client/types.ts", "deprecated": false, "trackAdoption": false, "returnComment": [], @@ -383,7 +571,7 @@ }, "" ], - "path": "x-pack/plugins/inference/server/types.ts", + "path": "x-pack/plugins/inference/server/inference_client/types.ts", "deprecated": false, "trackAdoption": false, "returnComment": [], @@ -425,7 +613,7 @@ "InferenceConnector", ">" ], - "path": "x-pack/plugins/inference/server/types.ts", + "path": "x-pack/plugins/inference/server/inference_client/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -439,7 +627,7 @@ "signature": [ "string" ], - "path": "x-pack/plugins/inference/server/types.ts", + "path": "x-pack/plugins/inference/server/inference_client/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -460,7 +648,9 @@ "type": "Interface", "tags": [], "label": "InferenceServerSetup", - "description": [], + "description": [ + "\nSetup contract of the inference plugin." + ], "path": "x-pack/plugins/inference/server/types.ts", "deprecated": false, "trackAdoption": false, @@ -474,7 +664,9 @@ "type": "Interface", "tags": [], "label": "InferenceServerStart", - "description": [], + "description": [ + "\nStart contract of the inference plugin, exposing APIs to interact with LLMs." + ], "path": "x-pack/plugins/inference/server/types.ts", "deprecated": false, "trackAdoption": false, @@ -486,10 +678,22 @@ "tags": [], "label": "getClient", "description": [ - "\nCreates an inference client, scoped to a request.\n" + "\nCreates an {@link InferenceClient}, scoped to a request.\n" ], "signature": [ - "(options: InferenceClientCreateOptions) => ", + "(options: T) => T extends ", + "InferenceBoundClientCreateOptions", + " ? ", + { + "pluginId": "inference", + "scope": "server", + "docId": "kibInferencePluginApi", + "section": "def-server.BoundInferenceClient", + "text": "BoundInferenceClient" + }, + " : ", { "pluginId": "inference", "scope": "server", @@ -505,14 +709,12 @@ { "parentPluginId": "inference", "id": "def-server.InferenceServerStart.getClient.$1", - "type": "Object", + "type": "Uncategorized", "tags": [], "label": "options", - "description": [ - "{@link InferenceClientCreateOptions }" - ], + "description": [], "signature": [ - "InferenceClientCreateOptions" + "T" ], "path": "x-pack/plugins/inference/server/types.ts", "deprecated": false, @@ -588,7 +790,7 @@ "text": "OutputAPI" } ], - "path": "x-pack/plugins/inference/common/create_output_api.ts", + "path": "x-pack/plugins/inference/common/output/create_output_api.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -608,7 +810,7 @@ "text": "ChatCompleteAPI" } ], - "path": "x-pack/plugins/inference/common/create_output_api.ts", + "path": "x-pack/plugins/inference/common/output/create_output_api.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -671,7 +873,7 @@ "AugmentedRequired", " | FromToolSchemaArray | undefined; }, never> | undefined; }>" ], - "path": "x-pack/plugins/inference/common/create_output_api.ts", + "path": "x-pack/plugins/inference/common/output/create_output_api.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -691,7 +893,7 @@ "text": "ChatCompleteAPI" } ], - "path": "x-pack/plugins/inference/common/create_output_api.ts", + "path": "x-pack/plugins/inference/common/output/create_output_api.ts", "deprecated": false, "trackAdoption": false, "isRequired": true diff --git a/api_docs/inference.mdx b/api_docs/inference.mdx index 3ab6b990d55e..7d499e10dfc2 100644 --- a/api_docs/inference.mdx +++ b/api_docs/inference.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inference title: "inference" image: https://source.unsplash.com/400x175/?github description: API docs for the inference plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inference'] --- import inferenceObj from './inference.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-ai-infra](https://github.com/orgs/elastic/teams/appex-ai | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 33 | 0 | 28 | 4 | +| 40 | 0 | 29 | 6 | ## Client diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 317377eecfe1..3c8931f37c18 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index f1d3271b3541..f75d0dd39ca3 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ingestPipelines title: "ingestPipelines" image: https://source.unsplash.com/400x175/?github description: API docs for the ingestPipelines plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ingestPipelines'] --- import ingestPipelinesObj from './ingest_pipelines.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 777c14bfd39e..327ee9d4b945 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/integration_assistant.mdx b/api_docs/integration_assistant.mdx index 61f06a0864b4..43128fcd7671 100644 --- a/api_docs/integration_assistant.mdx +++ b/api_docs/integration_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/integrationAssistant title: "integrationAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the integrationAssistant plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'integrationAssistant'] --- import integrationAssistantObj from './integration_assistant.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 7d454ed958f3..0acb84296319 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/inventory.mdx b/api_docs/inventory.mdx index ef6e77f76994..be5373645945 100644 --- a/api_docs/inventory.mdx +++ b/api_docs/inventory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inventory title: "inventory" image: https://source.unsplash.com/400x175/?github description: API docs for the inventory plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inventory'] --- import inventoryObj from './inventory.devdocs.json'; diff --git a/api_docs/investigate.mdx b/api_docs/investigate.mdx index 59f2e39e61d6..1da148497ed6 100644 --- a/api_docs/investigate.mdx +++ b/api_docs/investigate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigate title: "investigate" image: https://source.unsplash.com/400x175/?github description: API docs for the investigate plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigate'] --- import investigateObj from './investigate.devdocs.json'; diff --git a/api_docs/investigate_app.mdx b/api_docs/investigate_app.mdx index 3851de8ff05b..8e368221f13c 100644 --- a/api_docs/investigate_app.mdx +++ b/api_docs/investigate_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigateApp title: "investigateApp" image: https://source.unsplash.com/400x175/?github description: API docs for the investigateApp plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigateApp'] --- import investigateAppObj from './investigate_app.devdocs.json'; diff --git a/api_docs/kbn_actions_types.mdx b/api_docs/kbn_actions_types.mdx index 8c72ef0f715c..0976b60a5458 100644 --- a/api_docs/kbn_actions_types.mdx +++ b/api_docs/kbn_actions_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-actions-types title: "@kbn/actions-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/actions-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/actions-types'] --- import kbnActionsTypesObj from './kbn_actions_types.devdocs.json'; diff --git a/api_docs/kbn_ai_assistant.mdx b/api_docs/kbn_ai_assistant.mdx index eb6c931e3e47..8128e9639bc7 100644 --- a/api_docs/kbn_ai_assistant.mdx +++ b/api_docs/kbn_ai_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ai-assistant title: "@kbn/ai-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ai-assistant plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ai-assistant'] --- import kbnAiAssistantObj from './kbn_ai_assistant.devdocs.json'; diff --git a/api_docs/kbn_ai_assistant_common.mdx b/api_docs/kbn_ai_assistant_common.mdx index 036bb27eaa77..898d648ba9a2 100644 --- a/api_docs/kbn_ai_assistant_common.mdx +++ b/api_docs/kbn_ai_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ai-assistant-common title: "@kbn/ai-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ai-assistant-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ai-assistant-common'] --- import kbnAiAssistantCommonObj from './kbn_ai_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 4f3ba688c475..b21fb6d1244e 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_pattern_analysis.mdx b/api_docs/kbn_aiops_log_pattern_analysis.mdx index 020eb3b0518c..488f33b5013a 100644 --- a/api_docs/kbn_aiops_log_pattern_analysis.mdx +++ b/api_docs/kbn_aiops_log_pattern_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-pattern-analysis title: "@kbn/aiops-log-pattern-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-pattern-analysis plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-pattern-analysis'] --- import kbnAiopsLogPatternAnalysisObj from './kbn_aiops_log_pattern_analysis.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_rate_analysis.mdx b/api_docs/kbn_aiops_log_rate_analysis.mdx index dcf67a4049af..a7a1a4fb3877 100644 --- a/api_docs/kbn_aiops_log_rate_analysis.mdx +++ b/api_docs/kbn_aiops_log_rate_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-rate-analysis title: "@kbn/aiops-log-rate-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-rate-analysis plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-rate-analysis'] --- import kbnAiopsLogRateAnalysisObj from './kbn_aiops_log_rate_analysis.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index 227e03b0163c..6b9f8bdad791 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_comparators.mdx b/api_docs/kbn_alerting_comparators.mdx index 43d95aa8ef86..23b2ce62abda 100644 --- a/api_docs/kbn_alerting_comparators.mdx +++ b/api_docs/kbn_alerting_comparators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-comparators title: "@kbn/alerting-comparators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-comparators plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-comparators'] --- import kbnAlertingComparatorsObj from './kbn_alerting_comparators.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 5f751fe12d1c..c1fdcf7de1dc 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerting_types.mdx b/api_docs/kbn_alerting_types.mdx index b9938ed4f6e7..be586d0ae4ff 100644 --- a/api_docs/kbn_alerting_types.mdx +++ b/api_docs/kbn_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-types title: "@kbn/alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-types'] --- import kbnAlertingTypesObj from './kbn_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 1f5a2b326291..f9e83c23945b 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-as-data-utils'] --- import kbnAlertsAsDataUtilsObj from './kbn_alerts_as_data_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts_grouping.mdx b/api_docs/kbn_alerts_grouping.mdx index 5d84aaa280d2..63bc41eab719 100644 --- a/api_docs/kbn_alerts_grouping.mdx +++ b/api_docs/kbn_alerts_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-grouping title: "@kbn/alerts-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-grouping plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-grouping'] --- import kbnAlertsGroupingObj from './kbn_alerts_grouping.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 87e8e49d7851..e27bbc7f352f 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index bdcac5329ebc..493899de7549 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index 293d0fce0f36..6f18b63dec73 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index a0f2b0b10017..4478066f0513 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_data_view.mdx b/api_docs/kbn_apm_data_view.mdx index 09e6de0df472..a8f55d00181f 100644 --- a/api_docs/kbn_apm_data_view.mdx +++ b/api_docs/kbn_apm_data_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-data-view title: "@kbn/apm-data-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-data-view plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-data-view'] --- import kbnApmDataViewObj from './kbn_apm_data_view.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 884069ac587a..c711daf49a1e 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.devdocs.json b/api_docs/kbn_apm_synthtrace_client.devdocs.json index 4f458c6eb82a..5aa9b2ee2e09 100644 --- a/api_docs/kbn_apm_synthtrace_client.devdocs.json +++ b/api_docs/kbn_apm_synthtrace_client.devdocs.json @@ -3133,7 +3133,7 @@ "label": "LogDocument", "description": [], "signature": [ - "{ '@timestamp'?: number | undefined; } & Partial<{ 'input.type': string; 'log.file.path'?: string | undefined; 'service.name'?: string | undefined; 'service.environment'?: string | undefined; 'data_stream.namespace': string; 'data_stream.type': string; 'data_stream.dataset': string; message?: string | undefined; 'error.message'?: string | undefined; 'event.original'?: string | undefined; 'event.dataset': string; 'log.level'?: string | undefined; 'host.name'?: string | undefined; 'container.id'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.resource.id'?: string | undefined; 'kubernetes.pod.uid'?: string | undefined; 'aws.s3.bucket.name'?: string | undefined; 'aws.kinesis.name'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'container.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.exception.stacktrace'?: string | undefined; 'error.log.stacktrace'?: string | undefined; 'log.custom': Record; 'host.geo.location': number[]; 'host.ip': string; 'network.bytes': number; 'tls.established': boolean; 'event.duration': number; 'event.start': Date; 'event.end': Date; labels?: Record | undefined; test_field: string | string[]; date: Date; severity: string; msg: string; svc: string; hostname: string; thisisaverylongfieldnamethatevendoesnotcontainanyspaceswhyitcouldpotentiallybreakouruiinseveralplaces: string; }>" + "{ '@timestamp'?: number | undefined; } & Partial<{ _index?: string | undefined; 'input.type': string; 'log.file.path'?: string | undefined; 'service.name'?: string | undefined; 'service.environment'?: string | undefined; 'data_stream.namespace': string; 'data_stream.type': string; 'data_stream.dataset': string; message?: string | undefined; 'error.message'?: string | undefined; 'event.original'?: string | undefined; 'event.dataset': string; 'log.level'?: string | undefined; 'host.name'?: string | undefined; 'container.id'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.resource.id'?: string | undefined; 'kubernetes.pod.uid'?: string | undefined; 'aws.s3.bucket.name'?: string | undefined; 'aws.kinesis.name'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'container.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.exception.stacktrace'?: string | undefined; 'error.log.stacktrace'?: string | undefined; 'log.custom': Record; 'host.geo.location': number[]; 'host.ip': string; 'network.bytes': number; 'tls.established': boolean; 'event.duration': number; 'event.start': Date; 'event.end': Date; labels?: Record | undefined; test_field: string | string[]; date: Date; severity: string; msg: string; svc: string; hostname: string; 'http.status_code'?: number | undefined; 'http.request.method'?: string | undefined; 'url.path'?: string | undefined; 'process.name'?: string | undefined; 'kubernetes.namespace'?: string | undefined; 'kubernetes.pod.name'?: string | undefined; 'kubernetes.container.name'?: string | undefined; 'orchestrator.resource.name'?: string | undefined; thisisaverylongfieldnamethatevendoesnotcontainanyspaceswhyitcouldpotentiallybreakouruiinseveralplaces: string; }>" ], "path": "packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts", "deprecated": false, @@ -4167,6 +4167,34 @@ } ] }, + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.log.createForIndex", + "type": "Function", + "tags": [], + "label": "createForIndex", + "description": [], + "signature": [ + "(index: string) => Log" + ], + "path": "packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/apm-synthtrace-client", + "id": "def-common.log.createForIndex.$1", + "type": "string", + "tags": [], + "label": "index", + "description": [], + "path": "packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, { "parentPluginId": "@kbn/apm-synthtrace-client", "id": "def-common.log.createMinimal", diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 6ccf9a8e3977..f3c18c109873 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 268 | 0 | 268 | 38 | +| 270 | 0 | 270 | 38 | ## Common diff --git a/api_docs/kbn_apm_types.mdx b/api_docs/kbn_apm_types.mdx index 36d82070004f..142589a1c726 100644 --- a/api_docs/kbn_apm_types.mdx +++ b/api_docs/kbn_apm_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-types title: "@kbn/apm-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-types'] --- import kbnApmTypesObj from './kbn_apm_types.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 2aac59840591..9561df4fa781 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_avc_banner.mdx b/api_docs/kbn_avc_banner.mdx index a0b48fa1e328..7f90f67755e6 100644 --- a/api_docs/kbn_avc_banner.mdx +++ b/api_docs/kbn_avc_banner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-avc-banner title: "@kbn/avc-banner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/avc-banner plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/avc-banner'] --- import kbnAvcBannerObj from './kbn_avc_banner.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index e6f471f9656c..5e184651daec 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_bfetch_error.mdx b/api_docs/kbn_bfetch_error.mdx index 4b253689aead..e40bd88901e9 100644 --- a/api_docs/kbn_bfetch_error.mdx +++ b/api_docs/kbn_bfetch_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-bfetch-error title: "@kbn/bfetch-error" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/bfetch-error plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bfetch-error'] --- import kbnBfetchErrorObj from './kbn_bfetch_error.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index d6bf4cd1acc9..4ea64480de7d 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index aed5ceb8c528..78f92d67a3a1 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index c697be0daee3..b3d762f7f740 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cbor.mdx b/api_docs/kbn_cbor.mdx index 659e069e44ff..5221c426632b 100644 --- a/api_docs/kbn_cbor.mdx +++ b/api_docs/kbn_cbor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cbor title: "@kbn/cbor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cbor plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cbor'] --- import kbnCborObj from './kbn_cbor.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index c5395e4e448b..6e866f3409da 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_expressions_common.mdx b/api_docs/kbn_chart_expressions_common.mdx index ab41c47e44a1..c5b22948264b 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-expressions-common'] --- import kbnChartExpressionsCommonObj from './kbn_chart_expressions_common.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index cd2f0a75a170..eca2073a7805 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 23cd6c87dbac..78ff8ac4bb78 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 268153f5a644..d2a4c39dc3d2 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index c563d5e0d82c..b1afb22526d6 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index ddfe1518ed53..15aa4c0da0c9 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_cloud_security_posture.mdx b/api_docs/kbn_cloud_security_posture.mdx index 9a1278deb8f0..29b13d0d8d62 100644 --- a/api_docs/kbn_cloud_security_posture.mdx +++ b/api_docs/kbn_cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cloud-security-posture title: "@kbn/cloud-security-posture" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cloud-security-posture plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cloud-security-posture'] --- import kbnCloudSecurityPostureObj from './kbn_cloud_security_posture.devdocs.json'; diff --git a/api_docs/kbn_cloud_security_posture_common.mdx b/api_docs/kbn_cloud_security_posture_common.mdx index 738d8f2113a0..f542be543cf0 100644 --- a/api_docs/kbn_cloud_security_posture_common.mdx +++ b/api_docs/kbn_cloud_security_posture_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cloud-security-posture-common title: "@kbn/cloud-security-posture-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cloud-security-posture-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cloud-security-posture-common'] --- import kbnCloudSecurityPostureCommonObj from './kbn_cloud_security_posture_common.devdocs.json'; diff --git a/api_docs/kbn_cloud_security_posture_graph.mdx b/api_docs/kbn_cloud_security_posture_graph.mdx index f8e2e6ea9b86..3c4d5b472e4e 100644 --- a/api_docs/kbn_cloud_security_posture_graph.mdx +++ b/api_docs/kbn_cloud_security_posture_graph.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cloud-security-posture-graph title: "@kbn/cloud-security-posture-graph" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cloud-security-posture-graph plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cloud-security-posture-graph'] --- import kbnCloudSecurityPostureGraphObj from './kbn_cloud_security_posture_graph.devdocs.json'; diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx index 9b24b5dbe967..0f3d0be44c55 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index 13de220789e5..33ae03805b61 100644 --- a/api_docs/kbn_code_editor_mock.mdx +++ b/api_docs/kbn_code_editor_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mock title: "@kbn/code-editor-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mock plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mock'] --- import kbnCodeEditorMockObj from './kbn_code_editor_mock.devdocs.json'; diff --git a/api_docs/kbn_code_owners.mdx b/api_docs/kbn_code_owners.mdx index 1621f8ee4258..61b8f2569c39 100644 --- a/api_docs/kbn_code_owners.mdx +++ b/api_docs/kbn_code_owners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-owners title: "@kbn/code-owners" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-owners plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 27b6847eadb6..05b51dec1aa6 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 099b38ab4ae4..f2a5b134aa13 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index d703599c2574..56e0b59367be 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index f47c90480add..92b4ae88d869 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index d548eeaa1193..faf30dc214ea 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_insights_public.mdx b/api_docs/kbn_content_management_content_insights_public.mdx index 2b7a0159f9c9..a665b0db08ec 100644 --- a/api_docs/kbn_content_management_content_insights_public.mdx +++ b/api_docs/kbn_content_management_content_insights_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-insights-public title: "@kbn/content-management-content-insights-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-insights-public plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-insights-public'] --- import kbnContentManagementContentInsightsPublicObj from './kbn_content_management_content_insights_public.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_insights_server.mdx b/api_docs/kbn_content_management_content_insights_server.mdx index a222ca0e4c3c..00ce49ef6ad9 100644 --- a/api_docs/kbn_content_management_content_insights_server.mdx +++ b/api_docs/kbn_content_management_content_insights_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-insights-server title: "@kbn/content-management-content-insights-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-insights-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-insights-server'] --- import kbnContentManagementContentInsightsServerObj from './kbn_content_management_content_insights_server.devdocs.json'; diff --git a/api_docs/kbn_content_management_favorites_common.mdx b/api_docs/kbn_content_management_favorites_common.mdx index 589cea667605..2401c3a9eed8 100644 --- a/api_docs/kbn_content_management_favorites_common.mdx +++ b/api_docs/kbn_content_management_favorites_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-favorites-common title: "@kbn/content-management-favorites-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-favorites-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-favorites-common'] --- import kbnContentManagementFavoritesCommonObj from './kbn_content_management_favorites_common.devdocs.json'; diff --git a/api_docs/kbn_content_management_favorites_public.mdx b/api_docs/kbn_content_management_favorites_public.mdx index fba473922e11..7cd275df15c8 100644 --- a/api_docs/kbn_content_management_favorites_public.mdx +++ b/api_docs/kbn_content_management_favorites_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-favorites-public title: "@kbn/content-management-favorites-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-favorites-public plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-favorites-public'] --- import kbnContentManagementFavoritesPublicObj from './kbn_content_management_favorites_public.devdocs.json'; diff --git a/api_docs/kbn_content_management_favorites_server.mdx b/api_docs/kbn_content_management_favorites_server.mdx index e35d529bb607..a7b45b1118ad 100644 --- a/api_docs/kbn_content_management_favorites_server.mdx +++ b/api_docs/kbn_content_management_favorites_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-favorites-server title: "@kbn/content-management-favorites-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-favorites-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-favorites-server'] --- import kbnContentManagementFavoritesServerObj from './kbn_content_management_favorites_server.devdocs.json'; diff --git a/api_docs/kbn_content_management_tabbed_table_list_view.mdx b/api_docs/kbn_content_management_tabbed_table_list_view.mdx index 6fc52f7cda00..d456cb698a01 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-tabbed-table-list-view'] --- import kbnContentManagementTabbedTableListViewObj from './kbn_content_management_tabbed_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view.mdx b/api_docs/kbn_content_management_table_list_view.mdx index 8d62b41b446d..88bf9b727d3e 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_common.mdx b/api_docs/kbn_content_management_table_list_view_common.mdx index 701a35418d8b..ef7fa599c1fc 100644 --- a/api_docs/kbn_content_management_table_list_view_common.mdx +++ b/api_docs/kbn_content_management_table_list_view_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-common title: "@kbn/content-management-table-list-view-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-common'] --- import kbnContentManagementTableListViewCommonObj from './kbn_content_management_table_list_view_common.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_table.mdx b/api_docs/kbn_content_management_table_list_view_table.mdx index 6b75511e47a8..a03b070a94af 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_user_profiles.mdx b/api_docs/kbn_content_management_user_profiles.mdx index 5c0d1cc3c20e..eb76aa26d854 100644 --- a/api_docs/kbn_content_management_user_profiles.mdx +++ b/api_docs/kbn_content_management_user_profiles.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-user-profiles title: "@kbn/content-management-user-profiles" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-user-profiles plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-user-profiles'] --- import kbnContentManagementUserProfilesObj from './kbn_content_management_user_profiles.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index d5c8f2b832e8..15e0ea70c321 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 5d888f9e8e19..267d867411fd 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index a96d0a9c7c3f..b352c238d7bd 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 851107dcd2af..0fbce3beb191 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 01ceab5a6db8..38bf70ad3504 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index feb8588309ca..9c2d9d85b6a6 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 71240ddd9e50..7118db9267ca 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 131ac6eb4e78..0c5ca2917ab6 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index d78a586e3cc7..10a2c44231c4 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index cfb15871980a..1f904828c895 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 59d388274d9a..65edf8694b51 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 6ddfbae40907..d4666008af63 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index cfdfc46bcfb2..ba029252262c 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 2c713a196269..bdda7cc75c86 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 3faac6cedfd1..30a6893edfec 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 8587dc91c9f4..68b2f28c9956 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index ab757cbe9310..0df60c14545e 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 2da05d9aba1e..7f1444be35c4 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 764ecf540aa6..b97dcc9c375c 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 480ad43ef53a..41a74ff22235 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index c67d10860a5b..24707882c708 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 335a90785cee..c7dd4f86977b 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.devdocs.json b/api_docs/kbn_core_chrome_browser.devdocs.json index 31736959b737..acc96d2631c6 100644 --- a/api_docs/kbn_core_chrome_browser.devdocs.json +++ b/api_docs/kbn_core_chrome_browser.devdocs.json @@ -3765,7 +3765,7 @@ "label": "AppDeepLinkId", "description": [], "signature": [ - "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"searchInferenceEndpoints\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"serverlessWebCrawlers\" | \"searchPlayground\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"searchInferenceEndpoints:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"enterpriseSearchElasticsearch\" | \"enterpriseSearchVectorSearch\" | \"enterpriseSearchSemanticSearch\" | \"enterpriseSearchAISearch\" | \"elasticsearchIndices:createIndex\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:entity_analytics-entity_store_management\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" + "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"streams\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"searchInferenceEndpoints\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"serverlessWebCrawlers\" | \"searchPlayground\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"searchInferenceEndpoints:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"enterpriseSearchElasticsearch\" | \"enterpriseSearchVectorSearch\" | \"enterpriseSearchSemanticSearch\" | \"enterpriseSearchAISearch\" | \"elasticsearchIndices:createIndex\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"streams:overview\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:siem_migrations-rules\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:entity_analytics-entity_store_management\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" ], "path": "packages/core/chrome/core-chrome-browser/src/project_navigation.ts", "deprecated": false, diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 33f8241674b9..fa7a8a730abd 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index d5b7e4454923..bb08e7f0eda1 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 1bba607cb3f6..32b03b52c9bb 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index 7c6ab7db0cbf..3e3b48ddedc0 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index e6fe5825fbff..24be7286254c 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index 247295e4e63c..111cac56cacc 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index 6f7f8299af05..6dd2817d6374 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index e9722ceb4d94..b715a4d982d5 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index 61e3e3c076d1..3dda0b58ca34 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index 06b94cefce40..c1e766b0727e 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 607596364265..e02b162eeca9 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 2cbe84a62163..f3c0c714f7e8 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index d02d7e87f5af..a97b31689f76 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index ccd03569c962..b81a1e4e62b0 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index d66af619bc32..611fbd7a8cb7 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index c892400bed14..b6f0f5d4fcf7 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index ab7fa3db02c1..25e8f688c005 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index f8662c213dad..104ea341c319 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index ef773d4c4220..53b3b839bd21 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 43271d559d31..496677584445 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index eab6c0e40491..d5cef304bdfe 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 97fd26cd248a..cbd6cb06bb6a 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 21890bdc6331..ee42aa49459e 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 5a1083a8b701..3d6201efba21 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index e3e49a82e86b..79f41d1118a0 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 7c68ac60b26d..7af806feed6b 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 717dd5badeb5..edc8af60646c 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index d69b311845fd..7fd2356dbd86 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 27c7b6d2b1f9..9512ae2112ef 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 955a5ef82a17..62d94a3457f8 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 4875e794c934..54e8db879097 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index cbd685068f5a..bcfe3d579e30 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index d70801f80395..df2639917449 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index f6818880972e..a76c0b3b7123 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index cfe0ddc1e112..c0e457ff10f3 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 0c3b16f486f4..411a43b275fe 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 2c4f6c27ad7b..3384e5544bd4 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_browser.mdx b/api_docs/kbn_core_feature_flags_browser.mdx index d464b87362d4..7ce942930de0 100644 --- a/api_docs/kbn_core_feature_flags_browser.mdx +++ b/api_docs/kbn_core_feature_flags_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-browser title: "@kbn/core-feature-flags-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-browser'] --- import kbnCoreFeatureFlagsBrowserObj from './kbn_core_feature_flags_browser.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_browser_internal.mdx b/api_docs/kbn_core_feature_flags_browser_internal.mdx index 76222bb9aa5c..76cb37d55558 100644 --- a/api_docs/kbn_core_feature_flags_browser_internal.mdx +++ b/api_docs/kbn_core_feature_flags_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-browser-internal title: "@kbn/core-feature-flags-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-browser-internal'] --- import kbnCoreFeatureFlagsBrowserInternalObj from './kbn_core_feature_flags_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_browser_mocks.mdx b/api_docs/kbn_core_feature_flags_browser_mocks.mdx index b138984ca2f1..2bc580208430 100644 --- a/api_docs/kbn_core_feature_flags_browser_mocks.mdx +++ b/api_docs/kbn_core_feature_flags_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-browser-mocks title: "@kbn/core-feature-flags-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-browser-mocks'] --- import kbnCoreFeatureFlagsBrowserMocksObj from './kbn_core_feature_flags_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_server.mdx b/api_docs/kbn_core_feature_flags_server.mdx index 4d2debdb3cef..103fbf14a6fe 100644 --- a/api_docs/kbn_core_feature_flags_server.mdx +++ b/api_docs/kbn_core_feature_flags_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-server title: "@kbn/core-feature-flags-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-server'] --- import kbnCoreFeatureFlagsServerObj from './kbn_core_feature_flags_server.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_server_internal.mdx b/api_docs/kbn_core_feature_flags_server_internal.mdx index 2726c519c2fc..143f2d75eda6 100644 --- a/api_docs/kbn_core_feature_flags_server_internal.mdx +++ b/api_docs/kbn_core_feature_flags_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-server-internal title: "@kbn/core-feature-flags-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-server-internal'] --- import kbnCoreFeatureFlagsServerInternalObj from './kbn_core_feature_flags_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_feature_flags_server_mocks.mdx b/api_docs/kbn_core_feature_flags_server_mocks.mdx index 8004be41a2fc..592be7bdddec 100644 --- a/api_docs/kbn_core_feature_flags_server_mocks.mdx +++ b/api_docs/kbn_core_feature_flags_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-feature-flags-server-mocks title: "@kbn/core-feature-flags-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-feature-flags-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-feature-flags-server-mocks'] --- import kbnCoreFeatureFlagsServerMocksObj from './kbn_core_feature_flags_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index 03e4f6e140b7..7efd3ed4eac8 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 1c9fcaabb80c..1900eca1e512 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index d7fe65b26195..62f92f93e281 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index b65dee6aca86..bdd589093c40 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 86a3efff281c..3c72d4e26418 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index 2e8211342921..5abd44d15588 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index f5aabce44a96..346632ac28f2 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 9deed7f9b8bb..d0e27729be14 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index 001a7f99a3c9..77008db007bf 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.devdocs.json b/api_docs/kbn_core_http_router_server_internal.devdocs.json index 219635cec955..37b9e5a564b6 100644 --- a/api_docs/kbn_core_http_router_server_internal.devdocs.json +++ b/api_docs/kbn_core_http_router_server_internal.devdocs.json @@ -179,7 +179,7 @@ }, "<", "Method", - ">, \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ">, \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -266,7 +266,7 @@ }, "<", "Method", - ">, \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ">, \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -353,7 +353,7 @@ }, "<", "Method", - ">, \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ">, \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -440,7 +440,7 @@ }, "<", "Method", - ">, \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ">, \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -527,7 +527,7 @@ }, "<", "Method", - ">, \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ">, \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 754504541882..5c60a6ba6a41 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.devdocs.json b/api_docs/kbn_core_http_router_server_mocks.devdocs.json index df244e8c6f8c..f32d02105856 100644 --- a/api_docs/kbn_core_http_router_server_mocks.devdocs.json +++ b/api_docs/kbn_core_http_router_server_mocks.devdocs.json @@ -72,7 +72,7 @@ "section": "def-server.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ", \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 1fc4c544aa37..8ca9a349bea6 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.devdocs.json b/api_docs/kbn_core_http_server.devdocs.json index 27e1693d0905..9516368c4618 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -5125,6 +5125,10 @@ "plugin": "@kbn/core-http-router-server-internal", "path": "packages/core/http/core-http-router-server-internal/src/router.test.ts" }, + { + "plugin": "@kbn/core-http-router-server-internal", + "path": "packages/core/http/core-http-router-server-internal/src/router.test.ts" + }, { "plugin": "@kbn/core-http-server-internal", "path": "packages/core/http/core-http-server-internal/src/http_server.test.ts" @@ -11569,12 +11573,12 @@ { "parentPluginId": "@kbn/core-http-server", "id": "def-server.KibanaRequestRoute.options", - "type": "Uncategorized", + "type": "CompoundType", "tags": [], "label": "options", "description": [], "signature": [ - "Method extends \"get\" | \"options\" ? Required>" + ">) & { security?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" + }, + " | undefined; }" ], "path": "packages/core/http/core-http-server/src/router/request.ts", "deprecated": false, @@ -13415,29 +13427,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "@kbn/core-http-server", - "id": "def-server.RouteConfigOptions.security", - "type": "Object", - "tags": [], - "label": "security", - "description": [ - "\nDefines the security requirements for a route, including authorization and authentication.\n" - ], - "signature": [ - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.RouteSecurity", - "text": "RouteSecurity" - }, - " | undefined" - ], - "path": "packages/core/http/core-http-server/src/router/route.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "@kbn/core-http-server", "id": "def-server.RouteConfigOptions.httpResource", @@ -15312,6 +15301,10 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/workpad/resolve.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts" + }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/templates/list.ts" @@ -16319,7 +16312,7 @@ "section": "def-server.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ", \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -16555,6 +16548,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/tags/routes/create_tag.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/update.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts" @@ -16646,7 +16643,7 @@ "section": "def-server.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ", \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -17813,7 +17810,7 @@ "section": "def-server.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ", \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -17960,7 +17957,7 @@ "section": "def-server.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ", \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -18259,7 +18256,7 @@ "section": "def-server.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ", \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -18432,7 +18429,7 @@ "section": "def-server.RouteMethod", "text": "RouteMethod" }, - ">, \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; security?: ", + ">, \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; security?: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -19355,7 +19352,7 @@ "\nRoute options: If 'GET' or 'OPTIONS' method, body options won't be returned." ], "signature": [ - "Method extends \"get\" | \"options\" ? Required>" + ">) & { security?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" + }, + " | undefined; }" ], "path": "packages/core/http/core-http-server/src/router/request.ts", "deprecated": false, @@ -20754,7 +20759,7 @@ "\nThe set of supported parseable Content-Types" ], "signature": [ - "\"application/json\" | \"multipart/form-data\" | \"application/*+json\" | \"application/octet-stream\" | \"application/x-www-form-urlencoded\" | \"text/*\"" + "\"application/json\" | \"application/*+json\" | \"application/octet-stream\" | \"application/x-www-form-urlencoded\" | \"multipart/form-data\" | \"text/*\"" ], "path": "packages/core/http/core-http-server/src/router/route.ts", "deprecated": false, @@ -21350,7 +21355,7 @@ "section": "def-server.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ", \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -21434,7 +21439,7 @@ "section": "def-server.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"security\" | \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", + ", \"description\" | \"summary\" | \"deprecated\" | \"access\" | \"discontinued\"> | undefined; access: ", { "pluginId": "@kbn/core-http-server", "scope": "server", diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index b19290dab401..8a562a43d39f 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 568 | 2 | 242 | 0 | +| 567 | 2 | 242 | 0 | ## Server diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index ffbd3a920305..f6ce2b23f50d 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index e8638dfecea8..ec4a5314bb22 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_utils.devdocs.json b/api_docs/kbn_core_http_server_utils.devdocs.json new file mode 100644 index 000000000000..176980e37b08 --- /dev/null +++ b/api_docs/kbn_core_http_server_utils.devdocs.json @@ -0,0 +1,186 @@ +{ + "id": "@kbn/core-http-server-utils", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/core-http-server-utils", + "id": "def-server.isCoreKibanaRequest", + "type": "Function", + "tags": [], + "label": "isCoreKibanaRequest", + "description": [], + "signature": [ + "(req: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + ") => boolean" + ], + "path": "packages/core/http/core-http-server-utils/src/request.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-server-utils", + "id": "def-server.isCoreKibanaRequest.$1", + "type": "Object", + "tags": [], + "label": "req", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + "" + ], + "path": "packages/core/http/core-http-server-utils/src/request.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-http-server-utils", + "id": "def-server.kibanaRequestFactory", + "type": "Function", + "tags": [], + "label": "kibanaRequestFactory", + "description": [ + "\nAllows building a KibanaRequest from a RawRequest, leveraging internal CoreKibanaRequest." + ], + "signature": [ + "(req: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RawRequest", + "text": "RawRequest" + }, + ", routeSchemas: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteValidator", + "text": "RouteValidator" + }, + " | undefined, withoutSecretHeaders: boolean) => ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + "" + ], + "path": "packages/core/http/core-http-server-utils/src/request.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-http-server-utils", + "id": "def-server.kibanaRequestFactory.$1", + "type": "CompoundType", + "tags": [], + "label": "req", + "description": [ + "The raw request to build from" + ], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RawRequest", + "text": "RawRequest" + } + ], + "path": "packages/core/http/core-http-server-utils/src/request.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-http-server-utils", + "id": "def-server.kibanaRequestFactory.$2", + "type": "CompoundType", + "tags": [], + "label": "routeSchemas", + "description": [ + "The route schemas" + ], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteValidator", + "text": "RouteValidator" + }, + " | undefined" + ], + "path": "packages/core/http/core-http-server-utils/src/request.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "@kbn/core-http-server-utils", + "id": "def-server.kibanaRequestFactory.$3", + "type": "boolean", + "tags": [], + "label": "withoutSecretHeaders", + "description": [ + "Whether we want to exclude secret headers" + ], + "signature": [ + "boolean" + ], + "path": "packages/core/http/core-http-server-utils/src/request.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "A KibanaRequest object" + ], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_http_server_utils.mdx b/api_docs/kbn_core_http_server_utils.mdx new file mode 100644 index 000000000000..4977a0fc367a --- /dev/null +++ b/api_docs/kbn_core_http_server_utils.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCoreHttpServerUtilsPluginApi +slug: /kibana-dev-docs/api/kbn-core-http-server-utils +title: "@kbn/core-http-server-utils" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-http-server-utils plugin +date: 2024-11-26 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-utils'] +--- +import kbnCoreHttpServerUtilsObj from './kbn_core_http_server_utils.devdocs.json'; + + + +Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 6 | 0 | 2 | 0 | + +## Server + +### Functions + + diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index 5a580a1b7752..480691d70371 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 9699eed30997..21782eb5714f 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index 463371554e56..d4e3eaec7478 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 23f6a924684b..c9b9678768e3 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 0ba51d7c7622..39223f647df6 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 030bfa02b8dc..8fd89cfd2324 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index a64590d21631..bca0835f19cc 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index d2942703e8d8..5969ee376fe3 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 376e105b7678..5ddd5cb62d26 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 608522910c06..2ec705801513 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index 07f8d4f9f391..f116b7bc7990 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index a1b9765e6faa..35c5cca2f2af 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index 2b1ddfd8df74..2840db365ccb 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 504d43a891f8..3db0ca776e6e 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 6e841ef68805..221e308ce39f 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index ef831e5bee87..5c1c51cbe9fa 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 05ff863b2921..94701be651f7 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 65b8292a5a92..2d03056387e6 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index b9f54c882587..6e65f11fece1 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 234d5a161be1..0602832c594a 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 83feb8627232..dc5b585178b3 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index e29ad1b58d98..5f3b2f92cc84 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 507a64006665..cf4d949c30c1 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index 5b0cae2ad8bc..eb566cc508ca 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 16f179c77d4d..6b22020a4e72 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 068488ed97b9..bae877402fbb 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index e0e46a525c7b..480a622bf6c1 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index d2dec87d697e..bedba1ce4753 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index 5b82eb18cbc9..97782f183823 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index cddb553c267f..67bd882d1ec1 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index c430edb856a6..ec6547dc5431 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 717d1d77f94b..4830fb4bffd3 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 85f7ee347864..723e9c2cfdc4 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 11976ea9766e..3630ff553b68 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_browser.mdx b/api_docs/kbn_core_plugins_contracts_browser.mdx index b4d05022952f..d5eb5a79e904 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-browser'] --- import kbnCorePluginsContractsBrowserObj from './kbn_core_plugins_contracts_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_server.mdx b/api_docs/kbn_core_plugins_contracts_server.mdx index 6dff8424326d..ff744ed15cd1 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index a17c984a639d..08e8c42ab363 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index deef563d8cd8..4c33b9e3f2e4 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 2359957a9d90..d5294c547f27 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index d4a8b82fc4fe..97f77d282995 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser.mdx b/api_docs/kbn_core_rendering_browser.mdx index be44e567beb8..2aa7a6172c55 100644 --- a/api_docs/kbn_core_rendering_browser.mdx +++ b/api_docs/kbn_core_rendering_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser title: "@kbn/core-rendering-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser'] --- import kbnCoreRenderingBrowserObj from './kbn_core_rendering_browser.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 5c808d3f2e29..99f8bbdd68be 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index 52283243fb08..8852d3279832 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index 99b1b5ecc312..30b6cb6e4cf5 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 91552b7c9ef2..f105416f683d 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 1c55050ae5fc..727938a41899 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.devdocs.json b/api_docs/kbn_core_saved_objects_api_server.devdocs.json index 7b8b82e99059..faaa62769959 100644 --- a/api_docs/kbn_core_saved_objects_api_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_server.devdocs.json @@ -2794,6 +2794,14 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/workpad/find.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts" + }, { "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts" diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index adcff2d7e818..64ad14c3df8b 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 5b0fc1d79e29..b3df5ad99e3e 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 2dbab8fcc22d..3f16dc06f0e6 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index eb38fdf1c4c2..02abc62ebc68 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 848a4a2623fe..9e0b4ffc5d0e 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 075abb20ed09..0e47974efcbe 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 5b6b6f7959b4..c4a33ed55861 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 9db14d2c5d65..73c59b3c1fd7 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 3978e0dacfd9..a8605fa69acd 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 3fc4f0a34072..a2a79b40aa42 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 53c6dc20499c..13cf9755f221 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index 64036f18a430..89314fb75028 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.devdocs.json b/api_docs/kbn_core_saved_objects_server.devdocs.json index 92271de1c5c7..b64f85ca1c4e 100644 --- a/api_docs/kbn_core_saved_objects_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server.devdocs.json @@ -6323,6 +6323,14 @@ "plugin": "canvas", "path": "x-pack/plugins/canvas/server/routes/workpad/find.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts" + }, { "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts" @@ -10739,10 +10747,6 @@ "plugin": "ml", "path": "x-pack/plugins/ml/server/saved_objects/saved_objects.ts" }, - { - "plugin": "logsShared", - "path": "x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts" - }, { "plugin": "graph", "path": "x-pack/plugins/graph/server/saved_objects/graph_workspace.ts" diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 57597abfabd6..7f7ecd59f4fc 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 39adf47b294e..71775403d09a 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 73b8eaa15644..cb59efb0ac45 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 68052c1787dd..fbc2e8fcc7e5 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser.mdx b/api_docs/kbn_core_security_browser.mdx index d3038a2b0171..43d191b769d4 100644 --- a/api_docs/kbn_core_security_browser.mdx +++ b/api_docs/kbn_core_security_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser title: "@kbn/core-security-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser'] --- import kbnCoreSecurityBrowserObj from './kbn_core_security_browser.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_internal.mdx b/api_docs/kbn_core_security_browser_internal.mdx index 6f52f8c24d48..6930b5a6a570 100644 --- a/api_docs/kbn_core_security_browser_internal.mdx +++ b/api_docs/kbn_core_security_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-internal title: "@kbn/core-security-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-internal'] --- import kbnCoreSecurityBrowserInternalObj from './kbn_core_security_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_mocks.mdx b/api_docs/kbn_core_security_browser_mocks.mdx index bf1175488fce..f3b3f7c517ed 100644 --- a/api_docs/kbn_core_security_browser_mocks.mdx +++ b/api_docs/kbn_core_security_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-mocks title: "@kbn/core-security-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-mocks'] --- import kbnCoreSecurityBrowserMocksObj from './kbn_core_security_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_security_common.mdx b/api_docs/kbn_core_security_common.mdx index 64c7d24101cd..56d6259c388c 100644 --- a/api_docs/kbn_core_security_common.mdx +++ b/api_docs/kbn_core_security_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-common title: "@kbn/core-security-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-common'] --- import kbnCoreSecurityCommonObj from './kbn_core_security_common.devdocs.json'; diff --git a/api_docs/kbn_core_security_server.mdx b/api_docs/kbn_core_security_server.mdx index 54946eb18542..7bdd563cd14e 100644 --- a/api_docs/kbn_core_security_server.mdx +++ b/api_docs/kbn_core_security_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server title: "@kbn/core-security-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server'] --- import kbnCoreSecurityServerObj from './kbn_core_security_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_internal.mdx b/api_docs/kbn_core_security_server_internal.mdx index f9c11ea068a0..e24450339d30 100644 --- a/api_docs/kbn_core_security_server_internal.mdx +++ b/api_docs/kbn_core_security_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-internal title: "@kbn/core-security-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-internal'] --- import kbnCoreSecurityServerInternalObj from './kbn_core_security_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_mocks.mdx b/api_docs/kbn_core_security_server_mocks.mdx index a99ba26c3627..c487910daf3c 100644 --- a/api_docs/kbn_core_security_server_mocks.mdx +++ b/api_docs/kbn_core_security_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-mocks title: "@kbn/core-security-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-mocks'] --- import kbnCoreSecurityServerMocksObj from './kbn_core_security_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 734085819059..7b109b2f21f2 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 53daf61b42ba..7d13b41e805e 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 7a247df20e09..8fca45621252 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 7bc3949307bb..d658583a9e66 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 066cc40fdf6a..a2320c989db9 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 6e663b8708c5..16ffcb259974 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index c237d30b1f3a..03d960a583b8 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_model_versions.mdx b/api_docs/kbn_core_test_helpers_model_versions.mdx index f4e1a55857cb..e829ad7a1006 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-model-versions'] --- import kbnCoreTestHelpersModelVersionsObj from './kbn_core_test_helpers_model_versions.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 849829d2f439..3c1b7e6c4576 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index 639c781bd03e..4d13057873d1 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 86cb763dab38..e183cf16ca3d 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index bf65c5d90f23..068860c348d9 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index 726a7116acca..8971528f75b7 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index aaf620243877..da1cecb2601b 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 5ea1a68368ee..029851960e19 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 118c3d81e60f..55f237c39ab6 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index cd959d56be01..d6534396d5e4 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 4b561f0cfcbd..9a35185a645f 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 6bf6fa18efea..1156d22947f0 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 03bb11bc217f..de6641a17e6f 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index 920328b720cb..2037d2b7ccbc 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index f72ad53aa6af..e257357f19ef 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser.mdx b/api_docs/kbn_core_user_profile_browser.mdx index 09278f132b22..93e50303950f 100644 --- a/api_docs/kbn_core_user_profile_browser.mdx +++ b/api_docs/kbn_core_user_profile_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser title: "@kbn/core-user-profile-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser'] --- import kbnCoreUserProfileBrowserObj from './kbn_core_user_profile_browser.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_internal.mdx b/api_docs/kbn_core_user_profile_browser_internal.mdx index 765814694a95..ca316a9b7440 100644 --- a/api_docs/kbn_core_user_profile_browser_internal.mdx +++ b/api_docs/kbn_core_user_profile_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-internal title: "@kbn/core-user-profile-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-internal'] --- import kbnCoreUserProfileBrowserInternalObj from './kbn_core_user_profile_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_mocks.mdx b/api_docs/kbn_core_user_profile_browser_mocks.mdx index 42a3edb63f92..ce15c5d9187c 100644 --- a/api_docs/kbn_core_user_profile_browser_mocks.mdx +++ b/api_docs/kbn_core_user_profile_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-mocks title: "@kbn/core-user-profile-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-mocks'] --- import kbnCoreUserProfileBrowserMocksObj from './kbn_core_user_profile_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_common.mdx b/api_docs/kbn_core_user_profile_common.mdx index 5db08a5f9dd1..e176da7666e8 100644 --- a/api_docs/kbn_core_user_profile_common.mdx +++ b/api_docs/kbn_core_user_profile_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-common title: "@kbn/core-user-profile-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-common'] --- import kbnCoreUserProfileCommonObj from './kbn_core_user_profile_common.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server.mdx b/api_docs/kbn_core_user_profile_server.mdx index 28ce358edf96..e905ab234b2c 100644 --- a/api_docs/kbn_core_user_profile_server.mdx +++ b/api_docs/kbn_core_user_profile_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server title: "@kbn/core-user-profile-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server'] --- import kbnCoreUserProfileServerObj from './kbn_core_user_profile_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_internal.mdx b/api_docs/kbn_core_user_profile_server_internal.mdx index d5705c5d1bd2..47bdc7923c3d 100644 --- a/api_docs/kbn_core_user_profile_server_internal.mdx +++ b/api_docs/kbn_core_user_profile_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-internal title: "@kbn/core-user-profile-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-internal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-internal'] --- import kbnCoreUserProfileServerInternalObj from './kbn_core_user_profile_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_mocks.mdx b/api_docs/kbn_core_user_profile_server_mocks.mdx index 75960cff764e..fa9cdd8477c2 100644 --- a/api_docs/kbn_core_user_profile_server_mocks.mdx +++ b/api_docs/kbn_core_user_profile_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-mocks title: "@kbn/core-user-profile-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-mocks'] --- import kbnCoreUserProfileServerMocksObj from './kbn_core_user_profile_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index 296f21f09ff6..72135202b986 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index c211fe7acde2..9d9d877ece9f 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server-mocks'] --- import kbnCoreUserSettingsServerMocksObj from './kbn_core_user_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index c7121ee5ea5a..5d6b5950fb19 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index a6ced6ad2cfd..fc844713c66e 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index dd6d173164c8..8547775b1ef8 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index 651df6d85da7..5cae0b608c83 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index ea59678ff35e..a6024600ac34 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_forge.mdx b/api_docs/kbn_data_forge.mdx index 6334bb330730..15245ae56c28 100644 --- a/api_docs/kbn_data_forge.mdx +++ b/api_docs/kbn_data_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-forge title: "@kbn/data-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-forge plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-forge'] --- import kbnDataForgeObj from './kbn_data_forge.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 3e058dd352fc..e06e5e9aa994 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_data_stream_adapter.mdx b/api_docs/kbn_data_stream_adapter.mdx index 1f0fc72d10e4..41c273ff1f69 100644 --- a/api_docs/kbn_data_stream_adapter.mdx +++ b/api_docs/kbn_data_stream_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-stream-adapter title: "@kbn/data-stream-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-stream-adapter plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_data_view_utils.mdx b/api_docs/kbn_data_view_utils.mdx index a9fcac0474ad..3f27b427ed37 100644 --- a/api_docs/kbn_data_view_utils.mdx +++ b/api_docs/kbn_data_view_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-view-utils title: "@kbn/data-view-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-view-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-view-utils'] --- import kbnDataViewUtilsObj from './kbn_data_view_utils.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 5b8803459ee2..574e2dfa7928 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_analytics.mdx b/api_docs/kbn_deeplinks_analytics.mdx index 982612308ee9..7440a8f11b69 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-analytics'] --- import kbnDeeplinksAnalyticsObj from './kbn_deeplinks_analytics.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_devtools.mdx b/api_docs/kbn_deeplinks_devtools.mdx index 63fab2fdd13c..1b1f474bcea7 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_fleet.mdx b/api_docs/kbn_deeplinks_fleet.mdx index 8d08df96a823..553e5d780394 100644 --- a/api_docs/kbn_deeplinks_fleet.mdx +++ b/api_docs/kbn_deeplinks_fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-fleet title: "@kbn/deeplinks-fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-fleet plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-fleet'] --- import kbnDeeplinksFleetObj from './kbn_deeplinks_fleet.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 4a3f2b0d3fdf..c613fe012202 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-management'] --- import kbnDeeplinksManagementObj from './kbn_deeplinks_management.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_ml.mdx b/api_docs/kbn_deeplinks_ml.mdx index 4cd8adbf6515..760ef2a625f7 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-ml'] --- import kbnDeeplinksMlObj from './kbn_deeplinks_ml.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_observability.devdocs.json b/api_docs/kbn_deeplinks_observability.devdocs.json index 09daeec62406..820e8efc49cb 100644 --- a/api_docs/kbn_deeplinks_observability.devdocs.json +++ b/api_docs/kbn_deeplinks_observability.devdocs.json @@ -857,7 +857,7 @@ "label": "AppId", "description": [], "signature": [ - "\"profiling\" | \"metrics\" | \"apm\" | \"synthetics\" | \"ux\" | \"logs\" | \"slo\" | \"observabilityAIAssistant\" | \"observability-overview\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\"" + "\"profiling\" | \"metrics\" | \"apm\" | \"synthetics\" | \"ux\" | \"logs\" | \"slo\" | \"observabilityAIAssistant\" | \"observability-overview\" | \"streams\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\"" ], "path": "packages/deeplinks/observability/deep_links.ts", "deprecated": false, @@ -938,7 +938,7 @@ "section": "def-common.AppId", "text": "AppId" }, - " | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\"" + " | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"streams:overview\"" ], "path": "packages/deeplinks/observability/deep_links.ts", "deprecated": false, diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index efa0771127fd..ce46a707aed4 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 8392d67a8cf4..1e6b5a0da7bb 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_security.devdocs.json b/api_docs/kbn_deeplinks_security.devdocs.json index 8378fc426d00..30343ed59fe1 100644 --- a/api_docs/kbn_deeplinks_security.devdocs.json +++ b/api_docs/kbn_deeplinks_security.devdocs.json @@ -58,7 +58,7 @@ "label": "DeepLinkId", "description": [], "signature": [ - "\"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:entity_analytics-entity_store_management\" | \"securitySolutionUI:coverage-overview\"" + "\"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:siem_migrations-rules\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:entity_analytics-entity_store_management\" | \"securitySolutionUI:coverage-overview\"" ], "path": "packages/deeplinks/security/index.ts", "deprecated": false, @@ -73,7 +73,7 @@ "label": "LinkId", "description": [], "signature": [ - "\"\" | \"cases\" | \"alerts\" | \"rules\" | \"policy\" | \"overview\" | \"dashboards\" | \"kubernetes\" | \"cases_create\" | \"cases_configure\" | \"hosts\" | \"users\" | \"cloud_defend-policies\" | \"cloud_security_posture-dashboard\" | \"cloud_security_posture-findings\" | \"cloud_security_posture-benchmarks\" | \"network\" | \"data_quality\" | \"explore\" | \"assets\" | \"cloud_defend\" | \"notes\" | \"administration\" | \"attack_discovery\" | \"blocklist\" | \"cloud_security_posture-rules\" | \"detections\" | \"detection_response\" | \"endpoints\" | \"event_filters\" | \"exceptions\" | \"host_isolation_exceptions\" | \"hosts-all\" | \"hosts-anomalies\" | \"hosts-risk\" | \"hosts-events\" | \"hosts-sessions\" | \"hosts-uncommon_processes\" | \"investigations\" | \"get_started\" | \"machine_learning-landing\" | \"network-anomalies\" | \"network-dns\" | \"network-events\" | \"network-flows\" | \"network-http\" | \"network-tls\" | \"response_actions_history\" | \"rules-add\" | \"rules-create\" | \"rules-landing\" | \"threat_intelligence\" | \"timelines\" | \"timelines-templates\" | \"trusted_apps\" | \"users-all\" | \"users-anomalies\" | \"users-authentications\" | \"users-events\" | \"users-risk\" | \"entity_analytics\" | \"entity_analytics-management\" | \"entity_analytics-asset-classification\" | \"entity_analytics-entity_store_management\" | \"coverage-overview\"" + "\"\" | \"cases\" | \"alerts\" | \"rules\" | \"policy\" | \"overview\" | \"dashboards\" | \"kubernetes\" | \"cases_create\" | \"cases_configure\" | \"hosts\" | \"users\" | \"cloud_defend-policies\" | \"cloud_security_posture-dashboard\" | \"cloud_security_posture-findings\" | \"cloud_security_posture-benchmarks\" | \"network\" | \"data_quality\" | \"explore\" | \"assets\" | \"cloud_defend\" | \"notes\" | \"administration\" | \"attack_discovery\" | \"blocklist\" | \"cloud_security_posture-rules\" | \"detections\" | \"detection_response\" | \"endpoints\" | \"event_filters\" | \"exceptions\" | \"host_isolation_exceptions\" | \"hosts-all\" | \"hosts-anomalies\" | \"hosts-risk\" | \"hosts-events\" | \"hosts-sessions\" | \"hosts-uncommon_processes\" | \"investigations\" | \"get_started\" | \"machine_learning-landing\" | \"network-anomalies\" | \"network-dns\" | \"network-events\" | \"network-flows\" | \"network-http\" | \"network-tls\" | \"response_actions_history\" | \"rules-add\" | \"rules-create\" | \"rules-landing\" | \"siem_migrations-rules\" | \"threat_intelligence\" | \"timelines\" | \"timelines-templates\" | \"trusted_apps\" | \"users-all\" | \"users-anomalies\" | \"users-authentications\" | \"users-events\" | \"users-risk\" | \"entity_analytics\" | \"entity_analytics-management\" | \"entity_analytics-asset-classification\" | \"entity_analytics-entity_store_management\" | \"coverage-overview\"" ], "path": "packages/deeplinks/security/index.ts", "deprecated": false, diff --git a/api_docs/kbn_deeplinks_security.mdx b/api_docs/kbn_deeplinks_security.mdx index c28689575870..cd9f9d765bdd 100644 --- a/api_docs/kbn_deeplinks_security.mdx +++ b/api_docs/kbn_deeplinks_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-security title: "@kbn/deeplinks-security" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-security plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-security'] --- import kbnDeeplinksSecurityObj from './kbn_deeplinks_security.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_shared.mdx b/api_docs/kbn_deeplinks_shared.mdx index ce2dcade76b0..775abc35805c 100644 --- a/api_docs/kbn_deeplinks_shared.mdx +++ b/api_docs/kbn_deeplinks_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-shared title: "@kbn/deeplinks-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-shared plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-shared'] --- import kbnDeeplinksSharedObj from './kbn_deeplinks_shared.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index f5966c381d01..9e2c4d9ec6cb 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-analytics'] --- import kbnDefaultNavAnalyticsObj from './kbn_default_nav_analytics.devdocs.json'; diff --git a/api_docs/kbn_default_nav_devtools.mdx b/api_docs/kbn_default_nav_devtools.mdx index 29f1cd5c163e..9788992efbc8 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-devtools'] --- import kbnDefaultNavDevtoolsObj from './kbn_default_nav_devtools.devdocs.json'; diff --git a/api_docs/kbn_default_nav_management.mdx b/api_docs/kbn_default_nav_management.mdx index a0c1f75a2f3c..146e68be6028 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-management'] --- import kbnDefaultNavManagementObj from './kbn_default_nav_management.devdocs.json'; diff --git a/api_docs/kbn_default_nav_ml.mdx b/api_docs/kbn_default_nav_ml.mdx index 224dcbe35802..635d11c0f003 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/default-nav-ml'] --- import kbnDefaultNavMlObj from './kbn_default_nav_ml.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 40cacdd38e6c..48c72e3b58e2 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 42ae2ff32248..12f4173a5491 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index 67734f03761f..b61dd65e4bc3 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index ee11b1b18c77..17e4c4bb4121 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_contextual_components.mdx b/api_docs/kbn_discover_contextual_components.mdx index 0d9311452a63..adb3f8a97b7b 100644 --- a/api_docs/kbn_discover_contextual_components.mdx +++ b/api_docs/kbn_discover_contextual_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-contextual-components title: "@kbn/discover-contextual-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-contextual-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-contextual-components'] --- import kbnDiscoverContextualComponentsObj from './kbn_discover_contextual_components.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index be69a2c77a82..95caad56a301 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index 50ca91a1d682..2c3281ac89a9 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -1024,6 +1024,20 @@ "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/doc-links", + "id": "def-common.DocLinks.inferenceManagement", + "type": "Object", + "tags": [], + "label": "inferenceManagement", + "description": [], + "signature": [ + "{ readonly inferenceAPIDocumentation: string; }" + ], + "path": "packages/kbn-doc-links/src/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index c6eea288ed76..5871dc272d70 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/docs](https://github.com/orgs/elastic/teams/docs) for question | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 78 | 0 | 78 | 2 | +| 79 | 0 | 79 | 2 | ## Common diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 7e30f6999c6f..fb871eea8a50 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index 90bbbb257363..81ffdf51aed0 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dom-drag-drop'] --- import kbnDomDragDropObj from './kbn_dom_drag_drop.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index eae977529a6c..eefd3b81ad83 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index fbadad649a50..dffd2f935e9f 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index d30d33d74565..a04d031096f7 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index d44431dc307e..cbec230aa1dd 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant_common.devdocs.json b/api_docs/kbn_elastic_assistant_common.devdocs.json index 6be43d70e741..a864611adb34 100644 --- a/api_docs/kbn_elastic_assistant_common.devdocs.json +++ b/api_docs/kbn_elastic_assistant_common.devdocs.json @@ -1368,7 +1368,7 @@ "label": "ChatCompleteProps", "description": [], "signature": [ - "{ connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }" + "{ connectorId: string; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; persist: boolean; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }" ], "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", "deprecated": false, @@ -1383,7 +1383,7 @@ "label": "ChatCompleteRequestBody", "description": [], "signature": [ - "{ connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }" + "{ connectorId: string; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; persist: boolean; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }" ], "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", "deprecated": false, @@ -1398,7 +1398,7 @@ "label": "ChatCompleteRequestBodyInput", "description": [], "signature": [ - "{ connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }" + "{ connectorId: string; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; persist: boolean; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }" ], "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", "deprecated": false, @@ -4667,7 +4667,7 @@ "label": "ChatCompleteProps", "description": [], "signature": [ - "Zod.ZodObject<{ conversationId: Zod.ZodOptional; promptId: Zod.ZodOptional; isStream: Zod.ZodOptional; responseLanguage: Zod.ZodOptional; langSmithProject: Zod.ZodOptional; langSmithApiKey: Zod.ZodOptional; connectorId: Zod.ZodString; model: Zod.ZodOptional; persist: Zod.ZodBoolean; messages: Zod.ZodArray; role: Zod.ZodEnum<[\"system\", \"user\", \"assistant\"]>; data: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; fields_to_anonymize: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }, { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }>, \"many\">; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }, { connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }>" + "Zod.ZodObject<{ conversationId: Zod.ZodOptional; promptId: Zod.ZodOptional; isStream: Zod.ZodOptional; responseLanguage: Zod.ZodOptional; langSmithProject: Zod.ZodOptional; langSmithApiKey: Zod.ZodOptional; connectorId: Zod.ZodString; model: Zod.ZodOptional; persist: Zod.ZodBoolean; messages: Zod.ZodArray; role: Zod.ZodEnum<[\"system\", \"user\", \"assistant\"]>; data: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; fields_to_anonymize: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }, { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }>, \"many\">; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; persist: boolean; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }, { connectorId: string; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; persist: boolean; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }>" ], "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", "deprecated": false, @@ -4682,7 +4682,7 @@ "label": "ChatCompleteRequestBody", "description": [], "signature": [ - "Zod.ZodObject<{ conversationId: Zod.ZodOptional; promptId: Zod.ZodOptional; isStream: Zod.ZodOptional; responseLanguage: Zod.ZodOptional; langSmithProject: Zod.ZodOptional; langSmithApiKey: Zod.ZodOptional; connectorId: Zod.ZodString; model: Zod.ZodOptional; persist: Zod.ZodBoolean; messages: Zod.ZodArray; role: Zod.ZodEnum<[\"system\", \"user\", \"assistant\"]>; data: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; fields_to_anonymize: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }, { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }>, \"many\">; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }, { connectorId: string; persist: boolean; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }>" + "Zod.ZodObject<{ conversationId: Zod.ZodOptional; promptId: Zod.ZodOptional; isStream: Zod.ZodOptional; responseLanguage: Zod.ZodOptional; langSmithProject: Zod.ZodOptional; langSmithApiKey: Zod.ZodOptional; connectorId: Zod.ZodString; model: Zod.ZodOptional; persist: Zod.ZodBoolean; messages: Zod.ZodArray; role: Zod.ZodEnum<[\"system\", \"user\", \"assistant\"]>; data: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\">>>; fields_to_anonymize: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }, { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }>, \"many\">; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; persist: boolean; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }, { connectorId: string; messages: { role: \"user\" | \"system\" | \"assistant\"; data?: Zod.objectInputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; content?: string | undefined; fields_to_anonymize?: string[] | undefined; }[]; persist: boolean; conversationId?: string | undefined; model?: string | undefined; langSmithProject?: string | undefined; langSmithApiKey?: string | undefined; promptId?: string | undefined; isStream?: boolean | undefined; responseLanguage?: string | undefined; }>" ], "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/chat/post_chat_complete_route.gen.ts", "deprecated": false, diff --git a/api_docs/kbn_elastic_assistant_common.mdx b/api_docs/kbn_elastic_assistant_common.mdx index 2eb10acbb334..1e246228760b 100644 --- a/api_docs/kbn_elastic_assistant_common.mdx +++ b/api_docs/kbn_elastic_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant-common title: "@kbn/elastic-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_entities_schema.devdocs.json b/api_docs/kbn_entities_schema.devdocs.json index ae8daadc3978..5bc7ba4f31a9 100644 --- a/api_docs/kbn_entities_schema.devdocs.json +++ b/api_docs/kbn_entities_schema.devdocs.json @@ -120,6 +120,67 @@ } ], "interfaces": [ + { + "parentPluginId": "@kbn/entities-schema", + "id": "def-common.EntityV2", + "type": "Interface", + "tags": [], + "label": "EntityV2", + "description": [], + "path": "x-pack/packages/kbn-entities-schema/src/schema/entity.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/entities-schema", + "id": "def-common.EntityV2.entity.id", + "type": "string", + "tags": [], + "label": "'entity.id'", + "description": [], + "path": "x-pack/packages/kbn-entities-schema/src/schema/entity.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/entities-schema", + "id": "def-common.EntityV2.entity.last_seen_timestamp", + "type": "string", + "tags": [], + "label": "'entity.last_seen_timestamp'", + "description": [], + "path": "x-pack/packages/kbn-entities-schema/src/schema/entity.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/entities-schema", + "id": "def-common.EntityV2.entity.type", + "type": "string", + "tags": [], + "label": "'entity.type'", + "description": [], + "path": "x-pack/packages/kbn-entities-schema/src/schema/entity.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/entities-schema", + "id": "def-common.EntityV2.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[metadata: string]: any", + "description": [], + "signature": [ + "[metadata: string]: any" + ], + "path": "x-pack/packages/kbn-entities-schema/src/schema/entity.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/entities-schema", "id": "def-common.MetadataRecord", diff --git a/api_docs/kbn_entities_schema.mdx b/api_docs/kbn_entities_schema.mdx index 3a516c423274..fe47eda50bc3 100644 --- a/api_docs/kbn_entities_schema.mdx +++ b/api_docs/kbn_entities_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-entities-schema title: "@kbn/entities-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/entities-schema plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/entities-schema'] --- import kbnEntitiesSchemaObj from './kbn_entities_schema.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entiti | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 45 | 0 | 45 | 0 | +| 50 | 0 | 50 | 0 | ## Common diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index cb66f2174f49..08801d68d922 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index a2b17af0a952..4032015f2f38 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index b6748859b82b..1e212f9c25fc 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 06f8fb413b89..a18ed7ef8bf9 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.devdocs.json b/api_docs/kbn_es_types.devdocs.json index 60ef30c14df1..ec62dd1cd61f 100644 --- a/api_docs/kbn_es_types.devdocs.json +++ b/api_docs/kbn_es_types.devdocs.json @@ -137,12 +137,13 @@ { "parentPluginId": "@kbn/es-types", "id": "def-common.ESQLSearchParams.params", - "type": "Array", + "type": "CompoundType", "tags": [], "label": "params", "description": [], "signature": [ - "Record[] | undefined" + "ScalarValue", + "[] | Record[] | undefined" ], "path": "packages/kbn-es-types/src/search.ts", "deprecated": false, diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 98658aed0aa9..b11852542ff2 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 330b36168480..b42c3cb91b8f 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_esql_ast.mdx b/api_docs/kbn_esql_ast.mdx index 4e12cf344f38..20f834e8f624 100644 --- a/api_docs/kbn_esql_ast.mdx +++ b/api_docs/kbn_esql_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-ast title: "@kbn/esql-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-ast plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-ast'] --- import kbnEsqlAstObj from './kbn_esql_ast.devdocs.json'; diff --git a/api_docs/kbn_esql_editor.mdx b/api_docs/kbn_esql_editor.mdx index 56f7c2d8f395..d498a1578740 100644 --- a/api_docs/kbn_esql_editor.mdx +++ b/api_docs/kbn_esql_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-editor title: "@kbn/esql-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-editor plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-editor'] --- import kbnEsqlEditorObj from './kbn_esql_editor.devdocs.json'; diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index cbac737ae772..0480711869f3 100644 --- a/api_docs/kbn_esql_utils.mdx +++ b/api_docs/kbn_esql_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-utils title: "@kbn/esql-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.devdocs.json'; diff --git a/api_docs/kbn_esql_validation_autocomplete.mdx b/api_docs/kbn_esql_validation_autocomplete.mdx index 33222f1b5a73..d4121d5e4786 100644 --- a/api_docs/kbn_esql_validation_autocomplete.mdx +++ b/api_docs/kbn_esql_validation_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-validation-autocomplete title: "@kbn/esql-validation-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-validation-autocomplete plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-validation-autocomplete'] --- import kbnEsqlValidationAutocompleteObj from './kbn_esql_validation_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 9307e9ac693d..5268876010da 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-common'] --- import kbnEventAnnotationCommonObj from './kbn_event_annotation_common.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_components.mdx b/api_docs/kbn_event_annotation_components.mdx index 97fa68efa5d9..228d4b621ab0 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index 45dc870d037b..62abeb43f614 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/expandable-flyout'] --- import kbnExpandableFlyoutObj from './kbn_expandable_flyout.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index b8ae3fd0355e..b9a651d13ca9 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index 2320515a141a..69a0a5df9776 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-utils'] --- import kbnFieldUtilsObj from './kbn_field_utils.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 2e2a4054aa8b..87c6de8073c6 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_formatters.mdx b/api_docs/kbn_formatters.mdx index baf66dbd0864..befe2998feec 100644 --- a/api_docs/kbn_formatters.mdx +++ b/api_docs/kbn_formatters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-formatters title: "@kbn/formatters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/formatters plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/formatters'] --- import kbnFormattersObj from './kbn_formatters.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 58fa9a6cc4b0..ba202f8df7f6 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index 8300d67a4cb5..829005b1172f 100644 --- a/api_docs/kbn_ftr_common_functional_ui_services.mdx +++ b/api_docs/kbn_ftr_common_functional_ui_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-ui-services title: "@kbn/ftr-common-functional-ui-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-ui-services plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-ui-services'] --- import kbnFtrCommonFunctionalUiServicesObj from './kbn_ftr_common_functional_ui_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 66277e39c239..42890de4a8a4 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_generate_console_definitions.mdx b/api_docs/kbn_generate_console_definitions.mdx index 623e0b9ad022..0358d5918a03 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-console-definitions'] --- import kbnGenerateConsoleDefinitionsObj from './kbn_generate_console_definitions.devdocs.json'; diff --git a/api_docs/kbn_generate_csv.devdocs.json b/api_docs/kbn_generate_csv.devdocs.json index a95a53670c1b..535dee81a98d 100644 --- a/api_docs/kbn_generate_csv.devdocs.json +++ b/api_docs/kbn_generate_csv.devdocs.json @@ -64,7 +64,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -240,7 +240,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", diff --git a/api_docs/kbn_generate_csv.mdx b/api_docs/kbn_generate_csv.mdx index 09d1275b5266..8f050dcf9035 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_grid_layout.devdocs.json b/api_docs/kbn_grid_layout.devdocs.json index a0f579f199d4..acd664c6cac8 100644 --- a/api_docs/kbn_grid_layout.devdocs.json +++ b/api_docs/kbn_grid_layout.devdocs.json @@ -11,36 +11,29 @@ "label": "GridLayout", "description": [], "signature": [ - "React.ForwardRefExoticComponent>" + "({ layout, gridSettings, renderPanelContents, onLayoutChange, }: GridLayoutProps) => React.JSX.Element" ], "path": "packages/kbn-grid-layout/grid/grid_layout.tsx", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "@kbn/grid-layout", "id": "def-public.GridLayout.$1", - "type": "Uncategorized", + "type": "Object", "tags": [], - "label": "props", + "label": "{\n layout,\n gridSettings,\n renderPanelContents,\n onLayoutChange,\n}", "description": [], "signature": [ - "P" + "GridLayoutProps" ], - "path": "node_modules/@types/react/ts5.0/index.d.ts", + "path": "packages/kbn-grid-layout/grid/grid_layout.tsx", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } ], + "returnComment": [], "initialIsOpen": false }, { @@ -121,197 +114,6 @@ } ], "interfaces": [ - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi", - "type": "Interface", - "tags": [], - "label": "GridLayoutApi", - "description": [ - "\nThe external API provided through the GridLayout component" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.addPanel", - "type": "Function", - "tags": [], - "label": "addPanel", - "description": [], - "signature": [ - "(panelId: string, placementSettings: ", - "PanelPlacementSettings", - ") => void" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.addPanel.$1", - "type": "string", - "tags": [], - "label": "panelId", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.addPanel.$2", - "type": "Object", - "tags": [], - "label": "placementSettings", - "description": [], - "signature": [ - "PanelPlacementSettings" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.removePanel", - "type": "Function", - "tags": [], - "label": "removePanel", - "description": [], - "signature": [ - "(panelId: string) => void" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.removePanel.$1", - "type": "string", - "tags": [], - "label": "panelId", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.replacePanel", - "type": "Function", - "tags": [], - "label": "replacePanel", - "description": [], - "signature": [ - "(oldPanelId: string, newPanelId: string) => void" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.replacePanel.$1", - "type": "string", - "tags": [], - "label": "oldPanelId", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.replacePanel.$2", - "type": "string", - "tags": [], - "label": "newPanelId", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.getPanelCount", - "type": "Function", - "tags": [], - "label": "getPanelCount", - "description": [], - "signature": [ - "() => number" - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/grid-layout", - "id": "def-public.GridLayoutApi.serializeState", - "type": "Function", - "tags": [], - "label": "serializeState", - "description": [], - "signature": [ - "() => ", - { - "pluginId": "@kbn/grid-layout", - "scope": "public", - "docId": "kibKbnGridLayoutPluginApi", - "section": "def-public.GridLayoutData", - "text": "GridLayoutData" - }, - " & ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.SerializableRecord", - "text": "SerializableRecord" - } - ], - "path": "packages/kbn-grid-layout/grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/grid-layout", "id": "def-public.GridPanelData", diff --git a/api_docs/kbn_grid_layout.mdx b/api_docs/kbn_grid_layout.mdx index f58b0d99a3c4..3e594a0f60ca 100644 --- a/api_docs/kbn_grid_layout.mdx +++ b/api_docs/kbn_grid_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grid-layout title: "@kbn/grid-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grid-layout plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grid-layout'] --- import kbnGridLayoutObj from './kbn_grid_layout.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 27 | 0 | 25 | 2 | +| 16 | 0 | 16 | 1 | ## Client diff --git a/api_docs/kbn_grouping.mdx b/api_docs/kbn_grouping.mdx index 5003cf263d6e..881e6c211703 100644 --- a/api_docs/kbn_grouping.mdx +++ b/api_docs/kbn_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grouping title: "@kbn/grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grouping plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grouping'] --- import kbnGroupingObj from './kbn_grouping.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 1c175a0b39b3..7f6e1dd104f3 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 0ee879ab073c..7f8f716549a4 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 2ca7941beeaf..a49ee1c98010 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index ad0f02e85fb5..584edc08a3bf 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 39e3eb556c4c..0059f412e698 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 77d931ba3b47..1216eaceccf0 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 3f39b35417e8..fb7cb2cbfe0c 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index bec8dd11134b..8e887e24edc6 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 096bf06a6a12..bb916f942f6c 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_index_adapter.mdx b/api_docs/kbn_index_adapter.mdx index 5db81d95b33b..4a02a43542bc 100644 --- a/api_docs/kbn_index_adapter.mdx +++ b/api_docs/kbn_index_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-adapter title: "@kbn/index-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-adapter plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-adapter'] --- import kbnIndexAdapterObj from './kbn_index_adapter.devdocs.json'; diff --git a/api_docs/kbn_index_lifecycle_management_common_shared.mdx b/api_docs/kbn_index_lifecycle_management_common_shared.mdx index 135ced97fb2c..9f4c1059e25e 100644 --- a/api_docs/kbn_index_lifecycle_management_common_shared.mdx +++ b/api_docs/kbn_index_lifecycle_management_common_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-lifecycle-management-common-shared title: "@kbn/index-lifecycle-management-common-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-lifecycle-management-common-shared plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-lifecycle-management-common-shared'] --- import kbnIndexLifecycleManagementCommonSharedObj from './kbn_index_lifecycle_management_common_shared.devdocs.json'; diff --git a/api_docs/kbn_index_management_shared_types.mdx b/api_docs/kbn_index_management_shared_types.mdx index 2713ec447949..4075f36aa7eb 100644 --- a/api_docs/kbn_index_management_shared_types.mdx +++ b/api_docs/kbn_index_management_shared_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-management-shared-types title: "@kbn/index-management-shared-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-management-shared-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-management-shared-types'] --- import kbnIndexManagementSharedTypesObj from './kbn_index_management_shared_types.devdocs.json'; diff --git a/api_docs/kbn_inference_common.devdocs.json b/api_docs/kbn_inference_common.devdocs.json index e087e53be14e..9cb8c900b21d 100644 --- a/api_docs/kbn_inference_common.devdocs.json +++ b/api_docs/kbn_inference_common.devdocs.json @@ -1752,6 +1752,200 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/inference-common", + "id": "def-common.BoundChatCompleteAPI", + "type": "Type", + "tags": [], + "label": "BoundChatCompleteAPI", + "description": [ + "\nVersion of {@link ChatCompleteAPI} that got pre-bound to a set of static parameters" + ], + "signature": [ + " = ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ToolOptions", + "text": "ToolOptions" + }, + ", TStream extends boolean = false>(options: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.UnboundChatCompleteOptions", + "text": "UnboundChatCompleteOptions" + }, + ") => ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ChatCompleteCompositeResponse", + "text": "ChatCompleteCompositeResponse" + }, + "" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/inference-common", + "id": "def-common.BoundChatCompleteAPI.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ [P in \"system\" | \"stream\" | \"messages\" | Exclude]: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ChatCompleteOptions", + "text": "ChatCompleteOptions" + }, + "[P]; }" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/inference-common", + "id": "def-common.BoundChatCompleteOptions", + "type": "Type", + "tags": [], + "label": "BoundChatCompleteOptions", + "description": [ + "\nStatic options used to call the {@link BoundChatCompleteAPI}" + ], + "signature": [ + "{ connectorId: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ChatCompleteOptions", + "text": "ChatCompleteOptions" + }, + "[\"connectorId\"]; functionCalling?: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ChatCompleteOptions", + "text": "ChatCompleteOptions" + }, + "[\"functionCalling\"] | undefined; }" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/inference-common", + "id": "def-common.BoundOutputAPI", + "type": "Type", + "tags": [], + "label": "BoundOutputAPI", + "description": [ + "\nVersion of {@link OutputAPI} that got pre-bound to a set of static parameters" + ], + "signature": [ + "(options: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.UnboundOutputOptions", + "text": "UnboundOutputOptions" + }, + ") => ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.OutputCompositeResponse", + "text": "OutputCompositeResponse" + }, + "" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/inference-common", + "id": "def-common.BoundOutputAPI.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ id: TId; input: string; schema?: TOutputSchema | undefined; system?: string | undefined; stream?: TStream | undefined; previousMessages?: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.Message", + "text": "Message" + }, + "[] | undefined; }" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/inference-common", + "id": "def-common.BoundOutputOptions", + "type": "Type", + "tags": [], + "label": "BoundOutputOptions", + "description": [ + "\nStatic options used to call the {@link BoundOutputAPI}" + ], + "signature": [ + "{ connectorId: string; functionCalling?: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.FunctionCallingMode", + "text": "FunctionCallingMode" + }, + " | undefined; }" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/inference-common", "id": "def-common.ChatCompleteAPI", @@ -2760,6 +2954,56 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/inference-common", + "id": "def-common.UnboundChatCompleteOptions", + "type": "Type", + "tags": [], + "label": "UnboundChatCompleteOptions", + "description": [ + "\nOptions used to call the {@link BoundChatCompleteAPI}" + ], + "signature": [ + "{ [P in \"system\" | \"stream\" | \"messages\" | Exclude]: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.ChatCompleteOptions", + "text": "ChatCompleteOptions" + }, + "[P]; }" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/inference-common", + "id": "def-common.UnboundOutputOptions", + "type": "Type", + "tags": [], + "label": "UnboundOutputOptions", + "description": [ + "\nOptions used to call the {@link BoundOutputAPI}" + ], + "signature": [ + "{ id: TId; input: string; schema?: TOutputSchema | undefined; system?: string | undefined; stream?: TStream | undefined; previousMessages?: ", + { + "pluginId": "@kbn/inference-common", + "scope": "common", + "docId": "kibKbnInferenceCommonPluginApi", + "section": "def-common.Message", + "text": "Message" + }, + "[] | undefined; }" + ], + "path": "x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/inference-common", "id": "def-common.UserMessage", diff --git a/api_docs/kbn_inference_common.mdx b/api_docs/kbn_inference_common.mdx index ce1547c6d3cf..fbafb2393079 100644 --- a/api_docs/kbn_inference_common.mdx +++ b/api_docs/kbn_inference_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-inference-common title: "@kbn/inference-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/inference-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/inference-common'] --- import kbnInferenceCommonObj from './kbn_inference_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-ai-infra](https://github.com/orgs/elastic/teams/appex-ai | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 124 | 0 | 41 | 1 | +| 132 | 0 | 43 | 1 | ## Common diff --git a/api_docs/kbn_inference_integration_flyout.mdx b/api_docs/kbn_inference_integration_flyout.mdx index 23a04d4ae5f4..381d1f5f1e09 100644 --- a/api_docs/kbn_inference_integration_flyout.mdx +++ b/api_docs/kbn_inference_integration_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-inference_integration_flyout title: "@kbn/inference_integration_flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/inference_integration_flyout plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/inference_integration_flyout'] --- import kbnInferenceIntegrationFlyoutObj from './kbn_inference_integration_flyout.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index ad3ebd1a9b10..30f9899242b7 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 8a9834176e04..9fc6b19407f7 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_investigation_shared.mdx b/api_docs/kbn_investigation_shared.mdx index e7225eeb360c..05476bbb7194 100644 --- a/api_docs/kbn_investigation_shared.mdx +++ b/api_docs/kbn_investigation_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-investigation-shared title: "@kbn/investigation-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/investigation-shared plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/investigation-shared'] --- import kbnInvestigationSharedObj from './kbn_investigation_shared.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index d6965dd06dee..685b884c1a3d 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_ipynb.mdx b/api_docs/kbn_ipynb.mdx index 2165bfe53940..a173fce5d5ca 100644 --- a/api_docs/kbn_ipynb.mdx +++ b/api_docs/kbn_ipynb.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ipynb title: "@kbn/ipynb" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ipynb plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ipynb'] --- import kbnIpynbObj from './kbn_ipynb.devdocs.json'; diff --git a/api_docs/kbn_item_buffer.mdx b/api_docs/kbn_item_buffer.mdx index 8e47cb424a0d..bdbe57dd15a7 100644 --- a/api_docs/kbn_item_buffer.mdx +++ b/api_docs/kbn_item_buffer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-item-buffer title: "@kbn/item-buffer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/item-buffer plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/item-buffer'] --- import kbnItemBufferObj from './kbn_item_buffer.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 82e1dc4bc3c8..a6d26dbe5d77 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 768d42594a67..6b22b642635c 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index d1bbb532a47d..11d6493238c4 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_json_schemas.mdx b/api_docs/kbn_json_schemas.mdx index 42abfc18e51c..5500be114a84 100644 --- a/api_docs/kbn_json_schemas.mdx +++ b/api_docs/kbn_json_schemas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-schemas title: "@kbn/json-schemas" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-schemas plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-schemas'] --- import kbnJsonSchemasObj from './kbn_json_schemas.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 66ffc6f3568c..57bf04da6d25 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_language_documentation.mdx b/api_docs/kbn_language_documentation.mdx index 3cd63be64520..090c6d270264 100644 --- a/api_docs/kbn_language_documentation.mdx +++ b/api_docs/kbn_language_documentation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation title: "@kbn/language-documentation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation'] --- import kbnLanguageDocumentationObj from './kbn_language_documentation.devdocs.json'; diff --git a/api_docs/kbn_lens_embeddable_utils.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index 21f56a35b0f8..59c1110e68f2 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_lens_formula_docs.mdx b/api_docs/kbn_lens_formula_docs.mdx index 680919600f4c..8b74ddb43264 100644 --- a/api_docs/kbn_lens_formula_docs.mdx +++ b/api_docs/kbn_lens_formula_docs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-formula-docs title: "@kbn/lens-formula-docs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-formula-docs plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-formula-docs'] --- import kbnLensFormulaDocsObj from './kbn_lens_formula_docs.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index d17fc86798e2..d02514d9dd63 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 8d4f2def7720..6a37fb4fb026 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_content_badge.mdx b/api_docs/kbn_managed_content_badge.mdx index 24cde3d8cfca..2e40f2092ce1 100644 --- a/api_docs/kbn_managed_content_badge.mdx +++ b/api_docs/kbn_managed_content_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-content-badge title: "@kbn/managed-content-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-content-badge plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-content-badge'] --- import kbnManagedContentBadgeObj from './kbn_managed_content_badge.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 57f994af85bb..ca1af907c205 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 9edd4256e0d1..ec36db9e3b5d 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-cards-navigation'] --- import kbnManagementCardsNavigationObj from './kbn_management_cards_navigation.devdocs.json'; diff --git a/api_docs/kbn_management_settings_application.mdx b/api_docs/kbn_management_settings_application.mdx index 7fdb4dda5626..5193e5ff8f9e 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-application'] --- import kbnManagementSettingsApplicationObj from './kbn_management_settings_application.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_category.mdx b/api_docs/kbn_management_settings_components_field_category.mdx index 34af07f8b23f..84444c534151 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-category'] --- import kbnManagementSettingsComponentsFieldCategoryObj from './kbn_management_settings_components_field_category.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx index 4c4fcf612045..775375de4012 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input title: "@kbn/management-settings-components-field-input" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-input plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] --- import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx index 3dbb2b98152c..a9d1e0deda03 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row title: "@kbn/management-settings-components-field-row" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-row plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] --- import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_form.mdx b/api_docs/kbn_management_settings_components_form.mdx index 56199e70a357..3994861aad1e 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-form'] --- import kbnManagementSettingsComponentsFormObj from './kbn_management_settings_components_form.devdocs.json'; diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx index 0966148dcd76..aeb6bebfbe4c 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-field-definition title: "@kbn/management-settings-field-definition" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-field-definition plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] --- import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index 1fb181ae67b3..820a887822f7 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-management-settings-ids title: "@kbn/management-settings-ids" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-ids plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; -Contact [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. +Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 6c41640a2957..ab4b2d9db3eb 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-section-registry'] --- import kbnManagementSettingsSectionRegistryObj from './kbn_management_settings_section_registry.devdocs.json'; -Contact [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. +Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_management_settings_types.mdx b/api_docs/kbn_management_settings_types.mdx index 6542ce14728d..50002e44d367 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-types title: "@kbn/management-settings-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] --- import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx index 653da624db92..816b88e23313 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-utilities title: "@kbn/management-settings-utilities" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-utilities plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] --- import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index d63cd3157b8f..d920b6d2d314 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-storybook-config'] --- import kbnManagementStorybookConfigObj from './kbn_management_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_manifest.mdx b/api_docs/kbn_manifest.mdx index f93d357409f0..aff9287c745c 100644 --- a/api_docs/kbn_manifest.mdx +++ b/api_docs/kbn_manifest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-manifest title: "@kbn/manifest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/manifest plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/manifest'] --- import kbnManifestObj from './kbn_manifest.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 8c0a09f9dcbb..9abcee14c8e4 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index ce8305ce1331..f63532d26a52 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/maps-vector-tile-utils'] --- import kbnMapsVectorTileUtilsObj from './kbn_maps_vector_tile_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 3726a016e212..07170bae0914 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index dada5e006014..103b41617b97 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_cancellable_search.mdx b/api_docs/kbn_ml_cancellable_search.mdx index 09ec612dc1fb..ca3823fca9cb 100644 --- a/api_docs/kbn_ml_cancellable_search.mdx +++ b/api_docs/kbn_ml_cancellable_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-cancellable-search title: "@kbn/ml-cancellable-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-cancellable-search plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-cancellable-search'] --- import kbnMlCancellableSearchObj from './kbn_ml_cancellable_search.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index bf591bcf91be..b00f17a2b7b5 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-category-validator'] --- import kbnMlCategoryValidatorObj from './kbn_ml_category_validator.devdocs.json'; diff --git a/api_docs/kbn_ml_chi2test.mdx b/api_docs/kbn_ml_chi2test.mdx index 68f6ebaae87b..627406f8d014 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-chi2test'] --- import kbnMlChi2testObj from './kbn_ml_chi2test.devdocs.json'; diff --git a/api_docs/kbn_ml_data_frame_analytics_utils.mdx b/api_docs/kbn_ml_data_frame_analytics_utils.mdx index 19d8b9829888..0241cecdf5dd 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-frame-analytics-utils'] --- import kbnMlDataFrameAnalyticsUtilsObj from './kbn_ml_data_frame_analytics_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_data_grid.mdx b/api_docs/kbn_ml_data_grid.mdx index 975130156ec3..4b147ace76c9 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-data-grid'] --- import kbnMlDataGridObj from './kbn_ml_data_grid.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index 022b040f9fbf..6f8012fa8bbc 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index ebefc592988f..00a4bbf3adf1 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-utils'] --- import kbnMlDateUtilsObj from './kbn_ml_date_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_error_utils.mdx b/api_docs/kbn_ml_error_utils.mdx index fd8b6b8850ab..30f0bf06b92a 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-error-utils'] --- import kbnMlErrorUtilsObj from './kbn_ml_error_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_field_stats_flyout.mdx b/api_docs/kbn_ml_field_stats_flyout.mdx index 4f575927b8e2..66fb04140bd3 100644 --- a/api_docs/kbn_ml_field_stats_flyout.mdx +++ b/api_docs/kbn_ml_field_stats_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-field-stats-flyout title: "@kbn/ml-field-stats-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-field-stats-flyout plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-field-stats-flyout'] --- import kbnMlFieldStatsFlyoutObj from './kbn_ml_field_stats_flyout.devdocs.json'; diff --git a/api_docs/kbn_ml_in_memory_table.mdx b/api_docs/kbn_ml_in_memory_table.mdx index 268e3cf529e1..193779728388 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-in-memory-table'] --- import kbnMlInMemoryTableObj from './kbn_ml_in_memory_table.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 455e8cdd3993..35993d787a09 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 60aad2e3eca4..5bebef5106bd 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_kibana_theme.mdx b/api_docs/kbn_ml_kibana_theme.mdx index 875f37d0f0c5..5b24a4e177e0 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-kibana-theme'] --- import kbnMlKibanaThemeObj from './kbn_ml_kibana_theme.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index 4ebecb6dc44e..c8e3b38ead01 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index e142c5aaf8e4..5969cc11d46b 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_number_utils.mdx b/api_docs/kbn_ml_number_utils.mdx index ff465fbdba14..ea1e230ef9fa 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-number-utils'] --- import kbnMlNumberUtilsObj from './kbn_ml_number_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_parse_interval.mdx b/api_docs/kbn_ml_parse_interval.mdx index 86849f8c9f9f..f132ae5b8d04 100644 --- a/api_docs/kbn_ml_parse_interval.mdx +++ b/api_docs/kbn_ml_parse_interval.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-parse-interval title: "@kbn/ml-parse-interval" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-parse-interval plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-parse-interval'] --- import kbnMlParseIntervalObj from './kbn_ml_parse_interval.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index a29c599aee4a..f17bc1f24795 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_random_sampler_utils.mdx b/api_docs/kbn_ml_random_sampler_utils.mdx index 7902c2616fa4..6edeadd03cb3 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-random-sampler-utils'] --- import kbnMlRandomSamplerUtilsObj from './kbn_ml_random_sampler_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_route_utils.mdx b/api_docs/kbn_ml_route_utils.mdx index d2483acaa4b4..a2b896209236 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-route-utils'] --- import kbnMlRouteUtilsObj from './kbn_ml_route_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_runtime_field_utils.mdx b/api_docs/kbn_ml_runtime_field_utils.mdx index 4b571294ccd0..a5f8e062d3ce 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-runtime-field-utils'] --- import kbnMlRuntimeFieldUtilsObj from './kbn_ml_runtime_field_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index a004da0392bc..3ffd7bd37080 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_time_buckets.mdx b/api_docs/kbn_ml_time_buckets.mdx index 6695d845773f..a71518a993b9 100644 --- a/api_docs/kbn_ml_time_buckets.mdx +++ b/api_docs/kbn_ml_time_buckets.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-time-buckets title: "@kbn/ml-time-buckets" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-time-buckets plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-time-buckets'] --- import kbnMlTimeBucketsObj from './kbn_ml_time_buckets.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index a531d1f2e31a..3a0432c880c8 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index 332ed3b0074c..f2105856cceb 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index a264b3273d85..6662e6b89afe 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_ml_validators.mdx b/api_docs/kbn_ml_validators.mdx index c9d587f9e799..667311c58bb8 100644 --- a/api_docs/kbn_ml_validators.mdx +++ b/api_docs/kbn_ml_validators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-validators title: "@kbn/ml-validators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-validators plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-validators'] --- import kbnMlValidatorsObj from './kbn_ml_validators.devdocs.json'; diff --git a/api_docs/kbn_mock_idp_utils.mdx b/api_docs/kbn_mock_idp_utils.mdx index 938f4be48154..b0ddbd056b34 100644 --- a/api_docs/kbn_mock_idp_utils.mdx +++ b/api_docs/kbn_mock_idp_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mock-idp-utils title: "@kbn/mock-idp-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mock-idp-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mock-idp-utils'] --- import kbnMockIdpUtilsObj from './kbn_mock_idp_utils.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index b77f2e0cee69..53926c1c55a3 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index b8acf97eb670..65d5efb93daf 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning'] --- import kbnObjectVersioningObj from './kbn_object_versioning.devdocs.json'; diff --git a/api_docs/kbn_object_versioning_utils.mdx b/api_docs/kbn_object_versioning_utils.mdx index 8a706345b2cb..826a6d8dd2c1 100644 --- a/api_docs/kbn_object_versioning_utils.mdx +++ b/api_docs/kbn_object_versioning_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning-utils title: "@kbn/object-versioning-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/object-versioning-utils'] --- import kbnObjectVersioningUtilsObj from './kbn_object_versioning_utils.devdocs.json'; diff --git a/api_docs/kbn_observability_alert_details.mdx b/api_docs/kbn_observability_alert_details.mdx index 94583b2a2fec..56e416d7641d 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_rule_utils.mdx b/api_docs/kbn_observability_alerting_rule_utils.mdx index d4f433a3b1ea..dde8f5839cbe 100644 --- a/api_docs/kbn_observability_alerting_rule_utils.mdx +++ b/api_docs/kbn_observability_alerting_rule_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-rule-utils title: "@kbn/observability-alerting-rule-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-rule-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-rule-utils'] --- import kbnObservabilityAlertingRuleUtilsObj from './kbn_observability_alerting_rule_utils.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index 664d40292237..1d555ed8230f 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx index 5e4e6fa21f6c..f3380e3aa41e 100644 --- a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx +++ b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-get-padded-alert-time-range-util title: "@kbn/observability-get-padded-alert-time-range-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-get-padded-alert-time-range-util plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-get-padded-alert-time-range-util'] --- import kbnObservabilityGetPaddedAlertTimeRangeUtilObj from './kbn_observability_get_padded_alert_time_range_util.devdocs.json'; diff --git a/api_docs/kbn_observability_logs_overview.mdx b/api_docs/kbn_observability_logs_overview.mdx index 2fcc35634218..0a9c546304bd 100644 --- a/api_docs/kbn_observability_logs_overview.mdx +++ b/api_docs/kbn_observability_logs_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-logs-overview title: "@kbn/observability-logs-overview" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-logs-overview plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-logs-overview'] --- import kbnObservabilityLogsOverviewObj from './kbn_observability_logs_overview.devdocs.json'; diff --git a/api_docs/kbn_observability_synthetics_test_data.mdx b/api_docs/kbn_observability_synthetics_test_data.mdx index bb7fae7b1717..5b65c8600564 100644 --- a/api_docs/kbn_observability_synthetics_test_data.mdx +++ b/api_docs/kbn_observability_synthetics_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-synthetics-test-data title: "@kbn/observability-synthetics-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-synthetics-test-data plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-synthetics-test-data'] --- import kbnObservabilitySyntheticsTestDataObj from './kbn_observability_synthetics_test_data.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index 1edc45118eeb..408b93acb734 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index 8393499eb7e2..2cb8fea42618 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 013ae859c61e..e7bbc53b6b1f 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index ee607a39ebf2..78a27b3faa5d 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 823b5edffeac..51955ddbfa84 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index 99e5b146df19..cbabc2da6b75 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index f97a6c95e3e5..a6a447d77268 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_check.mdx b/api_docs/kbn_plugin_check.mdx index 71deab480231..e532cb43b974 100644 --- a/api_docs/kbn_plugin_check.mdx +++ b/api_docs/kbn_plugin_check.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-check title: "@kbn/plugin-check" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-check plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 6bd5baeb46fa..a621e1637afd 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 9f2877a4ed34..fe2913bec81f 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index 2830633d8462..c5cb58046e3e 100644 --- a/api_docs/kbn_presentation_containers.mdx +++ b/api_docs/kbn_presentation_containers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-containers title: "@kbn/presentation-containers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-containers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.devdocs.json'; diff --git a/api_docs/kbn_presentation_publishing.devdocs.json b/api_docs/kbn_presentation_publishing.devdocs.json index 59b096c6077e..fef2bc2149a7 100644 --- a/api_docs/kbn_presentation_publishing.devdocs.json +++ b/api_docs/kbn_presentation_publishing.devdocs.json @@ -930,6 +930,46 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/presentation-publishing", + "id": "def-public.apiPublishesRendered", + "type": "Function", + "tags": [], + "label": "apiPublishesRendered", + "description": [], + "signature": [ + "(unknownApi: unknown) => unknownApi is ", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "public", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-public.PublishesRendered", + "text": "PublishesRendered" + } + ], + "path": "packages/presentation/presentation_publishing/interfaces/publishes_rendered.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/presentation-publishing", + "id": "def-public.apiPublishesRendered.$1", + "type": "Unknown", + "tags": [], + "label": "unknownApi", + "description": [], + "signature": [ + "unknown" + ], + "path": "packages/presentation/presentation_publishing/interfaces/publishes_rendered.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/presentation-publishing", "id": "def-public.apiPublishesSavedObjectId", @@ -6068,6 +6108,184 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/presentation-publishing", + "id": "def-public.PublishesRendered", + "type": "Interface", + "tags": [], + "label": "PublishesRendered", + "description": [], + "path": "packages/presentation/presentation_publishing/interfaces/publishes_rendered.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/presentation-publishing", + "id": "def-public.PublishesRendered.rendered$", + "type": "Object", + "tags": [], + "label": "rendered$", + "description": [], + "signature": [ + "{ source: ", + "Observable", + " | undefined; readonly value: boolean; error: (err: any) => void; forEach: { (next: (value: boolean) => void): Promise; (next: (value: boolean) => void, promiseCtor: PromiseConstructorLike): Promise; }; complete: () => void; getValue: () => boolean; closed: boolean; pipe: { (): ", + "Observable", + "; (op1: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + ", op7: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + ", op7: ", + "OperatorFunction", + ", op8: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + ", op7: ", + "OperatorFunction", + ", op8: ", + "OperatorFunction", + ", op9: ", + "OperatorFunction", + "): ", + "Observable", + "; (op1: ", + "OperatorFunction", + ", op2: ", + "OperatorFunction", + ", op3: ", + "OperatorFunction", + ", op4: ", + "OperatorFunction", + ", op5: ", + "OperatorFunction", + ", op6: ", + "OperatorFunction", + ", op7: ", + "OperatorFunction", + ", op8: ", + "OperatorFunction", + ", op9: ", + "OperatorFunction", + ", ...operations: ", + "OperatorFunction", + "[]): ", + "Observable", + "; }; operator: ", + "Operator", + " | undefined; lift: (operator: ", + "Operator", + ") => ", + "Observable", + "; subscribe: { (observerOrNext?: Partial<", + "Observer", + "> | ((value: boolean) => void) | undefined): ", + "Subscription", + "; (next?: ((value: boolean) => void) | null | undefined, error?: ((error: any) => void) | null | undefined, complete?: (() => void) | null | undefined): ", + "Subscription", + "; }; toPromise: { (): Promise; (PromiseCtor: PromiseConstructor): Promise; (PromiseCtor: PromiseConstructorLike): Promise; }; observers: ", + "Observer", + "[]; isStopped: boolean; hasError: boolean; thrownError: any; unsubscribe: () => void; readonly observed: boolean; asObservable: () => ", + "Observable", + "; }" + ], + "path": "packages/presentation/presentation_publishing/interfaces/publishes_rendered.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/presentation-publishing", "id": "def-public.PublishesSavedObjectId", diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index b30f4dc45fcc..24a4bb038845 100644 --- a/api_docs/kbn_presentation_publishing.mdx +++ b/api_docs/kbn_presentation_publishing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-publishing title: "@kbn/presentation-publishing" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-publishing plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-publishing'] --- import kbnPresentationPublishingObj from './kbn_presentation_publishing.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kib | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 224 | 0 | 188 | 6 | +| 228 | 0 | 192 | 6 | ## Client diff --git a/api_docs/kbn_product_doc_artifact_builder.mdx b/api_docs/kbn_product_doc_artifact_builder.mdx index 858e72f200dd..cb6468a64706 100644 --- a/api_docs/kbn_product_doc_artifact_builder.mdx +++ b/api_docs/kbn_product_doc_artifact_builder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-product-doc-artifact-builder title: "@kbn/product-doc-artifact-builder" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/product-doc-artifact-builder plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/product-doc-artifact-builder'] --- import kbnProductDocArtifactBuilderObj from './kbn_product_doc_artifact_builder.devdocs.json'; diff --git a/api_docs/kbn_product_doc_common.mdx b/api_docs/kbn_product_doc_common.mdx index ba55e2573cd2..cc22add6ac4a 100644 --- a/api_docs/kbn_product_doc_common.mdx +++ b/api_docs/kbn_product_doc_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-product-doc-common title: "@kbn/product-doc-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/product-doc-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/product-doc-common'] --- import kbnProductDocCommonObj from './kbn_product_doc_common.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index ea28e0f2cdc7..6c955c46d272 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index d006435aa3b3..e64acc715b60 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/random-sampling'] --- import kbnRandomSamplingObj from './kbn_random_sampling.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 88481df04004..0e0c3f92862b 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_hooks.mdx b/api_docs/kbn_react_hooks.mdx index 3e154254260e..3e2055792400 100644 --- a/api_docs/kbn_react_hooks.mdx +++ b/api_docs/kbn_react_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-hooks title: "@kbn/react-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-hooks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-hooks'] --- import kbnReactHooksObj from './kbn_react_hooks.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index 268de5fe0379..44cf43856620 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-common'] --- import kbnReactKibanaContextCommonObj from './kbn_react_kibana_context_common.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_render.mdx b/api_docs/kbn_react_kibana_context_render.mdx index ee2145a96db9..aaa22aa53313 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-render'] --- import kbnReactKibanaContextRenderObj from './kbn_react_kibana_context_render.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_root.mdx b/api_docs/kbn_react_kibana_context_root.mdx index 95fd38d25ed1..fab748b70489 100644 --- a/api_docs/kbn_react_kibana_context_root.mdx +++ b/api_docs/kbn_react_kibana_context_root.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-root title: "@kbn/react-kibana-context-root" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-root plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-root'] --- import kbnReactKibanaContextRootObj from './kbn_react_kibana_context_root.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_styled.mdx b/api_docs/kbn_react_kibana_context_styled.mdx index c541352cbe70..989800844a42 100644 --- a/api_docs/kbn_react_kibana_context_styled.mdx +++ b/api_docs/kbn_react_kibana_context_styled.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-styled title: "@kbn/react-kibana-context-styled" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-styled plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-styled'] --- import kbnReactKibanaContextStyledObj from './kbn_react_kibana_context_styled.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_theme.mdx b/api_docs/kbn_react_kibana_context_theme.mdx index f92763c117d2..f49c2cde9409 100644 --- a/api_docs/kbn_react_kibana_context_theme.mdx +++ b/api_docs/kbn_react_kibana_context_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-theme title: "@kbn/react-kibana-context-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-theme plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-context-theme'] --- import kbnReactKibanaContextThemeObj from './kbn_react_kibana_context_theme.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_mount.mdx b/api_docs/kbn_react_kibana_mount.mdx index fc621f98049c..bf3050ab859c 100644 --- a/api_docs/kbn_react_kibana_mount.mdx +++ b/api_docs/kbn_react_kibana_mount.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-mount title: "@kbn/react-kibana-mount" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-mount plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_recently_accessed.mdx b/api_docs/kbn_recently_accessed.mdx index ad2c16e4f4ba..4940bb999e10 100644 --- a/api_docs/kbn_recently_accessed.mdx +++ b/api_docs/kbn_recently_accessed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-recently-accessed title: "@kbn/recently-accessed" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/recently-accessed plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/recently-accessed'] --- import kbnRecentlyAccessedObj from './kbn_recently_accessed.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index 0f76a20cc709..9e98e6924eff 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index 140cf699c487..b8cd9d852638 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index 5aeecde1e53f..40bb77a397c0 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.devdocs.json b/api_docs/kbn_repo_source_classifier.devdocs.json index 0d96329a321b..05f9cc5a8323 100644 --- a/api_docs/kbn_repo_source_classifier.devdocs.json +++ b/api_docs/kbn_repo_source_classifier.devdocs.json @@ -76,7 +76,13 @@ "description": [], "signature": [ "(absolute: string) => ", - "ModuleId" + { + "pluginId": "@kbn/repo-source-classifier", + "scope": "common", + "docId": "kibKbnRepoSourceClassifierPluginApi", + "section": "def-common.ModuleId", + "text": "ModuleId" + } ], "path": "packages/kbn-repo-source-classifier/src/repo_source_classifier.ts", "deprecated": false, @@ -105,7 +111,133 @@ } ], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "@kbn/repo-source-classifier", + "id": "def-common.ModuleId", + "type": "Interface", + "tags": [], + "label": "ModuleId", + "description": [], + "path": "packages/kbn-repo-source-classifier/src/module_id.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/repo-source-classifier", + "id": "def-common.ModuleId.type", + "type": "CompoundType", + "tags": [], + "label": "type", + "description": [ + "Type of the module" + ], + "signature": [ + "\"non-package\" | \"tests or mocks\" | \"static\" | \"tooling\" | \"server package\" | \"browser package\" | \"common package\"" + ], + "path": "packages/kbn-repo-source-classifier/src/module_id.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/repo-source-classifier", + "id": "def-common.ModuleId.group", + "type": "CompoundType", + "tags": [], + "label": "group", + "description": [ + "Specifies the group to which this module belongs" + ], + "signature": [ + "\"search\" | \"security\" | \"observability\" | \"platform\" | \"common\"" + ], + "path": "packages/kbn-repo-source-classifier/src/module_id.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/repo-source-classifier", + "id": "def-common.ModuleId.visibility", + "type": "CompoundType", + "tags": [], + "label": "visibility", + "description": [ + "Specifies the module visibility, i.e. whether it can be accessed by everybody or only modules in the same group" + ], + "signature": [ + "\"private\" | \"shared\"" + ], + "path": "packages/kbn-repo-source-classifier/src/module_id.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/repo-source-classifier", + "id": "def-common.ModuleId.repoRel", + "type": "string", + "tags": [], + "label": "repoRel", + "description": [ + "repo relative path to the module's source file" + ], + "path": "packages/kbn-repo-source-classifier/src/module_id.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/repo-source-classifier", + "id": "def-common.ModuleId.pkgInfo", + "type": "Object", + "tags": [], + "label": "pkgInfo", + "description": [ + "info about the package the source file is within, in the case the file is found within a package" + ], + "signature": [ + "PkgInfo", + " | undefined" + ], + "path": "packages/kbn-repo-source-classifier/src/module_id.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/repo-source-classifier", + "id": "def-common.ModuleId.manifest", + "type": "CompoundType", + "tags": [], + "label": "manifest", + "description": [ + "The type of package, as described in the manifest" + ], + "signature": [ + "KibanaPackageManifest", + " | undefined" + ], + "path": "packages/kbn-repo-source-classifier/src/module_id.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/repo-source-classifier", + "id": "def-common.ModuleId.dirs", + "type": "Array", + "tags": [], + "label": "dirs", + "description": [ + "path segments of the dirname of this" + ], + "signature": [ + "string[]" + ], + "path": "packages/kbn-repo-source-classifier/src/module_id.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [ { diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 685c1ddb9ee9..8604d0e30b56 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; @@ -21,13 +21,16 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 6 | 0 | 6 | 1 | +| 14 | 0 | 7 | 1 | ## Common ### Classes +### Interfaces + + ### Consts, variables and types diff --git a/api_docs/kbn_reporting_common.mdx b/api_docs/kbn_reporting_common.mdx index 167b26eeef11..d8c9725139d0 100644 --- a/api_docs/kbn_reporting_common.mdx +++ b/api_docs/kbn_reporting_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-common title: "@kbn/reporting-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_csv_share_panel.mdx b/api_docs/kbn_reporting_csv_share_panel.mdx index 3a10faf6cda2..2a36aa5e07ef 100644 --- a/api_docs/kbn_reporting_csv_share_panel.mdx +++ b/api_docs/kbn_reporting_csv_share_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-csv-share-panel title: "@kbn/reporting-csv-share-panel" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-csv-share-panel plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-csv-share-panel'] --- import kbnReportingCsvSharePanelObj from './kbn_reporting_csv_share_panel.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.devdocs.json b/api_docs/kbn_reporting_export_types_csv.devdocs.json index cab900fc5f38..9e4bdaccb78c 100644 --- a/api_docs/kbn_reporting_export_types_csv.devdocs.json +++ b/api_docs/kbn_reporting_export_types_csv.devdocs.json @@ -168,7 +168,7 @@ "section": "def-server.CoreSetup", "text": "CoreSetup" }, - ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -192,7 +192,7 @@ "section": "def-server.PluginInitializerContext", "text": "PluginInitializerContext" }, - "; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -565,7 +565,7 @@ "section": "def-server.CoreSetup", "text": "CoreSetup" }, - ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -589,7 +589,7 @@ "section": "def-server.PluginInitializerContext", "text": "PluginInitializerContext" }, - "; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index af40277e0ba4..3db1848c9823 100644 --- a/api_docs/kbn_reporting_export_types_csv.mdx +++ b/api_docs/kbn_reporting_export_types_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv title: "@kbn/reporting-export-types-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv'] --- import kbnReportingExportTypesCsvObj from './kbn_reporting_export_types_csv.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv_common.mdx b/api_docs/kbn_reporting_export_types_csv_common.mdx index 95e52a05b323..cdcfec2e06e9 100644 --- a/api_docs/kbn_reporting_export_types_csv_common.mdx +++ b/api_docs/kbn_reporting_export_types_csv_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv-common title: "@kbn/reporting-export-types-csv-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv-common'] --- import kbnReportingExportTypesCsvCommonObj from './kbn_reporting_export_types_csv_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf.devdocs.json b/api_docs/kbn_reporting_export_types_pdf.devdocs.json index 9e06d9c81052..f0fb7f203f94 100644 --- a/api_docs/kbn_reporting_export_types_pdf.devdocs.json +++ b/api_docs/kbn_reporting_export_types_pdf.devdocs.json @@ -176,7 +176,7 @@ "section": "def-server.CoreSetup", "text": "CoreSetup" }, - ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -200,7 +200,7 @@ "section": "def-server.PluginInitializerContext", "text": "PluginInitializerContext" }, - "; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -597,7 +597,7 @@ "section": "def-server.CoreSetup", "text": "CoreSetup" }, - ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -621,7 +621,7 @@ "section": "def-server.PluginInitializerContext", "text": "PluginInitializerContext" }, - "; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", diff --git a/api_docs/kbn_reporting_export_types_pdf.mdx b/api_docs/kbn_reporting_export_types_pdf.mdx index e1ef628f4700..b158e11ec529 100644 --- a/api_docs/kbn_reporting_export_types_pdf.mdx +++ b/api_docs/kbn_reporting_export_types_pdf.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf title: "@kbn/reporting-export-types-pdf" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf'] --- import kbnReportingExportTypesPdfObj from './kbn_reporting_export_types_pdf.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf_common.mdx b/api_docs/kbn_reporting_export_types_pdf_common.mdx index 318defc8d37e..61fff7e6e9d3 100644 --- a/api_docs/kbn_reporting_export_types_pdf_common.mdx +++ b/api_docs/kbn_reporting_export_types_pdf_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf-common title: "@kbn/reporting-export-types-pdf-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf-common'] --- import kbnReportingExportTypesPdfCommonObj from './kbn_reporting_export_types_pdf_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png.devdocs.json b/api_docs/kbn_reporting_export_types_png.devdocs.json index 7553df09eef8..99fb9ba2966b 100644 --- a/api_docs/kbn_reporting_export_types_png.devdocs.json +++ b/api_docs/kbn_reporting_export_types_png.devdocs.json @@ -176,7 +176,7 @@ "section": "def-server.CoreSetup", "text": "CoreSetup" }, - ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + ", config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -200,7 +200,7 @@ "section": "def-server.PluginInitializerContext", "text": "PluginInitializerContext" }, - "; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", diff --git a/api_docs/kbn_reporting_export_types_png.mdx b/api_docs/kbn_reporting_export_types_png.mdx index 2779579cb2af..09f70232100a 100644 --- a/api_docs/kbn_reporting_export_types_png.mdx +++ b/api_docs/kbn_reporting_export_types_png.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png title: "@kbn/reporting-export-types-png" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png'] --- import kbnReportingExportTypesPngObj from './kbn_reporting_export_types_png.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png_common.mdx b/api_docs/kbn_reporting_export_types_png_common.mdx index 11c51ede5c77..0cf4c8e6e451 100644 --- a/api_docs/kbn_reporting_export_types_png_common.mdx +++ b/api_docs/kbn_reporting_export_types_png_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png-common title: "@kbn/reporting-export-types-png-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png-common'] --- import kbnReportingExportTypesPngCommonObj from './kbn_reporting_export_types_png_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_mocks_server.devdocs.json b/api_docs/kbn_reporting_mocks_server.devdocs.json index 043566a99e0e..2ad48d68483b 100644 --- a/api_docs/kbn_reporting_mocks_server.devdocs.json +++ b/api_docs/kbn_reporting_mocks_server.devdocs.json @@ -29,7 +29,7 @@ "signature": [ "(overrides?: ", "_DeepPartialObject", - "; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -37,7 +37,7 @@ "section": "def-common.ByteSizeValue", "text": "ByteSizeValue" }, - "; useByteOrderMarkEncoding: boolean; maxConcurrentShardRequests: number; }>; capture: Readonly<{} & { maxAttempts: number; }>; roles: Readonly<{} & { enabled: boolean; allow: string[]; }>; kibanaServer: Readonly<{ hostname?: string | undefined; protocol?: string | undefined; port?: number | undefined; } & {}>; queue: Readonly<{} & { timeout: number | moment.Duration; pollInterval: number | moment.Duration; indexInterval: string; pollEnabled: boolean; pollIntervalErrorMultiplier: number; }>; poll: Readonly<{} & { jobCompletionNotifier: Readonly<{} & { interval: number; intervalErrorMultiplier: number; }>; jobsRefresh: Readonly<{} & { interval: number; intervalErrorMultiplier: number; }>; }>; export_types: Readonly<{} & { csv: Readonly<{} & { enabled: boolean; }>; png: Readonly<{} & { enabled: boolean; }>; pdf: Readonly<{} & { enabled: boolean; }>; }>; statefulSettings: Readonly<{} & { enabled: boolean; }>; }>>) => Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; useByteOrderMarkEncoding: boolean; maxConcurrentShardRequests: number; }>; capture: Readonly<{} & { maxAttempts: number; }>; roles: Readonly<{} & { enabled: boolean; allow: string[]; }>; kibanaServer: Readonly<{ hostname?: string | undefined; protocol?: string | undefined; port?: number | undefined; } & {}>; queue: Readonly<{} & { timeout: number | moment.Duration; pollInterval: number | moment.Duration; indexInterval: string; pollEnabled: boolean; pollIntervalErrorMultiplier: number; }>; poll: Readonly<{} & { jobCompletionNotifier: Readonly<{} & { interval: number; intervalErrorMultiplier: number; }>; jobsRefresh: Readonly<{} & { interval: number; intervalErrorMultiplier: number; }>; }>; export_types: Readonly<{} & { csv: Readonly<{} & { enabled: boolean; }>; png: Readonly<{} & { enabled: boolean; }>; pdf: Readonly<{} & { enabled: boolean; }>; }>; statefulSettings: Readonly<{} & { enabled: boolean; }>; }>>) => Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -60,7 +60,7 @@ "description": [], "signature": [ "_DeepPartialObject", - "; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", diff --git a/api_docs/kbn_reporting_mocks_server.mdx b/api_docs/kbn_reporting_mocks_server.mdx index 7e797413f6e2..a33d0e142cfe 100644 --- a/api_docs/kbn_reporting_mocks_server.mdx +++ b/api_docs/kbn_reporting_mocks_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-mocks-server title: "@kbn/reporting-mocks-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-mocks-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-mocks-server'] --- import kbnReportingMocksServerObj from './kbn_reporting_mocks_server.devdocs.json'; diff --git a/api_docs/kbn_reporting_public.mdx b/api_docs/kbn_reporting_public.mdx index 217b0a357c15..cc9fbdb846d2 100644 --- a/api_docs/kbn_reporting_public.mdx +++ b/api_docs/kbn_reporting_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-public title: "@kbn/reporting-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-public plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-public'] --- import kbnReportingPublicObj from './kbn_reporting_public.devdocs.json'; diff --git a/api_docs/kbn_reporting_server.devdocs.json b/api_docs/kbn_reporting_server.devdocs.json index 2edf31cbaf9e..29373857cb9d 100644 --- a/api_docs/kbn_reporting_server.devdocs.json +++ b/api_docs/kbn_reporting_server.devdocs.json @@ -416,7 +416,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -467,7 +467,7 @@ "section": "def-server.PluginInitializerContext", "text": "PluginInitializerContext" }, - "; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -949,7 +949,7 @@ "label": "getFullRedirectAppUrl", "description": [], "signature": [ - "(config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "(config: Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -973,7 +973,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "Readonly<{ encryptionKey?: string | undefined; } & { enabled: boolean; csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -1661,7 +1661,7 @@ "label": "ReportingConfigType", "description": [], "signature": [ - "{ readonly encryptionKey?: string | undefined; readonly enabled: boolean; readonly csv: Readonly<{} & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; enablePanelActionDownload: boolean; maxSizeBytes: number | ", + "{ readonly encryptionKey?: string | undefined; readonly enabled: boolean; readonly csv: Readonly<{ enablePanelActionDownload?: boolean | undefined; } & { scroll: Readonly<{} & { size: number; duration: string; strategy: \"scroll\" | \"pit\"; }>; checkForFormulas: boolean; escapeFormulaValues: boolean; maxSizeBytes: number | ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -1949,7 +1949,7 @@ "section": "def-common.Type", "text": "Type" }, - "; maxSizeBytes: ", + "; maxSizeBytes: ", { "pluginId": "@kbn/config-schema", "scope": "common", diff --git a/api_docs/kbn_reporting_server.mdx b/api_docs/kbn_reporting_server.mdx index 0ac44f527c10..d568f4981c68 100644 --- a/api_docs/kbn_reporting_server.mdx +++ b/api_docs/kbn_reporting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-server title: "@kbn/reporting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-server'] --- import kbnReportingServerObj from './kbn_reporting_server.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index 393949e5d3c8..a0ed515f89fa 100644 --- a/api_docs/kbn_resizable_layout.mdx +++ b/api_docs/kbn_resizable_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-resizable-layout title: "@kbn/resizable-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/resizable-layout plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_response_ops_feature_flag_service.mdx b/api_docs/kbn_response_ops_feature_flag_service.mdx index f71137984935..76db300e5b1f 100644 --- a/api_docs/kbn_response_ops_feature_flag_service.mdx +++ b/api_docs/kbn_response_ops_feature_flag_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-response-ops-feature-flag-service title: "@kbn/response-ops-feature-flag-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/response-ops-feature-flag-service plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/response-ops-feature-flag-service'] --- import kbnResponseOpsFeatureFlagServiceObj from './kbn_response_ops_feature_flag_service.devdocs.json'; diff --git a/api_docs/kbn_response_ops_rule_params.mdx b/api_docs/kbn_response_ops_rule_params.mdx index 3447eb3a8ae9..37d9384669a3 100644 --- a/api_docs/kbn_response_ops_rule_params.mdx +++ b/api_docs/kbn_response_ops_rule_params.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-response-ops-rule-params title: "@kbn/response-ops-rule-params" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/response-ops-rule-params plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/response-ops-rule-params'] --- import kbnResponseOpsRuleParamsObj from './kbn_response_ops_rule_params.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 1ce608500a6d..961d1403360c 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rollup.mdx b/api_docs/kbn_rollup.mdx index 6bd74dc60ec6..e1c003215906 100644 --- a/api_docs/kbn_rollup.mdx +++ b/api_docs/kbn_rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rollup title: "@kbn/rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rollup plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rollup'] --- import kbnRollupObj from './kbn_rollup.devdocs.json'; diff --git a/api_docs/kbn_router_to_openapispec.mdx b/api_docs/kbn_router_to_openapispec.mdx index 67ec799cb6cd..0ce7b1d43446 100644 --- a/api_docs/kbn_router_to_openapispec.mdx +++ b/api_docs/kbn_router_to_openapispec.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-to-openapispec title: "@kbn/router-to-openapispec" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-to-openapispec plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-to-openapispec'] --- import kbnRouterToOpenapispecObj from './kbn_router_to_openapispec.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index 07907f2acb3b..f73bc3665d78 100644 --- a/api_docs/kbn_router_utils.mdx +++ b/api_docs/kbn_router_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-utils title: "@kbn/router-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-utils'] --- import kbnRouterUtilsObj from './kbn_router_utils.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 1fdaeff1e1e8..e7ae23fb5cd2 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 139d22577ad8..a7bf92317abc 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index b7ef961a928b..c82f7fc78e84 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/saved-objects-settings'] --- import kbnSavedObjectsSettingsObj from './kbn_saved_objects_settings.devdocs.json'; diff --git a/api_docs/kbn_screenshotting_server.mdx b/api_docs/kbn_screenshotting_server.mdx index 028819e1434b..2648777ed4d1 100644 --- a/api_docs/kbn_screenshotting_server.mdx +++ b/api_docs/kbn_screenshotting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-screenshotting-server title: "@kbn/screenshotting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/screenshotting-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/screenshotting-server'] --- import kbnScreenshottingServerObj from './kbn_screenshotting_server.devdocs.json'; diff --git a/api_docs/kbn_search_api_keys_components.mdx b/api_docs/kbn_search_api_keys_components.mdx index 7fe06e07f8b4..252c311b2585 100644 --- a/api_docs/kbn_search_api_keys_components.mdx +++ b/api_docs/kbn_search_api_keys_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-keys-components title: "@kbn/search-api-keys-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-keys-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-keys-components'] --- import kbnSearchApiKeysComponentsObj from './kbn_search_api_keys_components.devdocs.json'; diff --git a/api_docs/kbn_search_api_keys_server.mdx b/api_docs/kbn_search_api_keys_server.mdx index cf75e51bb915..12f848f054b3 100644 --- a/api_docs/kbn_search_api_keys_server.mdx +++ b/api_docs/kbn_search_api_keys_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-keys-server title: "@kbn/search-api-keys-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-keys-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-keys-server'] --- import kbnSearchApiKeysServerObj from './kbn_search_api_keys_server.devdocs.json'; diff --git a/api_docs/kbn_search_api_panels.mdx b/api_docs/kbn_search_api_panels.mdx index 826b30300ff8..85b296ba8c93 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index 9cac406cf86e..bcd0893e3c95 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-connectors title: "@kbn/search-connectors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-connectors plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index 7d8b0dce96a1..34b04e49892f 100644 --- a/api_docs/kbn_search_errors.mdx +++ b/api_docs/kbn_search_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-errors title: "@kbn/search-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-errors plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-errors'] --- import kbnSearchErrorsObj from './kbn_search_errors.devdocs.json'; diff --git a/api_docs/kbn_search_index_documents.mdx b/api_docs/kbn_search_index_documents.mdx index bbb106f76eda..241b13a85419 100644 --- a/api_docs/kbn_search_index_documents.mdx +++ b/api_docs/kbn_search_index_documents.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-index-documents title: "@kbn/search-index-documents" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-index-documents plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-index-documents'] --- import kbnSearchIndexDocumentsObj from './kbn_search_index_documents.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index a2ce01a28bf1..1bf051442a33 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_search_shared_ui.mdx b/api_docs/kbn_search_shared_ui.mdx index a24fedd8bb22..01a9b14b956b 100644 --- a/api_docs/kbn_search_shared_ui.mdx +++ b/api_docs/kbn_search_shared_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-shared-ui title: "@kbn/search-shared-ui" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-shared-ui plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-shared-ui'] --- import kbnSearchSharedUiObj from './kbn_search_shared_ui.devdocs.json'; diff --git a/api_docs/kbn_search_types.mdx b/api_docs/kbn_search_types.mdx index 9d32574f8f63..de184274e37b 100644 --- a/api_docs/kbn_search_types.mdx +++ b/api_docs/kbn_search_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-types title: "@kbn/search-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-types'] --- import kbnSearchTypesObj from './kbn_search_types.devdocs.json'; diff --git a/api_docs/kbn_security_api_key_management.mdx b/api_docs/kbn_security_api_key_management.mdx index 246de9304e53..9b98d97b9f4d 100644 --- a/api_docs/kbn_security_api_key_management.mdx +++ b/api_docs/kbn_security_api_key_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-api-key-management title: "@kbn/security-api-key-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-api-key-management plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-api-key-management'] --- import kbnSecurityApiKeyManagementObj from './kbn_security_api_key_management.devdocs.json'; diff --git a/api_docs/kbn_security_authorization_core.mdx b/api_docs/kbn_security_authorization_core.mdx index 765a316752d0..eceb53a89e3b 100644 --- a/api_docs/kbn_security_authorization_core.mdx +++ b/api_docs/kbn_security_authorization_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-authorization-core title: "@kbn/security-authorization-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-authorization-core plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-authorization-core'] --- import kbnSecurityAuthorizationCoreObj from './kbn_security_authorization_core.devdocs.json'; diff --git a/api_docs/kbn_security_authorization_core_common.mdx b/api_docs/kbn_security_authorization_core_common.mdx index 97b5fdf8cff9..31212cad0aec 100644 --- a/api_docs/kbn_security_authorization_core_common.mdx +++ b/api_docs/kbn_security_authorization_core_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-authorization-core-common title: "@kbn/security-authorization-core-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-authorization-core-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-authorization-core-common'] --- import kbnSecurityAuthorizationCoreCommonObj from './kbn_security_authorization_core_common.devdocs.json'; diff --git a/api_docs/kbn_security_form_components.mdx b/api_docs/kbn_security_form_components.mdx index ad17418d53e5..5041a0a85706 100644 --- a/api_docs/kbn_security_form_components.mdx +++ b/api_docs/kbn_security_form_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-form-components title: "@kbn/security-form-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-form-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-form-components'] --- import kbnSecurityFormComponentsObj from './kbn_security_form_components.devdocs.json'; diff --git a/api_docs/kbn_security_hardening.mdx b/api_docs/kbn_security_hardening.mdx index 4a3d8286fdd9..c34042683f47 100644 --- a/api_docs/kbn_security_hardening.mdx +++ b/api_docs/kbn_security_hardening.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-hardening title: "@kbn/security-hardening" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-hardening plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-hardening'] --- import kbnSecurityHardeningObj from './kbn_security_hardening.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index f314f5ecf75b..da80a699e6d6 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index db098adec20a..251647d177a7 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index 9e465ade356d..300152e0bb72 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_role_management_model.mdx b/api_docs/kbn_security_role_management_model.mdx index 833783d2fa7d..4c9d3f2b67ee 100644 --- a/api_docs/kbn_security_role_management_model.mdx +++ b/api_docs/kbn_security_role_management_model.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-role-management-model title: "@kbn/security-role-management-model" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-role-management-model plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-role-management-model'] --- import kbnSecurityRoleManagementModelObj from './kbn_security_role_management_model.devdocs.json'; diff --git a/api_docs/kbn_security_solution_distribution_bar.mdx b/api_docs/kbn_security_solution_distribution_bar.mdx index a560e9cd808b..25da651b8271 100644 --- a/api_docs/kbn_security_solution_distribution_bar.mdx +++ b/api_docs/kbn_security_solution_distribution_bar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-distribution-bar title: "@kbn/security-solution-distribution-bar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-distribution-bar plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-distribution-bar'] --- import kbnSecuritySolutionDistributionBarObj from './kbn_security_solution_distribution_bar.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index 08c0ad223683..66476323413b 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-features title: "@kbn/security-solution-features" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-features plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] --- import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index 0bd1f557a5c7..000b86f086ab 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-navigation'] --- import kbnSecuritySolutionNavigationObj from './kbn_security_solution_navigation.devdocs.json'; diff --git a/api_docs/kbn_security_solution_side_nav.mdx b/api_docs/kbn_security_solution_side_nav.mdx index 0d768b2c8f2d..2b59e0892a47 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-side-nav'] --- import kbnSecuritySolutionSideNavObj from './kbn_security_solution_side_nav.devdocs.json'; diff --git a/api_docs/kbn_security_solution_storybook_config.mdx b/api_docs/kbn_security_solution_storybook_config.mdx index b877e509c95a..942e93d70f4c 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-storybook-config'] --- import kbnSecuritySolutionStorybookConfigObj from './kbn_security_solution_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_security_ui_components.devdocs.json b/api_docs/kbn_security_ui_components.devdocs.json index 56319c6bea16..63e0344530ee 100644 --- a/api_docs/kbn_security_ui_components.devdocs.json +++ b/api_docs/kbn_security_ui_components.devdocs.json @@ -1,26 +1,10 @@ { "id": "@kbn/security-ui-components", "client": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { "classes": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTable", + "id": "def-public.FeatureTable", "type": "Class", "tags": [], "label": "FeatureTable", @@ -28,9 +12,9 @@ "signature": [ { "pluginId": "@kbn/security-ui-components", - "scope": "common", + "scope": "public", "docId": "kibKbnSecurityUiComponentsPluginApi", - "section": "def-common.FeatureTable", + "section": "def-public.FeatureTable", "text": "FeatureTable" }, " extends React.Component" @@ -41,7 +25,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTable.defaultProps", + "id": "def-public.FeatureTable.defaultProps", "type": "Object", "tags": [], "label": "defaultProps", @@ -52,7 +36,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTable.defaultProps.privilegeIndex", + "id": "def-public.FeatureTable.defaultProps.privilegeIndex", "type": "number", "tags": [], "label": "privilegeIndex", @@ -63,7 +47,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTable.defaultProps.showLocks", + "id": "def-public.FeatureTable.defaultProps.showLocks", "type": "boolean", "tags": [], "label": "showLocks", @@ -71,23 +55,12 @@ "path": "x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTable.defaultProps.showTitle", - "type": "boolean", - "tags": [], - "label": "showTitle", - "description": [], - "path": "x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx", - "deprecated": false, - "trackAdoption": false } ] }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTable.Unnamed", + "id": "def-public.FeatureTable.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -101,7 +74,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTable.Unnamed.$1", + "id": "def-public.FeatureTable.Unnamed.$1", "type": "Object", "tags": [], "label": "props", @@ -119,7 +92,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTable.render", + "id": "def-public.FeatureTable.render", "type": "Function", "tags": [], "label": "render", @@ -138,7 +111,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator", + "id": "def-public.PrivilegeFormCalculator", "type": "Class", "tags": [], "label": "PrivilegeFormCalculator", @@ -151,7 +124,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.Unnamed", + "id": "def-public.PrivilegeFormCalculator.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -165,7 +138,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.Unnamed.$1", + "id": "def-public.PrivilegeFormCalculator.Unnamed.$1", "type": "Object", "tags": [], "label": "kibanaPrivileges", @@ -186,7 +159,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.Unnamed.$2", + "id": "def-public.PrivilegeFormCalculator.Unnamed.$2", "type": "Object", "tags": [], "label": "role", @@ -210,7 +183,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getBasePrivilege", + "id": "def-public.PrivilegeFormCalculator.getBasePrivilege", "type": "Function", "tags": [], "label": "getBasePrivilege", @@ -234,7 +207,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getBasePrivilege.$1", + "id": "def-public.PrivilegeFormCalculator.getBasePrivilege.$1", "type": "number", "tags": [], "label": "privilegeIndex", @@ -254,7 +227,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.isWildcardBasePrivilege", + "id": "def-public.PrivilegeFormCalculator.isWildcardBasePrivilege", "type": "Function", "tags": [], "label": "isWildcardBasePrivilege", @@ -270,7 +243,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.isWildcardBasePrivilege.$1", + "id": "def-public.PrivilegeFormCalculator.isWildcardBasePrivilege.$1", "type": "number", "tags": [], "label": "privilegeIndex", @@ -290,7 +263,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getDisplayedPrimaryFeaturePrivilegeId", + "id": "def-public.PrivilegeFormCalculator.getDisplayedPrimaryFeaturePrivilegeId", "type": "Function", "tags": [], "label": "getDisplayedPrimaryFeaturePrivilegeId", @@ -306,7 +279,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getDisplayedPrimaryFeaturePrivilegeId.$1", + "id": "def-public.PrivilegeFormCalculator.getDisplayedPrimaryFeaturePrivilegeId.$1", "type": "string", "tags": [], "label": "featureId", @@ -323,7 +296,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getDisplayedPrimaryFeaturePrivilegeId.$2", + "id": "def-public.PrivilegeFormCalculator.getDisplayedPrimaryFeaturePrivilegeId.$2", "type": "number", "tags": [], "label": "privilegeIndex", @@ -340,7 +313,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getDisplayedPrimaryFeaturePrivilegeId.$3", + "id": "def-public.PrivilegeFormCalculator.getDisplayedPrimaryFeaturePrivilegeId.$3", "type": "boolean", "tags": [], "label": "allSpacesSelected", @@ -360,7 +333,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.hasCustomizedSubFeaturePrivileges", + "id": "def-public.PrivilegeFormCalculator.hasCustomizedSubFeaturePrivileges", "type": "Function", "tags": [], "label": "hasCustomizedSubFeaturePrivileges", @@ -376,7 +349,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.hasCustomizedSubFeaturePrivileges.$1", + "id": "def-public.PrivilegeFormCalculator.hasCustomizedSubFeaturePrivileges.$1", "type": "string", "tags": [], "label": "featureId", @@ -393,7 +366,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.hasCustomizedSubFeaturePrivileges.$2", + "id": "def-public.PrivilegeFormCalculator.hasCustomizedSubFeaturePrivileges.$2", "type": "number", "tags": [], "label": "privilegeIndex", @@ -410,7 +383,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.hasCustomizedSubFeaturePrivileges.$3", + "id": "def-public.PrivilegeFormCalculator.hasCustomizedSubFeaturePrivileges.$3", "type": "boolean", "tags": [], "label": "allSpacesSelected", @@ -430,7 +403,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getEffectivePrimaryFeaturePrivilege", + "id": "def-public.PrivilegeFormCalculator.getEffectivePrimaryFeaturePrivilege", "type": "Function", "tags": [], "label": "getEffectivePrimaryFeaturePrivilege", @@ -454,7 +427,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getEffectivePrimaryFeaturePrivilege.$1", + "id": "def-public.PrivilegeFormCalculator.getEffectivePrimaryFeaturePrivilege.$1", "type": "string", "tags": [], "label": "featureId", @@ -471,7 +444,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getEffectivePrimaryFeaturePrivilege.$2", + "id": "def-public.PrivilegeFormCalculator.getEffectivePrimaryFeaturePrivilege.$2", "type": "number", "tags": [], "label": "privilegeIndex", @@ -488,7 +461,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getEffectivePrimaryFeaturePrivilege.$3", + "id": "def-public.PrivilegeFormCalculator.getEffectivePrimaryFeaturePrivilege.$3", "type": "CompoundType", "tags": [], "label": "allSpacesSelected", @@ -508,7 +481,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.isIndependentSubFeaturePrivilegeGranted", + "id": "def-public.PrivilegeFormCalculator.isIndependentSubFeaturePrivilegeGranted", "type": "Function", "tags": [], "label": "isIndependentSubFeaturePrivilegeGranted", @@ -524,7 +497,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.isIndependentSubFeaturePrivilegeGranted.$1", + "id": "def-public.PrivilegeFormCalculator.isIndependentSubFeaturePrivilegeGranted.$1", "type": "string", "tags": [], "label": "featureId", @@ -541,7 +514,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.isIndependentSubFeaturePrivilegeGranted.$2", + "id": "def-public.PrivilegeFormCalculator.isIndependentSubFeaturePrivilegeGranted.$2", "type": "string", "tags": [], "label": "privilegeId", @@ -558,7 +531,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.isIndependentSubFeaturePrivilegeGranted.$3", + "id": "def-public.PrivilegeFormCalculator.isIndependentSubFeaturePrivilegeGranted.$3", "type": "number", "tags": [], "label": "privilegeIndex", @@ -578,7 +551,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege", + "id": "def-public.PrivilegeFormCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege", "type": "Function", "tags": [], "label": "getSelectedMutuallyExclusiveSubFeaturePrivilege", @@ -610,7 +583,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege.$1", + "id": "def-public.PrivilegeFormCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege.$1", "type": "string", "tags": [], "label": "featureId", @@ -627,7 +600,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege.$2", + "id": "def-public.PrivilegeFormCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege.$2", "type": "Object", "tags": [], "label": "subFeatureGroup", @@ -650,7 +623,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege.$3", + "id": "def-public.PrivilegeFormCalculator.getSelectedMutuallyExclusiveSubFeaturePrivilege.$3", "type": "number", "tags": [], "label": "privilegeIndex", @@ -670,7 +643,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.canCustomizeSubFeaturePrivileges", + "id": "def-public.PrivilegeFormCalculator.canCustomizeSubFeaturePrivileges", "type": "Function", "tags": [], "label": "canCustomizeSubFeaturePrivileges", @@ -686,7 +659,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.canCustomizeSubFeaturePrivileges.$1", + "id": "def-public.PrivilegeFormCalculator.canCustomizeSubFeaturePrivileges.$1", "type": "string", "tags": [], "label": "featureId", @@ -703,7 +676,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.canCustomizeSubFeaturePrivileges.$2", + "id": "def-public.PrivilegeFormCalculator.canCustomizeSubFeaturePrivileges.$2", "type": "number", "tags": [], "label": "privilegeIndex", @@ -723,7 +696,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization", + "id": "def-public.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization", "type": "Function", "tags": [], "label": "updateSelectedFeaturePrivilegesForCustomization", @@ -739,7 +712,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization.$1", + "id": "def-public.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization.$1", "type": "string", "tags": [], "label": "featureId", @@ -756,7 +729,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization.$2", + "id": "def-public.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization.$2", "type": "number", "tags": [], "label": "privilegeIndex", @@ -773,7 +746,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization.$3", + "id": "def-public.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization.$3", "type": "boolean", "tags": [], "label": "willBeCustomizing", @@ -790,7 +763,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization.$4", + "id": "def-public.PrivilegeFormCalculator.updateSelectedFeaturePrivilegesForCustomization.$4", "type": "boolean", "tags": [], "label": "allSpacesSelected", @@ -810,7 +783,7 @@ }, { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.hasSupersededInheritedPrivileges", + "id": "def-public.PrivilegeFormCalculator.hasSupersededInheritedPrivileges", "type": "Function", "tags": [], "label": "hasSupersededInheritedPrivileges", @@ -826,7 +799,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.PrivilegeFormCalculator.hasSupersededInheritedPrivileges.$1", + "id": "def-public.PrivilegeFormCalculator.hasSupersededInheritedPrivileges.$1", "type": "number", "tags": [], "label": "privilegeIndex", @@ -851,7 +824,7 @@ "functions": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTableCell", + "id": "def-public.FeatureTableCell", "type": "Function", "tags": [], "label": "FeatureTableCell", @@ -865,7 +838,7 @@ "children": [ { "parentPluginId": "@kbn/security-ui-components", - "id": "def-common.FeatureTableCell.$1", + "id": "def-public.FeatureTableCell.$1", "type": "Object", "tags": [], "label": "{ feature, className }", @@ -887,5 +860,21 @@ "enums": [], "misc": [], "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_security_ui_components.mdx b/api_docs/kbn_security_ui_components.mdx index 896da7b9085f..e9e91ffa77ea 100644 --- a/api_docs/kbn_security_ui_components.mdx +++ b/api_docs/kbn_security_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-ui-components title: "@kbn/security-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-ui-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-ui-components'] --- import kbnSecurityUiComponentsObj from './kbn_security_ui_components.devdocs.json'; @@ -21,13 +21,13 @@ Contact [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 48 | 0 | 13 | 0 | +| 47 | 0 | 12 | 0 | -## Common +## Client ### Functions - + ### Classes - + diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index ba7cde5516fa..99371fca4450 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_data_table.mdx b/api_docs/kbn_securitysolution_data_table.mdx index b5eb377cb1a8..bbef05d20466 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-data-table'] --- import kbnSecuritysolutionDataTableObj from './kbn_securitysolution_data_table.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 3f0d900973e6..67f457a11059 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 25e40c78c722..9620ade2aaec 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 5ace884811dd..6742cd81d244 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 4f389b1f572d..a1768c42be7b 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index cecafb173a92..70c1fb010943 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index cc30f221fb03..306ebd271f34 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 3479f2d73b12..79cda27e5917 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 1539e376529e..3d5caaa549f7 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 286984bed67c..ce1408222967 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 259170e46461..072ea012d858 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index d289c807b0b3..9c90b07f00b3 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index a09f41a4c6e4..f4e420b4a3c2 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index d79c88e21d94..f866cebd99e3 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index f01d616d2856..ea6b11f36e56 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.devdocs.json b/api_docs/kbn_securitysolution_utils.devdocs.json index 7dbb4b59056c..d799309b1821 100644 --- a/api_docs/kbn_securitysolution_utils.devdocs.json +++ b/api_docs/kbn_securitysolution_utils.devdocs.json @@ -242,6 +242,60 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/securitysolution-utils", + "id": "def-common.debounceAsync", + "type": "Function", + "tags": [], + "label": "debounceAsync", + "description": [ + "\nUnlike lodash's debounce, which resolves intermediate calls with the most\nrecent value, this implementation waits to resolve intermediate calls until\nthe next invocation resolves.\n" + ], + "signature": [ + "(fn: (...args: Args) => Result, intervalMs: number) => (...args: Args) => Promise>" + ], + "path": "packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-utils", + "id": "def-common.debounceAsync.$1", + "type": "Function", + "tags": [], + "label": "fn", + "description": [ + "an async function" + ], + "signature": [ + "(...args: Args) => Result" + ], + "path": "packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/securitysolution-utils", + "id": "def-common.debounceAsync.$2", + "type": "number", + "tags": [], + "label": "intervalMs", + "description": [], + "signature": [ + "number" + ], + "path": "packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "A debounced async function that resolves on the next invocation" + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/securitysolution-utils", "id": "def-common.getIndexListFromEsqlQuery", diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 7bbef1e9d6dc..684e99e1216c 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-detection-engine](https://github.com/orgs/elastic/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 60 | 0 | 54 | 0 | +| 63 | 0 | 55 | 0 | ## Common diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 92d6c2122c1f..14ba1d76b784 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.devdocs.json b/api_docs/kbn_server_route_repository.devdocs.json index 274e388eb054..b402826a9d60 100644 --- a/api_docs/kbn_server_route_repository.devdocs.json +++ b/api_docs/kbn_server_route_repository.devdocs.json @@ -88,7 +88,15 @@ "label": "formatRequest", "description": [], "signature": [ - "(endpoint: string, pathParams: Record) => { method: Method; pathname: string; version: string; }" + "(endpoint: string, pathParams: Record) => { method: \"get\" | ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.DestructiveRouteMethod", + "text": "DestructiveRouteMethod" + }, + "; pathname: string; version: string; }" ], "path": "packages/kbn-server-route-repository-utils/src/format_request.ts", "deprecated": false, @@ -136,7 +144,15 @@ "label": "parseEndpoint", "description": [], "signature": [ - "(endpoint: string) => { method: Method; pathname: string; version: string; }" + "(endpoint: string) => { method: \"get\" | ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.DestructiveRouteMethod", + "text": "DestructiveRouteMethod" + }, + "; pathname: string; version: string; }" ], "path": "packages/kbn-server-route-repository-utils/src/parse_endpoint.ts", "deprecated": false, @@ -185,15 +201,15 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - ">; logger: ", + " | undefined, any, any, any>>; logger: ", { "pluginId": "@kbn/logging", "scope": "common", @@ -255,15 +271,15 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - "; }" + " | undefined, any, any, any>; }" ], "path": "packages/kbn-server-route-repository/src/register_routes.ts", "deprecated": false, @@ -344,49 +360,6 @@ } ], "interfaces": [ - { - "parentPluginId": "@kbn/server-route-repository", - "id": "def-server.DefaultRouteCreateOptions", - "type": "Interface", - "tags": [], - "label": "DefaultRouteCreateOptions", - "description": [], - "path": "packages/kbn-server-route-repository-utils/src/typings.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/server-route-repository", - "id": "def-server.DefaultRouteCreateOptions.options", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.RouteConfigOptions", - "text": "RouteConfigOptions" - }, - "<", - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.RouteMethod", - "text": "RouteMethod" - }, - "> | undefined" - ], - "path": "packages/kbn-server-route-repository-utils/src/typings.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/server-route-repository", "id": "def-server.DefaultRouteHandlerResources", @@ -468,7 +441,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>) => Promise<", + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">) => Promise<", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -513,7 +494,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>" + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -539,7 +528,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>) => ", + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">) => ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -588,7 +585,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>" + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -600,34 +605,6 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/server-route-repository", - "id": "def-server.RouteState", - "type": "Interface", - "tags": [], - "label": "RouteState", - "description": [], - "path": "packages/kbn-server-route-repository-utils/src/typings.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/server-route-repository", - "id": "def-server.RouteState.Unnamed", - "type": "IndexSignature", - "tags": [], - "label": "[endpoint: string]: any", - "description": [], - "signature": [ - "[endpoint: string]: any" - ], - "path": "packages/kbn-server-route-repository-utils/src/typings.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ], "enums": [], @@ -664,7 +641,7 @@ "section": "def-common.ServerRouteCreateOptions", "text": "ServerRouteCreateOptions" }, - "> ? TRouteParamsRT extends ", + " | undefined> ? TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -703,15 +680,7 @@ "section": "def-common.RouteParamsRT", "text": "RouteParamsRT" }, - " | undefined, any, any, ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.ServerRouteCreateOptions", - "text": "ServerRouteCreateOptions" - }, - "> ? TRouteParamsRT extends ", + " | undefined, any, any, any> ? TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -726,6 +695,36 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/server-route-repository", + "id": "def-server.DefaultRouteCreateOptions", + "type": "Type", + "tags": [], + "label": "DefaultRouteCreateOptions", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteConfigOptions", + "text": "RouteConfigOptions" + }, + "<\"get\" | ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.DestructiveRouteMethod", + "text": "DestructiveRouteMethod" + }, + ">" + ], + "path": "packages/kbn-server-route-repository-utils/src/typings.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/server-route-repository", "id": "def-server.EndpointOf", @@ -782,15 +781,7 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - " ? TReturnType extends ", + " ? TReturnType extends ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -852,7 +843,15 @@ "label": "ServerRoute", "description": [], "signature": [ - "{ endpoint: TEndpoint; handler: ServerRouteHandler; } & TRouteCreateOptions & (TRouteParamsRT extends ", + "{ endpoint: TEndpoint; handler: ServerRouteHandler; security?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" + }, + " | undefined; } & (TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -860,7 +859,15 @@ "section": "def-common.RouteParamsRT", "text": "RouteParamsRT" }, - " ? { params: TRouteParamsRT; } : {})" + " ? { params: TRouteParamsRT; } : {}) & (TRouteCreateOptions extends ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.DefaultRouteCreateOptions", + "text": "DefaultRouteCreateOptions" + }, + " ? { options: TRouteCreateOptions; } : {})" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -891,7 +898,15 @@ "section": "def-common.RouteParamsRT", "text": "RouteParamsRT" }, - " | undefined, any, any, Record>; }" + " | undefined, any, any, ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRouteCreateOptions", + "text": "ServerRouteCreateOptions" + }, + " | undefined>; }" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 0bbe43feb49a..84a0b0c457bb 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 42 | 0 | 42 | 0 | +| 39 | 0 | 39 | 0 | ## Server diff --git a/api_docs/kbn_server_route_repository_client.devdocs.json b/api_docs/kbn_server_route_repository_client.devdocs.json index be6f4e7ecf2d..03e5c79e9668 100644 --- a/api_docs/kbn_server_route_repository_client.devdocs.json +++ b/api_docs/kbn_server_route_repository_client.devdocs.json @@ -180,7 +180,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>) => Promise<", + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">) => Promise<", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -225,7 +233,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>" + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -251,7 +267,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>) => ", + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">) => ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -300,7 +324,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>" + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -348,7 +380,7 @@ "section": "def-common.ServerRouteCreateOptions", "text": "ServerRouteCreateOptions" }, - "> ? TRouteParamsRT extends ", + " | undefined> ? TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", diff --git a/api_docs/kbn_server_route_repository_client.mdx b/api_docs/kbn_server_route_repository_client.mdx index f1db3dfee2bb..594a8295b991 100644 --- a/api_docs/kbn_server_route_repository_client.mdx +++ b/api_docs/kbn_server_route_repository_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository-client title: "@kbn/server-route-repository-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository-client plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository-client'] --- import kbnServerRouteRepositoryClientObj from './kbn_server_route_repository_client.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository_utils.devdocs.json b/api_docs/kbn_server_route_repository_utils.devdocs.json index 4f96f6bafd37..b33006a95896 100644 --- a/api_docs/kbn_server_route_repository_utils.devdocs.json +++ b/api_docs/kbn_server_route_repository_utils.devdocs.json @@ -27,7 +27,15 @@ "label": "formatRequest", "description": [], "signature": [ - "(endpoint: string, pathParams: Record) => { method: Method; pathname: string; version: string; }" + "(endpoint: string, pathParams: Record) => { method: \"get\" | ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.DestructiveRouteMethod", + "text": "DestructiveRouteMethod" + }, + "; pathname: string; version: string; }" ], "path": "packages/kbn-server-route-repository-utils/src/format_request.ts", "deprecated": false, @@ -75,7 +83,15 @@ "label": "parseEndpoint", "description": [], "signature": [ - "(endpoint: string) => { method: Method; pathname: string; version: string; }" + "(endpoint: string) => { method: \"get\" | ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.DestructiveRouteMethod", + "text": "DestructiveRouteMethod" + }, + "; pathname: string; version: string; }" ], "path": "packages/kbn-server-route-repository-utils/src/parse_endpoint.ts", "deprecated": false, @@ -102,49 +118,6 @@ } ], "interfaces": [ - { - "parentPluginId": "@kbn/server-route-repository-utils", - "id": "def-common.DefaultRouteCreateOptions", - "type": "Interface", - "tags": [], - "label": "DefaultRouteCreateOptions", - "description": [], - "path": "packages/kbn-server-route-repository-utils/src/typings.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/server-route-repository-utils", - "id": "def-common.DefaultRouteCreateOptions.options", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.RouteConfigOptions", - "text": "RouteConfigOptions" - }, - "<", - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.RouteMethod", - "text": "RouteMethod" - }, - "> | undefined" - ], - "path": "packages/kbn-server-route-repository-utils/src/typings.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/server-route-repository-utils", "id": "def-common.DefaultRouteHandlerResources", @@ -226,7 +199,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>) => Promise<", + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">) => Promise<", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -271,7 +252,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>" + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -297,7 +286,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>) => ", + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">) => ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -346,7 +343,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions>" + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -361,10 +366,10 @@ }, { "parentPluginId": "@kbn/server-route-repository-utils", - "id": "def-common.RouteState", + "id": "def-common.ServerRouteCreateOptions", "type": "Interface", "tags": [], - "label": "RouteState", + "label": "ServerRouteCreateOptions", "description": [], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -372,13 +377,13 @@ "children": [ { "parentPluginId": "@kbn/server-route-repository-utils", - "id": "def-common.RouteState.Unnamed", + "id": "def-common.ServerRouteCreateOptions.Unnamed", "type": "IndexSignature", "tags": [], - "label": "[endpoint: string]: any", + "label": "[x: string]: any", "description": [], "signature": [ - "[endpoint: string]: any" + "[x: string]: any" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -422,7 +427,7 @@ "section": "def-common.ServerRouteCreateOptions", "text": "ServerRouteCreateOptions" }, - "> ? TRouteParamsRT extends ", + " | undefined> ? TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -461,15 +466,7 @@ "section": "def-common.RouteParamsRT", "text": "RouteParamsRT" }, - " | undefined, any, any, ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.ServerRouteCreateOptions", - "text": "ServerRouteCreateOptions" - }, - "> ? TRouteParamsRT extends ", + " | undefined, any, any, any> ? TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -505,6 +502,36 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/server-route-repository-utils", + "id": "def-common.DefaultRouteCreateOptions", + "type": "Type", + "tags": [], + "label": "DefaultRouteCreateOptions", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteConfigOptions", + "text": "RouteConfigOptions" + }, + "<\"get\" | ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.DestructiveRouteMethod", + "text": "DestructiveRouteMethod" + }, + ">" + ], + "path": "packages/kbn-server-route-repository-utils/src/typings.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/server-route-repository-utils", "id": "def-common.EndpointOf", @@ -561,15 +588,7 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - " ? TReturnType extends ", + " ? TReturnType extends ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -631,7 +650,15 @@ "label": "ServerRoute", "description": [], "signature": [ - "{ endpoint: TEndpoint; handler: ServerRouteHandler; } & TRouteCreateOptions & (TRouteParamsRT extends ", + "{ endpoint: TEndpoint; handler: ServerRouteHandler; security?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" + }, + " | undefined; } & (TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -639,22 +666,15 @@ "section": "def-common.RouteParamsRT", "text": "RouteParamsRT" }, - " ? { params: TRouteParamsRT; } : {})" - ], - "path": "packages/kbn-server-route-repository-utils/src/typings.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/server-route-repository-utils", - "id": "def-common.ServerRouteCreateOptions", - "type": "Type", - "tags": [], - "label": "ServerRouteCreateOptions", - "description": [], - "signature": [ - "{ [x: string]: any; }" + " ? { params: TRouteParamsRT; } : {}) & (TRouteCreateOptions extends ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.DefaultRouteCreateOptions", + "text": "DefaultRouteCreateOptions" + }, + " ? { options: TRouteCreateOptions; } : {})" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -700,7 +720,15 @@ "section": "def-common.RouteParamsRT", "text": "RouteParamsRT" }, - " | undefined, any, any, Record>; }" + " | undefined, any, any, ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRouteCreateOptions", + "text": "ServerRouteCreateOptions" + }, + " | undefined>; }" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, diff --git a/api_docs/kbn_server_route_repository_utils.mdx b/api_docs/kbn_server_route_repository_utils.mdx index bbd7074ba2d5..3b615f43bdbc 100644 --- a/api_docs/kbn_server_route_repository_utils.mdx +++ b/api_docs/kbn_server_route_repository_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository-utils title: "@kbn/server-route-repository-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository-utils'] --- import kbnServerRouteRepositoryUtilsObj from './kbn_server_route_repository_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 30 | 0 | 30 | 2 | +| 28 | 0 | 28 | 2 | ## Common diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index b0bd2992daf5..5af24bf26b9a 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-serverless-common-settings title: "@kbn/serverless-common-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-common-settings plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] --- import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; -Contact [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. +Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx index 390e029cfcfe..a32946e24eb7 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; -Contact [@elastic/appex-sharedux @elastic/kibana-management @elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/appex-sharedux ) for questions regarding this plugin. +Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index 34d5a3c75e9e..9372bb6b8263 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-project-switcher'] --- import kbnServerlessProjectSwitcherObj from './kbn_serverless_project_switcher.devdocs.json'; diff --git a/api_docs/kbn_serverless_search_settings.mdx b/api_docs/kbn_serverless_search_settings.mdx index fe91db9cf6ac..a2a16b391f4f 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-serverless-search-settings title: "@kbn/serverless-search-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-search-settings plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] --- import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; -Contact [@elastic/search-kibana @elastic/kibana-management](https://github.com/orgs/elastic/teams/search-kibana ) for questions regarding this plugin. +Contact [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx index 7a1d6dfb4419..ffdd8b555600 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,14 +8,14 @@ slug: /kibana-dev-docs/api/kbn-serverless-security-settings title: "@kbn/serverless-security-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-security-settings plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] --- import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; -Contact [@elastic/security-solution @elastic/kibana-management](https://github.com/orgs/elastic/teams/security-solution ) for questions regarding this plugin. +Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) for questions regarding this plugin. **Code health stats** diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 12bd0fdf0321..b01ce815b7f8 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-storybook-config'] --- import kbnServerlessStorybookConfigObj from './kbn_serverless_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index d9b63186f221..ac5533b9ef90 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index 8f4a1ff2d4b8..828275ec41a7 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index ed12919fdff6..4ed1b3dad478 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 93e743afd117..6be9795e43a1 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 9f297d9a93d5..e7a3613ba50a 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index c5557d4350f8..9eaf87bfe6f0 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index 5c135eb819e1..b607d85c38a4 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index 3c3e9edb5c4b..d02498469d3d 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-error-boundary'] --- import kbnSharedUxErrorBoundaryObj from './kbn_shared_ux_error_boundary.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 5f8f264b0e05..ce695b9110af 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 1e796f94213a..53e7342cc8ac 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index 810a7fc2fb3f..fe8fc854a427 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 3c35891212bb..3f94261f74cd 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index edf2508ea402..b9a44358a238 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_types.mdx b/api_docs/kbn_shared_ux_file_types.mdx index b1cb396c2abe..2b06df69432b 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-types'] --- import kbnSharedUxFileTypesObj from './kbn_shared_ux_file_types.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index 3885ce567e47..892a61449479 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index df148881560e..1eddf7801a4f 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index 4d7e1e0d5d91..afdd899fbc81 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 8806a0ce1d48..74f442793491 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 6fd2c87c1cd9..549526ba37a8 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index c91ecb526104..8e9a5640362e 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 49cab6cf262c..cff5ce57bec5 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 6df24d4e233f..264715c6829e 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 4e6b6984d06a..b80ec36ee70c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 329664d4530b..fd8981a1283c 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index 31982c8a604b..5fd147a25611 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 4c78da9ef52b..d1d156832fd8 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 8999a8709b07..ab4216852403 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 2149a4bf4e70..c2ef272b6bba 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 6156667ad8d9..cdbaf58b38f8 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index b1abed77a092..e016601968f9 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index c3865dd59fd5..2f1c18770d8b 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 9b076c14ef0d..4994db860061 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 603074099288..306bd2b22ba4 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index d6e8955784e7..0a27157b6556 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 0064edbc3f2b..424b3544a220 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 10a699059297..720448f825e3 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index d9ace942278b..b1d248e211e4 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 86dcc4aaaadd..347eaed67d63 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_tabbed_modal.mdx b/api_docs/kbn_shared_ux_tabbed_modal.mdx index 3a4815e2d36c..4e0adc9c9f00 100644 --- a/api_docs/kbn_shared_ux_tabbed_modal.mdx +++ b/api_docs/kbn_shared_ux_tabbed_modal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-tabbed-modal title: "@kbn/shared-ux-tabbed-modal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-tabbed-modal plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-tabbed-modal'] --- import kbnSharedUxTabbedModalObj from './kbn_shared_ux_tabbed_modal.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_table_persist.mdx b/api_docs/kbn_shared_ux_table_persist.mdx index e04656afd5ab..a9693db65a6f 100644 --- a/api_docs/kbn_shared_ux_table_persist.mdx +++ b/api_docs/kbn_shared_ux_table_persist.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-table-persist title: "@kbn/shared-ux-table-persist" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-table-persist plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-table-persist'] --- import kbnSharedUxTablePersistObj from './kbn_shared_ux_table_persist.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 2b3c9af122ae..2be21ab5d350 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index 9a75db8d281a..9bb30ca97383 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index bcd7cf6d81d6..31fb54dab561 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_predicates.mdx b/api_docs/kbn_sort_predicates.mdx index 2d1acc5cab71..8e00e7716996 100644 --- a/api_docs/kbn_sort_predicates.mdx +++ b/api_docs/kbn_sort_predicates.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-predicates title: "@kbn/sort-predicates" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-predicates plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-predicates'] --- import kbnSortPredicatesObj from './kbn_sort_predicates.devdocs.json'; diff --git a/api_docs/kbn_sse_utils.mdx b/api_docs/kbn_sse_utils.mdx index 0a2d1a9eb08d..512b2b381ca2 100644 --- a/api_docs/kbn_sse_utils.mdx +++ b/api_docs/kbn_sse_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sse-utils title: "@kbn/sse-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sse-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sse-utils'] --- import kbnSseUtilsObj from './kbn_sse_utils.devdocs.json'; diff --git a/api_docs/kbn_sse_utils_client.mdx b/api_docs/kbn_sse_utils_client.mdx index 5fb9f579e5b2..74d5a7ca98ee 100644 --- a/api_docs/kbn_sse_utils_client.mdx +++ b/api_docs/kbn_sse_utils_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sse-utils-client title: "@kbn/sse-utils-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sse-utils-client plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sse-utils-client'] --- import kbnSseUtilsClientObj from './kbn_sse_utils_client.devdocs.json'; diff --git a/api_docs/kbn_sse_utils_server.mdx b/api_docs/kbn_sse_utils_server.mdx index f12c34281b67..8acd0ef41ba9 100644 --- a/api_docs/kbn_sse_utils_server.mdx +++ b/api_docs/kbn_sse_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sse-utils-server title: "@kbn/sse-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sse-utils-server plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sse-utils-server'] --- import kbnSseUtilsServerObj from './kbn_sse_utils_server.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 875c7b21a773..dd30376ddadb 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 4d9ff82367d2..9fac6ebb7c8c 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 52a138c303d3..d91038307b5c 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_synthetics_e2e.mdx b/api_docs/kbn_synthetics_e2e.mdx index 3f9cd52799c2..47eccbb5bfdf 100644 --- a/api_docs/kbn_synthetics_e2e.mdx +++ b/api_docs/kbn_synthetics_e2e.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-synthetics-e2e title: "@kbn/synthetics-e2e" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/synthetics-e2e plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/synthetics-e2e'] --- import kbnSyntheticsE2eObj from './kbn_synthetics_e2e.devdocs.json'; diff --git a/api_docs/kbn_synthetics_private_location.mdx b/api_docs/kbn_synthetics_private_location.mdx index ddadf43b246d..420efbd1596c 100644 --- a/api_docs/kbn_synthetics_private_location.mdx +++ b/api_docs/kbn_synthetics_private_location.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-synthetics-private-location title: "@kbn/synthetics-private-location" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/synthetics-private-location plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/synthetics-private-location'] --- import kbnSyntheticsPrivateLocationObj from './kbn_synthetics_private_location.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 9edd25f3b785..ee2ba59bbc03 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index a7dcf1123396..0e4ae833e342 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index 11513f611d15..5064aaa658b0 100644 --- a/api_docs/kbn_test_eui_helpers.mdx +++ b/api_docs/kbn_test_eui_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-eui-helpers title: "@kbn/test-eui-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-eui-helpers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-eui-helpers'] --- import kbnTestEuiHelpersObj from './kbn_test_eui_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index a1f244825c78..fcb3f8e0fabb 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index dd7a20b14f41..9d362121294a 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_timerange.mdx b/api_docs/kbn_timerange.mdx index 4a846be61471..154ec8e5580c 100644 --- a/api_docs/kbn_timerange.mdx +++ b/api_docs/kbn_timerange.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-timerange title: "@kbn/timerange" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/timerange plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/timerange'] --- import kbnTimerangeObj from './kbn_timerange.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 5ccdee4201e7..6a5aa39d549b 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_transpose_utils.mdx b/api_docs/kbn_transpose_utils.mdx index 29b044439b4a..f7297c8bc4c7 100644 --- a/api_docs/kbn_transpose_utils.mdx +++ b/api_docs/kbn_transpose_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-transpose-utils title: "@kbn/transpose-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/transpose-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/transpose-utils'] --- import kbnTransposeUtilsObj from './kbn_transpose_utils.devdocs.json'; diff --git a/api_docs/kbn_triggers_actions_ui_types.mdx b/api_docs/kbn_triggers_actions_ui_types.mdx index a5b11ace4eb8..7954618a41f6 100644 --- a/api_docs/kbn_triggers_actions_ui_types.mdx +++ b/api_docs/kbn_triggers_actions_ui_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-triggers-actions-ui-types title: "@kbn/triggers-actions-ui-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/triggers-actions-ui-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/triggers-actions-ui-types'] --- import kbnTriggersActionsUiTypesObj from './kbn_triggers_actions_ui_types.devdocs.json'; diff --git a/api_docs/kbn_try_in_console.mdx b/api_docs/kbn_try_in_console.mdx index 9ae922086d92..fe6b8736b54d 100644 --- a/api_docs/kbn_try_in_console.mdx +++ b/api_docs/kbn_try_in_console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-try-in-console title: "@kbn/try-in-console" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/try-in-console plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/try-in-console'] --- import kbnTryInConsoleObj from './kbn_try_in_console.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.devdocs.json b/api_docs/kbn_ts_projects.devdocs.json index 6847c2a76965..f9a2cddcbfe0 100644 --- a/api_docs/kbn_ts_projects.devdocs.json +++ b/api_docs/kbn_ts_projects.devdocs.json @@ -213,6 +213,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/ts-projects", + "id": "def-common.TsProject.isEsm", + "type": "CompoundType", + "tags": [], + "label": "isEsm", + "description": [ + "the package is esm or not" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-ts-projects/ts_project.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/ts-projects", "id": "def-common.TsProject.rootImportReq", diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index a4ccd1baf11a..a2df53a19b78 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 39 | 0 | 25 | 1 | +| 40 | 0 | 25 | 1 | ## Common diff --git a/api_docs/kbn_typed_react_router_config.devdocs.json b/api_docs/kbn_typed_react_router_config.devdocs.json index 9bab557b023f..f20c6013c647 100644 --- a/api_docs/kbn_typed_react_router_config.devdocs.json +++ b/api_docs/kbn_typed_react_router_config.devdocs.json @@ -1,26 +1,10 @@ { "id": "@kbn/typed-react-router-config", "client": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { "classes": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.NotFoundRouteException", + "id": "def-public.NotFoundRouteException", "type": "Class", "tags": [], "label": "NotFoundRouteException", @@ -28,9 +12,9 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.NotFoundRouteException", + "section": "def-public.NotFoundRouteException", "text": "NotFoundRouteException" }, " extends Error" @@ -41,7 +25,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.NotFoundRouteException.Unnamed", + "id": "def-public.NotFoundRouteException.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -55,7 +39,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.NotFoundRouteException.Unnamed.$1", + "id": "def-public.NotFoundRouteException.Unnamed.$1", "type": "string", "tags": [], "label": "message", @@ -78,7 +62,52 @@ "functions": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.createRouter", + "id": "def-public.BreadcrumbsContextProvider", + "type": "Function", + "tags": [], + "label": "BreadcrumbsContextProvider", + "description": [], + "signature": [ + "({\n children,\n}: { children: React.ReactNode; }) => React.JSX.Element" + ], + "path": "packages/kbn-typed-react-router-config/src/breadcrumbs/context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/typed-react-router-config", + "id": "def-public.BreadcrumbsContextProvider.$1", + "type": "Object", + "tags": [], + "label": "{\n children,\n}", + "description": [], + "path": "packages/kbn-typed-react-router-config/src/breadcrumbs/context.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/typed-react-router-config", + "id": "def-public.BreadcrumbsContextProvider.$1.children", + "type": "CompoundType", + "tags": [], + "label": "children", + "description": [], + "signature": [ + "string | number | boolean | React.ReactElement> | Iterable | React.ReactPortal | null | undefined" + ], + "path": "packages/kbn-typed-react-router-config/src/breadcrumbs/context.tsx", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/typed-react-router-config", + "id": "def-public.createRouter", "type": "Function", "tags": [], "label": "createRouter", @@ -87,9 +116,9 @@ "(routes: TRoutes) => ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Router", + "section": "def-public.Router", "text": "Router" }, "" @@ -100,7 +129,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.createRouter.$1", + "id": "def-public.createRouter.$1", "type": "Uncategorized", "tags": [], "label": "routes", @@ -119,7 +148,43 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.CurrentRouteContextProvider", + "id": "def-public.createRouterBreadcrumbComponent", + "type": "Function", + "tags": [], + "label": "createRouterBreadcrumbComponent", + "description": [], + "signature": [ + "() => ", + "RouterBreadcrumb", + "" + ], + "path": "packages/kbn-typed-react-router-config/src/breadcrumbs/create_router_breadcrumb_component.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/typed-react-router-config", + "id": "def-public.createUseBreadcrumbs", + "type": "Function", + "tags": [], + "label": "createUseBreadcrumbs", + "description": [], + "signature": [ + "() => UseBreadcrumbs" + ], + "path": "packages/kbn-typed-react-router-config/src/breadcrumbs/use_router_breadcrumb.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/typed-react-router-config", + "id": "def-public.CurrentRouteContextProvider", "type": "Function", "tags": [], "label": "CurrentRouteContextProvider", @@ -128,17 +193,17 @@ "({ match, element, children, }: { match: ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMatch", + "section": "def-public.RouteMatch", "text": "RouteMatch" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Route", + "section": "def-public.Route", "text": "Route" }, ">; element: React.ReactElement>; children: React.ReactElement>; }) => React.JSX.Element" @@ -149,7 +214,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.CurrentRouteContextProvider.$1", + "id": "def-public.CurrentRouteContextProvider.$1", "type": "Object", "tags": [], "label": "{\n match,\n element,\n children,\n}", @@ -160,7 +225,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.CurrentRouteContextProvider.$1.match", + "id": "def-public.CurrentRouteContextProvider.$1.match", "type": "Object", "tags": [], "label": "match", @@ -168,17 +233,17 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMatch", + "section": "def-public.RouteMatch", "text": "RouteMatch" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Route", + "section": "def-public.Route", "text": "Route" }, ">" @@ -189,7 +254,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.CurrentRouteContextProvider.$1.element", + "id": "def-public.CurrentRouteContextProvider.$1.element", "type": "Object", "tags": [], "label": "element", @@ -203,7 +268,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.CurrentRouteContextProvider.$1.children", + "id": "def-public.CurrentRouteContextProvider.$1.children", "type": "Object", "tags": [], "label": "children", @@ -223,7 +288,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Outlet", + "id": "def-public.Outlet", "type": "Function", "tags": [], "label": "Outlet", @@ -240,7 +305,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.OutletContextProvider", + "id": "def-public.OutletContextProvider", "type": "Function", "tags": [], "label": "OutletContextProvider", @@ -254,7 +319,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.OutletContextProvider.$1", + "id": "def-public.OutletContextProvider.$1", "type": "Object", "tags": [], "label": "{\n element,\n children,\n}", @@ -265,7 +330,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.OutletContextProvider.$1.element", + "id": "def-public.OutletContextProvider.$1.element", "type": "Object", "tags": [], "label": "element", @@ -279,7 +344,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.OutletContextProvider.$1.children", + "id": "def-public.OutletContextProvider.$1.children", "type": "CompoundType", "tags": [], "label": "children", @@ -299,7 +364,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterContextProvider", + "id": "def-public.RouterContextProvider", "type": "Function", "tags": [], "label": "RouterContextProvider", @@ -308,17 +373,17 @@ "({ router, children, }: { router: ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Router", + "section": "def-public.Router", "text": "Router" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMap", + "section": "def-public.RouteMap", "text": "RouteMap" }, ">; children: React.ReactNode; }) => React.JSX.Element" @@ -329,7 +394,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterContextProvider.$1", + "id": "def-public.RouterContextProvider.$1", "type": "Object", "tags": [], "label": "{\n router,\n children,\n}", @@ -340,7 +405,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterContextProvider.$1.router", + "id": "def-public.RouterContextProvider.$1.router", "type": "Object", "tags": [], "label": "router", @@ -348,17 +413,17 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Router", + "section": "def-public.Router", "text": "Router" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMap", + "section": "def-public.RouteMap", "text": "RouteMap" }, ">" @@ -369,7 +434,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterContextProvider.$1.children", + "id": "def-public.RouterContextProvider.$1.children", "type": "CompoundType", "tags": [], "label": "children", @@ -389,7 +454,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouteRenderer", + "id": "def-public.RouteRenderer", "type": "Function", "tags": [], "label": "RouteRenderer", @@ -406,7 +471,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterProvider", + "id": "def-public.RouterProvider", "type": "Function", "tags": [], "label": "RouterProvider", @@ -415,17 +480,17 @@ "({\n children,\n router,\n history,\n}: { router: ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Router", + "section": "def-public.Router", "text": "Router" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMap", + "section": "def-public.RouteMap", "text": "RouteMap" }, ">; history: ", @@ -438,7 +503,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterProvider.$1", + "id": "def-public.RouterProvider.$1", "type": "Object", "tags": [], "label": "{\n children,\n router,\n history,\n}", @@ -449,7 +514,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterProvider.$1.router", + "id": "def-public.RouterProvider.$1.router", "type": "Object", "tags": [], "label": "router", @@ -457,17 +522,17 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Router", + "section": "def-public.Router", "text": "Router" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMap", + "section": "def-public.RouteMap", "text": "RouteMap" }, ">" @@ -478,7 +543,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterProvider.$1.history", + "id": "def-public.RouterProvider.$1.history", "type": "Object", "tags": [], "label": "history", @@ -493,7 +558,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouterProvider.$1.children", + "id": "def-public.RouterProvider.$1.children", "type": "CompoundType", "tags": [], "label": "children", @@ -513,7 +578,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.useCurrentRoute", + "id": "def-public.useCurrentRoute", "type": "Function", "tags": [], "label": "useCurrentRoute", @@ -522,17 +587,17 @@ "() => { match: ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMatch", + "section": "def-public.RouteMatch", "text": "RouteMatch" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Route", + "section": "def-public.Route", "text": "Route" }, ">; element: React.ReactElement>; }" @@ -546,7 +611,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.useMatchRoutes", + "id": "def-public.useMatchRoutes", "type": "Function", "tags": [], "label": "useMatchRoutes", @@ -555,17 +620,17 @@ "(path: string | undefined) => ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMatch", + "section": "def-public.RouteMatch", "text": "RouteMatch" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Route", + "section": "def-public.Route", "text": "Route" }, ">[]" @@ -576,7 +641,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.useMatchRoutes.$1", + "id": "def-public.useMatchRoutes.$1", "type": "string", "tags": [], "label": "path", @@ -595,7 +660,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.useParams", + "id": "def-public.useParams", "type": "Function", "tags": [], "label": "useParams", @@ -604,9 +669,9 @@ "(args: any[]) => ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.DefaultOutput", + "section": "def-public.DefaultOutput", "text": "DefaultOutput" }, " | undefined" @@ -617,7 +682,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.useParams.$1", + "id": "def-public.useParams.$1", "type": "Array", "tags": [], "label": "args", @@ -636,7 +701,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.useRoutePath", + "id": "def-public.useRoutePath", "type": "Function", "tags": [], "label": "useRoutePath", @@ -653,7 +718,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.useRouter", + "id": "def-public.useRouter", "type": "Function", "tags": [], "label": "useRouter", @@ -662,20 +727,12 @@ "() => ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Router", + "section": "def-public.Router", "text": "Router" }, - "<", - { - "pluginId": "@kbn/typed-react-router-config", - "scope": "common", - "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMap", - "text": "RouteMap" - }, - ">" + "" ], "path": "packages/kbn-typed-react-router-config/src/use_router.tsx", "deprecated": false, @@ -688,7 +745,7 @@ "interfaces": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.DefaultOutput", + "id": "def-public.DefaultOutput", "type": "Interface", "tags": [], "label": "DefaultOutput", @@ -699,7 +756,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.DefaultOutput.path", + "id": "def-public.DefaultOutput.path", "type": "Object", "tags": [], "label": "path", @@ -713,7 +770,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.DefaultOutput.query", + "id": "def-public.DefaultOutput.query", "type": "Object", "tags": [], "label": "query", @@ -730,7 +787,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Route", + "id": "def-public.Route", "type": "Interface", "tags": [], "label": "Route", @@ -741,7 +798,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Route.element", + "id": "def-public.Route.element", "type": "Object", "tags": [], "label": "element", @@ -755,7 +812,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Route.children", + "id": "def-public.Route.children", "type": "Object", "tags": [], "label": "children", @@ -763,9 +820,9 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMap", + "section": "def-public.RouteMap", "text": "RouteMap" }, " | undefined" @@ -776,7 +833,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Route.params", + "id": "def-public.Route.params", "type": "Object", "tags": [], "label": "params", @@ -791,7 +848,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Route.defaults", + "id": "def-public.Route.defaults", "type": "Object", "tags": [], "label": "defaults", @@ -805,7 +862,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Route.pre", + "id": "def-public.Route.pre", "type": "Object", "tags": [], "label": "pre", @@ -822,7 +879,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouteMatch", + "id": "def-public.RouteMatch", "type": "Interface", "tags": [], "label": "RouteMatch", @@ -830,9 +887,9 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMatch", + "section": "def-public.RouteMatch", "text": "RouteMatch" }, "" @@ -843,7 +900,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouteMatch.route", + "id": "def-public.RouteMatch.route", "type": "CompoundType", "tags": [], "label": "route", @@ -857,7 +914,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouteMatch.match", + "id": "def-public.RouteMatch.match", "type": "Object", "tags": [], "label": "match", @@ -878,7 +935,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router", + "id": "def-public.Router", "type": "Interface", "tags": [], "label": "Router", @@ -886,9 +943,9 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Router", + "section": "def-public.Router", "text": "Router" }, "" @@ -899,7 +956,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.matchRoutes", + "id": "def-public.Router.matchRoutes", "type": "Function", "tags": [], "label": "matchRoutes", @@ -908,9 +965,9 @@ "{ >(path: TPath, location: ", @@ -918,9 +975,9 @@ "): ToRouteMatch<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Match", + "section": "def-public.Match", "text": "Match" }, ">; (location: ", @@ -928,17 +985,17 @@ "): ToRouteMatch<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Match", + "section": "def-public.Match", "text": "Match" }, ">>; }" @@ -949,7 +1006,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.matchRoutes.$1", + "id": "def-public.Router.matchRoutes.$1", "type": "Uncategorized", "tags": [], "label": "path", @@ -964,7 +1021,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.matchRoutes.$2", + "id": "def-public.Router.matchRoutes.$2", "type": "Object", "tags": [], "label": "location", @@ -983,7 +1040,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.matchRoutes", + "id": "def-public.Router.matchRoutes", "type": "Function", "tags": [], "label": "matchRoutes", @@ -992,9 +1049,9 @@ "{ >(path: TPath, location: ", @@ -1002,9 +1059,9 @@ "): ToRouteMatch<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Match", + "section": "def-public.Match", "text": "Match" }, ">; (location: ", @@ -1012,17 +1069,17 @@ "): ToRouteMatch<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Match", + "section": "def-public.Match", "text": "Match" }, ">>; }" @@ -1033,7 +1090,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.matchRoutes.$1", + "id": "def-public.Router.matchRoutes.$1", "type": "Object", "tags": [], "label": "location", @@ -1052,7 +1109,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams", + "id": "def-public.Router.getParams", "type": "Function", "tags": [], "label": "getParams", @@ -1061,9 +1118,9 @@ "{ >(path: TPath, location: ", @@ -1071,17 +1128,17 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1089,33 +1146,33 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, location: ", @@ -1123,41 +1180,41 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ", T3 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, path3: T3, location: ", @@ -1165,33 +1222,33 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1199,17 +1256,17 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; }" @@ -1220,7 +1277,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$1", + "id": "def-public.Router.getParams.$1", "type": "Uncategorized", "tags": [], "label": "path", @@ -1235,7 +1292,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$2", + "id": "def-public.Router.getParams.$2", "type": "Object", "tags": [], "label": "location", @@ -1254,7 +1311,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams", + "id": "def-public.Router.getParams", "type": "Function", "tags": [], "label": "getParams", @@ -1263,9 +1320,9 @@ "{ >(path: TPath, location: ", @@ -1273,17 +1330,17 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1291,33 +1348,33 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, location: ", @@ -1325,41 +1382,41 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ", T3 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, path3: T3, location: ", @@ -1367,33 +1424,33 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1401,17 +1458,17 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; }" @@ -1422,7 +1479,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$1", + "id": "def-public.Router.getParams.$1", "type": "Uncategorized", "tags": [], "label": "path", @@ -1437,7 +1494,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$2", + "id": "def-public.Router.getParams.$2", "type": "Object", "tags": [], "label": "location", @@ -1453,7 +1510,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$3", + "id": "def-public.Router.getParams.$3", "type": "Uncategorized", "tags": [], "label": "optional", @@ -1471,7 +1528,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams", + "id": "def-public.Router.getParams", "type": "Function", "tags": [], "label": "getParams", @@ -1480,9 +1537,9 @@ "{ >(path: TPath, location: ", @@ -1490,17 +1547,17 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1508,33 +1565,33 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, location: ", @@ -1542,41 +1599,41 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ", T3 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, path3: T3, location: ", @@ -1584,33 +1641,33 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1618,17 +1675,17 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; }" @@ -1639,7 +1696,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$1", + "id": "def-public.Router.getParams.$1", "type": "Uncategorized", "tags": [], "label": "path1", @@ -1654,7 +1711,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$2", + "id": "def-public.Router.getParams.$2", "type": "Uncategorized", "tags": [], "label": "path2", @@ -1669,7 +1726,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$3", + "id": "def-public.Router.getParams.$3", "type": "Object", "tags": [], "label": "location", @@ -1688,7 +1745,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams", + "id": "def-public.Router.getParams", "type": "Function", "tags": [], "label": "getParams", @@ -1697,9 +1754,9 @@ "{ >(path: TPath, location: ", @@ -1707,17 +1764,17 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1725,33 +1782,33 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, location: ", @@ -1759,41 +1816,41 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ", T3 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, path3: T3, location: ", @@ -1801,33 +1858,33 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1835,17 +1892,17 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; }" @@ -1856,7 +1913,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$1", + "id": "def-public.Router.getParams.$1", "type": "Uncategorized", "tags": [], "label": "path1", @@ -1871,7 +1928,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$2", + "id": "def-public.Router.getParams.$2", "type": "Uncategorized", "tags": [], "label": "path2", @@ -1886,7 +1943,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$3", + "id": "def-public.Router.getParams.$3", "type": "Uncategorized", "tags": [], "label": "path3", @@ -1901,7 +1958,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$4", + "id": "def-public.Router.getParams.$4", "type": "Object", "tags": [], "label": "location", @@ -1920,7 +1977,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams", + "id": "def-public.Router.getParams", "type": "Function", "tags": [], "label": "getParams", @@ -1929,9 +1986,9 @@ "{ >(path: TPath, location: ", @@ -1939,17 +1996,17 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -1957,33 +2014,33 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, location: ", @@ -1991,41 +2048,41 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , T2 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ", T3 extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, ">(path1: T1, path2: T2, path3: T3, location: ", @@ -2033,33 +2090,33 @@ "): ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; , TOptional extends boolean>(path: TPath, location: ", @@ -2067,17 +2124,17 @@ ", optional: TOptional): TOptional extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, " | undefined : ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, "; }" @@ -2088,7 +2145,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$1", + "id": "def-public.Router.getParams.$1", "type": "Uncategorized", "tags": [], "label": "path", @@ -2103,7 +2160,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$2", + "id": "def-public.Router.getParams.$2", "type": "Object", "tags": [], "label": "location", @@ -2119,7 +2176,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getParams.$3", + "id": "def-public.Router.getParams.$3", "type": "Uncategorized", "tags": [], "label": "optional", @@ -2137,7 +2194,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.link", + "id": "def-public.Router.link", "type": "Function", "tags": [], "label": "link", @@ -2146,25 +2203,25 @@ ">(path: TPath, ...args: ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeAsArgs", + "section": "def-public.TypeAsArgs", "text": "TypeAsArgs" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, ">) => string" @@ -2175,7 +2232,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.link.$1", + "id": "def-public.Router.link.$1", "type": "Uncategorized", "tags": [], "label": "path", @@ -2190,7 +2247,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.link.$2", + "id": "def-public.Router.link.$2", "type": "Uncategorized", "tags": [], "label": "args", @@ -2198,17 +2255,17 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeAsArgs", + "section": "def-public.TypeAsArgs", "text": "TypeAsArgs" }, "<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.TypeOf", + "section": "def-public.TypeOf", "text": "TypeOf" }, ">" @@ -2223,7 +2280,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getRoutePath", + "id": "def-public.Router.getRoutePath", "type": "Function", "tags": [], "label": "getRoutePath", @@ -2232,9 +2289,9 @@ "(route: ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteWithPath", + "section": "def-public.RouteWithPath", "text": "RouteWithPath" }, ") => string" @@ -2245,7 +2302,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getRoutePath.$1", + "id": "def-public.Router.getRoutePath.$1", "type": "Object", "tags": [], "label": "route", @@ -2253,9 +2310,9 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteWithPath", + "section": "def-public.RouteWithPath", "text": "RouteWithPath" } ], @@ -2269,7 +2326,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getRoutesToMatch", + "id": "def-public.Router.getRoutesToMatch", "type": "Function", "tags": [], "label": "getRoutesToMatch", @@ -2278,9 +2335,9 @@ "(path: string) => ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.FlattenRoutesOf", + "section": "def-public.FlattenRoutesOf", "text": "FlattenRoutesOf" }, "" @@ -2291,7 +2348,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Router.getRoutesToMatch.$1", + "id": "def-public.Router.getRoutesToMatch.$1", "type": "string", "tags": [], "label": "path", @@ -2312,7 +2369,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouteWithPath", + "id": "def-public.RouteWithPath", "type": "Interface", "tags": [], "label": "RouteWithPath", @@ -2320,17 +2377,17 @@ "signature": [ { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteWithPath", + "section": "def-public.RouteWithPath", "text": "RouteWithPath" }, " extends ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Route", + "section": "def-public.Route", "text": "Route" } ], @@ -2340,7 +2397,7 @@ "children": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouteWithPath.path", + "id": "def-public.RouteWithPath.path", "type": "string", "tags": [], "label": "path", @@ -2357,7 +2414,7 @@ "misc": [ { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.FlattenRoutesOf", + "id": "def-public.FlattenRoutesOf", "type": "Type", "tags": [], "label": "FlattenRoutesOf", @@ -2375,7 +2432,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.Match", + "id": "def-public.Match", "type": "Type", "tags": [], "label": "Match", @@ -2390,7 +2447,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.OutputOf", + "id": "def-public.OutputOf", "type": "Type", "tags": [], "label": "OutputOf", @@ -2399,17 +2456,17 @@ "OutputOfRoutes<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Match", + "section": "def-public.Match", "text": "Match" }, "> & ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.DefaultOutput", + "section": "def-public.DefaultOutput", "text": "DefaultOutput" } ], @@ -2420,7 +2477,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.PathsOf", + "id": "def-public.PathsOf", "type": "Type", "tags": [], "label": "PathsOf", @@ -2431,9 +2488,9 @@ "<{ [key in keyof TRouteMap]: key | (TRouteMap[key] extends { children: ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.RouteMap", + "section": "def-public.RouteMap", "text": "RouteMap" }, "; } ? ", @@ -2441,9 +2498,9 @@ "<`${key & string}/*`> | ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.PathsOf", + "section": "def-public.PathsOf", "text": "PathsOf" }, " : never); }>" @@ -2455,7 +2512,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.RouteMap", + "id": "def-public.RouteMap", "type": "Type", "tags": [], "label": "RouteMap", @@ -2464,9 +2521,9 @@ "{ [x: string]: ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Route", + "section": "def-public.Route", "text": "Route" }, "; }" @@ -2478,7 +2535,7 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.TypeAsArgs", + "id": "def-public.TypeAsArgs", "type": "Type", "tags": [], "label": "TypeAsArgs", @@ -2495,7 +2552,24 @@ }, { "parentPluginId": "@kbn/typed-react-router-config", - "id": "def-common.TypeOf", + "id": "def-public.TypeAsParams", + "type": "Type", + "tags": [], + "label": "TypeAsParams", + "description": [], + "signature": [ + "keyof TObject extends never ? {} : ", + "RequiredKeys", + " extends never ? never : { params: TObject; }" + ], + "path": "packages/kbn-typed-react-router-config/src/types/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/typed-react-router-config", + "id": "def-public.TypeOf", "type": "Type", "tags": [], "label": "TypeOf", @@ -2504,17 +2578,17 @@ "TypeOfRoutes<", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.Match", + "section": "def-public.Match", "text": "Match" }, "> & (TWithDefaultOutput extends true ? ", { "pluginId": "@kbn/typed-react-router-config", - "scope": "common", + "scope": "public", "docId": "kibKbnTypedReactRouterConfigPluginApi", - "section": "def-common.DefaultOutput", + "section": "def-public.DefaultOutput", "text": "DefaultOutput" }, " : {})" @@ -2526,5 +2600,21 @@ } ], "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 0bfecd965636..6ac21c5e71b9 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; @@ -21,19 +21,19 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 86 | 0 | 86 | 1 | +| 92 | 0 | 92 | 2 | -## Common +## Client ### Functions - + ### Classes - + ### Interfaces - + ### Consts, variables and types - + diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index 19964443d00e..d55d6709b24d 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 2e54ad6a3313..70408e877b20 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 8d577bf266af..c2798b1527d7 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index 8744bcb763af..57154c77cce6 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-data-table title: "@kbn/unified-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-data-table plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.devdocs.json'; diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index 0622550a724d..25cc5d25f6dc 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-doc-viewer title: "@kbn/unified-doc-viewer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-doc-viewer plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] --- import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index 95339521de64..388383c3db2d 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index d02d9ff9befd..3744ea5c190e 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_prompt.mdx b/api_docs/kbn_unsaved_changes_prompt.mdx index 71ce2b2fe23d..da031414b9e8 100644 --- a/api_docs/kbn_unsaved_changes_prompt.mdx +++ b/api_docs/kbn_unsaved_changes_prompt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-prompt title: "@kbn/unsaved-changes-prompt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-prompt plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-prompt'] --- import kbnUnsavedChangesPromptObj from './kbn_unsaved_changes_prompt.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 6d24c6aaf734..bff0ef92d2fd 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 47102a7f213a..e427cbd8506f 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.devdocs.json b/api_docs/kbn_utility_types.devdocs.json index 6d177a2aa2cf..b9cdddfd3ad0 100644 --- a/api_docs/kbn_utility_types.devdocs.json +++ b/api_docs/kbn_utility_types.devdocs.json @@ -361,11 +361,11 @@ "signature": [ "(Exclude<", "ValuesType", - "<{ [TKey in keyof TObject]: {} extends Pick ? ", + "<{ [TKey in keyof TObject as string]: string extends TKey ? Record : {} extends Pick ? ", "DeepPartial", ">> : DedotKey; }>, undefined> extends any ? (k: Exclude<", "ValuesType", - "<{ [TKey in keyof TObject]: {} extends Pick ? ", + "<{ [TKey in keyof TObject as string]: string extends TKey ? Record : {} extends Pick ? ", "DeepPartial", ">> : DedotKey; }>, undefined>) => void : never) extends (k: infer I) => void ? I : never" ], diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 4303083d803c..6fffd02afd70 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index fcad1e2aab1f..68f1c44d0929 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index a94500343051..e6ae404396de 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_visualization_ui_components.mdx b/api_docs/kbn_visualization_ui_components.mdx index d42ce39a6489..c6064a6fbe6d 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index a9091803d8a5..cbfbdd595cf4 100644 --- a/api_docs/kbn_visualization_utils.mdx +++ b/api_docs/kbn_visualization_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-utils title: "@kbn/visualization-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index a4c0783e2f14..94c73215d817 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 36576b64f8a5..72af88d57102 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kbn_zod.devdocs.json b/api_docs/kbn_zod.devdocs.json index 4fbf59e5edf5..9f1ada94d8e6 100644 --- a/api_docs/kbn_zod.devdocs.json +++ b/api_docs/kbn_zod.devdocs.json @@ -19198,7 +19198,7 @@ "label": "ZodFirstPartySchemaTypes", "description": [], "signature": [ - "Zod.ZodString | Zod.ZodBoolean | Zod.ZodNumber | Zod.ZodUnknown | Zod.ZodNull | Zod.ZodAny | Zod.ZodUndefined | Zod.ZodBigInt | Zod.ZodDate | Zod.ZodSymbol | Zod.ZodNever | Zod.ZodVoid | Zod.ZodTuple | Zod.ZodNaN | Zod.ZodArray | Zod.ZodObject | Zod.ZodUnion | Zod.ZodDiscriminatedUnion | Zod.ZodIntersection | Zod.ZodRecord | Zod.ZodMap | Zod.ZodSet | Zod.ZodFunction | Zod.ZodLazy | Zod.ZodLiteral | Zod.ZodEnum | Zod.ZodEffects | Zod.ZodNativeEnum | Zod.ZodOptional | Zod.ZodNullable | Zod.ZodDefault | Zod.ZodCatch | Zod.ZodPromise | Zod.ZodBranded | Zod.ZodPipeline | Zod.ZodReadonly" + "Zod.ZodString | Zod.ZodBoolean | Zod.ZodNumber | Zod.ZodNull | Zod.ZodUnknown | Zod.ZodAny | Zod.ZodUndefined | Zod.ZodBigInt | Zod.ZodDate | Zod.ZodSymbol | Zod.ZodNever | Zod.ZodVoid | Zod.ZodTuple | Zod.ZodNaN | Zod.ZodArray | Zod.ZodObject | Zod.ZodUnion | Zod.ZodDiscriminatedUnion | Zod.ZodIntersection | Zod.ZodRecord | Zod.ZodMap | Zod.ZodSet | Zod.ZodFunction | Zod.ZodLazy | Zod.ZodLiteral | Zod.ZodEnum | Zod.ZodEffects | Zod.ZodNativeEnum | Zod.ZodOptional | Zod.ZodNullable | Zod.ZodDefault | Zod.ZodCatch | Zod.ZodPromise | Zod.ZodBranded | Zod.ZodPipeline | Zod.ZodReadonly" ], "path": "node_modules/zod/lib/types.d.ts", "deprecated": false, diff --git a/api_docs/kbn_zod.mdx b/api_docs/kbn_zod.mdx index 6f6397fd7917..3b2c6c9426c5 100644 --- a/api_docs/kbn_zod.mdx +++ b/api_docs/kbn_zod.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod title: "@kbn/zod" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod'] --- import kbnZodObj from './kbn_zod.devdocs.json'; diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index 7f6b0f52b014..e05b28083dde 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 2ec803afccf8..3e9be8571967 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 19e64ec5b944..a6f0de081762 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 6a08230406f9..a1589a4c0954 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 819883c209b7..7d0e464a720b 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 6e80cf99266d..0c9d7a8273d4 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index abe095f47404..bd9615bffba5 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index 098b13dedd09..d899125d85fd 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index bea6d9bd8888..18faeeaf9fe9 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/links.mdx b/api_docs/links.mdx index db264f08a297..2f2744df3947 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'links'] --- import linksObj from './links.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 17db21b932c2..4359102b2f85 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/llm_tasks.mdx b/api_docs/llm_tasks.mdx index 511b44f2bbb1..64a0480743e1 100644 --- a/api_docs/llm_tasks.mdx +++ b/api_docs/llm_tasks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/llmTasks title: "llmTasks" image: https://source.unsplash.com/400x175/?github description: API docs for the llmTasks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'llmTasks'] --- import llmTasksObj from './llm_tasks.devdocs.json'; diff --git a/api_docs/logs_data_access.mdx b/api_docs/logs_data_access.mdx index 71f02d9567d7..f519b78e8d3d 100644 --- a/api_docs/logs_data_access.mdx +++ b/api_docs/logs_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsDataAccess title: "logsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the logsDataAccess plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsDataAccess'] --- import logsDataAccessObj from './logs_data_access.devdocs.json'; diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index 1d8eb9fb675f..2c01072a671d 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index f5ad97ed5f98..3caa322fa2b8 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 3d2a8fd09e4e..73aa58c63c4b 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index ffa196573c9a..706136b3ba41 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 196a230660a4..04e10bd9621b 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index 3d0f1b6c4a1a..77743d2ab64e 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 50a789b0ab94..2a49a37a5e25 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index 4b1358eac81f..ad9da5bc80c4 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 3181393d059e..66318096eb92 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 7d58a064410e..4fd289aa19ad 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 00e7dca28d91..fe5b59379a4d 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 21a51e41b8d1..1905d9ecf908 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/no_data_page.mdx b/api_docs/no_data_page.mdx index a1b5581b838e..0a4821a72367 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'noDataPage'] --- import noDataPageObj from './no_data_page.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 201d95cd74ec..40517aef8a05 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 7ff6ef019cc9..48c5cad2a518 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -2863,6 +2863,27 @@ "path": "x-pack/plugins/observability_solution/observability/public/plugin.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsSetup.streams", + "type": "Object", + "tags": [], + "label": "streams", + "description": [], + "signature": [ + { + "pluginId": "streams", + "scope": "public", + "docId": "kibStreamsPluginApi", + "section": "def-public.StreamsPluginSetup", + "text": "StreamsPluginSetup" + }, + " | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -3813,6 +3834,27 @@ "path": "x-pack/plugins/observability_solution/observability/public/plugin.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsStart.streams", + "type": "Object", + "tags": [], + "label": "streams", + "description": [], + "signature": [ + { + "pluginId": "streams", + "scope": "public", + "docId": "kibStreamsPluginApi", + "section": "def-public.StreamsPluginStart", + "text": "StreamsPluginStart" + }, + " | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -8592,13 +8634,27 @@ "children": [ { "parentPluginId": "observability", - "id": "def-server.ObservabilityRouteCreateOptions.options", - "type": "Object", + "id": "def-server.ObservabilityRouteCreateOptions.tags", + "type": "Array", "tags": [], - "label": "options", + "label": "tags", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability_solution/observability/server/routes/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observability", + "id": "def-server.ObservabilityRouteCreateOptions.access", + "type": "CompoundType", + "tags": [], + "label": "access", "description": [], "signature": [ - "{ tags: string[]; access?: \"internal\" | \"public\" | undefined; }" + "\"internal\" | \"public\" | undefined" ], "path": "x-pack/plugins/observability_solution/observability/server/routes/types.ts", "deprecated": false, @@ -8717,20 +8773,6 @@ "path": "x-pack/plugins/observability_solution/observability/server/routes/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "observability", - "id": "def-server.ObservabilityRouteHandlerResources.config", - "type": "Object", - "tags": [], - "label": "config", - "description": [], - "signature": [ - "{ readonly enabled: boolean; readonly annotations: Readonly<{} & { index: string; enabled: boolean; }>; readonly unsafe: Readonly<{} & { alertDetails: Readonly<{} & { uptime: Readonly<{} & { enabled: boolean; }>; observability: Readonly<{} & { enabled: boolean; }>; metrics: Readonly<{} & { enabled: boolean; }>; logs: Readonly<{} & { enabled: boolean; }>; }>; thresholdRule: Readonly<{} & { enabled: boolean; }>; ruleFormV2: Readonly<{} & { enabled: boolean; }>; }>; readonly customThresholdRule: Readonly<{} & { groupByPageSize: number; }>; readonly createO11yGenericFeatureId: boolean; }" - ], - "path": "x-pack/plugins/observability_solution/observability/server/routes/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -8762,7 +8804,15 @@ "section": "def-common.RouteParamsRT", "text": "RouteParamsRT" }, - " | undefined, any, any, Record>; }" + " | undefined, any, any, ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRouteCreateOptions", + "text": "ServerRouteCreateOptions" + }, + " | undefined>; }" ], "path": "x-pack/plugins/observability_solution/observability/server/routes/types.ts", "deprecated": false, @@ -8922,15 +8972,7 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - " ? TReturnType extends ", + " ? TReturnType extends ", { "pluginId": "@kbn/core-http-server", "scope": "server", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 19fd0492944c..db6a0f97c7f7 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 695 | 2 | 687 | 23 | +| 697 | 2 | 689 | 23 | ## Client diff --git a/api_docs/observability_a_i_assistant.devdocs.json b/api_docs/observability_a_i_assistant.devdocs.json index 913dfebf5bf4..a11aa4c4548b 100644 --- a/api_docs/observability_a_i_assistant.devdocs.json +++ b/api_docs/observability_a_i_assistant.devdocs.json @@ -3159,7 +3159,15 @@ "Readable", ", ", "ObservabilityAIAssistantRouteCreateOptions", - ">; }, TEndpoint> & Omit & { signal: AbortSignal | null; }>) => Promise<", + ">; }, TEndpoint> & Omit & { signal: AbortSignal | null; } & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + ">) => Promise<", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -4169,7 +4177,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions> extends never ? [] | [", + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + "> extends never ? [] | [", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -4177,7 +4193,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions] : [", + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + "] : [", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -4185,7 +4209,15 @@ "section": "def-common.ClientRequestParamsOf", "text": "ClientRequestParamsOf" }, - " & TAdditionalClientOptions]" + " & TAdditionalClientOptions & ", + { + "pluginId": "@kbn/core-http-browser", + "scope": "public", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-public.HttpFetchOptions", + "text": "HttpFetchOptions" + }, + "]" ], "path": "packages/kbn-server-route-repository-utils/src/typings.ts", "deprecated": false, @@ -5832,15 +5864,7 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - " ? TReturnType extends ", + " ? TReturnType extends ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -7063,7 +7087,7 @@ "section": "def-common.ServerRouteCreateOptions", "text": "ServerRouteCreateOptions" }, - "> ? TRouteParamsRT extends ", + " | undefined> ? TRouteParamsRT extends ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index 83d4b0022fcd..83d632687876 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant_app.mdx b/api_docs/observability_a_i_assistant_app.mdx index d6f51c5724a5..4b6a28776147 100644 --- a/api_docs/observability_a_i_assistant_app.mdx +++ b/api_docs/observability_a_i_assistant_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistantApp title: "observabilityAIAssistantApp" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistantApp plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistantApp'] --- import observabilityAIAssistantAppObj from './observability_a_i_assistant_app.devdocs.json'; diff --git a/api_docs/observability_ai_assistant_management.mdx b/api_docs/observability_ai_assistant_management.mdx index e414f274518a..6ec6533a095c 100644 --- a/api_docs/observability_ai_assistant_management.mdx +++ b/api_docs/observability_ai_assistant_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAiAssistantManagement title: "observabilityAiAssistantManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAiAssistantManagement plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAiAssistantManagement'] --- import observabilityAiAssistantManagementObj from './observability_ai_assistant_management.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index 2358925f4dfc..bdac03ae09a8 100644 --- a/api_docs/observability_logs_explorer.mdx +++ b/api_docs/observability_logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogsExplorer title: "observabilityLogsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogsExplorer plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogsExplorer'] --- import observabilityLogsExplorerObj from './observability_logs_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 85d0a427c262..f7778665f7c2 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 943c9c5691bb..dfd831976712 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index b44b959701fc..ef91a7557368 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx index 7054cd2117f0..662fa1337917 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] --- import painlessLabObj from './painless_lab.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 7ce0d1dc1a02..70a50eac81a7 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 887 | 757 | 47 | +| 892 | 760 | 43 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 54466 | 247 | 40920 | 2015 | +| 54552 | 240 | 40981 | 2020 | ## Plugin Directory @@ -33,12 +33,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 72 | 0 | 8 | 2 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 880 | 1 | 848 | 50 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | The user interface for Elastic APM | 29 | 0 | 29 | 119 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 93 | 0 | 93 | 3 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 86 | 0 | 86 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 60 | 1 | 59 | 2 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | The Case management system in Kibana | 126 | 0 | 106 | 28 | -| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 268 | 2 | 253 | 10 | +| | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 267 | 2 | 252 | 9 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 83 | 0 | 20 | 1 | | cloudChat | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Chat available on Elastic Cloud deployments for quicker assistance. | 0 | 0 | 0 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | Static migration page where self-managed users can see text/copy about migrating to Elastic Cloud | 8 | 0 | 8 | 1 | @@ -61,7 +61,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 35 | 0 | 25 | 5 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Reusable data view field editor across Kibana | 72 | 0 | 33 | 1 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data view management app | 2 | 0 | 2 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1224 | 0 | 443 | 4 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1225 | 0 | 443 | 4 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 31 | 3 | 25 | 4 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin introduces the concept of data set quality, where users can easily get an overview on the data sets they have. | 14 | 0 | 14 | 8 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 15 | 0 | 9 | 2 | @@ -75,14 +75,15 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 54 | 0 | 47 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Adds dashboards for discovering and managing Enterprise Search products. | 5 | 0 | 5 | 0 | | | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | - | 2 | 0 | 2 | 0 | -| | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | Entity manager plugin for entity assets (inventory, topology, etc) | 20 | 0 | 20 | 3 | +| | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | Entity manager plugin for entity assets (inventory, topology, etc) | 35 | 0 | 35 | 2 | +| entityManagerApp | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | Entity manager plugin for entity assets (inventory, topology, etc) | 0 | 0 | 0 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 99 | 3 | 97 | 3 | | | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 25 | 0 | 9 | 0 | | | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The Event Annotation service contains expressions for event annotations | 201 | 0 | 201 | 6 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The listing page for event annotations. | 15 | 0 | 15 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 116 | 0 | 116 | 11 | -| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 126 | 0 | 126 | 12 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 127 | 0 | 127 | 12 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds 'error' renderer to expressions | 17 | 0 | 15 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Gauge plugin adds a `gauge` renderer and function to the expression plugin. The renderer will display the `gauge` chart. | 59 | 0 | 58 | 2 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Expression Heatmap plugin adds a `heatmap` renderer and function to the expression plugin. The renderer will display the `heatmap` chart. | 112 | 0 | 108 | 2 | @@ -103,7 +104,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 89 | 0 | 89 | 8 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 3 | 0 | 3 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1428 | 5 | 1303 | 81 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1430 | 5 | 1304 | 82 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 72 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -115,7 +116,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Image embeddable | 1 | 0 | 1 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 244 | 0 | 239 | 1 | -| | [@elastic/appex-ai-infra](https://github.com/orgs/elastic/teams/appex-ai-infra) | - | 33 | 0 | 28 | 4 | +| | [@elastic/appex-ai-infra](https://github.com/orgs/elastic/teams/appex-ai-infra) | - | 40 | 0 | 29 | 6 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 24 | 0 | 24 | 5 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 4 | 0 | 4 | 0 | | inputControlVis | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Input Control visualization to Kibana | 0 | 0 | 0 | 0 | @@ -153,7 +154,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | -| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 695 | 2 | 687 | 23 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 697 | 2 | 689 | 23 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 296 | 1 | 294 | 27 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 4 | 0 | 4 | 0 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 2 | 0 | 2 | 0 | @@ -185,6 +186,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 18 | 0 | 10 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 18 | 0 | 18 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 11 | 0 | 7 | 1 | +| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 21 | 0 | 21 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin to provide access to and rendering of python notebooks for use in the persistent developer console. | 10 | 0 | 10 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 22 | 0 | 16 | 1 | | searchprofiler | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 0 | 0 | 0 | 0 | @@ -202,7 +204,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides the Spaces feature, which allows saved objects to be organized into meaningful categories. | 269 | 0 | 73 | 1 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 25 | 0 | 25 | 3 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 10 | 0 | 10 | 0 | -| | @simianhacker @flash1293 @dgieselaar | A manager for Streams | 12 | 7 | 12 | 2 | +| | @simianhacker @flash1293 @dgieselaar | A manager for Streams | 13 | 0 | 13 | 4 | +| | @simianhacker @flash1293 @dgieselaar | - | 8 | 0 | 8 | 0 | | synthetics | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 1 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 108 | 0 | 64 | 7 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 45 | 0 | 1 | 0 | @@ -210,7 +213,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | telemetryCollectionXpack | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 0 | 0 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 30 | 0 | 14 | 4 | -| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 226 | 1 | 182 | 17 | +| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 236 | 1 | 192 | 18 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [@elastic/kibana-localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 594 | 1 | 568 | 51 | @@ -263,7 +266,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 18 | 0 | 18 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 4 | 0 | 4 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 87 | 0 | 87 | 11 | -| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 268 | 0 | 268 | 38 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 270 | 0 | 270 | 38 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 337 | 0 | 336 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 12 | 0 | 12 | 0 | | | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 3 | 0 | 3 | 0 | @@ -377,9 +380,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 54 | 7 | 54 | 6 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 15 | 0 | 15 | 1 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 568 | 2 | 242 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 567 | 2 | 242 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 96 | 0 | 83 | 10 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 46 | 0 | 45 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 6 | 0 | 2 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 2 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 1 | 0 | @@ -509,7 +513,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 38 | 2 | 33 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 37 | 0 | 34 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 284 | 0 | 234 | 4 | -| | [@elastic/docs](https://github.com/orgs/elastic/teams/docs) | - | 78 | 0 | 78 | 2 | +| | [@elastic/docs](https://github.com/orgs/elastic/teams/docs) | - | 79 | 0 | 79 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 5 | 0 | 5 | 1 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 57 | 0 | 30 | 6 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 37 | 0 | 28 | 2 | @@ -517,7 +521,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 42 | 0 | 41 | 0 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 169 | 0 | 140 | 10 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 442 | 0 | 405 | 0 | -| | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | - | 45 | 0 | 45 | 0 | +| | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | - | 50 | 0 | 50 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 55 | 0 | 40 | 7 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 32 | 0 | 19 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 6 | 0 | @@ -540,7 +544,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 0 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 26 | 0 | 26 | 1 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 27 | 0 | 25 | 2 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 16 | 0 | 16 | 1 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 17 | 0 | 12 | 11 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 49 | 0 | 47 | 0 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 33 | 3 | 24 | 6 | @@ -554,7 +558,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-threat-hunting](https://github.com/orgs/elastic/teams/security-threat-hunting) | - | 85 | 0 | 80 | 2 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 75 | 0 | 73 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 126 | 3 | 126 | 0 | -| | [@elastic/appex-ai-infra](https://github.com/orgs/elastic/teams/appex-ai-infra) | - | 124 | 0 | 41 | 1 | +| | [@elastic/appex-ai-infra](https://github.com/orgs/elastic/teams/appex-ai-infra) | - | 132 | 0 | 43 | 1 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 7 | 1 | 7 | 1 | | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 9 | 0 | 9 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 12 | 43 | 0 | @@ -581,8 +585,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 23 | 0 | 7 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 8 | 0 | 2 | 3 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 45 | 0 | 0 | 0 | -| | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 140 | 0 | 139 | 0 | -| | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 20 | 0 | 11 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 140 | 0 | 139 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 20 | 0 | 11 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 88 | 0 | 10 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 56 | 0 | 6 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 2 | 0 | 0 | 0 | @@ -639,7 +643,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 1 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 1 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 92 | 0 | 80 | 0 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 224 | 0 | 188 | 6 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 228 | 0 | 192 | 6 | | | [@elastic/appex-ai-infra](https://github.com/orgs/elastic/teams/appex-ai-infra) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-ai-infra](https://github.com/orgs/elastic/teams/appex-ai-infra) | - | 31 | 0 | 31 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 168 | 0 | 55 | 0 | @@ -656,7 +660,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 18 | 0 | 18 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 40 | 0 | 38 | 5 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 13 | 0 | 9 | 0 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 6 | 0 | 6 | 1 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 14 | 0 | 7 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 98 | 0 | 88 | 13 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 13 | 0 | 13 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 36 | 0 | 36 | 2 | @@ -702,7 +706,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 54 | 0 | 49 | 0 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 29 | 0 | 23 | 0 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 2 | 0 | 0 | 0 | -| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 48 | 0 | 13 | 0 | +| | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 47 | 0 | 12 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 56 | 1 | 41 | 0 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 92 | 0 | 70 | 6 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 341 | 1 | 337 | 32 | @@ -719,16 +723,16 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 211 | 0 | 162 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 28 | 0 | 25 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 120 | 0 | 116 | 0 | -| | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 60 | 0 | 54 | 0 | +| | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 63 | 0 | 55 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 69 | 0 | 64 | 0 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 42 | 0 | 42 | 0 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 39 | 0 | 39 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 15 | 0 | 15 | 0 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 30 | 0 | 30 | 2 | -| | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 1 | 0 | 1 | 0 | -| | [@elastic/appex-sharedux @elastic/kibana-management @elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 1 | 0 | 1 | 0 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 28 | 0 | 28 | 2 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 1 | 0 | 1 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 7 | 1 | -| | [@elastic/search-kibana @elastic/kibana-management](https://github.com/orgs/elastic/teams/search-kibana ) | - | 1 | 0 | 1 | 0 | -| | [@elastic/security-solution @elastic/kibana-management](https://github.com/orgs/elastic/teams/security-solution ) | - | 1 | 0 | 1 | 0 | +| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 1 | 0 | 1 | 0 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 1 | 0 | 1 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 0 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 5 | 0 | 5 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 2 | 2 | @@ -792,8 +796,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 9 | 0 | 7 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 15 | 0 | 15 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 2 | 0 | 2 | 1 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 39 | 0 | 25 | 1 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 86 | 0 | 86 | 1 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 40 | 0 | 25 | 1 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 92 | 0 | 92 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 42 | 0 | 28 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 61 | 0 | 52 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 9 | 0 | 8 | 0 | diff --git a/api_docs/presentation_panel.mdx b/api_docs/presentation_panel.mdx index c14bea1f7cd3..dcf4c63e6fa9 100644 --- a/api_docs/presentation_panel.mdx +++ b/api_docs/presentation_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationPanel title: "presentationPanel" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationPanel plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationPanel'] --- import presentationPanelObj from './presentation_panel.devdocs.json'; diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 24eedbd2b04f..eb5ea5d34472 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/product_doc_base.mdx b/api_docs/product_doc_base.mdx index 051e26bd5619..64daeee07e96 100644 --- a/api_docs/product_doc_base.mdx +++ b/api_docs/product_doc_base.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/productDocBase title: "productDocBase" image: https://source.unsplash.com/400x175/?github description: API docs for the productDocBase plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'productDocBase'] --- import productDocBaseObj from './product_doc_base.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 19992aa57b2a..d4e574ee7c53 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index 53d117a69922..c2e42aea3719 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index cc5d9a43fc25..06bb00664875 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 302e188bb4c8..b527b3781de4 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 7d95d39e9324..cb2de04b3503 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 4ebc4fe171a2..cb5f1c312fee 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 1af512192c6c..8035d2d52858 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 4ec61c7efdea..a613f2e9e51c 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 540d219be32e..f1d7ffd140e8 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 3031f3b67c43..297b97bb5d3f 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index c726717a954e..7a7633594526 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 39e883ed82bc..5e10e78038d1 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index cced69a9c30e..3e8c5afe0f51 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 0fecead7426e..dd3181adece1 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 13fe7789b2ba..1b475d9f8e81 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/search_assistant.mdx b/api_docs/search_assistant.mdx index bd7b702295dc..b08628810c98 100644 --- a/api_docs/search_assistant.mdx +++ b/api_docs/search_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchAssistant title: "searchAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the searchAssistant plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchAssistant'] --- import searchAssistantObj from './search_assistant.devdocs.json'; diff --git a/api_docs/search_connectors.mdx b/api_docs/search_connectors.mdx index cbec02e6b594..ec297198df27 100644 --- a/api_docs/search_connectors.mdx +++ b/api_docs/search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchConnectors title: "searchConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the searchConnectors plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchConnectors'] --- import searchConnectorsObj from './search_connectors.devdocs.json'; diff --git a/api_docs/search_homepage.mdx b/api_docs/search_homepage.mdx index 5983760f7504..608226b0265d 100644 --- a/api_docs/search_homepage.mdx +++ b/api_docs/search_homepage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchHomepage title: "searchHomepage" image: https://source.unsplash.com/400x175/?github description: API docs for the searchHomepage plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchHomepage'] --- import searchHomepageObj from './search_homepage.devdocs.json'; diff --git a/api_docs/search_indices.devdocs.json b/api_docs/search_indices.devdocs.json index 8b486873528f..8446bc09cbb0 100644 --- a/api_docs/search_indices.devdocs.json +++ b/api_docs/search_indices.devdocs.json @@ -85,7 +85,7 @@ "label": "startAppId", "description": [], "signature": [ - "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"searchInferenceEndpoints\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"serverlessWebCrawlers\" | \"searchPlayground\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"searchInferenceEndpoints:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"enterpriseSearchElasticsearch\" | \"enterpriseSearchVectorSearch\" | \"enterpriseSearchSemanticSearch\" | \"enterpriseSearchAISearch\" | \"elasticsearchIndices:createIndex\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:entity_analytics-entity_store_management\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" + "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"streams\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"searchInferenceEndpoints\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"serverlessWebCrawlers\" | \"searchPlayground\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"searchInferenceEndpoints:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"enterpriseSearchElasticsearch\" | \"enterpriseSearchVectorSearch\" | \"enterpriseSearchSemanticSearch\" | \"enterpriseSearchAISearch\" | \"elasticsearchIndices:createIndex\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"streams:overview\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:siem_migrations-rules\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:entity_analytics-entity_store_management\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" ], "path": "x-pack/plugins/search_indices/public/types.ts", "deprecated": false, diff --git a/api_docs/search_indices.mdx b/api_docs/search_indices.mdx index 1e1044d975b5..b21696699124 100644 --- a/api_docs/search_indices.mdx +++ b/api_docs/search_indices.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchIndices title: "searchIndices" image: https://source.unsplash.com/400x175/?github description: API docs for the searchIndices plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchIndices'] --- import searchIndicesObj from './search_indices.devdocs.json'; diff --git a/api_docs/search_inference_endpoints.mdx b/api_docs/search_inference_endpoints.mdx index dee5d98dcaba..5c485aa4fdf8 100644 --- a/api_docs/search_inference_endpoints.mdx +++ b/api_docs/search_inference_endpoints.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchInferenceEndpoints title: "searchInferenceEndpoints" image: https://source.unsplash.com/400x175/?github description: API docs for the searchInferenceEndpoints plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchInferenceEndpoints'] --- import searchInferenceEndpointsObj from './search_inference_endpoints.devdocs.json'; diff --git a/api_docs/search_navigation.devdocs.json b/api_docs/search_navigation.devdocs.json new file mode 100644 index 000000000000..9f9ff7e4294c --- /dev/null +++ b/api_docs/search_navigation.devdocs.json @@ -0,0 +1,390 @@ +{ + "id": "searchNavigation", + "client": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItem", + "type": "Interface", + "tags": [], + "label": "ClassicNavItem", + "description": [], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItem.datatestsubj", + "type": "string", + "tags": [], + "label": "'data-test-subj'", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItem.deepLink", + "type": "Object", + "tags": [], + "label": "deepLink", + "description": [], + "signature": [ + { + "pluginId": "searchNavigation", + "scope": "public", + "docId": "kibSearchNavigationPluginApi", + "section": "def-public.ClassicNavItemDeepLink", + "text": "ClassicNavItemDeepLink" + }, + " | undefined" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItem.iconToString", + "type": "string", + "tags": [], + "label": "iconToString", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItem.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItem.items", + "type": "Array", + "tags": [], + "label": "items", + "description": [], + "signature": [ + { + "pluginId": "searchNavigation", + "scope": "public", + "docId": "kibSearchNavigationPluginApi", + "section": "def-public.ClassicNavItem", + "text": "ClassicNavItem" + }, + "[] | undefined" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItem.name", + "type": "CompoundType", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string | number | boolean | React.ReactElement> | Iterable | React.ReactPortal | null | undefined" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItemDeepLink", + "type": "Interface", + "tags": [], + "label": "ClassicNavItemDeepLink", + "description": [], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItemDeepLink.link", + "type": "CompoundType", + "tags": [], + "label": "link", + "description": [], + "signature": [ + "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"profiling\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"canvas\" | \"logs\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"streams\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"ml:suppliedConfigurations\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"searchInferenceEndpoints\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"serverlessWebCrawlers\" | \"searchPlayground\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"searchInferenceEndpoints:inferenceEndpoints\" | \"elasticsearchStart\" | \"elasticsearchIndices\" | \"enterpriseSearchElasticsearch\" | \"enterpriseSearchVectorSearch\" | \"enterpriseSearchSemanticSearch\" | \"enterpriseSearchAISearch\" | \"elasticsearchIndices:createIndex\" | \"observability-logs-explorer\" | \"last-used-logs-viewer\" | \"observabilityOnboarding\" | \"inventory\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:services\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:functions\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"inventory:datastreams\" | \"streams:overview\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:notes\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:siem_migrations-rules\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:entity_analytics-entity_store_management\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:agents\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\"" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.ClassicNavItemDeepLink.shouldShowActiveForSubroutes", + "type": "CompoundType", + "tags": [], + "label": "shouldShowActiveForSubroutes", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginSetup", + "type": "Interface", + "tags": [], + "label": "SearchNavigationPluginSetup", + "description": [], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginStart", + "type": "Interface", + "tags": [], + "label": "SearchNavigationPluginStart", + "description": [], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginStart.registerOnAppMountHandler", + "type": "Function", + "tags": [], + "label": "registerOnAppMountHandler", + "description": [], + "signature": [ + "(onAppMount: () => Promise) => void" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginStart.registerOnAppMountHandler.$1", + "type": "Function", + "tags": [], + "label": "onAppMount", + "description": [], + "signature": [ + "() => Promise" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginStart.handleOnAppMount", + "type": "Function", + "tags": [], + "label": "handleOnAppMount", + "description": [], + "signature": [ + "() => Promise" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginStart.setGetBaseClassicNavItems", + "type": "Function", + "tags": [], + "label": "setGetBaseClassicNavItems", + "description": [], + "signature": [ + "(classicNavItemsFn: () => ", + { + "pluginId": "searchNavigation", + "scope": "public", + "docId": "kibSearchNavigationPluginApi", + "section": "def-public.ClassicNavItem", + "text": "ClassicNavItem" + }, + "[]) => void" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginStart.setGetBaseClassicNavItems.$1", + "type": "Function", + "tags": [], + "label": "classicNavItemsFn", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "searchNavigation", + "scope": "public", + "docId": "kibSearchNavigationPluginApi", + "section": "def-public.ClassicNavItem", + "text": "ClassicNavItem" + }, + "[]" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginStart.useClassicNavigation", + "type": "Function", + "tags": [], + "label": "useClassicNavigation", + "description": [], + "signature": [ + "(history: ", + { + "pluginId": "@kbn/core-application-browser", + "scope": "public", + "docId": "kibKbnCoreApplicationBrowserPluginApi", + "section": "def-public.ScopedHistory", + "text": "ScopedHistory" + }, + ") => ", + { + "pluginId": "@kbn/shared-ux-page-solution-nav", + "scope": "common", + "docId": "kibKbnSharedUxPageSolutionNavPluginApi", + "section": "def-common.SolutionNavProps", + "text": "SolutionNavProps" + }, + " | undefined" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchNavigation", + "id": "def-public.SearchNavigationPluginStart.useClassicNavigation.$1", + "type": "Object", + "tags": [], + "label": "history", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-application-browser", + "scope": "public", + "docId": "kibKbnCoreApplicationBrowserPluginApi", + "section": "def-public.ScopedHistory", + "text": "ScopedHistory" + }, + "" + ], + "path": "x-pack/plugins/search_solution/search_navigation/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "searchNavigation", + "id": "def-common.PLUGIN_ID", + "type": "string", + "tags": [], + "label": "PLUGIN_ID", + "description": [], + "signature": [ + "\"searchNavigation\"" + ], + "path": "x-pack/plugins/search_solution/search_navigation/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "searchNavigation", + "id": "def-common.PLUGIN_NAME", + "type": "string", + "tags": [], + "label": "PLUGIN_NAME", + "description": [], + "signature": [ + "\"searchNavigation\"" + ], + "path": "x-pack/plugins/search_solution/search_navigation/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/search_navigation.mdx b/api_docs/search_navigation.mdx new file mode 100644 index 000000000000..b748885ca4e8 --- /dev/null +++ b/api_docs/search_navigation.mdx @@ -0,0 +1,41 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibSearchNavigationPluginApi +slug: /kibana-dev-docs/api/searchNavigation +title: "searchNavigation" +image: https://source.unsplash.com/400x175/?github +description: API docs for the searchNavigation plugin +date: 2024-11-26 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchNavigation'] +--- +import searchNavigationObj from './search_navigation.devdocs.json'; + + + +Contact [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 21 | 0 | 21 | 0 | + +## Client + +### Setup + + +### Start + + +### Interfaces + + +## Common + +### Consts, variables and types + + diff --git a/api_docs/search_notebooks.mdx b/api_docs/search_notebooks.mdx index 87532f5bc974..de74e78045c7 100644 --- a/api_docs/search_notebooks.mdx +++ b/api_docs/search_notebooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchNotebooks title: "searchNotebooks" image: https://source.unsplash.com/400x175/?github description: API docs for the searchNotebooks plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchNotebooks'] --- import searchNotebooksObj from './search_notebooks.devdocs.json'; diff --git a/api_docs/search_playground.mdx b/api_docs/search_playground.mdx index 3cb3fd4d38a8..4830ad3ee816 100644 --- a/api_docs/search_playground.mdx +++ b/api_docs/search_playground.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchPlayground title: "searchPlayground" image: https://source.unsplash.com/400x175/?github description: API docs for the searchPlayground plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchPlayground'] --- import searchPlaygroundObj from './search_playground.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 3393225711d2..70f8c0209b17 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index f62340cda683..f76d77c7255e 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -880,8 +880,8 @@ "pluginId": "timelines", "scope": "common", "docId": "kibTimelinesPluginApi", - "section": "def-common.EqlOptionsSelected", - "text": "EqlOptionsSelected" + "section": "def-common.EqlOptions", + "text": "EqlOptions" } ], "path": "x-pack/plugins/security_solution/public/timelines/store/model.ts", @@ -3267,7 +3267,7 @@ "\nA list of allowed values that can be used in `xpack.securitySolution.enableExperimental`.\nThis object is then used to validate and parse the value entered." ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: true; readonly responseActionsSentinelOneKillProcessEnabled: true; readonly responseActionsSentinelOneProcessesEnabled: true; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: true; readonly endpointManagementSpaceAwarenessEnabled: false; readonly securitySolutionNotesDisabled: false; readonly entityAlertPreviewDisabled: false; readonly assistantModelEvaluation: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: true; readonly responseActionsTelemetryEnabled: false; readonly jamfDataInAnalyzerEnabled: true; readonly timelineEsqlTabDisabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly graphVisualizationInFlyoutEnabled: false; readonly prebuiltRulesCustomizationEnabled: false; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: true; readonly valueListItemsModalEnabled: true; readonly filterProcessDescendantsForEventFiltersEnabled: true; readonly dataIngestionHubEnabled: false; readonly entityStoreDisabled: false; readonly siemMigrationsEnabled: false; readonly defendInsights: false; }" + "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: false; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: true; readonly responseActionsSentinelOneKillProcessEnabled: true; readonly responseActionsSentinelOneProcessesEnabled: true; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: true; readonly endpointManagementSpaceAwarenessEnabled: false; readonly securitySolutionNotesDisabled: false; readonly entityAlertPreviewDisabled: false; readonly assistantModelEvaluation: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: true; readonly responseActionsTelemetryEnabled: false; readonly jamfDataInAnalyzerEnabled: true; readonly timelineEsqlTabDisabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly graphVisualizationInFlyoutEnabled: false; readonly prebuiltRulesCustomizationEnabled: false; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: true; readonly valueListItemsModalEnabled: true; readonly filterProcessDescendantsForEventFiltersEnabled: true; readonly dataIngestionHubEnabled: false; readonly entityStoreDisabled: false; readonly siemMigrationsEnabled: false; readonly defendInsights: false; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 5734bb79a598..d074e246f4cb 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index a7daf244a8ee..155a6860ec2b 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionEss'] --- import securitySolutionEssObj from './security_solution_ess.devdocs.json'; diff --git a/api_docs/security_solution_serverless.mdx b/api_docs/security_solution_serverless.mdx index 50154f5677b9..6f773dcd51a5 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index 35f2b91e45bd..473377964706 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index 30f758d13ef9..e4d126dc1774 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessObservability'] --- import serverlessObservabilityObj from './serverless_observability.devdocs.json'; diff --git a/api_docs/serverless_search.mdx b/api_docs/serverless_search.mdx index 4084786bef30..2b4b60dc53e1 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverlessSearch'] --- import serverlessSearchObj from './serverless_search.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 3b76e58f35bf..a82291e9e282 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 2ae99a0ff277..b5618512cfd7 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/slo.mdx b/api_docs/slo.mdx index 38a45378b47c..03fcb2e0efdc 100644 --- a/api_docs/slo.mdx +++ b/api_docs/slo.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/slo title: "slo" image: https://source.unsplash.com/400x175/?github description: API docs for the slo plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'slo'] --- import sloObj from './slo.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 59994451cfb4..3218b67bfa88 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index beb7543b8ded..2c97f988ca03 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 208a2fa3a6fe..7c5312908e7c 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index e6a037997600..a361cc1edd6b 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/streams.devdocs.json b/api_docs/streams.devdocs.json index 02800505b082..1d744b0580b2 100644 --- a/api_docs/streams.devdocs.json +++ b/api_docs/streams.devdocs.json @@ -6,12 +6,323 @@ "interfaces": [], "enums": [], "misc": [], - "objects": [] + "objects": [], + "setup": { + "parentPluginId": "streams", + "id": "def-public.StreamsPluginSetup", + "type": "Interface", + "tags": [], + "label": "StreamsPluginSetup", + "description": [], + "path": "x-pack/plugins/streams/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "streams", + "id": "def-public.StreamsPluginSetup.status$", + "type": "Object", + "tags": [], + "label": "status$", + "description": [], + "signature": [ + "Observable", + "<{ status: \"unknown\" | \"disabled\" | \"enabled\"; }>" + ], + "path": "x-pack/plugins/streams/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "streams", + "id": "def-public.StreamsPluginStart", + "type": "Interface", + "tags": [], + "label": "StreamsPluginStart", + "description": [], + "path": "x-pack/plugins/streams/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "streams", + "id": "def-public.StreamsPluginStart.streamsRepositoryClient", + "type": "Object", + "tags": [], + "label": "streamsRepositoryClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.RouteRepositoryClient", + "text": "RouteRepositoryClient" + }, + "<{ \"POST /api/streams/_disable\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /api/streams/_disable\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", + "StreamsRouteHandlerResources", + ", { acknowledged: true; }, undefined>; \"POST /internal/streams/esql\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /internal/streams/esql\", Zod.ZodObject<{ body: Zod.ZodObject<{ query: Zod.ZodString; operationName: Zod.ZodString; filter: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodTypeAny, \"passthrough\">>>; kuery: Zod.ZodOptional; start: Zod.ZodOptional; end: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { query: string; operationName: string; start?: number | undefined; end?: number | undefined; filter?: Zod.objectOutputType<{}, Zod.ZodTypeAny, \"passthrough\"> | undefined; kuery?: string | undefined; }, { query: string; operationName: string; start?: number | undefined; end?: number | undefined; filter?: Zod.objectInputType<{}, Zod.ZodTypeAny, \"passthrough\"> | undefined; kuery?: string | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { body: { query: string; operationName: string; start?: number | undefined; end?: number | undefined; filter?: Zod.objectOutputType<{}, Zod.ZodTypeAny, \"passthrough\"> | undefined; kuery?: string | undefined; }; }, { body: { query: string; operationName: string; start?: number | undefined; end?: number | undefined; filter?: Zod.objectInputType<{}, Zod.ZodTypeAny, \"passthrough\"> | undefined; kuery?: string | undefined; }; }>, ", + "StreamsRouteHandlerResources", + ", ", + "UnparsedEsqlResponse", + ", undefined>; \"GET /api/streams/_status\": { endpoint: \"GET /api/streams/_status\"; handler: ServerRouteHandler<", + "StreamsRouteHandlerResources", + ", undefined, { enabled: boolean; }>; security?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" + }, + " | undefined; }; \"GET /api/streams\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /api/streams\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", + "StreamsRouteHandlerResources", + ", { definitions: { id: string; children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }[]; trees: ", + "StreamTree", + "[]; }, undefined>; \"DELETE /api/streams/{id}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"DELETE /api/streams/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; }, { path: { id: string; }; }>, ", + "StreamsRouteHandlerResources", + ", { acknowledged: true; }, undefined>; \"PUT /api/streams/{id}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"PUT /api/streams/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; body: Zod.ZodObject<{ processing: Zod.ZodDefault>; config: Zod.ZodDiscriminatedUnion<\"type\", [Zod.ZodObject<{ type: Zod.ZodLiteral<\"grok\">; field: Zod.ZodString; patterns: Zod.ZodArray; pattern_definitions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; }, { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; }>, Zod.ZodObject<{ type: Zod.ZodLiteral<\"dissect\">; field: Zod.ZodString; pattern: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { type: \"dissect\"; field: string; pattern: string; }, { type: \"dissect\"; field: string; pattern: string; }>]>; }, \"strip\", Zod.ZodTypeAny, { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }, { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }>, \"many\">>; fields: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }>, \"many\">>; children: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { id: string; condition?: ", + "Condition", + "; }, { id: string; condition?: ", + "Condition", + "; }>, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }, { children?: { id: string; condition?: ", + "Condition", + "; }[] | undefined; fields?: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[] | undefined; processing?: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; body: { children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }; }, { path: { id: string; }; body: { children?: { id: string; condition?: ", + "Condition", + "; }[] | undefined; fields?: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[] | undefined; processing?: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[] | undefined; }; }>, ", + "StreamsRouteHandlerResources", + ", { acknowledged: boolean; }, undefined>; \"GET /api/streams/{id}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /api/streams/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; }, { path: { id: string; }; }>, ", + "StreamsRouteHandlerResources", + ", { id: string; children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; } & { inheritedFields: ({ type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; } & { from: string; })[]; }, undefined>; \"POST /api/streams/{id}/_fork\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /api/streams/{id}/_fork\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; body: Zod.ZodObject<{ stream: Zod.ZodObject>; config: Zod.ZodDiscriminatedUnion<\"type\", [Zod.ZodObject<{ type: Zod.ZodLiteral<\"grok\">; field: Zod.ZodString; patterns: Zod.ZodArray; pattern_definitions: Zod.ZodOptional>; }, \"strip\", Zod.ZodTypeAny, { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; }, { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; }>, Zod.ZodObject<{ type: Zod.ZodLiteral<\"dissect\">; field: Zod.ZodString; pattern: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { type: \"dissect\"; field: string; pattern: string; }, { type: \"dissect\"; field: string; pattern: string; }>]>; }, \"strip\", Zod.ZodTypeAny, { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }, { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }>, \"many\">>; fields: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }>, \"many\">>; children: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { id: string; condition?: ", + "Condition", + "; }, { id: string; condition?: ", + "Condition", + "; }>, \"many\">>; }, { id: Zod.ZodString; }>, \"children\">, \"strip\", Zod.ZodTypeAny, { id: string; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }, { id: string; fields?: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[] | undefined; processing?: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[] | undefined; }>; condition: Zod.ZodType<", + "Condition", + ", Zod.ZodTypeDef, ", + "Condition", + ">; }, \"strip\", Zod.ZodTypeAny, { stream: { id: string; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }; condition?: ", + "Condition", + "; }, { stream: { id: string; fields?: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[] | undefined; processing?: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[] | undefined; }; condition?: ", + "Condition", + "; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; body: { stream: { id: string; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }; condition?: ", + "Condition", + "; }; }, { path: { id: string; }; body: { stream: { id: string; fields?: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[] | undefined; processing?: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[] | undefined; }; condition?: ", + "Condition", + "; }; }>, ", + "StreamsRouteHandlerResources", + ", { acknowledged: true; }, undefined>; \"POST /api/streams/_resync\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /api/streams/_resync\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", + "StreamsRouteHandlerResources", + ", { acknowledged: true; }, undefined>; \"POST /api/streams/_enable\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"POST /api/streams/_enable\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", + "StreamsRouteHandlerResources", + ", { acknowledged: true; }, undefined>; }, ", + "StreamsRepositoryClientOptions", + ">" + ], + "path": "x-pack/plugins/streams/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "streams", + "id": "def-public.StreamsPluginStart.status$", + "type": "Object", + "tags": [], + "label": "status$", + "description": [], + "signature": [ + "Observable", + "<{ status: \"unknown\" | \"disabled\" | \"enabled\"; }>" + ], + "path": "x-pack/plugins/streams/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "lifecycle": "start", + "initialIsOpen": true + } }, "server": { "classes": [], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "streams", + "id": "def-server.ListStreamResponse", + "type": "Interface", + "tags": [], + "label": "ListStreamResponse", + "description": [], + "path": "x-pack/plugins/streams/server/lib/streams/stream_crud.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "streams", + "id": "def-server.ListStreamResponse.total", + "type": "number", + "tags": [], + "label": "total", + "description": [], + "path": "x-pack/plugins/streams/server/lib/streams/stream_crud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "streams", + "id": "def-server.ListStreamResponse.definitions", + "type": "Array", + "tags": [], + "label": "definitions", + "description": [], + "signature": [ + "{ id: string; children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }[]" + ], + "path": "x-pack/plugins/streams/server/lib/streams/stream_crud.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [ { @@ -37,7 +348,7 @@ "label": "StreamsRouteRepository", "description": [], "signature": [ - "{ \"GET /api/streams 2023-10-31\": ", + "{ \"POST /api/streams/_disable\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -45,25 +356,9 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/streams 2023-10-31\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", + "<\"POST /api/streams/_disable\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", "StreamsRouteHandlerResources", - ", ", - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.IKibanaResponse", - "text": "IKibanaResponse" - }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"DELETE /api/streams/{id} 2023-10-31\": ", + ", { acknowledged: true; }, undefined>; \"POST /internal/streams/esql\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -71,25 +366,37 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - "<\"DELETE /api/streams/{id} 2023-10-31\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; }, { path: { id: string; }; }>, ", + "<\"POST /internal/streams/esql\", Zod.ZodObject<{ body: Zod.ZodObject<{ query: Zod.ZodString; operationName: Zod.ZodString; filter: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodTypeAny, \"passthrough\">>>; kuery: Zod.ZodOptional; start: Zod.ZodOptional; end: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { query: string; operationName: string; start?: number | undefined; end?: number | undefined; filter?: Zod.objectOutputType<{}, Zod.ZodTypeAny, \"passthrough\"> | undefined; kuery?: string | undefined; }, { query: string; operationName: string; start?: number | undefined; end?: number | undefined; filter?: Zod.objectInputType<{}, Zod.ZodTypeAny, \"passthrough\"> | undefined; kuery?: string | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { body: { query: string; operationName: string; start?: number | undefined; end?: number | undefined; filter?: Zod.objectOutputType<{}, Zod.ZodTypeAny, \"passthrough\"> | undefined; kuery?: string | undefined; }; }, { body: { query: string; operationName: string; start?: number | undefined; end?: number | undefined; filter?: Zod.objectInputType<{}, Zod.ZodTypeAny, \"passthrough\"> | undefined; kuery?: string | undefined; }; }>, ", "StreamsRouteHandlerResources", ", ", + "UnparsedEsqlResponse", + ", undefined>; \"GET /api/streams/_status\": { endpoint: \"GET /api/streams/_status\"; handler: ServerRouteHandler<", + "StreamsRouteHandlerResources", + ", undefined, { enabled: boolean; }>; security?: ", { "pluginId": "@kbn/core-http-server", "scope": "server", "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.IKibanaResponse", - "text": "IKibanaResponse" + "section": "def-server.RouteSecurity", + "text": "RouteSecurity" }, - ", ", + " | undefined; }; \"GET /api/streams\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" + "section": "def-common.ServerRoute", + "text": "ServerRoute" }, - ">; \"PUT /api/streams/{id} 2023-10-31\": ", + "<\"GET /api/streams\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", + "StreamsRouteHandlerResources", + ", { definitions: { id: string; children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }[]; trees: ", + "StreamTree", + "[]; }, undefined>; \"DELETE /api/streams/{id}\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -97,7 +404,17 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - "<\"PUT /api/streams/{id} 2023-10-31\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; body: Zod.ZodObject<{ processing: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; }, { path: { id: string; }; }>, ", + "StreamsRouteHandlerResources", + ", { acknowledged: true; }, undefined>; \"PUT /api/streams/{id}\": ", + { + "pluginId": "@kbn/server-route-repository-utils", + "scope": "common", + "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", + "section": "def-common.ServerRoute", + "text": "ServerRoute" + }, + "<\"PUT /api/streams/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; body: Zod.ZodObject<{ processing: Zod.ZodDefault | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", "Condition", - "; }>, \"many\">>; fields: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }>, \"many\">>; children: Zod.ZodDefault, \"many\">>; fields: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }>, \"many\">>; children: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { id: string; condition?: ", + ">>; }, \"strip\", Zod.ZodTypeAny, { id: string; condition?: ", "Condition", "; }, { id: string; condition?: ", "Condition", @@ -131,23 +448,7 @@ "Condition", "; }[] | undefined; }; }>, ", "StreamsRouteHandlerResources", - ", ", - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.IKibanaResponse", - "text": "IKibanaResponse" - }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"GET /api/streams/{id} 2023-10-31\": ", + ", { acknowledged: boolean; }, undefined>; \"GET /api/streams/{id}\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -155,25 +456,13 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/streams/{id} 2023-10-31\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; }, { path: { id: string; }; }>, ", + "<\"GET /api/streams/{id}\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; }, \"strip\", Zod.ZodTypeAny, { path: { id: string; }; }, { path: { id: string; }; }>, ", "StreamsRouteHandlerResources", - ", ", - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.IKibanaResponse", - "text": "IKibanaResponse" - }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"POST /api/streams/{id}/_fork 2023-10-31\": ", + ", { id: string; children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; } & { inheritedFields: ({ type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; } & { from: string; })[]; }, undefined>; \"POST /api/streams/{id}/_fork\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -181,7 +470,7 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - "<\"POST /api/streams/{id}/_fork 2023-10-31\", Zod.ZodObject<{ path: Zod.ZodObject<{ id: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { id: string; }, { id: string; }>; body: Zod.ZodObject<{ stream: Zod.ZodObject; body: Zod.ZodObject<{ stream: Zod.ZodObject | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", "Condition", - "; }>, \"many\">>; fields: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }>, \"many\">>; children: Zod.ZodDefault, \"many\">>; fields: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }, { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }>, \"many\">>; children: Zod.ZodDefault; }, \"strip\", Zod.ZodTypeAny, { id: string; condition?: ", + ">>; }, \"strip\", Zod.ZodTypeAny, { id: string; condition?: ", "Condition", "; }, { id: string; condition?: ", "Condition", @@ -223,23 +512,7 @@ "Condition", "; }; }>, ", "StreamsRouteHandlerResources", - ", ", - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.IKibanaResponse", - "text": "IKibanaResponse" - }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"POST /api/streams/_resync 2023-10-31\": ", + ", { acknowledged: true; }, undefined>; \"POST /api/streams/_resync\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -247,25 +520,9 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - "<\"POST /api/streams/_resync 2023-10-31\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", + "<\"POST /api/streams/_resync\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", "StreamsRouteHandlerResources", - ", ", - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.IKibanaResponse", - "text": "IKibanaResponse" - }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; \"POST /api/streams/_enable 2023-10-31\": ", + ", { acknowledged: true; }, undefined>; \"POST /api/streams/_enable\": ", { "pluginId": "@kbn/server-route-repository-utils", "scope": "common", @@ -273,25 +530,9 @@ "section": "def-common.ServerRoute", "text": "ServerRoute" }, - "<\"POST /api/streams/_enable 2023-10-31\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", + "<\"POST /api/streams/_enable\", Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>, ", "StreamsRouteHandlerResources", - ", ", - { - "pluginId": "@kbn/core-http-server", - "scope": "server", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-server.IKibanaResponse", - "text": "IKibanaResponse" - }, - ", ", - { - "pluginId": "@kbn/server-route-repository-utils", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryUtilsPluginApi", - "section": "def-common.DefaultRouteCreateOptions", - "text": "DefaultRouteCreateOptions" - }, - ">; }" + ", { acknowledged: true; }, undefined>; }" ], "path": "x-pack/plugins/streams/server/routes/index.ts", "deprecated": false, @@ -299,120 +540,7 @@ "initialIsOpen": false } ], - "objects": [ - { - "parentPluginId": "streams", - "id": "def-server.StreamsRouteRepository", - "type": "Object", - "tags": [], - "label": "StreamsRouteRepository", - "description": [], - "path": "x-pack/plugins/streams/server/routes/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "streams", - "id": "def-server.StreamsRouteRepository.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/streams/server/routes/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "streams", - "id": "def-server.StreamsRouteRepository.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/streams/server/routes/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "streams", - "id": "def-server.StreamsRouteRepository.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/streams/server/routes/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "streams", - "id": "def-server.StreamsRouteRepository.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/streams/server/routes/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "streams", - "id": "def-server.StreamsRouteRepository.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/streams/server/routes/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "streams", - "id": "def-server.StreamsRouteRepository.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/streams/server/routes/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "streams", - "id": "def-server.StreamsRouteRepository.Unnamed", - "type": "Any", - "tags": [], - "label": "Unnamed", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/streams/server/routes/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - } - ], + "objects": [], "setup": { "parentPluginId": "streams", "id": "def-server.StreamsPluginSetup", @@ -447,7 +575,27 @@ "functions": [], "interfaces": [], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "streams", + "id": "def-common.StreamDefinition", + "type": "Type", + "tags": [], + "label": "StreamDefinition", + "description": [], + "signature": [ + "{ id: string; children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }" + ], + "path": "x-pack/plugins/streams/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "objects": [] } } \ No newline at end of file diff --git a/api_docs/streams.mdx b/api_docs/streams.mdx index f472dc831feb..711ba66f71ab 100644 --- a/api_docs/streams.mdx +++ b/api_docs/streams.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/streams title: "streams" image: https://source.unsplash.com/400x175/?github description: API docs for the streams plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'streams'] --- import streamsObj from './streams.devdocs.json'; @@ -21,7 +21,15 @@ Contact @simianhacker @flash1293 @dgieselaar for questions regarding this plugin | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 12 | 7 | 12 | 2 | +| 13 | 0 | 13 | 4 | + +## Client + +### Setup + + +### Start + ## Server @@ -31,9 +39,14 @@ Contact @simianhacker @flash1293 @dgieselaar for questions regarding this plugin ### Start -### Objects - +### Interfaces + ### Consts, variables and types +## Common + +### Consts, variables and types + + diff --git a/api_docs/streams_app.devdocs.json b/api_docs/streams_app.devdocs.json new file mode 100644 index 000000000000..2bb7bd48fb0c --- /dev/null +++ b/api_docs/streams_app.devdocs.json @@ -0,0 +1,148 @@ +{ + "id": "streamsApp", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "streamsApp", + "id": "def-public.StreamsAppPublicSetup", + "type": "Interface", + "tags": [], + "label": "StreamsAppPublicSetup", + "description": [], + "path": "x-pack/plugins/streams_app/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "streamsApp", + "id": "def-public.StreamsAppPublicStart", + "type": "Interface", + "tags": [], + "label": "StreamsAppPublicStart", + "description": [], + "path": "x-pack/plugins/streams_app/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "streamsApp", + "id": "def-server.StreamsAppServerSetup", + "type": "Interface", + "tags": [], + "label": "StreamsAppServerSetup", + "description": [], + "path": "x-pack/plugins/streams_app/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "streamsApp", + "id": "def-server.StreamsAppServerStart", + "type": "Interface", + "tags": [], + "label": "StreamsAppServerStart", + "description": [], + "path": "x-pack/plugins/streams_app/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "streamsApp", + "id": "def-common.EntityTypeDefinition", + "type": "Interface", + "tags": [], + "label": "EntityTypeDefinition", + "description": [], + "path": "x-pack/plugins/streams_app/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "streamsApp", + "id": "def-common.EntityTypeDefinition.displayName", + "type": "string", + "tags": [], + "label": "displayName", + "description": [], + "path": "x-pack/plugins/streams_app/common/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "streamsApp", + "id": "def-common.Entity", + "type": "Type", + "tags": [], + "label": "Entity", + "description": [], + "signature": [ + "EntityBase & { type: \"stream\"; properties: { id: string; children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }; }" + ], + "path": "x-pack/plugins/streams_app/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "streamsApp", + "id": "def-common.StreamEntity", + "type": "Type", + "tags": [], + "label": "StreamEntity", + "description": [], + "signature": [ + "EntityBase & { type: \"stream\"; properties: { id: string; children: { id: string; condition?: ", + "Condition", + "; }[]; fields: { type: \"boolean\" | \"ip\" | \"keyword\" | \"date\" | \"long\" | \"double\" | \"match_only_text\"; name: string; }[]; processing: { config: { type: \"grok\"; field: string; patterns: string[]; pattern_definitions?: Record | undefined; } | { type: \"dissect\"; field: string; pattern: string; }; condition?: ", + "Condition", + "; }[]; }; }" + ], + "path": "x-pack/plugins/streams_app/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/streams_app.mdx b/api_docs/streams_app.mdx new file mode 100644 index 000000000000..1a91877bccb0 --- /dev/null +++ b/api_docs/streams_app.mdx @@ -0,0 +1,49 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibStreamsAppPluginApi +slug: /kibana-dev-docs/api/streamsApp +title: "streamsApp" +image: https://source.unsplash.com/400x175/?github +description: API docs for the streamsApp plugin +date: 2024-11-26 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'streamsApp'] +--- +import streamsAppObj from './streams_app.devdocs.json'; + + + +Contact @simianhacker @flash1293 @dgieselaar for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 8 | 0 | 8 | 0 | + +## Client + +### Setup + + +### Start + + +## Server + +### Setup + + +### Start + + +## Common + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index e4e754803e32..1aa0629eaac5 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 93de1780c908..d11feca183d9 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 2e469f5dd20b..52bea13e8a62 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 229524e53cbb..cef1f9e119e0 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 2c095c7f45b6..141a225a8a66 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index 0904b1d8c25a..c404e4317d82 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -1465,7 +1465,181 @@ }, "common": { "classes": [], - "functions": [], + "functions": [ + { + "parentPluginId": "timelines", + "id": "def-common.getDataFromFieldsHits", + "type": "Function", + "tags": [], + "label": "getDataFromFieldsHits", + "description": [], + "signature": [ + "(fields: ", + "Fields", + ", prependField?: string | undefined, prependFieldCategory?: string | undefined) => ", + { + "pluginId": "timelines", + "scope": "common", + "docId": "kibTimelinesPluginApi", + "section": "def-common.TimelineEventsDetailsItem", + "text": "TimelineEventsDetailsItem" + }, + "[]" + ], + "path": "x-pack/plugins/timelines/common/utils/field_formatters.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-common.getDataFromFieldsHits.$1", + "type": "Object", + "tags": [], + "label": "fields", + "description": [], + "signature": [ + "Fields", + "" + ], + "path": "x-pack/plugins/timelines/common/utils/field_formatters.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "timelines", + "id": "def-common.getDataFromFieldsHits.$2", + "type": "string", + "tags": [], + "label": "prependField", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/timelines/common/utils/field_formatters.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.getDataFromFieldsHits.$3", + "type": "string", + "tags": [], + "label": "prependFieldCategory", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/timelines/common/utils/field_formatters.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.isGeoField", + "type": "Function", + "tags": [], + "label": "isGeoField", + "description": [], + "signature": [ + "(field: string) => boolean" + ], + "path": "x-pack/plugins/timelines/common/utils/field_formatters.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-common.isGeoField.$1", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/timelines/common/utils/field_formatters.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.toArray", + "type": "Function", + "tags": [], + "label": "toArray", + "description": [], + "signature": [ + "(value: T | T[] | null | undefined) => T[]" + ], + "path": "x-pack/plugins/timelines/common/utils/to_array.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-common.toArray.$1", + "type": "CompoundType", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "T | T[] | null | undefined" + ], + "path": "x-pack/plugins/timelines/common/utils/to_array.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.toObjectArrayOfStrings", + "type": "Function", + "tags": [], + "label": "toObjectArrayOfStrings", + "description": [], + "signature": [ + "(value: T | T[] | null) => { str: string; isObjectArray?: boolean | undefined; }[]" + ], + "path": "x-pack/plugins/timelines/common/utils/to_array.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-common.toObjectArrayOfStrings.$1", + "type": "CompoundType", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "T | T[] | null" + ], + "path": "x-pack/plugins/timelines/common/utils/to_array.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [ { "parentPluginId": "timelines", @@ -1843,10 +2017,10 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsData", + "id": "def-common.EqlFieldsComboBoxOptions", "type": "Interface", "tags": [], - "label": "EqlOptionsData", + "label": "EqlFieldsComboBoxOptions", "description": [], "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts", "deprecated": false, @@ -1854,7 +2028,7 @@ "children": [ { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsData.keywordFields", + "id": "def-common.EqlFieldsComboBoxOptions.keywordFields", "type": "Array", "tags": [], "label": "keywordFields", @@ -1869,7 +2043,7 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsData.dateFields", + "id": "def-common.EqlFieldsComboBoxOptions.dateFields", "type": "Array", "tags": [], "label": "dateFields", @@ -1884,7 +2058,7 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsData.nonDateFields", + "id": "def-common.EqlFieldsComboBoxOptions.nonDateFields", "type": "Array", "tags": [], "label": "nonDateFields", @@ -1902,10 +2076,10 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsSelected", + "id": "def-common.EqlOptions", "type": "Interface", "tags": [], - "label": "EqlOptionsSelected", + "label": "EqlOptions", "description": [], "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts", "deprecated": false, @@ -1913,7 +2087,7 @@ "children": [ { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsSelected.eventCategoryField", + "id": "def-common.EqlOptions.eventCategoryField", "type": "string", "tags": [], "label": "eventCategoryField", @@ -1927,7 +2101,7 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsSelected.tiebreakerField", + "id": "def-common.EqlOptions.tiebreakerField", "type": "string", "tags": [], "label": "tiebreakerField", @@ -1941,7 +2115,7 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsSelected.timestampField", + "id": "def-common.EqlOptions.timestampField", "type": "string", "tags": [], "label": "timestampField", @@ -1955,7 +2129,7 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsSelected.query", + "id": "def-common.EqlOptions.query", "type": "string", "tags": [], "label": "query", @@ -1969,7 +2143,7 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsSelected.size", + "id": "def-common.EqlOptions.size", "type": "number", "tags": [], "label": "size", @@ -4143,8 +4317,8 @@ "pluginId": "timelines", "scope": "common", "docId": "kibTimelinesPluginApi", - "section": "def-common.EqlOptionsSelected", - "text": "EqlOptionsSelected" + "section": "def-common.EqlOptions", + "text": "EqlOptions" } ], "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 5723d5da7100..88f7e01a3eec 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-threat-hunting-investigations](https://github.com/org | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 226 | 1 | 182 | 17 | +| 236 | 1 | 192 | 18 | ## Client @@ -50,6 +50,9 @@ Contact [@elastic/security-threat-hunting-investigations](https://github.com/org ### Objects +### Functions + + ### Interfaces diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index c2b15803ef43..0e972db1e229 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 016df87a06b4..401e974737e7 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 63e67e2912b7..9d126442020d 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index efe94eda0dbc..54888f409d0c 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx index b9881cc1a868..8619114a7169 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index fa6a90ef3c43..4b186ad113eb 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 27d48adfdd4d..7cf003158cdd 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 9d34ee86d077..862dbba5825d 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index 91414273e346..afeb97f9a234 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index f242ed8b5d12..d290f15b8d29 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 10c3efd066e1..82ca2063d347 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 4f61763b867a..ca1c1c544650 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index ac983214beac..e21c0b14aff6 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index fe647283551a..2a95c87e774d 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index b318b94fb8ab..e10792c5d7e6 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index f9f64ea67749..6cad51ffd9cf 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index ce49c0df7c69..f749b43b6da1 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index d4b47db92ec7..6c8c83062bb2 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 025159bbdfbd..8b11a9920127 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 2586437fb0b6..a86e7396aab6 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 7f3094d3c899..b1ff41b15eb9 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 493825c0b209..4a38efe31c72 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index e2bc48582369..831e757cf5ab 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2024-11-20 +date: 2024-11-26 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/config/serverless.es.yml b/config/serverless.es.yml index eafa7f311339..4b0d416bacdf 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -113,7 +113,7 @@ data_visualizer.resultLinks.fileBeat.enabled: false xpack.searchPlayground.ui.enabled: true # Search InferenceEndpoints -xpack.searchInferenceEndpoints.ui.enabled: false +xpack.searchInferenceEndpoints.ui.enabled: true # Search Notebooks xpack.search.notebooks.catalog.url: https://elastic-enterprise-search.s3.us-east-2.amazonaws.com/serverless/catalog.json diff --git a/config/serverless.yml b/config/serverless.yml index 0967df966f61..d62f26a4642e 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -229,3 +229,8 @@ monitoring.ui.enabled: false xpack.securitySolution.enableUiSettingsValidations: true data.enableUiSettingsValidations: true discover.enableUiSettingsValidations: true + +## Data Usage in stack management +xpack.dataUsage.enabled: true +# This feature is disabled in Serverless until fully tested within a Serverless environment +xpack.dataUsage.enableExperimental: ['dataUsageDisabled'] diff --git a/dev_docs/key_concepts/feature_privileges.mdx b/dev_docs/key_concepts/feature_privileges.mdx index 7666ca1e8239..87f650133be2 100644 --- a/dev_docs/key_concepts/feature_privileges.mdx +++ b/dev_docs/key_concepts/feature_privileges.mdx @@ -179,8 +179,10 @@ public setup(core: CoreSetup, deps: FeatureControlExampleDeps) { { path: '/internal/my_plugin/sensitive_action', validate: false, - options: { - tags: ['access:my_closed_example_api'], + security: { + authz: { + requiredPrivileges: ['my_closed_example_api'] + } }, }, async (context, request, response) => { @@ -193,8 +195,11 @@ public setup(core: CoreSetup, deps: FeatureControlExampleDeps) { ); } ``` + + For more information on the `security.authz` object and API authorization, please refer to our guide on + -Notice, we've added an `options.tags` property for the API route that returns sensitive information. This tag is then used in the privileges object as follow +Notice, we've added a `security.authz.requiredPrivileges` property for the API route that returns sensitive information. This added configuration is then used in the privileges object as follow ```ts { @@ -347,7 +352,6 @@ A deep dive into every option for the Kibana Feature configuration and what they } ``` - ### FeatureKibanaPrivileges Interface #### excludeFromBasePrivileges (optional) diff --git a/dev_docs/tutorials/advanced_settings.mdx b/dev_docs/tutorials/advanced_settings.mdx index d14da92edc93..6789e9d6af7a 100644 --- a/dev_docs/tutorials/advanced_settings.mdx +++ b/dev_docs/tutorials/advanced_settings.mdx @@ -53,26 +53,23 @@ On the client, the `uiSettings` service is accessible directly from `core` and t The following is a basic example for using the `uiSettings` service: **src/plugins/charts/public/plugin.ts** + ```ts import { Plugin, CoreSetup } from '@kbn/core/public'; -import { ExpressionsSetup } from '../../expressions/public'; +import { ExpressionsSetup } from '@kbn/expressions-plugin/public'; import { palette, systemPalette } from '../common'; -import { ThemeService, LegacyColorsService } from './services'; +import { ThemeService } from './services'; import { PaletteService } from './services/palettes/service'; import { ActiveCursor } from './services/active_cursor'; -export type Theme = Omit; -export type Color = Omit; - interface SetupDependencies { expressions: ExpressionsSetup; } /** @public */ export interface ChartsPluginSetup { - legacyColors: Color; - theme: Theme; + theme: Omit; palettes: ReturnType; } @@ -84,7 +81,6 @@ export type ChartsPluginStart = ChartsPluginSetup & { /** @public */ export class ChartsPlugin implements Plugin { private readonly themeService = new ThemeService(); - private readonly legacyColorsService = new LegacyColorsService(); private readonly paletteService = new PaletteService(); private readonly activeCursor = new ActiveCursor(); @@ -93,14 +89,12 @@ export class ChartsPlugin implements Plugin - Refer to [the server-side uiSettings service API docs](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md) + Refer to [the server-side uiSettings service API + docs](https://github.com/elastic/kibana/blob/main/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md) -**src/plugins/charts/server/plugin.ts** +**src/plugins/dev_tools/server/plugin.ts** ```ts -import { i18n } from '@kbn/i18n'; -import { schema } from '@kbn/config-schema'; -import { CoreSetup, Plugin } from '@kbn/core/server'; -import { COLOR_MAPPING_SETTING, LEGACY_TIME_AXIS, palette, systemPalette } from '../common'; -import { ExpressionsServerSetup } from '../../expressions/server'; +import { PluginInitializerContext, Plugin, CoreSetup } from '@kbn/core/server'; -interface SetupDependencies { - expressions: ExpressionsServerSetup; -} +import { uiSettings } from './ui_settings'; +export class DevToolsServerPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} -export class ChartsServerPlugin implements Plugin { - public setup(core: CoreSetup, dependencies: SetupDependencies) { - dependencies.expressions.registerFunction(palette); - dependencies.expressions.registerFunction(systemPalette); + public setup(core: CoreSetup) { + /** + * Register Dev Tools UI Settings + */ core.uiSettings.register({ - [COLOR_MAPPING_SETTING]: { - name: i18n.translate('charts.advancedSettings.visualization.colorMappingTitle', { - defaultMessage: 'Color mapping', - }), - value: JSON.stringify({ - Count: '#00A69B', - }), - type: 'json', - description: i18n.translate('charts.advancedSettings.visualization.colorMappingText', { + [ENABLE_PERSISTENT_CONSOLE_UI_SETTING_ID]: { + category: [DEV_TOOLS_FEATURE_ID], + description: i18n.translate('devTools.uiSettings.persistentConsole.description', { defaultMessage: - 'Maps values to specific colors in charts using the Compatibility palette.', + 'Enables a persistent console in the Kibana UI. This setting does not affect the standard Console in Dev Tools.', }), - deprecation: { - message: i18n.translate( - 'charts.advancedSettings.visualization.colorMappingTextDeprecation', - { - defaultMessage: - 'This setting is deprecated and will not be supported in a future version.', - } - ), - docLinksKey: 'visualizationSettings', - }, - category: ['visualization'], - schema: schema.string(), + name: i18n.translate('devTools.uiSettings.persistentConsole.name', { + defaultMessage: 'Persistent Console', + }), + requiresPageReload: true, + schema: schema.boolean(), + value: true, }, - ... }); - return {}; } - + public start() { return {}; } public stop() {} -} +} ``` -For optimal Kibana performance, `uiSettings` are cached. Any changes that require a cache refresh should use the `requiresPageReload` parameter on registration. + +For optimal Kibana performance, `uiSettings` are cached. Any changes that require a cache refresh should use the `requiresPageReload` parameter on registration. For example, changing the time filter refresh interval triggers a prompt in the UI that the page needs to be refreshed to save the new value: diff --git a/dev_docs/tutorials/generating_oas_for_http_apis.mdx b/dev_docs/tutorials/generating_oas_for_http_apis.mdx index 19852206f800..f0f497c4286f 100644 --- a/dev_docs/tutorials/generating_oas_for_http_apis.mdx +++ b/dev_docs/tutorials/generating_oas_for_http_apis.mdx @@ -57,7 +57,9 @@ Other useful query parameters for filtering are: import { schema, TypeOf } from '@kbn/config-schema'; export const fooResource = schema.object({ - name: schema.string() + name: schema.string({ + meta: { description: 'A unique identifier for...' }, + }), // ...and any other fields you may need }); @@ -114,8 +116,14 @@ function registerFooRoute(router: IRouter, docLinks: DoclinksStart) { access: 'public', summary: 'Create a foo resource' description: `A foo resource enables baz. See the following [documentation](${docLinks.links.fooResource}).`, - tags: ['oas-tag:my tag'], // Each operation must have a tag that's used to group similar endpoints in the docs - deprecated: true // An indicator that the operation is deprecated + deprecated: true, // An indicator that the operation is deprecated + options: { + tags: ['oas-tag:my tag'], // Each operation must have a tag that's used to group similar endpoints in the docs + availability: { + since: '1.0.0', + stability: 'experimental', + }, + }, }) .addVersion({ version: '2023-10-31', diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 85c5bcfbf112..848042e475fe 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -8,8984 +8,91 @@ :issue: https://github.com/elastic/kibana/issues/ :pull: https://github.com/elastic/kibana/pull/ -Review important information about the {kib} 8.x releases. +Review important information about the {kib} 9.x releases. -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> +* <> -- include::upgrade-notes.asciidoc[] +[[release-notes-9.0.0]] +== {kib} 9.0.0 -[[release-notes-8.16.0]] -== {kib} 8.16.0 +For information about the {kib} 9.0.0 release, review the following information. -For information about the {kib} 8.16.0 release, review the following information. - -The 8.16.0 release includes the following known issues. - -[float] -[[known-issues-8.16.0]] -=== Known issues - -[discrete] -[[known-199902]] -.Stack Monitoring shows "Unable to load page" error -[%collapsible] -==== -*Details* + -The Overview, Nodes, and Logs pages in Stack Monitoring show an "Unable to load page" error. The Stack trace mentions `TypeError: Cannot read properties of undefined (reading 'logsLocator')`. - -*Workaround* + -Disabling the `Set feature visibility > Logs` feature at the Kibana Space settings level will prevent the error from occurring. Please note the `Logs` feature will not be available on those spaces. - -It's also possible to set the `Observability > Logs` feature privilege to `None` at the role level. This will hide the `Logs` feature from individual users and prevent the error for these users as well. - -For more information, refer to {kibana-issue}199902[#199902]. -==== - -[discrete] -[[known-199891-199892]] -.Onboarding, tutorial of APM and OpenTelemetry and some "Beats Only" integrations shows "Unable to load page" error -[%collapsible] -==== -*Details* + -Tutorials linked from the {kib} home page show an "Unable to load page" error. The Stack trace mentions `The above error occurred in tutorial_TutorialUi`. - -*Workaround* + -The APM / OpenTelemetry tutorials represented a shortcut to quickly add important parameters to the configuration files. -It is still possible to obtain the same parameters following the tutorials in the APM documentation. - -More information can be found in the {observability-guide}/apm-collect-application-data.html[APM documentation] and the {observability-guide}/get-started-with-fleet-apm-server.html[Fleet documentation]. - -For information about how to create APM API keys, please check the {observability-guide}/apm-api-key.html#apm-create-an-api-key[API key documentation]. - -For more information, refer to {kibana-issue}199891[#199891] and {kibana-issue}199892[#199892]. -==== - -[float] -[[deprecations-8.16.0]] -=== Deprecations - -The following functionality is deprecated in 8.16.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.16.0. - -[discrete] -.The Logs Stream is now hidden by default in favor of the Logs Explorer app. -[%collapsible] -==== -*Details* + -You can find the Logs Explorer app in the navigation menu under Logs > Explorer, or as a separate tab in Discover. For more information, refer to ({kibana-pull}194519[#194519]). - -*Impact* + -You can still show the Logs Stream app again by navigating to Stack Management > Advanced Settings and by enabling the `observability:enableLogsStream` setting. -==== - -[discrete] -.Deprecates the Observability AI Assistant specific advanced setting `observability:aiAssistantLogsIndexPattern`. -[%collapsible] -==== -*Details* + -The Observability AI Assistant specific advanced setting for Logs index patterns `observability:aiAssistantLogsIndexPattern` is deprecated and no longer used. The AI Assistant will now use the existing **Log sources** setting `observability:logSources` instead. For more information, refer to ({kibana-pull}192003[#192003]). - -//*Impact* + -//!!TODO!! -==== [float] -[[breaking-changes-8.16.0]] +[[breaking-changes-9.0.0]] === Breaking changes -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.16.0, review the breaking changes, then mitigate the impact to your application. - [discrete] -.Updated request processing during shutdown. +.Removed all security v1 endpoints (9.0.0) [%collapsible] ==== *Details* + -During shutdown, {kib} now waits for all the ongoing requests to complete according to the `server.shutdownTimeout` setting. During that period, the incoming socket is closed and any new incoming requests are rejected. Before this update, new incoming requests received a response with the status code 503 and body `{"message": "Kibana is shutting down and not accepting new incoming requests"}`. For more information, refer to {kibana-pull}180986[#180986]. -==== - -[float] -[[features-8.16.0]] -=== Features -{kib} 8.16.0 adds the following new and notable features. - -AGPL license:: -* Adds AGPL 3.0 license ({kibana-pull}192025[#192025]). -Alerting:: -* Adds TheHive connector ({kibana-pull}180138[#180138]). -* Adds flapping settings per rule ({kibana-pull}189341[#189341]). -* Efficiency improvements in the Kibana task manager and alerting frameworks ({kibana-issue}188194[#188194]) -Cases:: -* Support TheHive connector in cases ({kibana-pull}180931[#180931]). -Dashboards and visualizations:: -* Adds the ability to star your favorite dashboards and quickly find them ({kibana-pull}189285[#189285]). -* Adds a chart showing usage statistics to the dashboard details ({kibana-pull}187993[#187993]). -* Adds metric styling options in *Lens* ({kibana-pull}186929[#186929]). -* Adds support for coloring table cells by terms with color mappings assignments. This is supported for both Rows and Metric dimensions ({kibana-pull}189895[#189895]). -Data ingestion and Fleet:: -* Support content packages in UI ({kibana-pull}195831[#195831]). -* Advanced agent monitoring options UI for HTTP endpoint and diagnostics ({kibana-pull}193361[#193361]). -* Adds option to have Kafka dynamic topics in outputs ({kibana-pull}192720[#192720]). -* Adds support for GeoIP processor databases in Ingest Pipelines ({kibana-pull}190830[#190830]). -// !!TODO!! The above PR had a lengthy release note description: -// The Ingest Pipelines app now supports adding and managing databases for the GeoIP processor. Additionally, the pipeline creation flow now includes support for the IP Location processor. -* Adds agentless ux creation flow ({kibana-pull}189932[#189932]). -* Enable feature flag for reusable integration policies ({kibana-pull}187153[#187153]). -Discover:: -* When writing ES|QL queries, you now get recommendations to help you get started ({kibana-pull}194418[#194418]). -* Enhances the inline documentation experience in ES|QL mode ({kibana-pull}192156[#192156]). -* Adds the ability to break down the histogram by field for ES|QL queries in Discover ({kibana-pull}193820[#193820]). -* Adds a summary column to the Documents table when exploring log data in Discover ({kibana-pull}192567[#192567]). -* Adds row indicators to the Documents table when exploring log data in Discover ({kibana-pull}190676[#190676]). -* Moves the button to switch between ES|QL and classic modes to the toolbar ({kibana-pull}188898[#188898]). -* Adds density settings to allow further customization of the Documents table layout ({kibana-pull}188495[#188495]). -* Enables the time picker for indices without the @timestamp field when editing ES|QL queries ({kibana-pull}184361[#184361]). -Elastic Observability solution:: -* Adds experimental logs overview to the observability hosts and service overviews ({kibana-pull}195673[#195673]). -* Show alerts for entities ({kibana-pull}195250[#195250]). -* Create sub-feature role to manage APM settings write permissions ({kibana-pull}194419[#194419]). -* Adds related alerts tab to the alert details page ({kibana-pull}193263[#193263]). -* Adds labels field !! ({kibana-pull}193250[#193250]). -* Implement _ignored root cause identification flow ({kibana-pull}192370[#192370]). -* Enable page for synthetics ({kibana-pull}191846[#191846]). -* Settings add config to enable default rules ({kibana-pull}190800[#190800]). -* Added alerts page ({kibana-pull}190751[#190751]). -* Monitor list add bulk delete ({kibana-pull}190674[#190674]). -* Delete monitor API via id param !! ({kibana-pull}190210[#190210]). -* Enable metrics and traces in the Data Set Quality page ({kibana-pull}190043[#190043]). -* Adds alert grouping functionality to the observability alerts page ({kibana-pull}189958[#189958]). -* Adds a new SLO Burn Rate embeddable ({kibana-pull}189429[#189429]). -* The Slack Web API Alert Connector is now supported as a default connector for Synthetics and Uptime rules ({kibana-pull}188437[#188437]). -* Adds option to enable backfill transform ({kibana-pull}188379[#188379]). -* Save the ECS group by fields at the AAD root level ({kibana-pull}188241[#188241]). -* Adds last value aggregation ({kibana-pull}187082[#187082]). -* Improve synthetics alerting ({kibana-pull}186585[#186585]). -* Make overview grid embeddable ({kibana-pull}160597[#160597]). -Elastic Security solution:: -For the Elastic Security 8.16.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Kibana security:: -* Adds an API endpoint `POST security/roles` that can be used to bulk create or update roles ({kibana-pull}189173[#189173]). -* Automatic Import can now create integrations for logs in the CSV format ({kibana-pull}194386[#194386]). -* Adds an error handling framework to Automatic Import that provides error messages with more context to user ({kibana-pull}193577[#193577]). -* When running in FIPS mode, Kibana forbids usage of PKCS12 configuration options ({kibana-pull}192627[#192627]). -Machine Learning:: -* Adds new section for creating daylight saving time calendar events ({kibana-pull}193605[#193605]). -* Anomaly Detection: Adds a page to list supplied job configurations ({kibana-pull}191564[#191564]). -* Redesigns start/update model deployment dialog to support adaptive resources ({kibana-pull}190243[#190243]). -* File upload: Adds support for PDF files ({kibana-pull}186956[#186956]). -* Adds Pattern analysis embeddable for dashboards ({kibana-pull}186539[#186539]). -Management:: -* This release introduces a fresh, modern look for the console, now featuring the Monaco editor. We've added a file import and export functionality, and the console is fully responsive with stackable panels for a smoother experience. New buttons allow for quick clearing of editor values and output. Additionally, the history and config tabs were improved to enhance usability. ({kibana-pull}189748[#189748]). - -For more information about the features introduced in 8.16.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.16.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.16.0 release, review the enhancements and bug fixes. - - -[float] -[[enhancement-v8.16.0]] -=== Enhancements -Alerting:: -* Allow users to select template while adding a case action in the rule ({kibana-pull}190701[#190701]). -* New full-page rule form in the Stack Management app ({kibana-pull}194655[#194655]). -Dashboards and visualizations:: -* Adds compressed style for dashboard controls ({kibana-pull}190636[#190636]). -* Adds the ability to duplicate a managed dashboard from its `managed` badge ({kibana-pull}189404[#189404]). -* Adds the ability to expand the height of various sections in the Edit ES|QL visualization flyout ({kibana-pull}193453[#193453]). -* Improves the query authoring experience when editing an ES|QL visualization ({kibana-pull}186875[#186875]). -* Syncs the cursor for time series charts powered by ES|QL ({kibana-pull}192837[#192837]). -* Gauge and metric Lens visualizations are no longer experimental ({kibana-pull}192359[#192359]). -* Sets gauge default palette to "temperature" in *Lens* ({kibana-pull}191853[#191853]). -* Supports fuzzy search on field pickers and field lists in *Lens* ({kibana-pull}186894[#186894]). -Data ingestion and Fleet:: -* Update max supported package version ({kibana-pull}196551[#196551]). -* Adds additional columns to Agent Logs UI ({kibana-pull}192262[#192262]). -* Show `+build` versions for Elastic Agent upgrades ({kibana-pull}192171[#192171]). -* Added format parameter to `agent_policies` APIs ({kibana-pull}191811[#191811]). -* Adds toggles for `agent.monitoring.http.enabled` and `agent.monitoring.http.buffer.enabled` to agent policy advanced settings ({kibana-pull}190984[#190984]). -* Support integration policies without agent policy references (aka orphaned integration policies) ({kibana-pull}190649[#190649]). -* Changed the UX of the Edit Integration Policy page to update agent policies ({kibana-pull}190583[#190583]). -* Allow `traces` to be added to the `monitoring_enabled` array in Agent policies ({kibana-pull}189908[#189908]). -* Create task that periodically unenrolls inactive agents ({kibana-pull}189861[#189861]). -* Adds setup technology selector to add integration page ({kibana-pull}189612[#189612]). -* Support integration-level outputs ({kibana-pull}189125[#189125]). -Discover:: -* Renames the Documents tab to Results in ES|QL mode ({kibana-pull}197833[#197833]). -* Adds a cluster details tab for CCS data sources when inspecting requests in ES|QL mode ({kibana-pull}195373[#195373]). -* Adds the query time to the list of statistics when inspecting requests in ES|QL mode ({kibana-pull}194806[#194806]). -* Improves display of error messages in ES|QL mode ({kibana-pull}191320[#191320]). -* Adds a help menu to the ES|QL mode ({kibana-pull}190579[#190579]). -* Initializes the ES|QL editor with time named parameters when switching from the classic mode with a data view without @timestamp ({kibana-pull}189367[#189367]). -* Adds the ability to select multiple rows from the Documents table using "Shift + Select" ({kibana-pull}193619[#193619]). -* Adds the ability to filter on field names and values in the expanded document view ({kibana-pull}192299[#192299]). -* Adds filtering for selected fields ({kibana-pull}191930[#191930]). -* Adds a dedicated column to the document viewer flyout for pinning and unpinning rows ({kibana-pull}190344[#190344]). -* Improves absolute column width handling ({kibana-pull}190288[#190288]). -* Allows filtering by field type in the document viewer flyout ({kibana-pull}189981[#189981]). -* Improves the document viewer flyout to remember the last active tab ({kibana-pull}189806[#189806]). -* Adds ability to hide fields with null values from the document viewer ({kibana-pull}189601[#189601]). -* Adds the ability to copy selected rows as text ({kibana-pull}189512[#189512]). -* Adds a log level badge cell renderer to the Discover logs profile ({kibana-pull}188281[#188281]). -* Shows ECS field descriptions in Discover and adds markdown support for field descriptions ({kibana-pull}187160[#187160]). -* Adds support for the Log overview tab to the Discover log profile ({kibana-pull}186680[#186680]). -* Adds default app state extension and log integration data source profiles ({kibana-pull}186347[#186347]). -* Allows to select and deselect all rows in the grid at once ({kibana-pull}184241[#184241]). -* Limits the height of long field values by default ({kibana-pull}183736[#183736]). -ES|QL editor:: -* Changes the auto-focus to be on the ES|QL editor when loading the page ({kibana-pull}193800[#193800]). -* Updates the autocomplete behavior for `SORT` to be in line with other field-list-based experiences like `KEEP` in ES|QL queries ({kibana-pull}193595[#193595]). -* Adds `all (*)` to the list of suggestions for `COUNT` functions in ES|QL queries ({kibana-pull}192205[#192205]). -* Improves ES|QL autocomplete suggestions for `case()` expressions ({kibana-pull}192135[#192135]). -* Opens suggestions automatically for sources lists and `ENRICH` functions when writing ES|QL queries ({kibana-pull}191312[#191312]). -* Improves wrapping and readability for ES|QL queries ({kibana-pull}191269[#191269]). -* Improves suggestions based on previous function arguments and date suggestions for `bucket` functions in ES|QL queries ({kibana-pull}190828[#190828]). -* Show the `LIMIT` information in the ES|QL editor's footer ({kibana-pull}190498[#190498]). -* Opens suggestions automatically for field lists in ES|QL queries ({kibana-pull}190466[#190466]). -* Integrates a time picker for date fields into the ES|QL editor ({kibana-pull}187047[#187047]). -* Improves ES|QL support for Elasticsearch sub-types in AST for both validation and autocomplete ({kibana-pull}189689[#189689]). -* Adds ECS information to the ES|QL editor suggestions and prioritizes fields based on ECS information on the editor ({kibana-pull}187922[#187922]). -* Improves `BY` suggestions in ES|QL queries to include pipe and comma operators ({kibana-pull}189458[#189458]). -* Makes the suggestion menu open automatically in more places in ES|QL queries ({kibana-pull}189585[#189585]). -* Adds hints upon hover for function argument types and time system types ({kibana-pull}191881[#191881]). -Elastic Observability solution:: -* Enable Kubernetes Otel flow ({kibana-pull}196531[#196531]). -* Pass function responses when copying conversation ({kibana-pull}195635[#195635]). -* Turn 'fast filter' on by default and ensure tech preview badge shows when turned on ({kibana-pull}193710[#193710]). -* Custom Service Name Cell ({kibana-pull}192381[#192381]). -* Remove manage_transform and manage_ingest_pipeline privilege requirements ({kibana-pull}190572[#190572]). -* Create new formula for CPU Usage metric ({kibana-pull}189261[#189261]). -* Adds customizable header for quickstart flows ({kibana-pull}188340[#188340]). -* Change Kubernetes guide to link to observability onboarding ({kibana-pull}188322[#188322]). -* Adds KB user instructions ({kibana-pull}187607[#187607]). -* Refactor Synthetics Overview page for increased scalability ({kibana-pull}187092[#187092]). -* Improve synthetics alerting ({kibana-pull}186585[#186585]). -* Annotations Initial phase ({kibana-pull}184325[#184325]). -Elastic Search solution:: -* Adds Alibaba AI Search to Deletion, search and filtering of inference endpoints ({kibana-pull}190783[#190783]). -Elastic Security solution:: -For the Elastic Security 8.16.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Kibana security:: -* Enhances Open API spec generation to include Route Security Authorization if available ({kibana-pull}197001[#197001]). -* Automatic Import now analyzes larger number of samples to generate an integration ({kibana-pull}196233[#196233]). -* Extended `KibanaRouteOptions` to include security configuration at the route definition level ({kibana-pull}191973[#191973]). -* Adds several UX improvements to the management of Spaces in **Stack Management > Spaces**, including the ability to assign Roles to an existing Space. ({kibana-pull}191795[#191795]). -* Displays an "invalid file" error when selecting unsupported file types for the user profile image ({kibana-pull}190077[#190077]). -* Displays a warning to users whenever role mappings with empty `any` or `all` rules are created or updated ({kibana-pull}189340[#189340]). -* Adds support for CHIPS cookies ({kibana-pull}188519[#188519]). -* Adds support for Permissions Policy reporting ({kibana-pull}186892[#186892]). -Machine Learning:: -* File upload: enables check for model allocations ({kibana-pull}197395[#197395]). -* Data visualizer: Adds icons for semantic text, sparse vector, and dense vector ({kibana-pull}196069[#196069]). -* Updates vCPUs ranges for start model deployment ({kibana-pull}195617[#195617]). -* Adds ML tasks to the Kibana audit log ({kibana-pull}195120[#195120]). -* Anomaly Detection: adds ability to delete forecasts from job ({kibana-pull}194896[#194896]). -* Updates for Trained Models table layout and model states ({kibana-pull}194614[#194614]). -* Log rate analysis: ensures ability to sort on Log rate change ({kibana-pull}193501[#193501]). -* Single Metric Viewer: Enables cross-filtering for 'by', 'over', and 'partition' field values ({kibana-pull}193255[#193255]). -* Adds link to anomaly detection configurations from Integration > Assets tab ({kibana-pull}193105[#193105]). -* Anomaly Explorer: Displays markers for scheduled events in distribution-type anomaly charts ({kibana-pull}192377[#192377]). -* Serverless Security: Adds ES|QL visualizer menu item to the nav ({kibana-pull}192314[#192314]). -* Updates icons for Machine Learning embeddable dashboard panel types ({kibana-pull}191718[#191718]). -* AIOps: Uses no minimum time range by default for pattern analysis ({kibana-pull}191192[#191192]). -* Links to ML assets from Integration > Assets tab ({kibana-pull}189767[#189767]). -* Utilizes the `DataViewLazy` in ML plugin ({kibana-pull}189188[#189188]). -* AIOps: Chunks groups of field candidates into single queries for top items and histograms ({kibana-pull}189155[#189155]). -* AIOps: Updates fields filter popover to be able to filter fields from analysis (not just grouping) ({kibana-pull}188913[#188913]). -* Single Metric Viewer embeddable: adds forecasting ({kibana-pull}188791[#188791]). -* Adds new custom rule action to force time shift ({kibana-pull}188710[#188710]). -* AIOps: Chunks groups of field candidates into single queries ({kibana-pull}188137[#188137]). -* AIOps: Adds log rate analysis to alert details page contextual insight ({kibana-pull}187690[#187690]). -* Adds ability to toggle visibility for empty fields when choosing an aggregation or field in Anomaly detection, data frame analytics ({kibana-pull}186670[#186670]). -* Anomaly Detection: Adds popover links menu to anomaly explorer charts ({kibana-pull}186587[#186587]). -Management:: -* Adds an option to show or hide empty fields in dropdown lists in Transform ({kibana-pull}195485[#195485]). -* Adds a confirmation dialog when deleting a transform from a warning banner ({kibana-pull}192080[#192080]). -* Improves the autocomplete to suggest fields for the `dense_vector` type in Console ({kibana-pull}190769[#190769]). -* Adds the ability to view an ILM policy details in read-only mode ({kibana-pull}186955[#186955]). - -[float] -[[fixes-v8.16.0]] -=== Bug fixes -Alerting:: -* Show up to 1k maintenance windows in the UI ({kibana-pull}198504[#198504]) -* Skip scheduling actions for the alerts without scheduledActions ({kibana-pull}195948[#195948]). -* Fixes Stack Alerts feature API access control ({kibana-pull}193948[#193948]). -* Remove unintended internal find routes API with public access ({kibana-pull}193757[#193757]). -* Convert timestamp before passing to validation ({kibana-pull}192379[#192379]). -* Grouped over field is not populated correctly when editing a rule ({kibana-pull}192297[#192297]). -* Mark slack rate-limiting errors as user errors ({kibana-pull}192200[#192200]). -* Fixes maintenance window filtering with wildcards ({kibana-pull}194777[#194777]). -* Fixes search filters in rules, alerts, and maintenance windows ({kibana-pull}193623[#193623]). -Cases:: -* Use absolute time ranges when adding visualizations to a case ({kibana-pull}189168[#189168]). -* Fixes custom fields with long text that could not be edited in the UI ({kibana-pull}190490[#190490]). -Dashboards and visualizations:: -* Correctly show full screen mode when opening a dashboard or panel from a URL that contains the fullScreenMode parameter ({kibana-pull}196275[#196275]) and ({kibana-pull}190086[#190086]). -* Fixes an issue that could cause a the dashboard list to stay in loading state ({kibana-pull}195277[#195277]). -* Correctly use the same field icons as Discover ({kibana-pull}194095[#194095]). -* Fixes an issue where panels could disappear from a dashboard when canceling edit after saving the dashboard ({kibana-pull}193914[#193914]). -* Adds scroll margin to panels ({kibana-pull}193430[#193430]). -* Fixes an issue with the breadcrumb update icon not working when clicked ({kibana-pull}192240[#192240]). -* Fixes an issue where unsaved changes could remain after saving a dashboard ({kibana-pull}190165[#190165]). -* Fixes an issue causing the flyout to close when canceling the Save to library action ({kibana-pull}188995[#188995]). -* Fixes incomplete string escaping and encoding in *TSVB* ({kibana-pull}196248[#196248]). -* Fixes an issue where label truncation in heat map legends was not working properly in *Lens* ({kibana-pull}195928[#195928]). -* Fixes an issue where the color picker and axis side settings were incorrectly available in the breakdown dimension editor for XY charts in *Lens* ({kibana-pull}195845[#195845]). -* Fixes the tooltip position on faceted charts in *Vega* ({kibana-pull}194620[#194620]). -* Fixes the filter out legend action for ES|QL visualizations ({kibana-pull}194374[#194374]). -* Fixes element sizing issues in full screen mode in *Vega* ({kibana-pull}194330[#194330]). -* Fixes the default cell text alignment setting for non-numeric field types in *Lens* ({kibana-pull}193886[#193886]). -* Limits the height of the query bar input for long KQL queries ({kibana-pull}193737[#193737]). -* Makes the title correctly align left after removing an icon in **Lens** metric charts ({kibana-pull}191057[#191057]). -* Fixes a "No data" error caused by the "Collapse by" setting in **Lens** metric charts ({kibana-pull}190966[#190966]). -* Fixes an issue causing the color of a cell to disappear when clicking the "Expand cell" icon in *Lens* ({kibana-pull}190618[#190618]). -* Removes unnecessary index pattern references from Lens charts ({kibana-pull}190296[#190296]). -* Fixes several accessibility issues ({kibana-pull}188624[#188624]). -Data ingestion and Fleet:: -* Revert "Fix client-side validation for agent policy timeout fields" ({kibana-pull}194338[#194338]). -* Adds proxy arguments to install snippets ({kibana-pull}193922[#193922]). -* Rollover if dimension mappings changed in dynamic templates ({kibana-pull}192098[#192098]). -Discover:: -* Fixes an issue with search highlighting ({kibana-pull}197607[#197607]). -* Correctly pass embeddable filters to the Surrounding Documents page ({kibana-pull}197190[#197190]). -* Fixes trailing decimals dropped from client side validation messages ({kibana-pull}196570[#196570]). -* Fixes several validation issues and creates an expression type evaluator for ES|QL queries ({kibana-pull}195989[#195989]). -* Fixes duplicate autocomplete suggestions for `WHERE` clauses and suggestions with no space in between in ES|QL queries ({kibana-pull}195771[#195771]). -* Improves variable and field name handling in ES|QL queries ({kibana-pull}195149[#195149]). -* Fixes an issue where the Unified Field List popover could get cut off ({kibana-pull}195147[#195147]). -* Fixes the width for saved object type columns ({kibana-pull}194388[#194388]). -* Adds tooltips to Discover button icons ({kibana-pull}192963[#192963]). -* Excludes inactive integration data stream suggestions ({kibana-pull}192953[#192953]). -* Fixes new variables being suggested in incorrect places ({kibana-pull}192405[#192405]). -* Only log requests in the Inspector when they completed ({kibana-pull}191232[#191232]). -ES|QL editor:: -* Fixes an issue where the autocomplete suggestions could cause duplicate entries in ES|QL queries ({kibana-pull}190465[#190465]). -* Fixes several styling issues in the ES|QL editor ({kibana-pull}190170[#190170]). -Elastic Observability solution:: -* Change the slice outcome from bad to good whenever there is no data during the slice window ({kibana-pull}196942[#196942]). -* Make agent names generic with otel-native mode ({kibana-pull}195594[#195594]). -* Avoid showing unnecessary error toast ({kibana-pull}195331[#195331]). -* Use `fields` instead of `_source` on APM queries ({kibana-pull}195242[#195242]). -* Fixes ping heatmap payload ({kibana-pull}195107[#195107]). -* Fixes rule modal warnings in the developer console ({kibana-pull}194766[#194766]). -* Avoid AI assistant overlaying AI conversations ({kibana-pull}194722[#194722]). -* Improve loading state for metric items ({kibana-pull}192930[#192930]). -* Fixes issue where heatmap UI crashes on undefined histogram data ({kibana-pull}192508[#192508]). -* Calculate the latest metadata lookback based on the calculated history delay ({kibana-pull}191324[#191324]). -* Remove dedicated language setting ({kibana-pull}190983[#190983]). -* Change latest metric to use @timestamp ({kibana-pull}190417[#190417]). -* Prevent initial error when adding filters ({kibana-pull}190214[#190214]). -* Display error message when failing to enable machine learning anomaly detection in Inventory ({kibana-pull}189627[#189627]). -* Convert route validation to Zod ({kibana-pull}188691[#188691]). -* Fixes functions table height in asset details view profiling tab ({kibana-pull}188650[#188650]). -* Adds four decimal places float validation for transaction_sample_rate ({kibana-pull}188555[#188555]). -* Centralize data fetching and better control of when data can be refreshed ({kibana-pull}187736[#187736]). -* Fixes heatmap on monitor detail/history page for very large doc counts ({kibana-pull}184177[#184177]). -* Adds settings to serverless allowlist ({kibana-pull}190098[#190098]). -* Set missing group to false by default and show checkbox value in disable mode ({kibana-pull}188402[#188402]). -Elastic Search solution:: -* Fixes an issue with the {ref}/es-connectors-network-drive.html[Network Drive connector] where advanced configuration fields were not displayed for CSV file role mappings with `Drive Type: Linux` selected. -Elastic Security solution:: -For the Elastic Security 8.16.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Kibana platform:: -* Fixes an issue causing a wrong date to show in the header of a report when generated from relative date ({kibana-pull}197027[#197027]). -* Fixes an issue where the Created and Updated timestamps for Dashboards were ignoring the default timezone settings in Advanced settings. ({kibana-pull}196977[#196977]). -* Fixes an issue causing searches including a colon `:` character to show inaccurate results ({kibana-pull}190464[#190464]). -Kibana security:: -* Fixes an issue where an LLM was likely to generate invalid processors containing array access in Automatic Import ({kibana-pull}196207[#196207]). -Machine Learning:: -* File upload: fixes PDF character count limit ({kibana-pull}197333[#197333]). -* Data Drift: Updates brush positions on window resize fix ({kibana-pull}196830[#196830]). -* AIOps: Fixes issue where some queries cause filters to not be applied ({kibana-pull}196585[#196585]). -* Transforms: Limits the data grid result window ({kibana-pull}196510[#196510]). -* Fixes Anomaly Swim Lane Embeddable not updating properly on query change ({kibana-pull}195090[#195090]). -* Hides ES|QL based saved searches in ML & Transforms ({kibana-pull}195084[#195084]). -* Fixes query for pattern analysis and change point analysis ({kibana-pull}194742[#194742]). -* Anomaly explorer: Shows data gaps and connect anomalous points on Single Metric Charts ({kibana-pull}194119[#194119]). -* Fixes file upload with no ingest pipeline ({kibana-pull}193744[#193744]). -* Disables field statistics panel in Dashboard if ES|QL is disabled ({kibana-pull}193587[#193587]). -* Fixes display of assignees when attaching ML panels to a new case ({kibana-pull}192163[#192163]). -* Anomaly explorer: Fixes the order of the coordinates displayed on the map tooltip ({kibana-pull}192077[#192077]). -* Fixes links to the Single Metric Viewer from the Annotations and Forecasts tables ({kibana-pull}192000[#192000]). -* Trained models: fixes responsiveness of state column for smaller displays ({kibana-pull}191900[#191900]). -* File upload: increases timeout for upload request ({kibana-pull}191770[#191770]). -* Improves expired license check ({kibana-pull}191503[#191503]). -Management:: -* Fixes the pagination of the source documents data grid in Transforms ({kibana-pull}196119[#196119]). -* Fixes autocomplete suggestions after a comma in Console ({kibana-pull}189656[#189656]). - -[[release-notes-8.15.4]] -== {kib} 8.15.4 - -The 8.15.4 release includes the following bug fixes. - -[float] -[[fixes-v8.15.4]] -=== Bug fixes -Dashboards and visualizations:: -* Fixes incomplete string escaping and encoding in *TSVB* ({kibana-pull}196248[#196248]). -* Adds scroll margin to panels ({kibana-pull}193430[#193430]). -* Fixes an issue where label truncation in heat map legends was not working properly in *Lens* ({kibana-pull}195928[#195928]). -Discover:: -* Fixes the width for saved object Type column ({kibana-pull}194388[#194388]). -Elastic Observability solution:: -* Changes the slice outcome from bad to good whenever there is no data during the slice window ({kibana-pull}196942[#196942]). -Elastic Search solution:: -* Fixes a bug with the {ref}/es-connectors-network-drive.html[Network Drive connector] where advanced configuration fields were not displayed for CSV file role mappings with `Drive Type: Linux` selected ({kibana-pull}195567[#195567]). -Elastic Security solution:: -For the Elastic Security 8.15.4 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Kibana platform:: -* Fixes an issue causing the wrong date to show in the header of a report when generated from a relative date ({kibana-pull}197027[#197027]). -* Fixes an issue with the export options for PNG/PDF reports in a dashboard ({kibana-pull}192530[#192530]). -Machine Learning:: -* Fixes an issue preventing Anomaly swim lane panels from updating on query changes ({kibana-pull}195090[#195090]). -Management:: -* Fixes the pagination of the source documents data grid in Transforms ({kibana-pull}196119[#196119]). - -[[release-notes-8.15.3]] -== {kib} 8.15.3 - -The 8.15.3 release includes the following bug fixes. - -[float] -[[fixes-v8.15.3]] -=== Bug fixes -Alerting:: -* Fixes a storage configuration error that could prevent the Stack Management > Alerts page from loading correctly ({kibana-pull}194785[#194785]). -* Fixes a bug preventing certain alerts with Role visibility set to "Stack Rules" from being shown on the Stack Management page ({kibana-pull}194615[#194615]). -* Fixes an issue where rules created from Discover before version 8.11.0 could no longer be accessed after upgrading ({kibana-pull}192321[#192321]). -Dashboards:: -* Fixes an issue where the `embed=true` parameter was missing when sharing a dashboard with the Embed code option ({kibana-pull}194366[#194366]). -Discover:: -* Fixes an issue with the document viewer panel not opening in focus mode ({kibana-pull}191039[#191039]). -Elastic Observability solution:: -* Fixes the OpenTelemetry guided onboarding for MacOS with x86_64 architectures ({kibana-pull}194915[#194915]). -* Fixes a bug where the SLO creation form was allowing multiple values for timestamp fields ({kibana-pull}194311[#194311]). -Elastic Search solution:: -* Fixes a bug with the https://www.elastic.co/guide/en/enterprise-search/8.15/connectors-network-drive.html[Network Drive connector] where advanced configuration fields were not displayed for CSV file role mappings with `Drive Type: Linux` selected ({kibana-pull}195567[#195567]). -Elastic Security solution:: -For the Elastic Security 8.15.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Kibana security:: -* Automatic Import no longer asks the LLM to map fields to reserved ECS fields ({kibana-pull}195168[#195168]). -* Fixes an issue that was causing the Grok processor to return non-ECS compatible fields when processing structured or unstructured syslog samples in Automatic Import ({kibana-pull}194727[#194727]). -* Fixes the integrationName when uploading a new version of an existing integration using a ZIP upload ({kibana-pull}194298[#194298]). -* Fixes a bug that caused the Deploy step of Automatic Import to fail after a pipeline was edited and saved ({kibana-pull}194203[#194203]). -* Fixes an issue in the Kibana Management > Roles page where users could not sort the table by clicking the column headers ({kibana-pull}194196[#194196]). -Lens & Visualizations:: -* Fixes an issue where the legend label truncation setting wasn't working properly for heat maps in Lens ({kibana-pull}195928[#195928]). -Machine Learning:: -* Fixes an issue preventing Anomaly swim lane panels from updating on query changes ({kibana-pull}195090[#195090]). -* Fixes an issue that could cause the "rows per page" option to disappear from the Anomaly timeline view in the Anomaly Explorer ({kibana-pull}194531[#194531]). -* Fixes an issue causing screen flickering on the Results Explorer and Analytics Map pages when no jobs are available ({kibana-pull}193890[#193890]). - +All `v1` Kibana security HTTP endpoints have been removed. -[[release-notes-8.15.2]] -== {kib} 8.15.2 +`GET /api/security/v1/logout` has been replaced by `GET /api/security/logout` +`GET /api/security/v1/oidc/implicit` has been replaced by `GET /api/security/oidc/implicit` +`GET /api/security/v1/oidc` has been replaced by GET `/api/security/oidc/callback` +`POST /api/security/v1/oidc` has been replaced by POST `/api/security/oidc/initiate_login` +`POST /api/security/v1/saml` has been replaced by POST `/api/security/saml/callback` +`GET /api/security/v1/me` has been removed with no replacement. -The 8.15.2 release includes the following enhancements and bug fixes. +For more information, refer to {kibana-pull}199656[#199656]. -[float] -[[enhancement-v8.15.2]] -=== Enhancements -Elastic Security solution:: -For the Elastic Security 8.15.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Bumps maximum supported package spec version to 3.2 ({kibana-pull}193574[#193574]). -Kibana security:: -* Adds a feature to add support for handling `syslogs with unsupported message body` ({kibana-pull}192817[#192817]). -* Automatic Import now performs reproducible sampling from the list of log entries instead of just truncating them ({kibana-pull}191598[#191598]). -* Adds support for Google Gemini, OpenAI, and Azure OpenAI connectors to Automatic Import ({kibana-pull}191577[#191577]). -* Displays better error messages for issues with logs sample file upload in Automatic Import ({kibana-pull}191310[#191310]). - -[float] -[[fixes-v8.15.2]] -=== Bug fixes -Alerting:: -* Fixes error when saving a rule after toggling alerts filter properties on and off ({kibana-pull}192522[#192522]). -Dashboards:: -* Fixes map layers that disappear from map panel when using session storage to continue editing a dashboard ({kibana-pull}193629[#193629]). -Discover:: -* Fixes "View conflicts" button when a data view ID has special characters ({kibana-pull}192374[#192374]). -Elastic Observability solution:: -* Fixes OpenTelemetry agent names ({kibana-pull}193134[#193134]). -* Resolves an issue for multi-step browser journeys where timings for cached resources within the same step were inaccurate within the waterfall chart ({kibana-pull}193089[#193089]). -* Updates parsing to skip replacing missing values in Synthetics monitors ({kibana-pull}192662[#192662]). -Elastic Security solution:: -For the Elastic Security 8.15.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Prevents extra agent_status call with empty policyId ({kibana-pull}192549[#192549]). -* Sets correct title for Restart upgrade agent modal under agent list ({kibana-pull}192536[#192536]). -Kibana security:: -* Stops removing message field for unstructured logs when using Automatic Import ({kibana-pull}193678[#193678]). -* Integrations created using Automatic Import now indicate that they have been developed by the `Community` instead of Elastic ({kibana-pull}193002[#193002]). -* Fixes issues with rendering the package manifest in Automatic Import ({kibana-pull}192316[#192316]). -Machine Learning:: -* Fixes deletion in Check interval input for anomaly detection rule ({kibana-pull}193420[#193420]). -* Fixes unnecessary ML services initialization during plugin setup ({kibana-pull}193153[#193153]). -* Fixes link to anomaly detection wizard from pattern analysis in Discover ({kibana-pull}192375[#192375]). -* Fixes an issue with the `http_endpoint` input config loading incorrectly in an Automatic Import workflow ({kibana-pull}191964[#191964]). - - -[[release-notes-8.15.1]] -== {kib} 8.15.1 - -The 8.15.1 release includes the following bug fixes. - -// [float] -// [[enhancement-v8.15.1]] -// === Enhancements -// Other:: -// * Automatic Import now supports the 'multiline newline-delimited JSON' log sample format for the Filestream input ({kibana-pull}190588[#190588]). - -[float] -[[fixes-v8.15.1]] -=== Bug fixes -Data Discovery:: -* Fixes time range filter ({kibana-pull}187010[#187010]). -Elastic Security:: -For the Elastic Security 8.15.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Remove duplicative retries from client-side requests to APIs that depend on EPR ({kibana-pull}190722[#190722]). -Lens & Visualizations:: -* Visualization blows up when invalid color is passed in *TSVB* ({kibana-pull}190658[#190658]). -Observability:: -* Enables wildcard search for the Synthetics waterfall chart ({kibana-pull}191132[#191132]). -* Fixes accordion disclosure keyboard focus border ({kibana-pull}190436[#190436]). -* Always pass allowLeadingWildcards as true to the KQL validation in the custom threshold rule API param validation ({kibana-pull}190031[#190031]). -* Prevent excess calls to get agent namespace ({kibana-pull}189995[#189995]). -* Fixes blank storage explorer summary when filter string is active ({kibana-pull}189760[#189760]). -* Observability AI Assistant: Use internal user when fetching connectors ({kibana-pull}190462[#190462]). -* Observability AI Assistant: Fixes bug “Cannot set initialMessages if initialConversationId is set" ({kibana-pull}189885[#189885]). -Platform:: -* Fixes handling of splittable subkeys when processing values ({kibana-pull}190590[#190590]). Fixes a bug when processing YAML configuration keys that contain dotted notation in objects in arrays. This can manifest as a validation error causing Kibana to not start. -Presentation:: -* Fixes by-value map embeddables have broken layers ({kibana-pull}190996[#190996]). -* Fixes text readability on map scale, attribution, and coordinate controls ({kibana-pull}189639[#189639]). -Search:: -* Fixes index error incorrectly showing up ({kibana-pull}189283[#189283]). Fixes a bug where an index error about the `semantic_text` field would be incorrectly displayed when the inference endpoint was configured and available. -Uptime:: -* Fixes broken pagination in Uptime when a filter is applied ({kibana-pull}189831[#189831]). -// Security:: -// * Resolve a bug in ECS missing fields detection ({kibana-pull}191502[#191502]). -// * Improve sample merge functionality ({kibana-pull}190656[#190656]). -// * Try parsing samples as both NDJSON and JSON ({kibana-pull}190046[#190046]). - -[[release-notes-8.15.0]] -== {kib} 8.15.0 - -For information about the {kib} 8.15.0 release, review the following information. - -The 8.15.0 release includes the following known issues. - -[float] -[[known-issues-8.15.0]] -=== Known issues - -[discrete] -[[known-178114]] -.Kibana fails to start when processing YAML configuration keys that contain dotted notation in objects in arrays -[%collapsible] -==== -*Details* + -In 8.15.1, We fixed a bug when processing YAML configuration keys that contain dotted notation in objects in arrays. -This can manifest as a validation error causing Kibana to not start. +*Impact* + +Any HTTP API calls to the `v1` Kibana security endpoints will fail with a 404 status code starting from version 9.0.0. +Third party OIDC and SAML identity providers configured with `v1` endpoints will no longer work. -For more information, refer to {kibana-pull}190590[#190590]. +*Action* + +Update any OIDC and SAML identity providers to reference the corresponding replacement endpoint listed above. +Remove references to the `/api/security/v1/me` endpoint from any automations, applications, tooling, and scripts. ==== [discrete] -[[known-187823]] -.Connectors require update due to Microsoft Teams product retirement +.Access to all internal APIs is blocked (9.0.0) [%collapsible] ==== *Details* + -The original method for configuring incoming webhooks in Microsoft Teams is being retired. -Refer to https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/[Retirement of Office 365 connectors within Microsoft Teams] and {kibana-issue}187823[#187823]. +Access to internal Kibana HTTP APIs is restricted from version 9.0.0. This is to ensure +that HTTP API integrations with Kibana avoid unexpected breaking changes. +Refer to {kibana-pull}193792[#193792]. *Impact* + -If you used the *Incoming Webhook* app in Microsoft Teams to generate a webhook URL for a <>, it will stop working in December 2024. - -*Workaround* + -Use the *Workflows* app in Microsoft Teams to create a new webhook URL, as described in <>. -Update your Microsoft Teams connector to use the new URL before the end of December 2024. -==== - -[discrete] -[[known-189283]] -.Incorrect index errors related to inference endpoints -[%collapsible] -==== -*Details* + -In 8.15.1, we fixed a UI bug where an index error about the `semantic_text` field would be incorrectly displayed when the inference endpoint was configured and available. +Any HTTP API calls to internal Kibana endpoints will fail with a 400 status code starting +from version 9.0.0. -You can ignore this error if you've confirmed that the inference endpoint is configured and the model is deployed. +*Action* + +**Do not integrate with internal HTTP APIs**. They may change or be removed without notice, +and lead to unexpected behaviors. If you would like some capability to be exposed over an +HTTP API, https://github.com/elastic/kibana/issues/new/choose[create an issue]. +We would love to discuss your use case. -This bug is fixed in {kibana-pull}189283[#189283]. ==== [float] -[[deprecations-8.15.0]] +[[deprecations-9.0.0]] === Deprecations -The following functionality is deprecated in 8.15.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.15.0. - -[discrete] -[[deprecation-uptime]] -.Uptime is deprecated in 8.15.0 and will be removed in a future version. -[%collapsible] -==== -*Details* + -The Uptime app is already hidden from Kibana when there is no recent Heartbeat data, and will be completely removed in early 2026. You should migrate to Synthetics as an alternative. For more details, refer to the {observability-guide}/uptime-intro.html[Uptime documentation]. -==== - -[discrete] -.<> are deprecated in 8.15.0 and will be removed in a future version. -[%collapsible] -==== -*Details* + -Search sessions are now deprecated and will be removed in a future version. By default, queries that take longer than 10 minutes (the default for the advanced setting `search:timeout`) will be canceled. To allow queries to run longer, consider increasing `search:timeout` or setting it to `0` which will allow queries to continue running as long as a user is waiting on-screen for results. -==== - -[float] -[[breaking-changes-8.15.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.15.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Adds rate limiting to install by upload endpoint. -[%collapsible] -==== -*Details* + -Rate limiting was added to the upload `api/fleet/epm/packages` endpoint. For more information, refer to {kibana-pull}184036[#184036]. - -*Impact* + -If you do two or more requests in less than 10 seconds, the subsequent requests fail with `429 Too Many Requests`. -Wait 10 seconds before uploading again. -This change could potentially break automations for users that rely on frequent package uploads. -==== - [float] -[[features-8.15.0]] +[[features-9.0.0]] === Features -{kib} 8.15.0 adds the following new and notable features. -Alerting:: -* Allow decimals in the threshold filed for the Failed transaction rate threshold rule ({kibana-pull}184647[#184647]). -Cases:: -* Cases custom fields and the cases webhook are now GA ({kibana-pull}187880[#187880]). -* Allow users to create case using templates ({kibana-pull}187138[#187138]). -Dashboards:: -* Adding a panel to a dashboard now opens a flyout and the list of panels available is now organized more logically ({kibana-pull}183764[#183764]). -Discover:: -* In ES|QL mode, you can now create WHERE clause filters more intuitively by interacting with the table, sidebar and table row viewer, including for ordinal charts ({kibana-pull}181399[#181399]) & ({kibana-pull}184420[#184420]). -* You can now filter an ES|QL chart by brushing a date histogram ({kibana-pull}184012[#184012]). -Elastic Security:: -* For the Elastic Security 8.15.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* UI for the custom integration creation with AI ({kibana-pull}186304[#186304]). -//* Change agent policies in edit package policy page ({kibana-pull}186084[#186084]). -//* Create shared package policy ({kibana-pull}185916[#185916]). -//* Introduce policy_ids in package policy SO ({kibana-pull}184636[#184636]). -* Surface option to delete diagnostics files ({kibana-pull}183690[#183690]). -* Allow to reset log level for agents >= 8.15.0 ({kibana-pull}183434[#183434]). -* Adds warning if need root integrations trying to be used with unprivileged agents ({kibana-pull}183283[#183283]). -* Adds unprivileged vs privileged agent count to Fleet UI ({kibana-pull}183077[#183077]). -Lens & Visualizations:: -* You can now show additional statistics in the legend of your time series charts created with *Lens* ({kibana-pull}182357[#182357]). -Machine Learning:: -* Adds Field statistics to the list of available panels in Dashboards ({kibana-pull}184030[#184030]). -* Adds ES|QL support for field statistics table in Discover ({kibana-pull}180849[#180849]). -* AIOps: Moves Pattern analysis to a tab instead of a flyout in Discover ({kibana-pull}178916[#178916]). -* AIOps: Adds AI Assistant contextual insights to the Log Rate Analysis page in the Machine Learning plugin for Observability serverless projects ({kibana-pull}186509[#186509]). -Management:: -* Adds an advanced `search:timeout` setting and changes the timeout behavior to display partial results instead of just an error ({kibana-pull}179679[#179679]). -Observability:: -* Updates links for integration buttons in Observability solution ({kibana-pull}184477[#184477]). -* Adds SLO status, SLI value, error budget remaining and consumed to the burn rate alert context ({kibana-pull}184471[#184471]). -* Adds an option to prevent initial backfill for SLOs ({kibana-pull}184312[#184312]). -* Updates `Add data` links to use improved deep linking ({kibana-pull}184164[#184164]). -* Changes the navigation behavior for the Observability guide cards to pre-select the correct solution ({kibana-pull}184065[#184065]). -* Updates the Alerts details to now show an history chart for all types of alerts ({kibana-pull}181824[#181824]). -* Adds Docs count, Size, Services, Hosts, and Degraded docs KPIs to the dataset quality flyout ({kibana-pull}179479[#179479]). -Platform:: -* Improves the **Share** menu to let you navigate through a tabbed modal to copy links for Discover, Dashboards, and Lens. ({kibana-pull}180406[#180406]). -* Changes the behavior of the "Endpoints and API keys" button in the header to open the Connection details flyout ({kibana-pull}183236[#183236]). -* Adds a quick way to create an API keys with a 90-days expiration to the Connection details flyout, and clarifies the Elasticsearch endpoint and Cloud ID information ({kibana-pull}180912[#180912]). -* Adds a feedback button to the header in Serverless projects({kibana-pull}180942[#180942]). +For more information about the features introduced in 9.0.0, refer to <>. -For more information about the features introduced in 8.15.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.15.0]] +[[enhancements-and-bug-fixes-v9.0.0]] === Enhancements and bug fixes -For detailed information about the 8.15.0 release, review the enhancements and bug fixes. +For detailed information about the 9.0.0 release, review the enhancements and bug fixes. [float] -[[enhancement-v8.15.0]] -=== Enhancements -Alerting:: -* Adds support of additional fields for ServiceNow ITSM and SecOps ({kibana-pull}184023[#184023]). -* Adds support for the additional info field in the ServiceNow ITOM connector ({kibana-pull}183380[#183380]). -Cases:: -* The Cases webhook connector now supports SSL certificate authentication ({kibana-pull}185925[#185925]). -Dashboards:: -* Adds a "Creator" column to the Dashboards list ({kibana-pull}182256[#182256]). -* Adds the ability to filter dashboards by creator for dashboards created on or after version 8.14 ({kibana-pull}180147[#180147]). -* When switching back from maximized to minimized view on a panel, you return to your original position in the dashboard ({kibana-pull}184696[#184696]). -* Improves the dashboard background color to match the current color mode wen margins are turned off ({kibana-pull}181450[#181450]). -* Simplifies the workflow for creating a copy of the dashboard currently open in both view and edit modes ({kibana-pull}180938[#180938]). -Discover:: -* Adds a button to expand the time range on demand when no results are found for a search ({kibana-pull}181723[#181723]). -* Changes the Discover document viewer flyout to push the rest of the UI instead of hiding it with an overlay ({kibana-pull}166406[#166406]). -* CSV reports now include custom field labels when they exist ({kibana-pull}181565[#181565]). -Elastic Security:: -* For the Elastic Security 8.15.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -ES|QL:: -* Improves metadata autocomplete suggestions with comma and pipe ({kibana-pull}188338[#188338]). -* Automatically encapsulate index names with special characters with quotes ({kibana-pull}187899[#187899]). -* Adds support for integrations ({kibana-pull}184716[#184716]). -* Adds the ability to comment out the current line with the keyboard using `CMD + /` ({kibana-pull}184637[#184637]). - -Fleet:: -* Use API key for standalone agent onboarding ({kibana-pull}187133[#187133]). -//* Adds action for upgrading all agents on policy ({kibana-pull}186827[#186827]). -* Makes Fleet & Integrations layouts full width ({kibana-pull}186056[#186056]). -* Adds support for setting `add_fields` processors on all agents under an agent policy ({kibana-pull}184693[#184693]). -* Adds force flag to delete agent_policies API ({kibana-pull}184419[#184419]). -* Adds data tags to agent policy APIs ({kibana-pull}183563[#183563]). -* Adds support for mappings with store: true ({kibana-pull}183390[#183390]). -* Shows all integration assets on detail page ({kibana-pull}182180[#182180]). -* Adds overrides to package policies update endpoint ({kibana-pull}181453[#181453]). -* Enables `agent.monitoring.http` settings on agent policy UI ({kibana-pull}180922[#180922]). -* Removes unnecessary field definitions for custom integrations and adds `logs@mappings` to log streams ({kibana-pull}178083[#178083]). -Lens & Visualizations:: -* Adds wildcard matching to field pickers across {kib} in *Lens* ({kibana-pull}182631[#182631]). -Machine Learning:: -* AIOps: Adds cardinality check to Log Rate Analysis ({kibana-pull}181129[#181129]). -* AIOps: Reduces rerenders when streaming analysis results ({kibana-pull}182793[#182793]). -* AIOps Log Rate Analysis: Improves explanation of log rate spike/dip ({kibana-pull}186342[#186342]). -* AIOps Log Rate Analysis: Merges fetch queue for keyword and text field candidates ({kibana-pull}183649[#183649]). -* AIOps Log Rate Analysis: Adds controls for controlling which columns will be visible ({kibana-pull}184262[#184262]). -* Anomaly Detection: Single Metric Viewer - Adds cases action ({kibana-pull}183423[#183423]). -* Anomaly Detection: Adds 'Add to dashboard' action for Single Metric Viewer ({kibana-pull}182538[#182538]). -* Anomaly swim lane: UX improvements ({kibana-pull}182586[#182586]). -* Single Metric Viewer embeddable: Ensures chart height is responsive on resize ({kibana-pull}185907[#185907]). -* Single Metric Viewer embeddable in dashboards: Moves all config to flyout ({kibana-pull}182756[#182756]). -* Adds progress bar for trained models download ({kibana-pull}184906[#184906]). -* Updates code editors for Transform, Data Frame and Anomaly Detection wizards ({kibana-pull}184518[#184518]). -Management:: -* The SIEM Query rule loads fewer fields on query execution ({kibana-pull}184890[#184890]). -* The ES Query rule loads fewer fields on query execution ({kibana-pull}183694[#183694]). -* Adds the ability to horizontally resize the autocomplete popup in the Dev Tools Console ({kibana-pull}180243[#180243]). -* The Kibana configuration file now supports assigning a default value for environment variables, using the `${VAR_ENV:defaultValue}` syntax. ({kibana-pull}182139[#182139]). -Observability:: -* Adds the ability to clone a monitor in Synthetics ({kibana-pull}184393[#184393]). -* Adds 10 and 30 seconds frequency options to lightweight monitors in Synthetics ({kibana-pull}184380[#184380]). -* Improves the destination of `Add data` links in Observability to make data ingestion more efficient ({kibana-pull}184164[#184164]). -* Adds an option to mark AI Assistant Knowledge Base entries as public ({kibana-pull}184094[#184094]). -* Updates the AI assistant to query all search connectors by default and adds a setting to override this new default ({kibana-pull}183712[#183712]). -* Adds downstream dependency service name to logs and errors to improve alert insights ({kibana-pull}183215[#183215]). -Operations:: -* Adds password support to the Kibana keystore ({kibana-pull}180414[#180414]). -Platform:: -* Adds http2 support to the Kibana server, that can be enabled using the `server.protocol: http2` Kibana setting ({kibana-pull}183465[#183465]). -* Kibana's `rolling-file` appender now supports more advanced retention policies. Refer to the <> for more details ({kibana-pull}182346[#182346]). -Security:: -* Adds an optional role description field to roles ({kibana-pull}183145[#183145]). -* Adds the ability to filter audit logs by username using the `xpack.security.audit.ignore_filters.users` configuration setting ({kibana-pull}183137[#183137]). -* Adds support for `remote_cluster` privileges in ES role definition ({kibana-pull}182377[#182377]). -* Improves the experience for managing a larger number of API keys by adding server side filtering, pagination and querying. ({kibana-pull}168970[#168970]). - -[float] -[[fixes-v8.15.0]] -=== Bug Fixes -Alerting:: -* Fixes kibana.alert.rule.execution.timestamp timezone and format ({kibana-pull}183905[#183905]). -* Fixes undefined error source in alerting log tags ({kibana-pull}182352[#182352]). -* Allow the rule types to throw user errors ({kibana-pull}184213[#184213]). -* Sets validation errors in subaction framework as user errors ({kibana-pull}184317[#184317]). -* Fixes x-axis time zone on alertSummaryWidget full size ({kibana-pull}187468[#187468]). -Dashboards:: -* Prevent jumping control drag handle between view modes ({kibana-pull}184533[#184533]). -* Fixes error on navigation when invalid selections tour step is open ({kibana-pull}189449[#189449]). -* Fixes positioning of dragged link in Links editor ({kibana-pull}189122[#189122]). -* Don't close the flyout when canceling the Save to library action ({kibana-pull}188995[#188995]). -* Fixes error thrown on numeric options list ({kibana-pull}188789[#188789]). -* Adds tooltip support to `PresentationPanel` header badges ({kibana-pull}186102[#186102]). -* Fixes unsaved changes on new dashboards bug ({kibana-pull}184955[#184955]). -* Reset `maximizedPanelId` on Dashboard navigation ({kibana-pull}183060[#183060]). -Discover:: -* Fixes time range filter ({kibana-pull}187010[#187010]). -* Reset selected fields when modifying the ES|QL query ({kibana-pull}185997[#185997]). -* Fixes document comparison mode and the field statistics tab when using Smart Fields ({kibana-pull}184172[#184172]). -* Disables sorting for Document view ({kibana-pull}187553[#187553]). -* Correctly adds the limit to the field statistics queries ({kibana-pull}186967[#186967]). -* Fixes overlapping on error messages ({kibana-pull}181416[#181416]). -Elastic Search:: -* Allow to save mappings with errors ({kibana-pull}188326[#188326]). - -Elastic Security:: -* For the Elastic Security 8.15.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -ES|QL:: -* Suppress empty syntax error ({kibana-pull}184246[#184246]). -* Accept null values for function arguments ({kibana-pull}184254[#184254]). -* Recognize transformational commands with ast parsing ({kibana-pull}184291[#184291]). -* Accept negated index patterns ({kibana-pull}184528[#184528]). -* Fixes the overflow problem of the editor ({kibana-pull}186166[#186166]). -* Removes inaccurate value suggestions ({kibana-pull}189228[#189228]). -* Improves support for `Invoke` completion trigger type ({kibana-pull}188877[#188877]). -Fleet:: -* Fixes navigating back to Agent policy integration list ({kibana-pull}189165[#189165]). -* Fixes copy agent policy, missed bump revision ({kibana-pull}188935[#188935]). -* Force field enabled=false on inputs that have all their streams disabled ({kibana-pull}188919[#188919]). -* Fill in empty values for `constant_keyword` fields from existing mappings ({kibana-pull}188145[#188145]). -* Enrollment token table may show an empty last page ({kibana-pull}188049[#188049]). -* Separated `showInactive` from unenrolled status filter ({kibana-pull}187960[#187960]). -* Fixes missing policy filter in Fleet Server check to enable secrets ({kibana-pull}187935[#187935]). -* Allow preconfigured agent policy only with name and id ({kibana-pull}187542[#187542]). -* Show warning callout in configs tab when an error occurs ({kibana-pull}187487[#187487]). -* Enable rollover in custom integrations install when getting mapper_exception error ({kibana-pull}186991[#186991]). -* Adds concurrency limit to EPM bulk install API + fix duplicate installations ({kibana-pull}185900[#185900]). -* Include inactive agents in agent policy agent count ({kibana-pull}184517[#184517]). -* Fixes KQL filtering ({kibana-pull}183757[#183757]). -* Prevent concurrent runs of Fleet setup ({kibana-pull}183636[#183636]). -Lens & Visualizations:: -* Do not pass incorrect filters to the state in *Lens* ({kibana-pull}189292[#189292]). -* Adds error reason in workspace panel when error happens in *Lens* ({kibana-pull}189161[#189161]). -* Fixes "Unable to load" page error on edit/add ES|QL panel ({kibana-pull}188664[#188664]). -* Improves the performance of the table ES|QL visualization ({kibana-pull}187142[#187142]). -* Fixes sort field error message for last value ({kibana-pull}184883[#184883]). -* Fixes reference line width stale update in *Lens* ({kibana-pull}184414[#184414]). -* Fixes prepend sizing on duration formatter in *Lens* ({kibana-pull}184403[#184403]). -* Fixes data table actions when the first row is empty in *Lens* ({kibana-pull}181344[#181344]). -* Fixes y-axis scale/custom domain issues and help/error text in *Lens* ({kibana-pull}180532[#180532]). -Logs:: -* Fixes log stream flyout when embedded in APM or the Infrastructure UI ({kibana-pull}189763[#189763]). -* Fixes log entry flyout when response is slow ({kibana-pull}187303[#187303]). -* Fixes flyout link to the legacy Uptime app ({kibana-pull}186328[#186328]). -Machine Learning:: -* Fixes display of model state in trained models list with starting and stopping deployments ({kibana-pull}188847[#188847]). -* AIOps: Fixes runtime mappings in pattern analysis ({kibana-pull}188530[#188530]). -* Fixes Field statistics panel displaying multiple errors if associated index is deleted and race condition when refreshes too fast ({kibana-pull}188327[#188327]). -* Hides ML embeddables from the "Add panel" flyout when ML isn't available ({kibana-pull}187639[#187639]). -* Removes info callout mentioning ML nodes for serverless environment ({kibana-pull}187583[#187583]). -* Fixes upgrade warning ({kibana-pull}187387[#187387]). -* Fixes change point menu which can get stuck open ({kibana-pull}186063[#186063]). -* Do not retry model deployment ({kibana-pull}185012[#185012]). -* Refreshes jobs list after import ({kibana-pull}184757[#184757]). -* AIOps Log Rate Analysis: Fixes date picker refresh button ({kibana-pull}183768[#183768]). -* Single Metric Viewer embeddable: Ensures creating job rule from anomaly click actions is successful ({kibana-pull}183554[#183554]). -* Adds bucket span validation to job creation flyouts ({kibana-pull}183510[#183510]). -* Fixes Choropleth map disappears when time range is changed ({kibana-pull}181933[#181933]). -Management:: -* Allows selection of timestamp when some index pattern segments are unmet ({kibana-pull}189336[#189336]). -* Transforms and Anomaly detection: Updates width for icon in messages tab to prevent overlap ({kibana-pull}188374[#188374]). -* Transform: Fixes transform stats API call in the transform health alerting rule ({kibana-pull}187586[#187586]). -* Transforms: Improves data view checks ({kibana-pull}181892[#181892]). -* Fixes human-readable (precise) formatting ({kibana-pull}181391[#181391]). -Observability:: -* Decreases bucket size top_dependencies sends to get_connection_stats query ({kibana-pull}182884[#182884]). -* Fixes SLO history details data ({kibana-pull}183097[#183097]). -* Improves permission check on SLO pages ({kibana-pull}182609[#182609]). -* Fixes contextual insights for APM errors ({kibana-pull}184642[#184642]). -* Fixes Alerts page history navigation ({kibana-pull}186068[#186068]). -* Accept project monitors with `monitor.url` of type `string` that contains commas ({kibana-pull}186112[#186112]). -* Fixes TLS certificate view for > 3 monitors per certificate ({kibana-pull}186204[#186204]). -* Fixes Synthetics/Uptime fields alerts autocomplete for query bar ({kibana-pull}186588[#186588]). -* Hides AI Assistant menu item when in a disabled space ({kibana-pull}188017[#188017]). -* Fixes AI Assistant settings when plugin is disabled ({kibana-pull}188160[#188160]). -* Fixes bug “Cannot set initialMessages if initialConversationId is set" ({kibana-pull}189885[#189885]). -* Respect query:allowLeadingWildcards in optional query filter ({kibana-pull}189488[#189488]). -* Fixes showing the correct log view in the rule creation flyout ({kibana-pull}189205[#189205]). -* Fixes a bug where "Retest on failure" couldn't be turned off when creating a monitor in the Synthetics app. ({kibana-pull}189013[#189013]). -* Fixes incorrect Redis and AWS CPU percentage metrics displayed on the Infrastructure Inventory page ({kibana-pull}188768[#188768]). -* Improves information communicated in case of insufficient privileges in the Dataset Quality UI ({kibana-pull}183947[#183947]). -Platform:: -* Accessibility fixes for user profile input labels ({kibana-pull}186471[#186471]). -* Fixes several internationalization and localization inconsistencies ({kibana-pull}181735[#181735]). -* Fixes case sensitivity in tag search ({kibana-pull}183092[#183092]). -Querying & Filtering:: -* Fixes performance issues with nested sub-queries ({kibana-pull}181208[#181208]). -Security:: -* Fixes `ComboBox` overflow with large chips ({kibana-pull}184722[#184722]). -* Adds `disabledFeatures` back to mappings, so it can be aggregated on ({kibana-pull}184195[#184195]). -* Supports read-only remote index and cluster sections with read_security access ({kibana-pull}183126[#183126]). -* Adds transformation of application wildcard `*` privilege to `all` to correctly filter and display roles as `superuser`. ({kibana-pull}181400[#181400]). -Sharing:: -* Improves error handling ({kibana-pull}185903[#185903]). - - -[[release-notes-8.14.3]] -== {kib} 8.14.3 - -The 8.14.3 release includes the following bug fixes and known issues. - -[float] -[[known-issues-8.14.3]] -=== Known issues - -include::CHANGELOG.asciidoc[tag=known-issue-186969] - -include::CHANGELOG.asciidoc[tag=known-issue-189394] - -[discrete] -[[known-185691]] -.When using the Observability AI Assistant with the OpenAI connector, function calling will result in an error -[%collapsible] -==== -*Details* + -In 8.14.3, if you are using the Observability AI Assistant with the OpenAI connector, function calling will result in an error. -The error message will look similar to this: - -[source] ----- -Error: an error occurred while running the action - Status code: 400. Message: API Error: model_error - Missing required parameter: 'messages[4].function_call.arguments'. ----- - -A fix will be available in 8.15. Users on 8.14.3 can get around this error by turning on synthetic function calling in _AI Assistant Settings_: - -. In {kib}, go to *Stack Management* → *AI Assistant* → *Observability* -. Toggle the _Simulate function calling_ option to *On*. - -For more information, refer to {kibana-pull}185691[#185691]. -==== - -[float] -[[fixes-v8.14.3]] -=== Bug Fixes -Dashboard:: -* Fixes controls getting overwritten on navigation ({kibana-pull}187509[#187509]). -Elastic Security:: -For the Elastic Security 8.14.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -[[release-notes-8.14.2]] -== {kib} 8.14.2 - -The 8.14.2 release includes the following bug fixes and known issues. - -[float] -[[known-issues-8.14.2]] -=== Known issues - -include::CHANGELOG.asciidoc[tag=known-issue-186969] - -include::CHANGELOG.asciidoc[tag=known-issue-189394] - -[float] -[[fixes-v8.14.2]] -=== Bug Fixes - -Alerting:: -* Rule runs recovered actions without ever running active actions ({kibana-pull}183646[#183646]). -Fleet:: -* Updates health_check endpoint to accept hosts ids ({kibana-pull}185014[#185014]). -Machine Learning:: -* AIOps Log Rate Analysis: Fixes text field selection ({kibana-pull}186176[#186176]). -Presentation:: -* Fixes PresentationPanelError component throwing when error.message is empty string ({kibana-pull}186098[#186098]). - -[[release-notes-8.14.1]] -== {kib} 8.14.1 - -The 8.14.1 release includes the following bug fixes and known issues. - -[float] -[[known-issues-8.14.1]] -=== Known issues - -include::CHANGELOG.asciidoc[tag=known-issue-186969] - -include::CHANGELOG.asciidoc[tag=known-issue-189394] - -[float] -[[fixes-v8.14.1]] -=== Bug Fixes -Data Discovery:: -* Notify the user about issues with access to the default data view ({kibana-pull}184740[#184740]). -Discover:: -* Fixes resetting of breakdown field in a saved search ({kibana-pull}184668[#184668]). -Elastic Security:: -For the Elastic Security 8.14.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Fixes restart upgrade disabled condition ({kibana-pull}184586[#184586]). -Observability:: -* Fixes editing enabled state for project monitor ({kibana-pull}184775[#184775]). - -[[release-notes-8.14.0]] -== {kib} 8.14.0 - -For information about the {kib} 8.14.0 release, review the following information. - -[float] -[[known-issues-8.14.0]] -=== Known issues - -// tag::known-issue-186969[] -.Creating or editing APM, {observability} and {stack-monitor-app} rules fails -[%collapsible] -==== -*Details* + -When you attempt to create or edit some rule types in **{stack-manage-app}** > **{rules-ui}**, the request will fail with errors similar to `Cannot read properties of undefined (reading 'eui')` or `e.theme.eui is undefined`. -Refer to https://github.com/elastic/kibana/issues/186969[#186969] - -*Impact* + -This known issue impacts only {observability}, {stack-monitor-app}, and APM and {user-experience} rules. - -*Workaround* + -To work around this issue for {observability} and APM and {user-experience} rules, create them from the {observability} *Alerts* page. -Refer to <> and {observability-guide}/create-alerts-rules.html[Create and manage {observability} rules]. - -*Resolved* + -This issue is resolved in 8.15.0. -==== -// end::known-issue-186969[] - -// tag::known-issue-189394[] -.{webhook-cm} connector fails to send HTTP headers -[%collapsible] -==== -*Details* + -If you configured the {webhook-cm} connector to send key-value pairs as headers, that information is not sent unles you have also enabled the basic authentication option for the connector. -Refer to https://github.com/elastic/kibana/issues/189394[#189394]. - -*Impact* + -The impact of this issue will vary depending on the purpose of your headers. -For example, if you added an `ApiKey` authorization header, you might receive a `401` authorization error since it's no longer sent by the connector. - -*Workaround* + -To work around this issue, enable the *Require authentication for this webhook* option, which is the `hasAuth` property in the API. -You must then provide a username and password for authentication. - -*Resolved* + -This issue is resolved in 8.15.0. -==== -// end::known-issue-189394[] - -[float] -[[breaking-changes-8.14.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.14.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Renamed an advanced setting to enable {esql}. -[%collapsible] -==== -*Details* + -The advanced setting which hides {esql} from the UI has been renamed from `discover:enableESQL` to `enableESQL`. It is enabled by default and must be switched off to disable {esql} features from your {kib} applications. For more information, refer to ({kibana-pull}182074[#182074]). -==== - -[discrete] -[[breaking-178879]] -.The unified search filter builder is Generally Available. -[%collapsible] -==== -*Details* + -The unified search filter builder (OR / AND) is out of technical preview. For more information, refer to ({kibana-pull}178879[#178879]). -==== - -[discrete] -[[breaking-178860]] -.{esql} is Generally Available. -[%collapsible] -==== -*Details* + -{esql} comes out of technical preview and is generally available. For more information, refer to ({kibana-pull}178860[#178860]). -==== - -[discrete] -[[breaking-177549]] -.The region map visualization type is Generally Available in Lens. -[%collapsible] -==== -*Details* + -The visualization type, region map, comes out of technical preview and is generally available. For more information, refer to ({kibana-pull}177549[#177549]). -==== - -[discrete] -[[breaking-177089]] -.UI enhancements to managed tags. -[%collapsible] -==== -*Details* + -UI improvements for managed tags. For more information, refer to ({kibana-pull}177089[#177089]). -==== - -[discrete] -[[breaking-178159]] -.Downloading a CSV file from a saved search panel in a dashboard has become deprecated in favor of generating a CSV report. -[%collapsible] -==== -*Details* + -The mechanism of exporting CSV data from a saved search panel in a dashboard has been changed to generate a CSV report, rather than allowing the CSV data to be downloaded -without creating a report. To preserve the original behavior, it is necessary to update `kibana.yml` with the setting of `xpack.reporting.csv.enablePanelActionDownload: -true`. The scope of this breaking change is limited to downloading CSV files from saved search panels only; downloading CSV files from other types of dashboard panels is -unchanged. For more information, refer to {kibana-pull}178159[#178159]. -==== - -[float] -[[features-8.14.0]] -=== Features -{kib} 8.14.0 adds the following new and notable features. - -Alerting:: -* Adds a warning when changing the index pattern while at least one rule relies on the current one ({kibana-pull}180310[#180310]). -* Enrich the alert flyout with a new overview tab ({kibana-pull}178863[#178863]). -* Adds history chart for multiple conditions ({kibana-pull}180578[#180578]). -* Implements a tabbed design for existing alert detail pages ({kibana-pull}179529[#179529]). -* Stops reporting no data alert for missing groups that are untracked ({kibana-pull}179512[#179512]). -* Adds a new modal window that lets users interact with value lists directly ({kibana-pull}179339[#179339]). -* Adds new rule type selection modal ({kibana-pull}179285[#179285]). -* Enables filters for the alert search bar on the Observability Alerts page ({kibana-pull}178886[#178886]). -APM:: -* Adds a new API to support linking APM from the Profiling UI ({kibana-pull}180677[#180677]). -* Enables fast filter on Service inventory ({kibana-pull}179096[#179096]). -Cases:: -* Adds automatically creating cases when an alert is triggered ({kibana-pull}168369[#168369]). -* Adds "Additional Fields" field to the Jira action form UI ({kibana-pull}179262[#179262]). -Dashboards:: -* Adds logic and UI improvements where invalid controls selections are no longer ignored, improving the overall loading speed of a dashboard ({kibana-pull}174201[#174201]). -Discover:: -* Allows storing a configured {esql} visualization ({kibana-pull}175227[#175227]). -* Adds document comparison mode ({kibana-pull}166577[#166577]). -Elastic Security:: -For the Elastic Security 8.14.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -ES|QL:: -* Adds a query history component which displays the 20 most recent queries ({kibana-pull}178302[#178302]). -Fleet:: -* Adds subfeatures privileges for Fleet, for Agents, Agent policies and Settings, this feature is in technical preview ({kibana-pull}179889[#179889]). -* Implements state machine behavior for package install ({kibana-pull}178657[#178657]). -* Lowers the default `total_fields` limit to 1000 from 10k ({kibana-pull}178398[#178398]). -* Avoids subobject and scalar mapping conflicts by setting `subobjects: false` on custom integrations ({kibana-pull}178397[#178397]). -* Adds functionality to `default_fields` field, so a query can run against all fields in the mapping ({kibana-pull}178020[#178020]). -* Relaxes delete restrictions for managed content installed by Fleet ({kibana-pull}179113[#179113]). -Infrastructure:: -* Adds a dashboard tab in the UI to the asset details view ({kibana-pull}178518[#178518]). -Lens & Visualizations:: -* Replaces `expression_gauge` from `Goal` to `Bullet` in *Lens* ({kibana-pull}177766[#177766]). -Machine Learning:: -* Removes the technical preview badge for pattern analysis ({kibana-pull}181020[#181020]). -* Adds query history for the {esql} Data visualizer ({kibana-pull}179098[#179098]). -Management:: -* {kib} now uses Elasticsearch's `_async_search/status/{id}` endpoint (instead of `_async_search/{id}`) when polling on search requests to improve performance.({kibana-pull}178921[#178921]). -Observability:: -* The timeslice SLOs calculation for the SLI value now includes the no data slices as good slices. For existing "Timeslice" SLOs you will need to use the `POST /api/observability/slos/{slo.id}/_reset` endpoint to reset the transforms to take advantage of the new calculation ({kibana-pull}181888[#181888]). -* Adds support for user instructions via Knowledge base or API request ({kibana-pull}180263[#180263]). -* Adds baseline alert detail pages ({kibana-pull}180256[#180256]). -* Adds a new connector that can call the AI assistant ({kibana-pull}179980[#179980]). -* Adds a link to Discover to view good/bad events in the event panel ({kibana-pull}178008[#178008]). -* Adds customization for Virtual Columns in Field List ({kibana-pull}177626[#177626]). -* Adds dependencies for Burn Rate rule suppression ({kibana-pull}177078[#177078]). -* Adds grouping by multiple values when creating SLOs, allowing for dynamic creation of multiple SLOs from a single SLI definition ({kibana-pull}175063[#175063]). -Uptime:: -* Adds Monitor public API ({kibana-pull}169928[#169928]). - -For more information about the features introduced in 8.14.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.14.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.14.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.14.0]] -=== Enhancements -Alerting:: -* Adds history chart for multiple conditions ({kibana-pull}180578[#180578]). -* Show Alerting rule JSON for API requests ({kibana-pull}180085[#180085]). -* Implement tabbed design for existing alert detail pages ({kibana-pull}179529[#179529]). -* Adds new rule type selection modal ({kibana-pull}179285[#179285]). -* Moves alerts filter controls to `@kbn/alerts-ui-shared` package ({kibana-pull}179243[#179243]). -* Improves alerts table actions column performance ({kibana-pull}178632[#178632]). -* Adds Insights component to alerts details ({kibana-pull}178330[#178330]). -* Adds support to dhow the number of additional filters that are applied on the alerts table ({kibana-pull}177275[#177275]). -* Adds error boundary to AlertsTable ({kibana-pull}176412[#176412]). -* Improves the performance of `join_by_key` ({kibana-pull}175177[#175177]). -APM:: -* Show Universal Profiling data on transaction details page ({kibana-pull}176922[#176922]). -Connectors:: -* Adds support for the Jira connector API to support the `otherFields` property to pass additional fields to be used when updating or creating issues via the link:https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-post[Jira API] ({kibana-pull}178627[#178627]). -Dashboard:: -* Reorganized panel actions in the actions menu ({kibana-pull}178596[#178596]). -* Adds panel styling improvements ({kibana-pull}178139[#178139]). -* Adds "Apply" button to stop controls selections being automatically applied ({kibana-pull}174714[#174714]). -Discover:: -* Support field stats for {esql} query ({kibana-pull}178433[#178433]). -Elastic Security:: -For the Elastic Security 8.14.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -ES|QL:: -* Implicit casting changes ({kibana-pull}182989[#182989]). -* Adds validation and auto-complete for `date_diff` ({kibana-pull}182513[#182513]). -* Adds more functions to the validator ({kibana-pull}180640[#180640]). -* Adds an enhanced chart switcher to the *Lens* inline editing flyout ({kibana-pull}177790[#177790]). -Fleet:: -* Adds support for dimension mappings in dynamic templates ({kibana-pull}180023[#180023]). -* Adds CPU metrics to request diagnostics ({kibana-pull}179819[#179819]). -* Adds Settings Framework API and UI ({kibana-pull}179795[#179795]). -* Adds an Elastic Defend advanced policy option for pruning capability arrays ({kibana-pull}179766[#179766]). -* Adds Agent activity flyout enhancements ({kibana-pull}179161[#179161]). -* Adds unhealthy reason (input/output/other) to agent metrics ({kibana-pull}178605[#178605]). -* Adds a warning which is displayed when trying to upgrade agent to version > max fleet server version ({kibana-pull}178079[#178079]). -Infrastructure:: -* Adds alerts count to hosts data ({kibana-pull}176034[#176034]). -Lens & Visualizations:: -* combines the chart type selection options into a single, layer-based chart switch ({kibana-pull}178971[#178971]). -Machine Learning:: -* Hides file upload document count chart until data is searchable ({kibana-pull}181460[#181460]). -* Removes technical preview badge for pattern analysis ({kibana-pull}181020[#181020]). -* Adds support for ad-hoc Data Views for testing models in Trained Models UI ({kibana-pull}180795[#180795]). -* Adds open and edit panel actions for the Single Metric Viewer ({kibana-pull}179364[#179364]). -* Improves {esql} data visualizer performance with early limit and remove option to Analyze all ({kibana-pull}179286[#179286]). -* Adds query history for the {esql} Data visualizer ({kibana-pull}179098[#179098]). -* Hides the Filebeat configuration card for Serverless Search file upload ({kibana-pull}178987[#178987]). -* Improves performance of Field stats / Index data visualizer by reducing requests for empty fields, making it convenient to add multi-field ({kibana-pull}178766[#178766]). -* AIOps: Identify spike/dips with change point detection for log rate analysis ({kibana-pull}178338[#178338]). -* Adds ML feature privileges tooltip ({kibana-pull}181595[#181595]). -Management:: -* Transforms: Use basic stats for transform list, call full stats only for expanded rows ({kibana-pull}180271[#180271]). -* Allows adding a custom description for data view fields ({kibana-pull}168577[#168577]). -Observability:: -* Adds an advanced setting to enable simulated function calling ({kibana-pull}180621[#180621]). -* Adds a public API for /chat/complete ({kibana-pull}179618[#179618]). -* Persists settings in {es} instead of local storage ({kibana-pull}179380[#179380]). -* Allows filtering data views by type ({kibana-pull}179069[#179069]). -* Implements contextual actions ({kibana-pull}178405[#178405]). -* Adds the ability to create an SLI based on the availability of your synthetics monitors ({kibana-pull}177842[#177842]). -* Adds setting for user's preferred language for the AI assistant ({kibana-pull}176444[#176444]). -* Adds improvements to the AI assistants handling where a generated {esql} query has syntax errors ({kibana-pull}179919[#179919]). -* Adds grouping by multiple values when creating SLOs, allowing for dynamic creation of multiple SLOs from a single SLI definition({kibana-pull}175063[#175063]). -Platform:: -* Adds a new option, `system`, to the `theme:darkMode` Kibana advanced setting, that can be used to have Kibana's theme follow the system's (light or dark) ({kibana-pull}173044[#173044]). -Reporting:: -* A feature has been deprecated which allowed users to download a CSV file from a saved search panel in a dashboard, without having a report generated. Now, when users need to access saved search data from a dashboard panel as CSV, a normal report will be generated. To access the deprecated functionality, you can add `xpack.reporting.csv.enablePanelActionDownload: true` to kibana.yml, but this ability will be removed in a future version of Kibana ({kibana-pull}178159[#178159]). -Security:: -* Adds `Content-Security-Policy-Report-Only` header support ({kibana-pull}179949[#179949]). -* Renders a user-friendly UI for unhandled login failures ({kibana-pull}173959[#173959]). -* Migrates the Security AI Assistant into a flyout ({kibana-pull}176657[#176657]). -* Adds support for LangChain streaming for the `openai-functions` agent ({kibana-pull}174126[#174126]). -Unified Search:: -* Adds auto-refresh pause when the page is not visible ({kibana-pull}177693[#177693]). -* Adds support for not clearing the value on the filter builder when the operator changes ({kibana-pull}176911[#176911]). - -[float] -[[fixes-v8.14.0]] -=== Bug Fixes -Alerting:: -* Fixes bug with aggregation building for {es} query rule when there are multi-terms and a group by field ({kibana-pull}182865[#182865]). -* Fixes using `recoveredCurrent` and `activeCurrent` to determine how to update old alerts ({kibana-pull}180934[#180934]). -* Fixes logging the errors reported by `addLastRunError` to the console ({kibana-pull}179962[#179962]). -* Preserves relative snooze when adding or removing snooze schedules ({kibana-pull}178344[#178344]). -* Reverts changes to notify when there is a change on connector configuration ({kibana-pull}177054[#177054]). -APM:: -* Fixes the cardinality count for SLOs generated from a single SLI definition was previously incorrect for APM latency and APM availability SLIs ({kibana-pull}183171[#183171]). -* Fixes the telemetry collection of Logstash with metricbeat monitoring ({kibana-pull}182304[#182304]). -* Fixes otel service detection ({kibana-pull}180574[#180574]). -Cases:: -* Displays the link to the Cases page under observability when Cases {kib} privileges are granted regardless of the other application privileges ({kibana-pull}182569[#182569]). -Canvas:: -* Fixes workpad templates using legacy filters function ({kibana-pull}176093[#176093]). -Connectors:: -* Removes secrets from the connectors before validating in `actionsClient` getAll ({kibana-pull}179837[#179837]). -Dashboard:: -* Fixes opening panel title edit flyout only when panel title is clicked ({kibana-pull}180137[#180137]). -* Disallows copy to dashboard from a maximized panel ({kibana-pull}179422[#179422]). -Discover:: -* Fixes issue where an ES query rule could be created with a data view, then the data view is changed but there's still a reference to the previous data view's timestamp field. ({kibana-pull}182883[#182883]). -* Fixes view all matches button for timestamps with numeric date formats ({kibana-pull}181769[#181769]). -* Fixes time range filters for CSV when a relative time filter is specified in UI ({kibana-pull}181067[#181067]). -* Fixes the status list to be static ({kibana-pull}177435[#177435]). -* Fixes a timeout for a "View all matches" request ({kibana-pull}181859[#181859]). -* Fixes tracking total hits for "View all matches" button ({kibana-pull}181811[#181811]). -* Fixes comments bugs in Discover and Data Visualizer ({kibana-pull}181283[#181283]). -* Fixes the problem with Discover and queries without the from command ({kibana-pull}180692[#180692]). -* Fixes displaying "Unsaved changes" badge on time filter changes in case time range is stored along with saved search ({kibana-pull}178659[#178659]). -Elastic Security:: -For the Elastic Security 8.14.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -ES|QL:: -* Fixes validation on string implicit casting for dates and other minor issues ({kibana-pull}181571[#181571]). -* Fixes validation for some specific {esql} types ({kibana-pull}181381[#181381]). -* Fixes retrieving the indices from AST parsing ({kibana-pull}181271[#181271]). -* Fixes max and min accepting date fields ({kibana-pull}180945[#180945]). -* Fixes autocomplete with incompatible arguments ({kibana-pull}180874[#180874]). -* Fixes providing the CCS indices on the autosuggestion ({kibana-pull}180610[#180610]). -* Fixes to `auto_bucket` and constant-only parameters ({kibana-pull}180509[#180509]). -* Fixes persisting columns sorting in saved search ({kibana-pull}180193[#180193]). -* Fixes client-side validation: make `and` and `or` accept `null` ({kibana-pull}179707[#179707]). -* Fixes to the @timestamp column ({kibana-pull}176834[#176834]). -* Fixes validation running on outdated queries when typing ({kibana-pull}180977[#180977]). -Fleet:: -* Adds validation to dataset field in input packages to disallow special characters ({kibana-pull}182925[#182925]). -* Fixes rollback input package install on failure ({kibana-pull}182665[#182665]). -* Fixes cloudflare template error ({kibana-pull}182645[#182645]). -* Fixes displaying `Config` and `API reference` tabs if they are not needed ({kibana-pull}182518[#182518]). -* Fixes allowing fleet-server agent upgrade to newer than fleet-server ({kibana-pull}181575[#181575]). -* Fixes flattened inputs in the configuration tab ({kibana-pull}181155[#181155]). -* Adds callout when editing an output about plain text secrets being re-saved to secret storage ({kibana-pull}180334[#180334]). -* Removes unnecessary field definitions for custom integrations ({kibana-pull}178293[#178293]). -* Fixes secrets UI inputs in forms when secrets storage is disabled server side ({kibana-pull}178045[#178045]). -* Fixes not being able to preview or download files with special characters ({kibana-pull}176822[#176822]). -* Fixes KQL validation being applied in search boxes ({kibana-pull}176806[#176806]). -Lens & Visualizations:: -* Fixes import to other spaces ({kibana-pull}183076[#183076]). -* Fixes clip path cutting mobile view in *Lens* ({kibana-pull}182376[#182376]). -* Fixes error message layer indexing in *Lens* ({kibana-pull}180898[#180898]). -* Fixes markdown table borders being visible in the text panels ({kibana-pull}180454[#180454]). -* Fixes default formatter for gauge charts in *Lens* ({kibana-pull}179473[#179473]). -* Fixes error for non-date histogram charts that contain `showCurrentTimeMarker:true` setting in *Lens* ({kibana-pull}179452[#179452]). -* Fixes overriding title when using the inline *Lens* editor ({kibana-pull}182897[#182897]). -Machine Learning:: -* Single Metric Viewer: Ensures edit to different job works as expected ({kibana-pull}183086[#183086]). -* Single Metric Viewer: Fixes hover functionality in the anomalies table ({kibana-pull}182297[#182297]). -* Single Metric Viewer: Ensures chart displays correctly when opening from a job annotation ({kibana-pull}182176[#182176]). -* Single Metric Viewer: Displays error message when insufficient permissions ({kibana-pull}180858[#180858]). -* Fixes retention of categorization example limits ({kibana-pull}182103[#182103]). -* Fixes responsive layout for Trained Models table ({kibana-pull}181541[#181541]). -* Removes datafeed preview frozen tier message in serverless ({kibana-pull}181440[#181440]). -* ML anomaly swim lane: Ensure dashboard reset works correctly ({kibana-pull}181346[#181346]). -* AIOps: Fixes query string for the change point detection metric charts ({kibana-pull}181314[#181314]). -* AIOps: Fixes missing field caps filters for log rate analysis ({kibana-pull}181109[#181109]). -* AIOps: Fixes not running log rate analysis twice when no spike/dip is detected ({kibana-pull}180980[#180980]). -* Removes all SCSS files in favor of CSS ({kibana-pull}178314[#178314]). -* Fixes polling for blocked anomaly detection jobs ({kibana-pull}178246[#178246]). -* Adds trained model list permission UI tests ({kibana-pull}174045[#174045]). -Management:: -* Fixes transform health rule failure with a long list of continuous transforms ({kibana-pull}183153[#183153]). -* The runtime field creation modal now shows indexed values instead of source values in the preview pane ({kibana-pull}181246[#181246]). -Monitoring:: -* Fixes broken KQL filter for Cluster Health rule ({kibana-pull}183259[#183259]). -Observability:: -* Fixes Triggered column timezone and format ({kibana-pull}182653[#182653]). -* Fixes refetching data views on save ({kibana-pull}181033[#181033]). -* Allows editing of charts when {es} query fails ({kibana-pull}180500[#180500]). -* Fixes Agent ID not being parsed correctly ({kibana-pull}180301[#180301]). -* Changes `Custom KQL` to `Custom Query` ({kibana-pull}179497[#179497]). -* Fixes filtering for a histogram legend value ({kibana-pull}178551[#178551]). -* Fixes an OpenAI Connector default model assignment bug ({kibana-pull}178369[#178369]). -Platform:: -* Update static asset headers to include `public` and `immutable` link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control[cache control directives] to a large number of assets served by Kibana, which should reduce the number of requests for static assets in browsers that respect these directives. ({kibana-pull}180378[#180378]). -* Omits apiKey from RUM agent ({kibana-pull}178902[#178902]). -Presentation:: -* Fixes clicking "Explore in maps" button not taking users to maps ({kibana-pull}181903[#181903]). -Security:: -* Fixed escaped terminal codes logging in interactive plugin setup ({kibana-pull}180342[#180342]). -* Fixes an issue with Security Assistant send to timeline functionality ({kibana-pull}177771[#177771]). -SharedUX:: -* Fixes multiline query in expanded mode displaying undefined for line number ({kibana-pull}181544[#181544]). -* Removes unused legacy markdown component ({kibana-pull}179272[#179272]). -* Fixes typo in chromium driver factory page event ({kibana-pull}178708[#178708]). -* Fixes not being able to enter the {esql} editor when the column menu is open ({kibana-pull}178622[#178622]). -* Fixes an issue in Reporting with consistently showing the toast message for completed report jobs ({kibana-pull}177537[#177537]). -* Fixes time picker to show allowed formats properly in absolute tabs ({kibana-pull}182152[#182152]). - -[[release-notes-8.13.4]] -== {kib} 8.13.4 - -The 8.13.4 release includes the following bug fixes. - -[float] -[[fixes-v8.13.4]] -=== Bug Fixes -Lens & Visualizations:: -* Fixes table sorting when changing the interval on the time picker in *Lens* ({kibana-pull}182173[#182173]). -Dashboards:: -* Fixes a bug with drilldowns where the control group on a source dashboard could be replaced by the control group from the destination dashboard ({kibana-pull}179485[#179485]). - -[[release-notes-8.13.3]] -== {kib} 8.13.3 - -The 8.13.3 release includes the following bug fixes. - -[float] -[[fixes-v8.13.3]] -=== Bug Fixes - -Alerting:: -* Manage loading fields at initialization ({kibana-pull}180412[#180412]). -Elastic Security:: -For the Elastic Security 8.13.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Fixes managed agent policy preconfiguration update ({kibana-pull}181624[#181624]). -* Use lowercase dataset in template names ({kibana-pull}180887[#180887]). -* Fixes KQL/kuery for getting Fleet Server agent count ({kibana-pull}180650[#180650]). -Index Management:: -* Fixes `allow_auto_create` field in the Index Template form ({kibana-pull}178321[#178321]). -Lens & Visualizations:: -* Fixes controls on fields with custom label ({kibana-pull}180615[#180615]). -Machine Learning:: -* Fixes deep link for Index data visualizer & ES|QL data visualizer ({kibana-pull}180389[#180389]). -Observability:: -* Make anomalyDetectorTypes optional ({kibana-pull}180717[#180717]). -SharedUX:: -* Revert change to shared UX markdown component for dashboard vis ({kibana-pull}180906[#180906]). -Sharing:: -* Default to saved object description when panel description is not provided ({kibana-pull}181177[#181177]). - -[[release-notes-8.13.2]] -== {kib} 8.13.2 - -The 8.13.2 release includes the following bug fixes. - -[float] -[[fixes-v8.13.2]] -=== Bug Fixes -Canvas:: -* Fixes text settings to be honored in Canvas markdown elements ({kibana-pull}179948[#179948]). -* Fixes custom styling for Canvas markdown Workpads ({kibana-pull}180070[#180070]). -Discover:: -* Fixes keyboard navigation for search input on the document viewer flyout ({kibana-pull}180022[#180022]). -Elastic Security:: -For the Elastic Security 8.13.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Fixes having to wait ten minutes after agent upgrade if agent cleared watching state ({kibana-pull}179917[#179917]). -Fixes using the latest available version in K8's manifest instead of the latest compatible version ({kibana-pull}179662[#179662]). -* Fixes a step in add agent instructions where a query to get all agents was unnecessary ({kibana-pull}179603[#179603]). -Lens & Visualizations:: -* Fixes custom styling for TSVB markdown visualizations ({kibana-pull}180053[#180053]). -Machine Learning:: -* Single Metric Viewer embeddable: Ensures the detector index is passed to chart correctly ({kibana-pull}179761[#179761]). -* AIOps: Fixes text field candidate selection for log rate analysis ({kibana-pull}179699[#179699]). -Management:: -* Fixes the Response tab loading time to be faster ({kibana-pull}180035[#180035]). -Maps:: -* Fixes APM data view ID ({kibana-pull}179257[#179257]). -Monitoring:: -* Fixes a runtime error by adding a default value for source and target ({kibana-pull}180043[#180043]). -Operations:: -* Fixes an issue with {kib} looking for a configuration file outside of the {kib} home directory, potentially preventing startup due to insufficient permissions ({kibana-pull}179847[#179847]). - -[[release-notes-8.13.1]] -== {kib} 8.13.1 - -The 8.13.1 release includes the following bug fixes. - -[float] -[[enhancement-v8.13.1]] +[[enhancement-v9.0.0]] === Enhancements -Fleet:: -* Remove `index.query.default_field` setting from managed component template settings ({kibana-pull}178020[#178020]). - -[float] -[[fixes-v8.13.1]] -=== Bug Fixes -Alerting:: -* Limit useEffect to calculate view in app URL ({kibana-pull}179197[#179197]). -Data Discovery:: -* Hide "Save"/"Save as" actions from "Unsaved changes" badge for read-only users ({kibana-pull}179132[#179132]). -Elastic Security:: -For the Elastic Security 8.13.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Use index exists check in fleet-metrics-task ({kibana-pull}179404[#179404]). -Lens & Visualizations:: -* Fixes wilcard complex scenarios ({kibana-pull}178938[#178938]). -Machine Learning:: -* AIOps: Fix text field candidate selection for log rate analysis ({kibana-pull}179699[#179699]). -Observability:: -* Fall back to top 5 docs on scoring error ({kibana-pull}179615[#179615]). -* Fixing APM data view id ({kibana-pull}179257[#179257]). - -[[release-notes-8.13.0]] -== {kib} 8.13.0 - -For information about the {kib} 8.13.0 release, review the following information. [float] -[[known-issues-8.13.0]] -=== Known issues - -[discrete] -[[known-179457]] -.In Canvas, an empty text element incorrectly triggers a toast notification -[%collapsible] -==== -*Details* + -In Canvas, an empty text element incorrectly triggers a "Markdown content is required in [readOnly] mode" toast notification. For more information, refer to ({kibana-pull}179457[#179457]). -==== - -[discrete] -[[known-177938-8.13]] -.Index templates UI incorrectly sets the `allow_auto_create` field to `false` by default. -[%collapsible] -==== -*Details* + -If you are creating or editing an index template using the Index Templates form under the Index Management page, the `allow_auto_create` field is incorrectly set to `false` by default (the default value should be `undefined`). For more information, refer to ({kibana-issue}177938[#177938]). -==== - -[float] -[[breaking-changes-8.13.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.13.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Removes conditional topics for Kafka outputs -[%collapsible] -==== -*Details* + -The Kafka output no longer supports conditional topics. For more information, refer to ({kibana-pull}176879[#176879]). -==== - -[discrete] -.Most Fleet installed integrations are now read-only and labelled with a *Managed* tag in the Kibana UI -[%collapsible] -==== -*Details* + - -Integration content installed by {fleet} is no longer editable. This content is tagged with *Managed* in the {kib} UI, and is Elastic managed. This content cannot be edited or deleted, however managed visualizations, dashboards, and saved searches can be cloned. The clones can be customized. - -When cloning a dashboard the cloned panels become entirely independent copies that are unlinked from the original configurations and dependencies. - -Managed content relating to specific visualization editors such as Lens, TSVB, and Maps, the clones retain the original reference configurations. The same applies to editing any saved searches in a managed visualization. - -For more information, refer to ({kibana-pull}172393[#172393]). -==== - -[discrete] -.Removes `is_nan`, `is_finite`, and `is_infinite` functions from {esql} -[%collapsible] -==== -*Details* + -These functions have been removed from {esql} queries as they are not supported. Errors would be thrown when trying to use them. For more information, refer to ({kibana-pull}174674[#174674]). -==== - -[float] -[[features-8.13.0]] -=== Features -{kib} 8.13.0 adds the following new and notable features. - -Alerting:: -* The Custom Threshold rule is now out of technical preview and generally available ({kibana-pull}176514[#176514]). -* Adds threshold to the custom threshold alert document ({kibana-pull}176043[#176043]). -* Adds the ability to post Block Kit messages to the Slack Web API action ({kibana-pull}174303[#174303]). -* Adds criticality fields and risk score fields to alert schema ({kibana-pull}174626[#174626]). -* Adds fields table to rule details page alert flyout ({kibana-pull}172830[#172830]). -APM:: -* Show Universal Profiling on Transaction view ({kibana-pull}176302[#176302]). -* Adds a specific metrics dashboard for opentelemetry-node services ({kibana-pull}174700[#174700]). -Dashboards:: -* Adds the creating of {esql} charts from the dashboard ({kibana-pull}171973[#171973]). -Elastic Security:: -For the Elastic Security 8.13.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -{esql}:: -* Adds enhanced {esql} query editing experience with client side validation ({kibana-pull}170071[#170071]). -Fleet:: -* Adds reference to `ecs@mappings` for each index template ({kibana-pull}174855[#174855]). -* Adds support for the `subobjects` setting on the object type mapping ({kibana-pull}171826[#171826]). -Infrastructure:: -* Adds a new Services component to host details UI ({kibana-pull}176539[#176539]). -Integrations:: -* Adds tiles for Notion and Redis connectors ({kibana-pull}177306[#177306]). -Lens & Visualizations:: -* Adds workspace panel dimensions by chart type ({kibana-pull}168651[#168651]). -Machine Learning:: -* Adds the single metric viewer embeddable for dashboards ({kibana-pull}175857[#175857]). -* Adds support for {esql} in Data visualizer ({kibana-pull}174188[#174188]). -Management:: -* Adds a method of excluding data tiers when getting a field list ({kibana-pull}167946[#167946]). -Reporting:: -* Adds support for 'auto' value for CSV scrolling duration ({kibana-pull}175005[#175005]). -* Adds CSV reporting with {esql} in Discover ({kibana-pull}174511[#174511]). -Observability:: -* Adds actions column ({kibana-pull}175872[#175872]). -* Adds resource column with tooltip ({kibana-pull}175287[#175287]). -* Adds support for the Timeslice Metric visualization on the SLO detail page ({kibana-pull}175281[#175281]). -* Refactor alert table registration and change default columns ({kibana-pull}175119[#175119]). -* Adds customization for virtual columns and add the 1st virtual column ({kibana-pull}173732[#173732]). -* Adds a new option, Visualize this query, to the generated {esql} quires in the Elastic Assistant ({kibana-pull}174677[#174677]). - -For more information about the features introduced in 8.13.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.13.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.13.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.13.0]] -=== Enhancements -Alerting:: -* Improve default AlertsTable columns configuration ({kibana-pull}176137[#176137]). -* Evenly distribute bulk-enabled alerting rules ({kibana-pull}172742[#172742]). -* Implement a mechanism to copy source data into the alerts-as-data documents for ES Query rules ({kibana-pull}171129[#171129]). -APM:: -* Adds detectors for anomaly rules creation ({kibana-pull}171901[#171901]). -* Allows Universal Profiling agent to send error frames ({kibana-pull}176537[#176537]). -* Adds Azure settings ({kibana-pull}176386[#176386]). -* Adds table search to services, transactions and errors ({kibana-pull}174490[#174490]). -* Adds memoization to hooks consumed on service inventory page ({kibana-pull}173973[#173973]). -* Adds stack traces Threads embeddable ({kibana-pull}173905[#173905]). -* Enhances the diff topN functions ({kibana-pull}173397[#173397]). -* Updates Indices API to support sourcemap parameters ({kibana-pull}177847[#177847]). -* Fixes sorting instances table results on server-side ({kibana-pull}174164[#174164]). -Cases:: -* Required custom fields now support default values, which will be used to automatically populate the custom fields if they are not defined when creating and updating cases ({kibana-pull}175961[#175961]). -* Persists all filter options of the cases table, including custom fields, in the URL. The filtering is also persisted when navigating back and forth between pages ({kibana-pull}175237[#175237]). -* Enables the alerts table for cases in the Stack Management ({kibana-pull}172217[#172217]). -Dashboard:: -* Adds step setting for range slider control ({kibana-pull}174717[#174717]). -* Adds a deprecation badge in Dashboard on legacy control panels ({kibana-pull}174302[#174302]). -* Adds external link icon to external URL links in the Links panel ({kibana-pull}174407[#174407]). -* Re-adds filtering settings in the Control settings UI ({kibana-pull}172857[#172857]). -* Adds number field support the the Options List control({kibana-pull}172106[#172106]). -Discover:: -* Hides the Empty fields section if there are no fields in it ({kibana-pull}172956[#172956]). -* Adds data table header row height configuration ({kibana-pull}175501[#175501]). -* Adds new fields ingested in the background to the field list with valid mappings ({kibana-pull}172329[#172329]). -* Adds caching to data view field list request with a `stale-while-revalidate` strategy ({kibana-pull}168910[#168910]). -* Distinguish among empty and available fields in Discover {esql} mode ({kibana-pull}174585[#174585]). -Elastic Security:: -For the Elastic Security 8.13.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Elastic Search:: -* Adds `ignore_empty_value` to generated set processor ({kibana-pull}175172[#175172]). -{esql}:: -* Adds clickable badges on compact view for {esql} queries ({kibana-pull}176568[#176568]). -* Adds link on the documentation popover to navigate to our external docs ({kibana-pull}176377[#176377]). -* Adds support for canceling {esql} queries in *Lens* ({kibana-pull}176277[#176277]). -* Adds quick fixes feature to {esql} query validation errors ({kibana-pull}175553[#175553]). -* Adds support for command settings ({kibana-pull}175114[#175114]). -* Allows line breaks on the {esql} editor ({kibana-pull}173596[#173596]). -Fleet:: -* Adds `skipRateLimitCheck` flag to the Upgrade API and Bulk_upgrade API ({kibana-pull}176923[#176923]). -* Adds making datastream rollover lazy ({kibana-pull}176565[#176565]). -* Stops creating the `{type}-{datastet}@custom` component template during package installation ({kibana-pull}175469[#175469]). -* Adds the `xpack.fleet.isAirGapped` flag ({kibana-pull}174214[#174214]). -* Adds a warning when download upgrade is failing ({kibana-pull}173844[#173844]). -* Adds a message explaining why an agent is not upgradeable ({kibana-pull}173253[#173253]). -* Makes logs-* and metrics-* data views available across all spaces ({kibana-pull}172991[#172991]). -* Adds flag for pre-release to templates/inputs endpoint ({kibana-pull}174471[#174471]). -* Adds concurrency control to Fleet data stream API handler ({kibana-pull}174087[#174087]). -* Adds a handlebar helper to percent encode a given string ({kibana-pull}173119[#173119]). -Integrations:: -* Enables minute frequency for incremental syncs ({kibana-pull}176603[#176603]). -Lens & Visualizations:: -* Datatable improvements in *Lens* ({kibana-pull}174994[#174994]). -* Adds IP Prefix Aggregation-based Visualization ({kibana-pull}173474[#173474]). -* Displays the suggestions on the dataview mode charts in *Lens* ({kibana-pull}172924[#172924]). -* Enables treemap in suggestions in *Lens* ({kibana-pull}169095[#169095]). -Machine Learning:: -* AIOps: Adds UI action for Change Point Detection embeddable to open in the ML app ({kibana-pull}176694[#176694]). -* AIOps: Enhances display of results for alias field types in pattern analysis ({kibana-pull}176586[#176586]). -* Enhances support for {esql} Data visualizer ({kibana-pull}176515[#176515]). -* Adds a prompt to delete alerting rules upon the anomaly detection job deletion ({kibana-pull}176049[#176049]). -* Adds grok highlighting to the file data visualizer ({kibana-pull}175913[#175913]). -* Adds a warning if trained model is referenced by the `_inference` API ({kibana-pull}175880[#175880]). -* Adds a feedback button to anomaly explorer and single metric viewer for metrics hosts anomaly detection jobs ({kibana-pull}175613[#175613]). -* Adds actions menu to anomaly markers in Single Metric Viewer chart ({kibana-pull}175556[#175556]). -* AIOps: Adds expanded rows to pattern analysis table ({kibana-pull}175320[#175320]). -* AIOps: Adds link to log rate analysis from anomaly table ({kibana-pull}175289[#175289]). -* AIOps: Improves pattern analysis refresh behavior ({kibana-pull}174516[#174516]). -* Adds option for using table layout in the Change Point Detection embeddable ({kibana-pull}174348[#174348]). -* Adds high count option to the anomaly detection categorization wizard ({kibana-pull}174252[#174252]). -* Improves Data drift time range selection & shows hints for analysis process ({kibana-pull}174049[#174049]). -* Adds link to anomaly detection job creation from the alerting rule form ({kibana-pull}174016[#174016]). -* Adds warning for legacy method for installing pre-configured APM transaction job ({kibana-pull}173375[#173375]). -* Enhances toast notifications to improve error reporting ({kibana-pull}173362[#173362]). -* Adds document count chart for file upload ({kibana-pull}173210[#173210]). -* Trained models: Adds workflow for creating an ingest pipeline for a trained model ({kibana-pull}170902[#170902]). -* Updates alerts-as-data payload for Anomaly detection health and Transform health rules ({kibana-pull}176307[#176307]). -Management:: -* Changes the column "Components" in the index templates table to display number of component templates ({kibana-pull}175823[#175823]). -Maps:: -* Adds support in maps for the {esql} `geo_shape` column type ({kibana-pull}175156[#175156]). -* Adds a {esql} card to the add layer UI in maps ({kibana-pull}173481[#173481]). -* Displays vector tile results in vector tile inspector ({kibana-pull}172627[#172627]). -Observability:: -* Enable burn rate alert by default during creation via UI ({kibana-pull}176317[#176317]). -* Implements Bedrock support for the Claude models ({kibana-pull}176191[#176191]). -* Adds link for AI Assistant in Observability left hand navigation ({kibana-pull}176144[#176144]). -* Handle token limit error message improvement ({kibana-pull}175871[#175871]). -* Corrects common {esql} mistakes ({kibana-pull}175520[#175520]). -* Adds resource column with tooltip ({kibana-pull}175287[#175287]). -* Adds alert fields table to Observability flyout ({kibana-pull}174685[#174685]). -Platform:: -* Adds an option to disable APM user redaction ({kibana-pull}176566[#176566]). -Reporting:: -* Updated CSV export to insert error messages into the contents if the export results in an empty file due to an error ({kibana-pull}175852[#175852]). -* Adds setting to use PIT or Scroll API ({kibana-pull}174980[#174980]). -Security:: -* Default value of `server.securityResponseHeaders.referrerPolicy` changed to `strict-origin-when-cross-origin` ({kibana-pull}177559[#177559]). -* Adds server side validation for uploaded file types ({kibana-pull}173960[#173960]). -Sharing:: -* Show 'View details' UI action to open clusters inspector tab when request fails ({kibana-pull}172971[#172971]). -Unified Search:: -* Adds 'greater than or equals to' and 'less than' options to filter options for date ranges and numbers ({kibana-pull}174283[#174283]). -* Adds a one minute option to the date picker ({kibana-pull}172944[#172944]). -* Adds multiple improvements to saved query management ({kibana-pull}170599[#170599]). - -[float] -[[fixes-v8.13.0]] -=== Bug Fixes -Alerting:: -* Fixes Elasticsearch query rule with KQL evaluation matched document count ({kibana-pull}176620[#176620]). -* Fixes alerts not being visible when number of alerts are more than max alert limit ({kibana-pull}178019[#178019]). -* Fixes `ruleClient.getAlertState` error when a task is no longer available ({kibana-pull}177077[#177077]). -* Fixes AlertsTable sorting with inactive columns and default sort direction ({kibana-pull}176172[#176172]). -APM:: -* Fixes an infinite loop caused by matching child and parent IDs ({kibana-pull}177914[#177914]). -* Fixes inconsistencies on Service Overview page ({kibana-pull}176293[#176293]). -* Fixes occurrences cut off value to be fully visible ({kibana-pull}175307[#175307]). -Cases:: -* Fixes a bug where if there are required custom fields on a case whose values are empty, it is not possible to update any of those fields ({kibana-pull}176574[#176574]). -Dashboard:: -* Fixes hidden add panel popover on smaller viewports ({kibana-pull}178593[#178593]). -* Fixes form validation when saving Links to library ({kibana-pull}176021[#176021]). -Design:: -* Fixes a11y concerns ({kibana-pull}174772[#174772]). -Discover:: -* Update full screen handling to fix z-index issues in {kib} ({kibana-pull}178788[#178788]). -* Fixes "New" link in {esql} mode ({kibana-pull}177038[#177038]). -* Fixes grid column settings on Surrounding Documents page ({kibana-pull}177003[#177003]). -* Fixes time zone for field popover histogram and removes `getTimeZone` duplicates ({kibana-pull}172705[#172705]). -* Fixes including global filters when opening a saved search ({kibana-pull}175814[#175814]). -* Fixes loading a missing data view in the case where it's not provided by the consuming plugin ({kibana-pull}173017[#173017]). -Elastic Security:: -For the Elastic Security 8.13.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Elastic Search:: -* Fixes minor problems with integrations for Enterprise Search ({kibana-pull}177570[#177570]). -Fleet:: -* Fixes a bug where secret values were not deleted on output type change ({kibana-pull}178964[#178964]). -* Fixes formatting for some integrations on the overview page ({kibana-pull}178937[#178937]). -* Fixes the name of {es} output workers configuration key ({kibana-pull}178329[#178329]). -* Fixes clean up of the `.fleet-policies` entries when deleting an agent policy. ({kibana-pull}178276[#178276]). -* Fixes only showing remote {es} output health status if later than last updated time ({kibana-pull}177685[#177685]). -* Fixes status summary when `showUpgradeable` is selected ({kibana-pull}177618[#177618]). -* Fixes issue of agent sometimes not getting inputs using a new agent policy with system integration ({kibana-pull}177594[#177594]). -* Fixes the activity flyout keeping the scroll state on rerender ({kibana-pull}177029[#177029]). -* Fixes inactive popover tour not resetting ({kibana-pull}176929[#176929]). -* Fixes `isPackageVersionOrLaterInstalled` to check for installed package ({kibana-pull}176532[#176532]). -* Removes pre-release exception for Synthetics package ({kibana-pull}176249[#176249]). -* Fixes output validation when creating package policy ({kibana-pull}175985[#175985]). -* Fixes allowing an agent to upgrade to a newer patch version than fleet-server ({kibana-pull}175775[#175775]). -* Fixes asset creation during custom integration installation ({kibana-pull}174869[#174869]). -* Fixes cascading agent policy's namespace to package policies ({kibana-pull}174776[#174776]). -Infrastructure:: -* Fixing derivative aggregation on kubernetes pods ({kibana-pull}177295[#177295]). -Lens & Visualizations:: -* Fixes handling of `doc_count` on time shift scenarios ({kibana-pull}178394[#178394]). -* Fixes the title of a formula based metric visualization defaulting to `Formula` in *Lens* ({kibana-pull}177299[#177299]). -* Fixes sorting on table when using Last value on date field in *Lens* ({kibana-pull}177288[#177288]). -* Align formatters for point and range annotations in *Lens* ({kibana-pull}177199[#177199]). -* Fixes the mapping of {es} fields ({kibana-pull}176665[#176665]). -* Fixes the display of warnings with additional information ({kibana-pull}176660[#176660]). -* Fixes clicking the editor closing the {esql} documentation popover ({kibana-pull}176394[#176394]). -* Fixes creating or removing layers in *Lens* loosing focus ({kibana-pull}175893[#175893]). -* Fixes using the same adhoc data views for queries with the same index pattern ({kibana-pull}174736[#174736]). -* Fixes the markdown editor not expanding to fill vertical space ({kibana-pull}174276[#174276]). -Machine Learning:: -* Preserves field formatters between rule executions ({kibana-pull}178621[#178621]). -* Fixes quick create geo job created by ID ({kibana-pull}177691[#177691]). -* AIOps: Fixes incomplete edge buckets for change point detection ({kibana-pull}177579[#177579]). -* AIOps: Fixes grouping for fields with large arrays ({kibana-pull}177438[#177438]). -* Fixes Single Metric Viewer's zoom settings in URL are not restored if URL specifies a forecast ID ({kibana-pull}176969[#176969]). -* Adds delay to deletion modal to avoid flickering ({kibana-pull}176424[#176424]). -* Fixes Single Metric Viewer not showing chart for metric functions and mismatch function in tooltip ({kibana-pull}176354[#176354]). -* Fixes multi-match query overriding filters in Data Visualizer and Data Drift ({kibana-pull}176347[#176347]). -* Fixes only enabling apply button in anomaly detection datafeed chart if changes have been made ({kibana-pull}174425[#174425]). -Management:: -* Fixes editing a rollup data view ({kibana-pull}177446[#177446]). -* Fixes showing previously selected no time field setting ({kibana-pull}177221[#177221]). -* Fixes package showing 'Needs authorization' warning even after transform assets were authorized successfully ({kibana-pull}176647[#176647]). -* Removes the polling interval to reload indices in the background ({kibana-pull}174681[#174681]). -* Fixes keeping the filters value in the URL for the indices list ({kibana-pull}174515[#174515]). -* Fixes categorizing fields as empty that never had a value in matching indices ({kibana-pull}174063[#174063]). -* Fixes the badge for managed data streams in Index Management ({kibana-pull}173408[#173408]). -* Some input fields are now disabled when editing managed repositories in Snapshot & Restore ({kibana-pull}173137[#173137]). -* Input fields to change snapshot name and repository are now disabled when editing managed SLM policies in Snapshot & Restore ({kibana-pull}172291[#172291]). -Maps:: -* Fixes Request URL Too Long (414) with heatmap layer when data view has larger number of date fields ({kibana-pull}177900[#177900]). -* Fixes the maps application breaking if you open a map with layers or sources that do not exist (#176419). -Observability:: -* SLOs: Does not display group by cardinality when group by is not selected ({kibana-pull}178133[#178133]). -* Fixes bug in inventory rule for Inbound and Outbound traffic threshold (both preview and executor) ({kibana-pull}177997[#177997]). -* Fixes more lenient parsing of suggestion scores ({kibana-pull}177898[#177898]). -* Fixes refreshing the conversations list on conversation update ({kibana-pull}177897[#177897]). -* Fixes SLO details path is broken when `instanceId` contains a forward slash ({kibana-pull}177843[#177843]). -* Prevents users from picking date fields for the group-by selector ({kibana-pull}177830[#177830]). -* Fixes undefined issue cased due row check missing ({kibana-pull}177293[#177293]). -* Fixes making IDs unique to capture multiple invocations of the same query ({kibana-pull}173433[#173433]). -Querying & Filtering:: -* Fixes autocomplete value suggestions for KQL when the corresponding index has no tier preference set ({kibana-pull}176355[#176355]). -SharedUX:: -* Fixes how sample data test install state is determined in test ({kibana-pull}178529[#178529]). -* Fixed a bug in Stack Management Reporting where the Delete button was not disabled after click ({kibana-pull}173707[#173707]). -Uptime:: -* Require `unifiedSearch` plugin and include in top-level Kibana Context Provider ({kibana-pull}178421[#178421]). -* Omit the request `Content-Type` header if body check is empty ({kibana-pull}178399[#178399]). -* Fixes Certificates page for monitors that have status alert disabled ({kibana-pull}178336[#178336]). -* Fixes allowing Synthetics global parameters to include dashes ({kibana-pull}178054[#178054]). -* Change test now trigger route from GET to POST ({kibana-pull}177093[#177093]). -* Fixes and simplifies write access default behavior ({kibana-pull}177088[#177088]). - -[[release-notes-8.12.2]] -== {kib} 8.12.2 - -The 8.12.2 release includes the following bug fixes. - -[float] -[[fixes-v8.12.2]] -=== Bug Fixes -Alerting:: -* Fixes Discover results when an alert excludes matches from previous runs ({kibana-pull}176690[#176690]). -* Fixes bug where using select all on the rules list bypassed filters ({kibana-pull}176962[#176962]). -Elastic Security:: -For the Elastic Security 8.12.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Fixes a popover about inactive agents not being dismissible ({kibana-pull}176929[#176929]). -* Fixes logstash output being link:https://www.rfc-editor.org/rfc/rfc952[RFC-952] compliant ({kibana-pull}176298[#176298]). -* Fixes assets being unintentionally moved to the default space during Fleet setup ({kibana-pull}176173[#176173]). -* Fixes categories labels in integration overview ({kibana-pull}176141[#176141]). -* Fixes the ability to delete agent policies with inactive agents from UI, the inactive agents need to be unenrolled first ({kibana-pull}175815[#175815]). -Machine Learning:: -* Fixes Single Metric Viewer's zoom range settings in URL not being restored if the URL specifies a `forecastId` ({kibana-pull}176969[#176969]). -* Fixes incorrect document count values in Top Values statistics ({kibana-pull}176328[#176328]). -* Fixes color of markers in Single Metric Viewer when there is sparse data for anomaly detection ({kibana-pull}176303[#176303]). -Management:: -* Fixes package showing 'Needs authorization' warning even after transform assets were authorized successfully ({kibana-pull}176647[#176647]). -Observability:: -* Fixes and simplifies write access default behavior ({kibana-pull}177088[#177088]). -* Fixes recall speed when using CVS output ({kibana-pull}176428[#176428]). - -[[release-notes-8.12.1]] -== {kib} 8.12.1 - -The 8.12.1 release includes the following enhancements and bug fixes. - -[float] -[[enhancement-v8.12.1]] -=== Enhancements - -Elastic Security:: -For the Elastic Security 8.12.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Observability:: -* Adds `defer_validation: true` to transforms on creation to stop errors when the destination index doesn't exist yet ({kibana-pull}174463[#174463]). - -[float] -[[fixes-v8.12.1]] -=== Bug Fixes -Alerting:: -* Fixes context variables not being passed in to the action parameters when an alert- as-data document is available ({kibana-pull}175682[#175682]). -* Fixes the Rules page loosing user selections when navigating back ({kibana-pull}174954[#174954]). -* Fixes the custom threshold rendering in the create rule flyout ({kibana-pull}174982[#174982]). -APM:: -* Fixes a transactions error link for mobile ({kibana-pull}174655[#174655]). -* Increases the number of maximum function calls from 3 to 5 ({kibana-pull}175588[#175588]). -Dashboard:: -* Fixes a caching issue that caused problems updating dashboard information ({kibana-pull}175635[#175635]). -Elastic Security:: -For the Elastic Security 8.12.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Fixes the display of category label on the Integration overview page ({kibana-pull}176141[#176141]). -* Fixes conflicting dynamic template mappings for intermediate objects ({kibana-pull}175970[#175970]). -* Fixes reserved keys for Elasticsearch output YAML box ({kibana-pull}175901[#175901]). -* Prevent deletion of agent policies with inactive agents from UI ({kibana-pull}175815[#175815]). -* Fixes incorrect count of agents in bulk actions ({kibana-pull}175318[#175318]). -* Fixes a custom integrations not displaying on the Installed integrations page ({kibana-pull}174804[#174804]). -Lens & Visualizations:: -* Fixes a validation error for invalid formula and math columns in *Lens* ({kibana-pull}175644[#175644]). -Machine Learning:: -* Fixes Allocation rendering for failed deployments ({kibana-pull}174882[#174882]). -* Fixes an issue where a user could create an anomaly rule but couldn't see it or interact with the rule via stack management ({kibana-pull}174791[#174791]). -Security:: -* Fixes API Key table sorting ({kibana-pull}175813[#175813]). -* Ensures all API Keys have a defined name ({kibana-pull}175721[#175721]). -* Fixes an issue with `@kbn-handlebars`, where nested inputs were not being escaped properly ({kibana-pull}175490[#175490]). - -[[release-notes-8.12.0]] -== {kib} 8.12.0 - -For information about the {kib} 8.12.0 release, review the following information. - -[float] -[[known-issues-8.12.0]] -=== Known issues - -[discrete] -[[known-177938-8.12]] -.Index templates UI incorrectly sets the `allow_auto_create` field to `false` by default. -[%collapsible] -==== -*Details* + -If you are creating or editing an index template using the Index Templates form under the Index Management page, the `allow_auto_create` field is incorrectly set to `false` by default (the default value should be `undefined`). For more information, refer to ({kibana-issue}177938[#177938]). -==== - -[float] -[[breaking-changes-8.12.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.12.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.New SLO architecture -[%collapsible] -==== -*Details* + -We introduce a breaking change in the SLO features that will break any SLOs created before 8.12. These SLOs have to be manually reset through an API until we provide a UI for it. The data aggregated over time (rollup) is still available in the sli v2 index, but won't be used for summary calculation when reset. - -The previous summary transforms summarizing every SLOs won't be used anymore and can be stopped and deleted: - -* slo-summary-occurrences-7d-rolling -* slo-summary-occurrences-30d-rolling -* slo-summary-occurrences-90d-rolling -* slo-summary-occurrences-monthly-aligned -* slo-summary-occurrences-weekly-aligned -* slo-summary-timeslices-7d-rolling -* slo-summary-timeslices-30d-rolling -* slo-summary-timeslices-90d-rolling -* slo-summary-timeslices-monthly-aligned -* slo-summary-timeslices-weekly-aligned - -Be aware that when installing a new SLO (or after resetting an SLO), we install two transforms (one for the rollup data and one that summarize the rollup data). Do not delete the new `slo-summary-{slo_id}-{slo_revision}` transforms. For more information, refer to ({kibana-pull}172224[#172224]). -==== - -[discrete] -.A new sub-feature privilege to control user access to the cases settings -[%collapsible] -==== -*Details* + -Roles with at least a sub-feature privilege configured will not have access to the cases setting like they had previously. All roles without a sub-feature privilege configured will not be affected. For more information, refer to ({kibana-pull}170635[#170635]). -==== - -[float] -[[features-8.12.0]] -=== Features -{kib} 8.12.0 adds the following new and notable features. - -APM:: -* Adds viewInApp URL to the custom threshold rule type ({kibana-pull}171985[#171985]). -* Adds back the mobile crashes & errors tab ({kibana-pull}165892[#165892]). -Cases:: -* The case list filter bar is now customizable, filters are removable and custom fields can be used as filters ({kibana-pull}172276[#172276]). -Elastic Security:: -For the Elastic Security 8.12.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Elastic Search:: -* Display E5 multilingual callout ({kibana-pull}171887[#171887]). -* Replace model selection dropdown with list ({kibana-pull}171436[#171436]). -Fleet:: -* Adds support for preconfigured output secrets (Scrypt edition) ({kibana-pull}172041[#172041]). -* Adds UI components to create and edit output secrets ({kibana-pull}169429[#169429]). -* Adds support for remote ES output ({kibana-pull}169252[#169252]). -* Adds the ability to specify secrets in outputs ({kibana-pull}169221[#169221]). -* Adds an integrations configs tab to display input templates ({kibana-pull}168827[#168827]). -* Adds a {kib} task to publish Agent metrics ({kibana-pull}168435[#168435]). -Lens & Visualizations:: -* Adds the ability to edit charts made by {esql} queries in Dashboard ({kibana-pull}169911[#169911]). -Machine Learning:: -* Adds E5 model configurations ({kibana-pull}172053[#172053]). -* Adds the ability to create a categorization anomaly detection job from pattern analysis ({kibana-pull}170567[#170567]). -* Adds and displays alerts data in the Anomaly Explorer ({kibana-pull}167998[#167998]). -Observability:: -* Adds logic to update flyout highlights ({kibana-pull}172193[#172193]). -* Adds logic to display highlights in the flyout ({kibana-pull}170650[#170650]). -* Changes the Custom threshold title to Beta ({kibana-pull}172360[#172360]). -Security:: -* Disables the connector parameters field ({kibana-pull}173610[#173610]). -* Adds a risk engine missing privileges callout ({kibana-pull}171250[#171250]). -* Asset criticality privileges API ({kibana-pull}172441[#172441]). -Uptime:: -* Global params Public APIs ({kibana-pull}169669[#169669]). -* Private location public API's ({kibana-pull}169376[#169376]). -* Settings public API ({kibana-pull}163400[#163400]). - -For more information about the features introduced in 8.12.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.12.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.12.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.12.0]] -=== Enhancements -Alerting:: -* Auto close ServiceNow incidents when alerts are resolved ({kibana-pull}171760[#171760]). -* PagerDuty connector now supports the links and `custom_details` attributes ({kibana-pull}171748[#171748]). -* Adds a mute and unmute action component in the alerts table row actions ({kibana-pull}170651[#170651]). -* Extends the PagerDuty connector API to support the `links` and `custom_details` attributes provided by the Event API ({kibana-pull}170459[#170459]). -* Adds toggle for alert as data fields in alert templating ({kibana-pull}170162[#170162]). -APM:: -* Perform functions and LLM interactions on the server ({kibana-pull}172590[#172590]). -* Adds viewInApp URL to the custom threshold rule type ({kibana-pull}171985[#171985]). -* Adds the KQL bar to embeddables ({kibana-pull}171016[#171016]). -* Enables the average mobile app launch time panel ({kibana-pull}170773[#170773]). -* Enables the mobile most launches panel ({kibana-pull}168925[#168925]). -* Improves the Differential Top N functions grid view ({kibana-pull}170008[#170008]). -Cases:: -* Users can copy to the clipboard the hashes of files uploaded to cases ({kibana-pull}172450[#172450]). -* Allow users to configure which columns are displayed in the cases list including custom fields ({kibana-pull}170950[#170950]). -* Adds a new sub-feature privilege to control user access to the cases settings ({kibana-pull}170635[#170635]). -Dashboard:: -* Adds Links to the Visualization library ({kibana-pull}170810[#170810]). -Discover:: -* Adds a field tokens column in the grid header ({kibana-pull}167179[#167179]). -* Enables the addition of columns from the document viewer when using ES|QL ({kibana-pull}171083[#171083]). -* Adds field search via wildcards in the document viewer ({kibana-pull}168616[#168616]). -* Improves search for field names by handling spaces like wildcards ({kibana-pull}168381[#168381]). -* Updates mapping conflict popover with types list ({kibana-pull}169855[#169855]). -* On search source error, show 'view details' action that opens request in inspector ({kibana-pull}170790[#170790]). -* Adds an Unsaved changes label when in an unsaved state of saved search ({kibana-pull}169548[#169548]). -* Allows changing the current sample size and saving it with a saved search ({kibana-pull}157269[#157269]). -* Adds new sparse vector and dense vector icons ({kibana-pull}169493[#169493]). -* Adds `sparse_vector` field support ({kibana-pull}168186[#168186]). -Elastic Security:: -For the Elastic Security 8.12.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Elastic Search:: -* Trained models can now be deployed and started directly from the Machine Learning inference pipeline configuration flyout ({kibana-pull}173434[#173434]). -Fleet:: -* Adds support for Elasticsearch output performance presets ({kibana-pull}172359[#172359]). -* Adds a new `keep_monitoring_alive` flag to agent policies ({kibana-pull}168865[#168865]). -* Adds support for additional types for dynamic mappings ({kibana-pull}168842[#168842]). -* Implements Elastic Agent upgrade states UI ({kibana-pull}167539[#167539]). -* Use default component templates from Elasticsearch ({kibana-pull}163731[#163731]). -Lens & Visualizations:: -* Moves the tagcloud visualization in *Lens* out of experimental status ({kibana-pull}168824[#168824]). -* Allows coloring an entire metric panel when applying a maximum value to the metric visualization in **Lens** ({kibana-pull}172531[#172531]). -* Adds truncation for data view pickers and field lists with many characters ({kibana-pull}172296[#172296]). -* Allows searching in the {esql} inline documentation description ({kibana-pull}171916[#171916]). -* Allows setting non-numeric metrics for metric visualizations in *Lens* ({kibana-pull}169258[#169258]). -Machine Learning:: -* Removes the beta badge from ML alerting rules ({kibana-pull}173545[#173545]). -* Removes the technical preview badge from AIOps log rate analysis ({kibana-pull}172722[#172722]). -* Adds anomaly description as an alert message for the anomaly detection rule type ({kibana-pull}172473[#172473]). -* Adds a sampled percentage of documents, and cardinality, for text fields for the Data Visualizer Field statistics tab and addresses an issue with a missing bucket in the document count chart ({kibana-pull}172378[#172378]). -* Adds option to display an overlay chart on the data drift expanded row ({kibana-pull}172239[#172239]). -* AIOps: Shows top N results when no documents are in baseline or deviation in log rate analysis({kibana-pull}171924[#171924]). -* AIOps: Adds support to restore baseline and deviation from URL state on page refresh for log rate analysis ({kibana-pull}171398[#171398]). -* Validates and limits threading parameters for starting a model deployment ({kibana-pull}171921[#171921]). -* Trained models: Adds a missing job node to models map view when original job has been deleted ({kibana-pull}171590[#171590]). -* Trained models list: Disables the View training data action if data frame analytics job no longer exists ({kibana-pull}171061[#171061]). -* Adds a trained model flyout with available models to download for in the Trained Models UI ({kibana-pull}171024[#171024]). -* Allows temporary data views in the anomaly detection jobs wizards ({kibana-pull}170112[#170112]). -* Assigns downloaded ELSER models to the `*` space ({kibana-pull}169939[#169939]). -* Adds pattern analysis to the anomaly action menu ({kibana-pull}169400[#169400]). -* Adds test pipeline action for data frame analysis trained models in models list ({kibana-pull}168400[#168400]). -Management:: -* Adds a search bar to the Clusters and shards tab ({kibana-pull}171806[#171806]). -* Aligns data view and destination index creation workflows in Transforms and Data Frame Analytics wizards ({kibana-pull}171202[#171202]). -* The index lifecycle summary on the Index lifecycle page is now displayed in a separate tab ({kibana-pull}170726[#170726]). -* Adds the ability to view mappings conflicts in data views on the data view management page ({kibana-pull}169381[#169381]). -* Implements index overview cards ({kibana-pull}168153[#168153]). -Observability:: -* Reset UI for updating outdated SLOs ({kibana-pull}172883[#172883]). -* Adds timeslice metric indicator for SLOs ({kibana-pull}168539[#168539]). -* Adds logic to update flyout highlights ({kibana-pull}172193[#172193]). -* Sets budget consumed mode as the default mode for burn rate rule configuration ({kibana-pull}171433[#171433]). -* Allow users to define burn rate windows using budget consumed ({kibana-pull}170996[#170996]). -* Makes rules created in Discover visible in Observability ({kibana-pull}171364[#171364]). -* Adds support for document count to custom metric indicator ({kibana-pull}170913[#170913]). -* Improves displaying inline frames ({kibana-pull}169212[#169212]). -* Adds summary insight to the Differential flamegraph ({kibana-pull}168978[#168978]). -* Include `search-*` when recalling documents ({kibana-pull}173710[#173710]). -Platform:: -* Limits `elasticsearch.maxSockets` to 800 by default ({kibana-pull}151911[#151911]). -Presentation:: -* Adds popover message in the control title ({kibana-pull}172094[#172094]). -* Displays incomplete results warning in layer legend ({kibana-pull}171144[#171144]). -* Updates incomplete data messaging ({kibana-pull}169578[#169578]). -Reporting:: -* Makes searches used for CSV export inspectable ({kibana-pull}171248[#171248]). -* Adds `max_concurrent_shards` setting to schema for the point in time CSV report generation ({kibana-pull}170344[#170344]). -Security:: -* The default value of the `elasticsearch.requestHeadersWhitelist` configuration option has been expanded to include the `es-client-authentication` HTTP header, in addition to `authorization` ({kibana-pull}172444[#172444]). -* Adds risk engine missing privileges callout ({kibana-pull}171250[#171250]). -* Implements Asset Criticality Create, Read & Delete APIs ({kibana-pull}172073[#172073]). - -[float] -[[fixes-v8.12.0]] -=== Bug Fixes -Alerting:: -* Fixes the alert details page search bar not considering Query configurations in the {kib} advanced settings ({kibana-pull}172498[#172498]). -* Fixes adding evaluation threshold to alert payload for ES query rule ({kibana-pull}171571[#171571]). -* Hides the Logs tab in Rules page to unauthorized users ({kibana-pull}171417[#171417]). -* Fixes hyperlinks in Slack messages being broken when there is "_" or "*" in the URL ({kibana-pull}170067[#170067]). -APM:: -* Removes usage of internal client when fetching agent configuration etags metrics ({kibana-pull}173001[#173001]). -* Fixes encoding custom links values ({kibana-pull}171032[#171032]). -* Fixes an issue where data views were previously not space aware ({kibana-pull}170857[#170857]). -* Fixes issue with onboarding page around java agent ({kibana-pull}168816[#168816]). -* Adds a data tier filter to the `/has_data` API ({kibana-pull}173382[#173382]). -Cases:: -* Fixes a bug that prevented users with read permission from being assigned to cases ({kibana-pull}172047[#172047]). -Dashboard:: -* Prevents unnecessary loss of dashboard unsaved state ({kibana-pull}167707[#167707]). -Discover:: -* Fixes escaping column names when copying ({kibana-pull}170997[#170997]). -* Discover sharing links now preserve customized column widths ({kibana-pull}172405[#172405]). -* Fixes displaying the columns as they are returned from the query ({kibana-pull}171874[#171874]). -* Fixes issue with `defaultColumns` when changing data views ({kibana-pull}168994[#168994]). -Elastic Security:: -For the Elastic Security 8.12.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Allows agent upgrades if patch version is higher than {kib} ({kibana-pull}173167[#173167]). -* Fixes secrets with dot-separated variable names ({kibana-pull}173115[#173115]). -* Fixes endpoint privilege management endpoints return errors ({kibana-pull}171722[#171722]). -* Fixes expiration time for immediate bulk upgrades being too short ({kibana-pull}170879[#170879]). -* Fixes incorrect overwrite of `logs-*` and `metrics-*` data views on every integration install ({kibana-pull}170188[#170188]). -* Creates intermediate objects when using dynamic mappings ({kibana-pull}169981[#169981]). -Lens & Visualizations:: -* Fixes the sorting of null values so they are displayed last ({kibana-pull}172691[#172691]). -* Fixes the overwriting of chart descriptions after editing a visualization in *Lens* ({kibana-pull}172653[#172653]). -* Fixes an issue where the timerange panel wasn't correctly assigned during a conversion from dashboard to *Lens* ({kibana-pull}172647[#172647]). -* Various fixes for heatmap in *Lens* ({kibana-pull}172602[#172602]). -* Fixes filters being lost when navigating from dashboard -> editor -> *Lens* in *TSVB* ({kibana-pull}172566[#172566]). -* Ignore drop ES|QL commands for date histogram in discover ({kibana-pull}171769[#171769]). -Machine Learning:: -* Ensures data frame analytics job can be deleted from analytics map ({kibana-pull}174212[#174212]). -* Fixes filter for boolean fields filtering for numbers in Field statistics / Data Visualizer ({kibana-pull}174212[#174050]) -* Fixes registering of the ML alerting rules with the basic license ({kibana-pull}173644[#173644]). -* Fixes display of actions column in the datafeed chart flyout ({kibana-pull}173365[#173365]). -* Fixes View in Discover option in Anomaly explorer not handling multiple field values or values with quotation marks ({kibana-pull}172897[#172897]). -* Fixes field stats in Discover showing 0 sample count at times when switching data views ({kibana-pull}172734[#172734]). -* Fixes long field names overflowing in Anomaly detection wizard detector selection ({kibana-pull}172715[#172715]). -* Fixes data drift numeric fields not showing correctly ({kibana-pull}172504[#172504]). -* Fixes Data Visualizer / ML field stats and Data Frame Analytics to exclude _tier fields ({kibana-pull}172223[#172223]). -* Uses standard analyzer in log pattern analysis to ensure filter in Discover matches correct documents ({kibana-pull}172188[#172188]). -* Fixes ML node check and checks user privileges to create job button in dashboard ({kibana-pull}172022[#172022]). -* Fixes {kib} object list in new job from recognized index page ({kibana-pull}171935[#171935]). -Management:: -* Fixes retention policy field name not setting by default correctly in the Transform creation wizard ({kibana-pull}172609[#172609]). -Metrics:: -* Moves formulas and dashboard config to inventory models ({kibana-pull}171872[#171872]). -Platform:: -* Fixes a bug that could cause the `rollingFile` log appender to not properly rotate files on DST switch days ({kibana-pull}173811[#173811]). -* Fixes context formula functions ({kibana-pull}172710[#172710]). -Observability:: -* Removes legacy screenshot image data from the codepath in Synthetics ({kibana-pull}172684[#172684]). -* Fixes incorrect rule parameters when changing aggregation type using a custom equation ({kibana-pull}171958[#171958]). -* Adds parent link in host detail's breadcrumb ({kibana-pull}170792[#170792]). -Presentation:: -* Fixes validation query for nested fields ({kibana-pull}173690[#173690]). -* Fixes user privileges around Links panels saved to the library ({kibana-pull}173332[#173332]). -* Prevents overflowing dashboard title on saved toast notifications ({kibana-pull}172620[#172620]). -* Ignore indices without geometry field in vector tile requests ({kibana-pull}171472[#171472]). -* Fixes layer displaying no data instead of error ({kibana-pull}170084[#170084]). - -[[release-notes-8.11.4]] -== {kib} 8.11.4 - -[float] -[[fixes-v8.11.4]] -=== Bug fixes and enhancements -There are no user-facing changes in the 8.11.4 release. - -[[release-notes-8.11.3]] -== {kib} 8.11.3 - -The 8.11.3 release includes the following bug fixes. - -[float] -[[fixes-v8.11.3]] -=== Bug Fixes -Elastic Security:: -For the Elastic Security 8.11.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Fixes a 500 error in the Fleet API when a request for the product versions endpoint throws `ECONNREFUSED` ({kibana-pull}172850[#172850]). -* Fixes agent policy timeout to accept only integers ({kibana-pull}172222[#172222]). -Machine Learning:: -* Fixes data drift numeric fields not displaying correctly ({kibana-pull}172504[#172504]). -* Fixes Data visualizer, ML field stats, and Data Frame Analytics so the `_tier` field can be excluded ({kibana-pull}172223[#172223]). -Operations:: -* Fixes an issue where running `kibana-keystore` commands required `kibana.yml` to exist ({kibana-pull}172943[#172943]). - -[[release-notes-8.11.2]] -== {kib} 8.11.2 - -The 8.11.2 release includes the following bug fixes. - -[float] -[[security-update-v8.11.2]] -=== Security updates - -* The 8.11.2 patch release contains a fix for a potential security vulnerability. https://discuss.elastic.co/c/announcements/security-announcements/31[Please see our security advisory for more details]. - -[float] -[[enhancement-v8.11.2]] -=== Enhancements -APM:: -* Added `context_propagation_only` APM agent setting ({kibana-pull}170405[#170405]). -Elastic Security:: -For the Elastic Security 8.11.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Improve UX for policy secrets ({kibana-pull}171405[#171405]). -Observability:: -* Adds `date_formats` to SLI ingest pipeline template ({kibana-pull}172377[#172377]). -Platform:: -* It is now possible to hot reload Kibana's TLS (`server.ssl`) configuration by updating it and then sending a `SIGHUP` signal to the Kibana process ({kibana-pull}171823[#171823]). - -[float] -[[fixes-v8.11.2]] -=== Bug Fixes -Dashboard:: -* Fixes reference extract method ({kibana-pull}171360[#171360]). -* Adds Dashboard title to browser tab title ({kibana-pull}171255[#171255]). -Elastic Security:: -For the Elastic Security 8.11.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Support integration secrets in a local package registry with variables `secret: true` and `required: false` ({kibana-pull}172078[#172078]). -* Fixes agents metrics retrieval on the agent list page, previously displaying N/A for metrics for users with more than 10 agents. ({kibana-pull}172016[#172016]). -* Only add `time_series_metric` if TSDB is enabled ({kibana-pull}171712[#171712]). -* Fixes inability to upgrade agents from version 8.10.4 to version 8.11 ({kibana-pull}170974[#170974]). -Lens & Visualizations:: -* Handle invalid values gracefully for static value operation in *Lens* ({kibana-pull}172198[#172198]). -* Make the dashboard SO lighter ({kibana-pull}172130[#172130]). -Machine Learning:: -* Fixes blocked jobs polling interval ({kibana-pull}171878[#171878]). -Management:: -* Fixes autocomplete to show suggestions even if user types in every letter ({kibana-pull}171952[#171952]). -* Fixes wrong autocomplete suggestions when using the slash symbol ({kibana-pull}171948[#171948]). -* Fixes clusters and shards table expanded row not updating when request is changed ({kibana-pull}171232[#171232]). -Maps:: -* Fixes using `max_result_window` to set up a mapbox vector tile (MVT) size request leading to all results not showing ({kibana-pull}171344[#171344]). -Observability:: -* Fixes the custom threshold document link ({kibana-pull}171125[#171125]). -SharedUX:: -* Fixes custom branding for users without "Saved Object Management" privilege ({kibana-pull}171308[#171308]). -* Fixes the custom threshold document link ({kibana-pull}171125[#171125]). -Uptime:: -* Fixes advanced fields broken for ICMP monitors ({kibana-pull}171161[#171161]). -Ê -[[release-notes-8.11.1]] -== {kib} 8.11.1 - -For information about the {kib} 8.11.1 release, review the following information. - -[float] -[[security-update-v8.11.1]] -=== Security updates - -* An issue was discovered by Elastic whereby sensitive information is recorded -in {kib} logs in the event of an error. The error message recorded in the log -may contain account credentials for the `kibana_system` user, API Keys, and -credentials of {kib} end users. -+ --- -The issue impacts {kib} {kib} versions on or after 8.0.0 and before 8.11.1. The -issue is resolved in {kib} 8.11.1. - -**Nov 15, 2023 Update:** After additional investigation, it has been determined -that {kib} 7.x versions are not affected by this issue. We previously reported -this issue impacted {kib} versions before 7.17.15. - -For more information, see our related -https://discuss.elastic.co/t/8-11-1-7-17-15-security-update-esa-2023-25/347149[security -announcement]. --- - -[float] -[[fixes-v8.11.1]] -=== Bug Fixes - -Fleet:: -* Append space ID to security solution tag ({kibana-pull}170789[#170789]). -* Modify bulk unenroll to include inactive agents ({kibana-pull}170249[#170249]). -Lens & Visualizations:: -* Fixes error handling for ES|QL nested error messages ({kibana-pull}170005[#170005]). -Machine Learning:: -* Disable anomaly detection job creation from ES|QL lens visualizations ({kibana-pull}170711[#170711]). -Presentation:: -* Fixes vector tile layer with joins stuck in loading state when not visible ({kibana-pull}170984[#170984]). -* Fixes expand layer control is not clickable when layers are loading ({kibana-pull}170912[#170912]). - -[[release-notes-8.11.0]] -== {kib} 8.11.0 - - -For information about the {kib} 8.11.0 release, review the following information. - - -[float] -[[known-issues-8.11.0]] -=== Known issues - -// tag::known-issue-169170[] -[discrete] -.Gatekeeper error on macOS -[%collapsible] -==== -*Details* + -Due to a version upgrade of the server binary used by {kib} and an upstream notarization issue, a Gatekeeper error may display for "node" -when starting {kib} in macOS environments. - -*Workaround* + -More information can be found at <>. - -==== -// end::known-issue-169170[] - -[float] -[[breaking-changes-8.11.0]] -=== Breaking changes - - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.11.0, review the breaking changes, then mitigate the impact to your application. - - -[discrete] -.Improve config output validation for default output. -[%collapsible] -==== -*Details* + -Improve config output validation to not allow to defining multiple default outputs in {kib} configuration. For more information, refer to ({kibana-pull}167085[#167085]). -==== - -[discrete] -.Convert filterQuery to KQL. -[%collapsible] -==== -*Details* + -Converts `filterQuery` to a KQL query string. For more information, refer to ({kibana-pull}161806[#161806]). -==== - -[float] -[[deprecations-8.11.0]] -=== Deprecations - - -The following functionality is deprecated in 8.11.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.11.0. - - -[discrete] -[[deprecation-164651]] -.Updates to move from doc_root.vulnerability.package -> doc_root.package (ECS). -[%collapsible] -==== -*Details* + -This updates all instances of vulnerability.package to the ECS standard package fieldset. For more information, refer to ({kibana-pull}164651[#164651]). -==== -[float] -[[features-8.11.0]] -=== Features -{kib} 8.11.0 adds the following new and notable features. - - -Alerting:: -* Adds support for the new ES|QL language for {es} query rules ({kibana-pull}165973[#165973]). -* Elasticsearch query rule can select multiple group-by terms ({kibana-pull}166146[#166146]). -* Adds a Log tab to the Observability Rules page ({kibana-pull}165115[#165115]). -APM:: -* Adds bulk action to untrack selected alerts ({kibana-pull}167579[#167579]). -* Introduce custom dashboards tab in service overview ({kibana-pull}166789[#166789]). -* Adds service profiling Top 10 Functions ({kibana-pull}166226[#166226]). -* Adds service profiling flamegraph ({kibana-pull}165360[#165360]). -Cases:: -* Adds custom fields in Cases ({kibana-pull}167016[#167016]). -Dashboard:: -* Copy panel refactor ({kibana-pull}166991[#166991]). -* Make links panel available under technical preview ({kibana-pull}166896[#166896]). -* Store view mode in local storage ({kibana-pull}166523[#166523]). -* Adds a read only state for Managed Dashboards ({kibana-pull}166204[#166204]). -Discover:: -* Adds resize support to the Discover field list sidebar ({kibana-pull}167066[#167066]). -Elastic Security:: -For the Elastic Security 8.11.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Enterprise Search service:: -For the Elastic Enterprise Search service 8.11.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Release notes_]. -Fleet:: -* Set env variable `ELASTIC_NETINFO:false` in {kib} ({kibana-pull}166156[#166156]). -* Added restart upgrade action ({kibana-pull}166154[#166154]). -* Adds ability to set a proxy for agent binary source ({kibana-pull}164168[#164168]). -* Adds ability to set a proxy for agent download source ({kibana-pull}164078[#164078]). -Lens & Visualizations:: -* Adds color mapping for categorical dimensions in *Lens* available under technical preview ({kibana-pull}162389[#162389]). -* Inline editing of **Lens** panels on a dashboard or canvas ({kibana-pull}166169[#166169]). -* Individual annotation editing from library ({kibana-pull}163346[#163346]). -Logs:: -* Convert log explorer profile into standalone app available under technical preview ({kibana-pull}164493[#164493]). -Machine Learning:: -* Adds support for the ELSER v2 download in the Trained Models UI ({kibana-pull}167407[#167407]). -* Adds data drift detection workflow from Trained Models to Data comparison view ({kibana-pull}162853[#162853]). -Management:: -* Supports for viewing and editing data retention per data stream in Index Management is available under technical preview ({kibana-pull}167006[#167006]). -* Supports for viewing and editing data retention per data stream in Index Management is available under technical preview ({kibana-pull}167006[#167006]). -* Index details can now be viewed on a new index details page in Index Management ({kibana-pull}165705[#165705]). -* Supports for managing, executing, and deleting enrich policies in Index Management ({kibana-pull}164080[#164080]). -Platform:: -* ES|QL, a new query language, is available under technical preview in Discover and Dashboards ({kibana-pull}146971[#146971]). -Querying & Filtering:: -* Saved queries can now be shared between multiple spaces ({kibana-pull}163436[#163436]). -Uptime:: -* Adds a document viewer to the summary pings table ({kibana-pull}163926[#163926]). - - -For more information about the features introduced in 8.11.0, refer to <>. - - -[[enhancements-and-bug-fixes-v8.11.0]] -=== Enhancements and bug fixes - - -For detailed information about the 8.11.0 release, review the enhancements and bug fixes. - - -[float] -[[enhancement-v8.11.0]] -=== Enhancements -APM:: -* Changed mobile badge from 'technical preview' to 'beta' ({kibana-pull}167543[#167543]). -* New Profiling ES Flamegraph API ({kibana-pull}167477[#167477]). -* Adds Universal Profiling to O11y overview and Setup guide ({kibana-pull}165092[#165092]). -* Mark disabled alerts as Untracked in both Stack Management and o11y ({kibana-pull}164788[#164788]). -* Adds time range to event metadata API ({kibana-pull}167132[#167132]). -* New settings to control CO2 calculation ({kibana-pull}166637[#166637]). -* Adds permissions for "input-only" package ({kibana-pull}166234[#166234]). -* Adds selecting the consumer based on the authorized consumers when a user is creating an ES Query threshold rule ({kibana-pull}166032[#166032]). -* Migrate Ace based `EuiCodeEditor` to Monaco based code editor ({kibana-pull}165951[#165951]). -* Mobile UI crash widget added ({kibana-pull}163527[#163527]). -Cases:: -* Show a warning message to inform user that navigating after the 10Kth case is not possible ({kibana-pull}164323[#164323]). -Dashboard:: -* Focus on a single panel while disabling all other panels ({kibana-pull}165417[#165417]). -* Adds filter details to panel settings ({kibana-pull}162913[#162913]). -* Adds support for date fields in the options list controls ({kibana-pull}164362[#164362]). -Discover:: -* Redesign for the grid, panels and sidebar ({kibana-pull}165866[#165866]). -* Set data table row height to auto-fit by default ({kibana-pull}164218[#164218]). -* Allow fetching more documents on Discover page ({kibana-pull}163784[#163784]). -Elastic Security:: -For the Elastic Security 8.11.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Search:: -* Self-managed connector clients now show advanced configuration options in the UI ({kibana-pull}167770[#167770]). -Fleet:: -* Adds sidebar navigation showing headings extracted from the readme ({kibana-pull}167216[#167216]). -Inspector:: -* Clusters tab added under Inspector ({kibana-pull}166025[#166025]). -* Open incomplete response warning in Inspector ({kibana-pull}167205[#167205]). -Lens & Visualizations:: -* Other bucket defaults to false for top values greater than equal 1000 in *Lens* ({kibana-pull}167141[#167141]). -* Adds support for decimals in percentiles in *Lens* ({kibana-pull}165703[#165703]). -Machine Learning:: -* Updates ELSER version for Elastic Assistant ({kibana-pull}167522[#167522]). -* Retains `created_by` setting when exporting anomaly detection jobs ({kibana-pull}167319[#167319]). -* Improves the wording of awaiting ML nodes messages ({kibana-pull}167306[#167306]). -* Adds `created_by` job property for the advanced wizard ({kibana-pull}167021[#167021]). -* Trained model testing: only show indices with supported fields ({kibana-pull}166490[#166490]). -* Alerts as data integration for Anomaly Detection rule type ({kibana-pull}166349[#166349]). -* Data Frame Analytics Trained models: adds the ability to reindex after pipeline creation ({kibana-pull}166312[#166312]). -* Adds Create a data view button to index or saved search selector in ML pages and Transforms management ({kibana-pull}166668[#166668]). -* Improvements to UX of adding ML embeddables to a dashboard ({kibana-pull}165714[#165714]). -* AIOps: Supports text fields in log rate analysis ({kibana-pull}165124[#165124]). -* Data Frame Analytics creation wizard: adds ability to add custom URLs to jobs ({kibana-pull}164520[#164520]). -Management:: -* Adds Create a data view button to index or saved search selector in ML pages and Transforms management ({kibana-pull}166668[#166668]). -* Improve loading behavior of Transforms list if stats request is slow or is not available ({kibana-pull}166320[#166320]). -* Adds support for PATCH requests in Console ({kibana-pull}165634[#165634]). -* Improves autocomplete to suggest knn in search query ({kibana-pull}165531[#165531]). -* Improves display for long descriptions in Transforms ({kibana-pull}165149[#165149]). -* Improve transform list reloading behavior ({kibana-pull}164296[#164296]). -Maps:: -* Allow by value styling for EMS boundary fields ({kibana-pull}166306[#166306]). -* Adds support for `geo_shape` fields as the entity geospatial field when creating tracking containment alerts ({kibana-pull}164100[#164100]). -Observability:: -* ES|QL query generation ({kibana-pull}166041[#166041]). -Querying & Filtering:: -* New "Saved Query Management" privilege to allow saving queries across Kibana ({kibana-pull}166937[#166937]). -* Improvements to the filter builder inputs for long fields ({kibana-pull}166024[#166024]). -Uptime:: -* Added ability to hide public locations ({kibana-pull}164863[#164863]). - - -[float] -[[fixes-v8.11.0]] -=== Bug Fixes -Alerting:: -* Improve error handling in ES Index action response ({kibana-pull}164841[#164841]). -* Bring back toggle column on alert table ({kibana-pull}168158[#168158]). -* Fixes Errors rules link on observability alert page ({kibana-pull}167027[#167027]). -* Enable read-only users to access rules ({kibana-pull}167003[#167003]). -* Fixes rule snooze toast copy ({kibana-pull}166030[#166030]). -APM:: -* Ensure APM data view is available across all spaces ({kibana-pull}167704[#167704]). -* Adds an environment param to the service metadata details endpoint ({kibana-pull}167173[#167173]). -* Fixes set up process ({kibana-pull}167067[#167067]). -Dashboard:: -* Generate new panel IDs on Dashboard clone ({kibana-pull}166299[#166299]). -Elastic Security:: -For the Elastic Security 8.11.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Search:: -* Native connector external documentation links are now rendered conditionally to avoid empty links ({kibana-pull}169121[#169121]). -* Fixed an issue which caused Access Control Syncs to be scheduled when Document Level Security was disabled ({kibana-pull}168987[#168987]). -* Restored access and admin checks for App Search and Workplace Search product cards ({kibana-pull}168890[#168890]). -* The filter box in the *Browse documents* tab under *Search > Content > Indices* now escapes Lucene reserved characters instead of throwing errors ({kibana-pull}168092[#168092]). -* Fixed an issue associated with changing the indices underlying a search application. When a user modifies the indices underlying a search application in Kibana, the associated search template is now reverted to the default template ({kibana-pull}167532[#167532]). -* Fixed an issue where the Search plugin was inaccessible for unauthenticated users, eg. for Kibana in read-only demo setups ({kibana-pull}167171[#167171]). -* Fixed an issue with the welcome banner in Search ({kibana-pull}166814[#166814]). -* Self managed connector clients now show advanced configuration options in the UI ({kibana-pull}167770[#167770]). -Fleet:: -* Vastly improve performance of Fleet final pipeline's date formatting logic for `event.ingested` ({kibana-pull}167318[#167318]). -Lens & Visualizations:: -* Fixes heatmap color assignment on single value scenario in *Lens* ({kibana-pull}167995[#167995]). -* Fixes mosaic with 2 axis coloring in *Lens* ({kibana-pull}167035[#167035]). -* Show icons/titles instead of previews in suggestions panel in *Lens* ({kibana-pull}166808[#166808]). -* Consider root level filters buckets correctly when building other terms bucket ({kibana-pull}165656[#165656]). -* Prevent user to use decimals for custom Percentile rank function in Top values in *Lens* ({kibana-pull}165616[#165616]). -* Fixes the Graph application settings tab when in dark mode ({kibana-pull}165614[#165614]). -* Fixes Visualize List search and CRUD operations via content management ({kibana-pull}165485[#165485]). -Logs:: -* Use correct ML API to query blocking tasks ({kibana-pull}167779[#167779]). -Machine Learning:: -* AIOps: Fixes log pattern analysis sparklines and chart ({kibana-pull}168337[#168337]). -* AIOps: Fixes Data View runtime fields support in the Change point detection UI ({kibana-pull}168249[#168249]). -* Fixes anomaly charts when partition field contains an empty string ({kibana-pull}168102[#168102]). -* Data Frame analytics outlier detection results: ensure scatterplot matrix adheres to bounding box ({kibana-pull}167941[#167941]). -* Fixes Anomaly charts embeddable fails to load if partition value is empty string ({kibana-pull}167827[#167827]). -Management:: -* Fixes `isErrorResponse` when cluster details are provided ({kibana-pull}166667[#166667]). -* Fixes autocomplete not to be prompted between triple quotes ({kibana-pull}165535[#165535]). -* Fixes autocomplete on only 1 letter typed in Console's request editor ({kibana-pull}164707[#164707]). -* Fixing duration field formatter showing 0 seconds instead of "few seconds" ({kibana-pull}164659[#164659]). -* Fixes a bug that autocomplete does not work right after a comma ({kibana-pull}164608[#164608]). -* Fixes unnecessary autocompletes on HTTP methods ({kibana-pull}163233[#163233]). -Presentation:: -* Fixes ES query rule boundary field changed when editing the rule ({kibana-pull}165155[#165155]). - -[[release-notes-8.10.4]] -== {kib} 8.10.4 - -The 8.10.4 release includes the following bug fixes. - -[float] -[[fixes-v8.10.4]] -=== Bug Fixes -Elastic Security:: -For the Elastic Security 8.10.4 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Fixes validation errors in KQL queries ({kibana-pull}168329[#168329]). - -[[release-notes-8.10.3]] -== {kib} 8.10.3 - -[float] -[[security-update-8.10.3]] -=== Security updates - -* **Kibana heap buffer overflow vulnerability** -+ -On Sept 11, 2023, Google Chrome announced CVE-2023-4863, described as “Heap buffer overflow in libwebp in Google Chrome prior to 116.0.5845.187 and libwebp 1.3.2 allowed a remote attacker to perform an out of bounds memory write via a crafted HTML page”. Kibana includes a bundled version of headless Chromium that is only used for Kibana’s reporting capabilities and which is affected by this vulnerability. An exploit for Kibana has not been identified, however as a resolution, the bundled version of Chromium is updated in this release. -+ -The issue is resolved in 8.10.3. -+ -For more information, see our related -https://discuss.elastic.co/t/kibana-8-10-3-7-17-14-security-update/344735[security -announcement]. - -[float] -[[enhancement-v8.10.3]] -=== Enhancements -Elastic Security:: -For the Elastic Security 8.10.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -[float] -[[fixes-v8.10.3]] -=== Bug Fixes -Dashboard:: -* Fixes an error the panel descriptions weren't retrieved from the right method ({kibana-pull}166825[#166825]). -Discover:: -* Soften saved search content management response `sort` schema ({kibana-pull}166886[#166886]). -Elastic Security:: -For the Elastic Security 8.10.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Enterprise Search:: -For the Elastic Enterprise Search 8.10.3 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. -Fleet:: -* Fixes incorrect index template used from the data stream name ({kibana-pull}166941[#166941]). -* Increase package install max timeout limit and add concurrency control to rollovers ({kibana-pull}166775[#166775]). -* Fixes bulk action dropdown ({kibana-pull}166475[#166475]). -Machine Learning:: -* AIOps: Fixes render loop when using a saved search ({kibana-pull}166934[#166934]). -Monitoring:: -* Convert node roles into array ({kibana-pull}167628[#167628]). -Observability:: -* Fixes a set up process error in Universal Profiling ({kibana-pull}167068[#167068]). -Uptime:: -* Fixes an error when updating browser monitor in a project ({kibana-pull}168064[#168064]). - -[[release-notes-8.10.2]] -== {kib} 8.10.2 - -The 8.10.2 release includes the following bug fixes. - -[float] -[[fixes-v8.10.2]] -=== Bug fixes - -Fleet:: -* Fixes force delete package, updated used by agents check ({kibana-pull}166623[#166623]). -Management:: -* Fixes showing `Received partial message` instead of results when there are some remote shard errors in a {ccs} ({kibana-pull}166544[#166544]). - -[[release-notes-8.10.1]] -== {kib} 8.10.1 - -The 8.10.1 release includes the following bug fixes. - -[float] -[[fixes-v8.10.1]] -=== Bug fixes - -Dashboard:: -* Fixes content editor flyout footer ({kibana-pull}165907[#165907]). -Elastic Security:: -For the Elastic Security 8.10.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Show snapshot version in agent upgrade modal and allow custom values ({kibana-pull}165978[#165978]). -Observability:: -* Fix(slo): Use comma-separarted list of source index for transform ({kibana-pull}166294[#166294]). -Presentation:: -* Fixes air-gapped enviroment hitting `400` error when loading fonts for layer ({kibana-pull}165986[#165986]). - -[[release-notes-8.10.0]] -== {kib} 8.10.0 - -IMPORTANT: {kib} 8.10.0 has been withdrawn. - -For information about the {kib} 8.10.0 release, review the following information. - -[float] -[[security-updates-v8.10.0]] -=== Security updates - -* An issue was discovered by Elastic whereby sensitive information is recorded -in {kib} logs in the event of an error. The issue impacts only {kib} version -8.10.0 when logging in the JSON layout or when the pattern layout is configured -to log the `%meta` pattern. -+ -The issue is resolved in {kib} 8.10.1. Version 8.10.0 has been removed from our -download sites. -+ -For more information, see our related -https://discuss.elastic.co/t/kibana-8-10-1-security-update/343287[security -announcement]. - -[float] -[[breaking-changes-8.10.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.10.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.New summary search capabilities cause existing SLOs to stop working -[%collapsible] -==== -*Details* + -New summary search capabilities introduce breaking changes in various places, and we have decided not to handle backward compatibility: - -* SLO find API body parameters have changed. -* The index mapping used by the rollup data has changed, and we have added a summary index that becomes the new source of truth for search. -* The rollup transforms have been updated, but existing SLO with their transforms won't be updated. - -If some SLOs have been installed in a prior version at 8.10, they won't work after migrating to 8.10. There are two approaches to handle this breaking change. The recommended route is to delete all SLOs before migrating to 8.10. The alternative is to migrate to 8.10 and manually remove the SLOs. - -*Removing SLOs before migrating to 8.10* - -Use the SLO UI or the SLO delete API to delete all existing SLOs. This takes care of the Saved Object, Transform and rollup data. When all SLOs have been deleted, then delete the residual rollup indices: `.slo-observability.sli-v1*`. Note that this is v1. - -*Removing SLOs after migrating to 8.10* - -After migrating to 8.10, the previously created SLOs won’t appear in the UI because the API is using a new index. The previously created SLOs still exist, and associated transforms are still rolling up data into the previous index `.slo-observability.sli-v1*`. The SLO delete API can't be used now, so remove the resources resources manually: - -. Find all existing transforms -All SLO related transforms start with the `slo-` prefix, this request returns them all: -+ -[source, bash] ----- -GET _transform/slo-* ----- -+ -Make a note of all the transforms IDs for later. - -. Stop all transforms -+ -[source, bash] ----- -POST _transform/slo-*/_stop?force=true ----- - -. Remove all transforms -+ -From the list of transforms returned during the first step, now delete them one by one: -+ -[source, bash] ----- -DELETE _transform/{transform_id}?force=true ----- - -. Find the SLO saved objects -+ -This request lists all the SLO saved objects. The SLO IDs and the saved object IDs are not the same. -+ -[source, bash] ----- -GET kbn:/api/saved_objects/_find?type=slo ----- -+ -Make a note of all the saved object IDs from the response. - -. Remove the SLO saved objects -+ -For each saved object ID, run the following: -+ -[source, bash] ----- -DELETE kbn:/api/saved_objects/slo/{Saved_Object_Id} ----- - -. Delete the rollup indices v1 -+ -Note that this is v1. -+ -[source, bash] ----- -DELETE .slo-observability.sli-v1* ----- -==== - -[discrete] -.Get case metrics APIs now internal -[%collapsible] -==== -*Details* + -The get case metrics APIs are now internal. For more information, refer to ({kibana-pull}162506[#162506]). -==== - -[discrete] -.Case limits -[%collapsible] -==== -*Details* + -Limits are now imposed on the number of objects cases can process or the amount of data those objects can store. -//// -For example: -* Updating a case comment is now included in the 10000 user actions restriction. ({kibana-pull}163150[#163150]) -* Updating a case now fails if the operation makes it reach more than 10000 user actions. ({kibana-pull}161848[#161848]) -* The total number of characters per comment is limited to 30000. ({kibana-pull}161357[#161357]) -* The getConnectors API now limits the number of supported connectors returned to 1000. ({kibana-pull}161282[#161282]) -* There are new limits and restrictions when retrieving cases. ({kibana-pull}162411[#162411]), ({kibana-pull}162245[#162245]), ({kibana-pull}161111[#161111]), ({kibana-pull}160705[#160705]) -* A case can now only have 100 external references and persistable state(excluding files) attachments combined. ({kibana-pull}162071[#162071]). -* New limits on titles, descriptions, tags and category. ({kibana-pull}160844[#160844]). -* The maximum number of cases that can be updated simultaneously is now 100. The minimum is 1. ({kibana-pull}161076[#161076]). -* The Delete cases API now limits the number of cases to be deleted to 100.({kibana-pull}160846[#160846]). -//// -For the full list, refer to {kib-issue}146945[#146945]. -==== - -[discrete] -.`addProcessorDefinition` is removed -[%collapsible] -==== -*Details* + -The function `addProcessorDefinition` is removed from the Console plugin start contract (server side). For more information, refer to ({kibana-pull}159041[#159041]). -==== - -[discrete] -.The Download CSV endpoint has changed. -[%collapsible] -==== -*Details* + -The API endpoint for downloading a CSV file from a saved search in the Dashboard application has changed to reflect the fact that this is an internal API. The previous API path of -`/api/reporting/v1/generate/immediate/csv_searchsource` has been changed to `/internal/reporting/generate/immediate/csv_searchsource`. For more information, refer to {kibana-pull}162288[#162288]. -==== - -[float] -[[deprecations-8.10.0]] -=== Deprecations - -The following functionality is deprecated in 8.10.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.10.0. - -[discrete] -[[deprecation-161136]] -.Action variables in the UI and in tests that were no longer used have been replaced -[%collapsible] -==== -*Details* + -The following rule action variables have been deprecated; use the recommended variables (in parentheses) instead: - -* alertActionGroup (alert.actionGroup) -* alertActionGroupName (alert.actionGroupName) -* alertActionSubgroup (alert.actionSubgroup) -* alertId (rule.id) -* alertInstanceId (alert.id) -* alertName (rule.name) -* params (rule.params) -* spaceId (rule.spaceId) -* tags (rule.tags) - -For more information, refer to ({kibana-pull}161136[#161136]). -==== - -[float] -[[features-8.10.0]] -=== Features -{kib} 8.10.0 adds the following new and notable features. - -Alerting:: -* Adds support for self-signed SSL certificate authentication in webhook connector ({kibana-pull}161894[#161894]). -* Allow runtime fields to be selected for {es} query rule type 'group by' or 'aggregate over' options ({kibana-pull}160319[#160319]). -APM:: -* Adds KQL filtering in APM rules ({kibana-pull}163825[#163825]). -* Make service group saved objects exportable ({kibana-pull}163569[#163569]). -* Added ability to manage cross-cluster API keys ({kibana-pull}162363[#162363]). -* Enable Trace Explorer by default ({kibana-pull}162308[#162308]). -* Adds error.grouping_name to group alerts in Error Count rule ({kibana-pull}161810[#161810]). -* Adds query to check for overflow bucket in service groups ({kibana-pull}159990[#159990]). -Elastic Security:: -For the Elastic Security 8.10.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Enterprise Search:: -For the Elastic Enterprise Search 8.10.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. -Fleet:: -* Only enable secret storage once all fleet servers are above 8.10.0 ({kibana-pull}163627[#163627]). -* Kafka integration API ({kibana-pull}159110[#159110]). -Machine Learning:: -* AIOps: Adds/edits change point charts embeddable from the Dashboard app ({kibana-pull}163694[#163694]). -* AIOps: Adds change point detection charts embeddable ({kibana-pull}162796[#162796]). -* Adds ability to deploy trained models for data frame analytics jobs ({kibana-pull}162537[#162537]). -* Adds map view for models in Trained Models and expands support for models in Analytics map ({kibana-pull}162443[#162443]). -* Adds new Data comparison view ({kibana-pull}161365[#161365]). -Management:: -* Added ability to create a remote cluster with the API key based security model ({kibana-pull}161836[#161836]). -* REST endpoint for swapping saved object references ({kibana-pull}157665[#157665]). -Maps:: -* Maps tracks layer now uses group by time series logic ({kibana-pull}159267[#159267]). -Observability:: -* SLO definition and computed values are now summarized periodically into a summary search index, allowing users to search by name, tags, SLO budgeting type or time window, and even by and sort by error budget consumed, error budget remaining, SLI value or status ({kibana-pull}162665[#162665]). -* Adds indicator to support histogram fields ({kibana-pull}161582[#161582]). - -For more information about the features introduced in 8.10.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.10.0-revised]] -=== Enhancements and bug fixes - -For detailed information about the 8.10.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.10.0]] -=== Enhancements -Alerting:: -* Fixes the event log data stream to use Data stream lifecycle instead of Index Lifecycle Management. If you had previously customized the Kibana event log ILM policy, you should now update the lifecycle of the event log data stream itself. ({kibana-pull}163210[#163210]). -* KQL search bar for Rules page ({kibana-pull}158106[#158106]). -APM:: -* Lens function ({kibana-pull}163872[#163872]). -* Adds several function implementations to the AI Assistant ({kibana-pull}163764[#163764]). -* Feature controls ({kibana-pull}163232[#163232]). -* Added improved JVM runtime metrics dashboard ({kibana-pull}162460[#162460]). -* Move to new plugin, update design and use connectors ({kibana-pull}162243[#162243]). -* Adding endpoint to upload android map files ({kibana-pull}161252[#161252]). -* Navigate from the transaction details view into the Profiling ({kibana-pull}159686[#159686]). -Dashboard:: -* Change badge color in options list ({kibana-pull}161401[#161401]). -* Edit title, description, and tags from dashboard listing page ({kibana-pull}161399[#161399]). -* Editing toolbar update ({kibana-pull}154966[#154966]). -Discover:: -* Inline shard failures warnings ({kibana-pull}161271[#161271]). -* Improve shard error message formatting ({kibana-pull}161098[#161098]). -Elastic Security:: -For the Elastic Security 8.10.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Enterprise Search:: -For the Elastic Enterprise Search 8.10.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. -Fleet:: -* Adds support for runtime fields ({kibana-pull}161129[#161129]). -Lens & Visualizations:: -* Migrate range slider to `EuiDualRange` and make styling consistent across all controls ({kibana-pull}162651[#162651]). -* Introduce new duration formatter in *Lens* ({kibana-pull}162246[#162246]). -* Create a filter with field:value when last value metric is used on a data table in *Lens* ({kibana-pull}160509[#160509]). -* Adds tooltip for partition and heatmap filtering ({kibana-pull}162716[#162716]). -Machine Learning:: -* Hides paging controls in anomaly swim lane if only one page is available ({kibana-pull}163931[#163931]). -* Adds a throughput description in the Trained Models Deployment stats table ({kibana-pull}163481[#163481]). -* Provides hints for empty fields in dropdown options in Anomaly detection & Transform creation wizards, Change point detection view ({kibana-pull}163371[#163371]). -* AIOps: Adds dip support to log rate analysis in ML AIOps Labs ({kibana-pull}163100[#163100]). -* AIOps: Improves table hovering for log rate analysis ({kibana-pull}162941[#162941]). -* AIOps: Adds dip support for log rate analysis in observability alert details page ({kibana-pull}162476[#162476]). -* Adds validation of field selected for log pattern analysis ({kibana-pull}162319[#162319]). -* AIOps: Renames Explain Log Rate Spikes to Log Rate Analysis ({kibana-pull}161764[#161764]). -* Adds new Data comparison view ({kibana-pull}161365[#161365]). -* Adds test UI for text expansion models ({kibana-pull}159150[#159150]). -* Adds update index mappings step to ML pipeline config workflow ({kibana-pull}163723[#163723]). -Management:: -* Adds multiple formats for geo_point fields and make geo conversion tools part of field_format/common/utils ({kibana-pull}147272[#147272]). -Maps:: -* Support time series split for top hits per entity source ({kibana-pull}161799[#161799]). -Observability:: -* Adds support for group by to SLO burn rate rule ({kibana-pull}163434[#163434]). -* Applying consistent design to Histogram and Custom Metric forms ({kibana-pull}162083[#162083]). -* Adds preview chart to custom metric indicator ({kibana-pull}161597[#161597]). -* Support filters for good/total custom metrics ({kibana-pull}161308[#161308]). -Reporting:: -* Increase max size bytes default to 250mb ({kibana-pull}161318[#161318]). -Security:: -* Change the default value of `session.idleTimeout` from 8 hours to 3 days ({kibana-pull}162313[#162313]). -* User roles displayed on the user profile page ({kibana-pull}161375[#161375]). -Uptime:: -* Implement private location run once ({kibana-pull}162582[#162582]). - -[float] -[[fixes-v8.10.0]] -=== Bug fixes -APM:: -* Fixes styling and port issue with new onboarding ({kibana-pull}163922[#163922]). -* Fixes missing alert index issue ({kibana-pull}163600[#163600]). -* Fixes trace waterfall loading logic to handle empty scenarios ({kibana-pull}163397[#163397]). -* Fixes the action menu overlapping the 'Create custom link' flyout ({kibana-pull}162664[#162664]). -* Improve service groups error messages and validations ({kibana-pull}162556[#162556]). -* Fixes throwing appropriate error when user is missing necessary permission ({kibana-pull}162466[#162466]). -* Hide components when there is no support from agents ({kibana-pull}161970[#161970]). -* Fixes link to onboarding page in the Observability Onboarding plugin ({kibana-pull}161847[#161847]). -Dashboard:: -* Disables top navigation actions when cloning or overlay is showing ({kibana-pull}162091[#162091]). -Discover:: -* Fixes document failing to load when the ID contains a slash ({kibana-pull}160239[#160239]). -* Fixes NoData screen in alignment with Dashboard and Lends behavior ({kibana-pull}160747[#160747]). -Elastic Security:: -For the Elastic Security 8.10.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Enterprise Search:: -For the Elastic Enterprise Search 8.10.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. -Fleet:: -* Only show agent dashboard links if there is more than one non-server agent and if the dashboards exist ({kibana-pull}164469[#164469]). -* Exclude Synthetics from per-policy-outputs ({kibana-pull}161949[#161949]). -* Fixing the path for hint templates for auto-discover ({kibana-pull}161075[#161075]). -Lens & Visualizations:: -* Fixes params._interval conversion to Lens formula ({kibana-pull}164150[#164150]). -* Fixes issues with field name that contains the `:` character in it in *Lens* ({kibana-pull}163626[#163626]). -* Fixes other request merge behavior when empty string key is retrieved ({kibana-pull}163187[#163187]). -* Fixes editing of multi values filters when adding a custom item ({kibana-pull}161940[#161940]). -* Allow setting custom colors to be collapsed by slices pie's multiple metrics in *Lens* ({kibana-pull}160592[#160592]). -* Fixes data table sorting for keyword values in *Lens* ({kibana-pull}160163[#160163]). -* Fixes override parameter when creating data views ({kibana-pull}160953[#160953]). -Logs:: -* Amend lazy imports in logs_shared plugin ({kibana-pull}164102[#164102]). -Machine Learning:: -* Fixes Trained models list crashes on browser refresh if not on page 1 ({kibana-pull}164163[#164163]). -* Fixes query bar not switching from KQL to Lucene and vice versa in Anomaly explorer ({kibana-pull}163625[#163625]). -* Data Frame Analytics creation wizard: ensures validation works correctly ({kibana-pull}163446[#163446]). -* Fixes capabilities polling in manage page ({kibana-pull}163399[#163399]). -* Fixes unhandled promise rejection from ML plugin ({kibana-pull}163129[#163129]). -* AIOps: Uses Kibana's http service instead of fetch and fixes throttling ({kibana-pull}162335[#162335]). -* Uses model supplied mask token for testing trained models ({kibana-pull}162168[#162168]). -Management:: -* Fixes how a bulk request body with variables are processed in Console ({kibana-pull}162745[#162745]). -* Improves a way of variable substitution and its documentation in Console ({kibana-pull}162382[#162382]). -* Transforms: Reduces rerenders and multiple fetches of source index on transform wizard load ({kibana-pull}160979[#160979]). -Maps:: -* Fixes Map layer preview blocks adding layer until all tiles are loaded ({kibana-pull}161994[#161994]). -Monitoring:: -* Rewrite CPU usage rule to improve accuracy ({kibana-pull}159351[#159351]). -Observability:: -* Fixes email connector rule URL ({kibana-pull}162954[#162954]). -* Disable the 'View rule details' option from the Alert Details' Actions list when the rule is deleted ({kibana-pull}163183[#163183]). -Reporting:: -* Fixes a bug where Kibana Reporting would not work in Elastic Docker without adding a special setting in kibana.yml to disable the Chromium sandbox. ({kibana-pull}149080[#149080]). -* Fixes an issue where certain international characters would not appear correctly in the contents of a print-optimized PDF report of a dashboard ({kibana-pull}161825[#161825]). -Uptime:: -* Fixes auto-expand feature for failed step detail ({kibana-pull}162747[#162747]). - -[[release-notes-8.9.2]] -== {kib} 8.9.2 - -Review the following information about the {kib} 8.9.2 release. - -[float] -[[enhancement-v8.9.2]] -=== Enhancements - -Fleet:: -* Adds the configuration setting `xpack.fleet.packageVerification.gpgKeyPath` as an environment variable in the {kib} container ({kibana-pull}163783[#163783]). - -[float] -[[fixes-v8.9.2]] -=== Bug fixes - -Dashboard:: -* Fixes missing state on short URLs could be lost on an alias match redirect ({kibana-pull}163658[#163658]). -* Fixes 'Download CSV' returning no data when panel has custom time range outside the time range of the global time picker ({kibana-pull}163887[#163887]). -* Fixes **Dashboard** getting stuck at loading in {kib} when Controls is used and mapping changed from integer to keyword ({kibana-pull}163529[#163529]). -Elastic Security:: -For the Elastic Security 8.9.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Lens & Visualizations:: -* Allow removing temporary data view from event annotation group in *Lens* ({kibana-pull}163976[#163976]). -Machine Learning:: -* Anomaly detection wizard: ensure custom URLs test functionality works as expected ({kibana-pull}165055[#165055]). -* Fixes anomaly detection module manifest queries for {kib} sample data sets, so cold and frozen tiers are not queried ({kibana-pull}164332[#164332]). -Management:: -* Transforms: Fixes privileges check ({kibana-pull}163687[#163687]). -Operations:: -* Fixes an issue where {kib} did not start on CentOS/RHEL 7 ({kibana-pull}165151[#165151]). -Reporting:: -* Allow custom roles to use image reporting in **Dashboard** ({kibana-pull}163873[#163873]). - -[[release-notes-8.9.1]] -== {kib} 8.9.1 - -Review the following information about the {kib} 8.9.1 release. - -[float] -[[breaking-changes-8.9.1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.9.0, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in the {kib} 8.9.1 release. - -To review the breaking changes in the previous release, check {kibana-ref-all}/8.9/release-notes-8.9.0.html#breaking-changes-8.9.0[8.9.0]. - -[float] -[[fixes-v8.9.1]] -=== Bug fixes -APM:: -* Fixes flame graph rendering on the transaction detail page ({kibana-pull}162968[#162968]). -* Check if documents are missing `span.name` ({kibana-pull}162899[#162899]). -* Fixes transaction action menu for Trace Explorer and dependency operations ({kibana-pull}162213[#162213]). -Canvas:: -* Fixes embeddables not rendering in Canvas ({kibana-pull}163013[#163013]). -Discover:: -* Fixes grid styles to enable better content wrapping ({kibana-pull}162325[#162325]). -* Fixes search sessions using temporary data views ({kibana-pull}161029[#161029]). -* Make share links and search session information shorter for temporary data views ({kibana-pull}161180[#161180]). -Elastic Security:: -For the Elastic Security 8.9.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Fixes for query error on Agents list in the UI ({kibana-pull}162816[#162816]). -* Remove duplicate path being pushed to package archive ({kibana-pull}162724[#162724]). -Management:: -* Resolves potential errors present in v8.9.0 with data views that contain field filters that have been edited ({kibana-pull}162860[#162860]). -Uptime:: -* Fixes Monitor not found 404 message display ({kibana-pull}163501[#163501]). - -[float] -[[enhancement-v8.9.1]] -=== Enhancements -Discover:: -* Set legend width to extra large and enable text wrapping in legend labels ({kibana-pull}163009[#163009]). - -[[release-notes-8.9.0]] -== {kib} 8.9.0 - -For information about the {kib} 8.9.0 release, review the following information. - -[float] -[[known-issues-8.9.0]] -=== Known issues - -// tag::known-issue-160116[] -[discrete] -.Changes to Lens visualizations do not appear in the saved object. -[%collapsible] -==== -*Details* + -Changes to Lens visualizations do not appear in the saved object. - -*Impact* + -When you remove fields from Lens visualizations, then save your changes, the removed fields continue to appear in the Lens visualization saved objects. -For example, when you remove runtime fields from a Lens visualization and {kib}, then inspect the Lens visualization saved object, the runtime fields continue to appear and an error message appears. - -*Workaround* + -In 8.10.0, we are addressing this issue by merging the existing and changed saved object instead of replacing the saved object. - -==== -// end::known-issue-161249[] - -[float] -[[breaking-changes-8.9.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.9.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Hide Uptime app if no data is available -[%collapsible] -==== -*Details* + -The Uptime app now gets hidden from the interface when it doesn't have any data for more than a week. If you have a standalone Heartbeat pushing data to Elasticsearch, the Uptime app is considered active. You can disable this automatic behavior from the advanced settings in Kibana using the **Always show legacy Uptime app** option. -For synthetic monitoring, we now recommend to use the new Synthetics app. For more information, refer to {kibana-pull}159118[#159118] -==== - -[discrete] -.Remove synthetics pattern from Uptime settings -[%collapsible] -==== -*Details* + -Data from browser monitors and monitors of all types created within the Synthetics App or via the Elastic Synthetics Fleet Integration will no longer appear in Uptime. For more information, refer to {kibana-pull}159012[#159012] -==== - -[float] -[[deprecations-8.9.0]] -=== Deprecations - -The following functionality is deprecated in 8.9.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.9.0. - -[discrete] -[[deprecation-156455]] -.Hide ability to create legacy input controls -[%collapsible] -==== -*Details* + -The option to create legacy input controls when creating a new visualization is hidden. For more information, refer to {kibana-pull}156455[#156455] -==== - -[discrete] -[[deprecation-155503]] -.Remove legacy field stats -[%collapsible] -==== -*Details* + -Legacy felid stats that were previously shown within a popover have been removed. For more information, refer to {kibana-pull}155503[#155503] -==== - -[float] -[[features-8.9.0]] -=== Features -{kib} 8.9.0 adds the following new and notable features. - -APM:: -* Removes default service name and environment {kibana-pull}159901[#159901] -* Adds Agent status action {kibana-pull}159227[#159227] -* Added `sessionSampleRate` to agent configuration, which is a mobile specific setting {kibana-pull}159061[#159061] -* Adds storage explorer improvements {kibana-pull}157303[#157303] - -Elastic Security:: -For the Elastic Security 8.9.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Enterprise Search:: -For the Elastic Enterprise Search 8.9.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Adds CloudFormation install method to CSPM {kibana-pull}159994[#159994] -* Adds flags to give permissions to write to any dataset and namespace {kibana-pull}157897[#157897] -* Disables Agent ID verification for Observability projects {kibana-pull}157400[#157400] -* Setup ignore_malformed in fleet {kibana-pull}157184[#157184] - -Lens & Visualizations:: -* Adds several new capabilities for annotation groups in *Lens* {kibana-pull}152623[#152623] - -Observability:: -* Adds SLO create callout to service overview, transactions page and transactions details {kibana-pull}159958[#159958] -* Adds the Logs threshold alert detail page, which provides more information and context about the Logs threshold alert {kibana-pull}159947[#159947] - -Security:: -* Adds vulnerability dashboard tables {kibana-pull}159699[#159699] -* Adds new Vulnerabilities tab to the Group by Resource page {kibana-pull}158987[#158987] -* Adds display errors and check licenses for actions in response actions {kibana-pull}155254[#155254] -* Adds common response actions tab in the alert flyout {kibana-pull}155362[#155362] - -For more information about the features introduced in 8.9.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.9.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.9.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.9.0]] -=== Enhancements -Alerting:: -* Adds a Mustache lambda for alerting actions to format numbers with `{{#FormatNumber}}` using `Intl.NumberFormat` {kibana-pull}159644[#159644] -* Removes bulk snoozing of rules in Select All mode {kibana-pull}159749[#159749] -* Adds refresh button to maintenance windows list {kibana-pull}159618[#159618] -* Adds the feature for a Slack API to have allowed list on channels {kibana-pull}159534[#159534] -* Integrate Conditional Actions with several Observability rule types {kibana-pull}159522[#159522] -* Adds AAD Fields API {kibana-pull}158516[#158516] -* Adds API to retrieve the `fieldsForAAD` from a given rule type {kibana-pull}158516[#158516] -* Improves the performance of clearing expired snooze schedules {kibana-pull}157909[#157909] -APM:: -* Ensure Saved Objects are versionable {kibana-pull}159881[#159881] -* Adds active alerts column for transaction group table {kibana-pull}159552[#159552] -* Adds an OpenAI integration {kibana-pull}158678[#158678] -* Adds Storage explorer improvements {kibana-pull}157303[#157303] -* Adds logic to replace transaction histogram with summary for backwards compatibility {kibana-pull}155714[#155714] -Dashboard:: -* Adds design enhancements to the clone experience in Dashboards {kibana-pull}159752[#159752] -* Adds enhancements to the empty state screen in Dashboards {kibana-pull}158496[#158496] -* Adds a query DSL documentation link to filters UI {kibana-pull}156543[#156543] -* Adds a counter displaying the min/max values of the time series counter field in the field popover {kibana-pull}155499[#155499] -* Adds the ability for Controls to recover from non-fatal error state {kibana-pull}158087[#158087] -Discover:: -* Updates Discover sharing capabilities to enable sharing a link when using temporary data views {kibana-pull}154947[#154947] -Elastic Security:: -For the Elastic Security 8.9.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Enterprise Search:: -For the Elastic Enterprise Search 8.9.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. -Fleet:: -* Adds agent integration health reporting in the Fleet UI {kibana-pull}158826[#158826] -Lens & Visualizations:: -* Adds feature to ignore global filters at layer level in *Lens* {kibana-pull}159248[#159248] -* Adds significant terms support as ranking function in Top Values in *lens* {kibana-pull}158962[#158962] -* Adds support for curved, linear and stepped lines in *Lens* {kibana-pull}158896[#158896] -* Allow wildcard searching in options list {kibana-pull}158427[#158427] -* Adds tag cloud to *Lens* {kibana-pull}157751[#157751] -Machine Learning:: -* Data Frame Analytics: Allow interval time range selection in custom URLs {kibana-pull}159874[#159874] -* Increase limit of anomaly charts to max of 50 {kibana-pull}159816[#159816] -* Enable auto-complete on filter by influencer search box on anomaly explorer page {kibana-pull}159739[#159739] -* Moves import buttons in file data visualizer {kibana-pull}159722[#159722] -* Explain Log Rate Spikes: Adds secondary sort by `doc_count` only if sorted by `p-value` {kibana-pull}159568[#159568] -* Adds hyperlink to anomaly explorer for job from anomaly layer in maps {kibana-pull}159268[#159268] -* Support pipelines deletion and force flag for delete action {kibana-pull}158671[#158671] -* Use auto layout for anomalies table columns {kibana-pull}157687[#157687] -* Versioning all ML APIs {kibana-pull}156949[#156949] -Management:: -* Transforms: Adds extra checkpoint stats to details tab {kibana-pull}157287[#157287] -* Adds in-product docs for cross cluster search index pattern input {kibana-pull}156155[#156155] -Maps:: -* Adds a new layer wizard for the spatial join option {kibana-pull}156618[#156618] -Metrics:: -* Adds copilot to hosts process {kibana-pull}159413[#159413] -Observability:: -* Adds TLS Certificates page copied from Uptime, which only displays certificates from the synthetics application {kibana-pull}159541[#159541] -* Adds creating, editing, deleting monitors in private locations, which no longer requires all permissions in feet. You will only need synthetics write permissions. {kibana-pull}159378[#159378] -* Adds burn rate windows to SLO detail page {kibana-pull}159750[#159750] -* Adds *Normalize by* time and scale factor on Diff TopN functions page {kibana-pull}159394[#159394] -* Adds a severity label to SLO burn rate rule reason message {kibana-pull}158954[#158954] -* Adds Copy JSON button to Clipboard for SLO form {kibana-pull}157902[#157902] -* Adds Custom Metric SLI {kibana-pull}157421[#157421] -* Adds Data Views to index pattern selector {kibana-pull}158033[#158033] -Platform:: -* The savedObjects export API now supports exporting all types using the `*` wildcard. Please refer to the documentation {kibana-pull}159289[#159289] -Security:: -* Hide create spaces button when limit is reached {kibana-pull}159102[#159102] -Uptime:: -* Adds the option to send an HTML email instead of a plaintext email to users when assigned to a case {kibana-pull}159335[#159335] -* Implement standard time formatting {kibana-pull}143799[#143799] -* Adds a new field, called `category`, to categorize cases. Users can create a new category, set or select an existing one on a case, and filter by multiple categories on the cases table. {kibana-pull}159890[#159890] - -[float] -[[fixes-v8.9.0]] -=== Bug fixes -Alerting:: -* Fixes containment boundaries not being re-fetched when a query changes {kibana-pull}157408[#157408] -* Fixes the charts on Log threshold breached details page {kibana-pull}160321[#160321] -APM:: -* Fixes infinite loading of APM alert table {kibana-pull}161134[#161134] -* Fixes other bucket message and sorting {kibana-pull}159919[#159919] -* Fixes percentiles for service transaction metrics {kibana-pull}158913[#158913] -* Fixes stack trace on errors when only available as plain text {kibana-pull}156831[#156831] -Cases:: -* Fixes a bug in the alerts table where you cannot create a new case when attaching alerts to a case from the cases modal {kibana-pull}160526[#160526] -* Fixes an issue where the following special characters could not be included in the case tags: `\\():<>"*` because it resulted in a bug where the case would not be displayed in the cases table when filtered for those tags. These characters are now handled correctly and the cases will be shown in the table. {kibana-pull}159815[#159815] -Dashboard:: -* Fixes an issue where a Dashboard redirect alias displayed a blank screen instead of redirecting properly {kibana-pull}161043[#161043] -* Fixes an issue where the time slider would override custom time ranges {kibana-pull}160938[#160938] -* Fixes an issue where if the Reset button is clicked after changing the Title or Description in Panel settings before clicking Save, the Title or Description would revert to previous values {kibana-pull}159430[#159430] -* Fixes dashboard reset when initial state has no controls {kibana-pull}159404[#159404] -Design:: -* Accessibility: Make links on job validation step distinguished from surrounding text {kibana-pull}160608[#160608] -* Fixes the range slider rendering issues and performance improvements {kibana-pull}159271[#159271] -Discover:: -* Fixes refresh fields when entering Discover {kibana-pull}160195[#160195] -Elastic Security:: -For the Elastic Security 8.9.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Enterprise Search:: -For the Elastic Enterprise Search 8.9.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. -Fleet:: -* Fixes bug that prevented `index.mapping` settings to be propagated into component templates from default settings {kibana-pull}157289[#157289] -Lens & Visualizations:: -* Fixes missing datasource migration sometimes failing to run in *Lens* {kibana-pull}160129[#160129] -* Removes wrong padding on the dashboard in *Lens* {kibana-pull}159992[#159992] -* Fixes counter fields being used with Top values in *Lens* {kibana-pull}159709[#159709] -* Fixes dimension labels being partially translated on language change in *Lens* {kibana-pull}159089[#159089] -* Fixes the threshold visibility rendering in XY charts {kibana-pull}158917[#158917] -* Fixes a tag cloud warning about container being too small that never disappears {kibana-pull}159611[#159611] -* Default format in *Lens* will apply dataView field format as usual now in new Metric visualization {kibana-pull}158468[#158468] -* Fixes the handling of partition chart empty slices in *Lens* {kibana-pull}158346[#158346] -* Make reference lines use the correct formatter when configured in *Lens* {kibana-pull}158266[#158266] -* Fixes an issue where dropping a date field into a XY visualization with multiple mixed layers resets the chart type of all layers in *Lens* {kibana-pull}157871[#157871] -* Fixes an issue where incompatible actions were visible from the panels {kibana-pull}156667[#156667] -Machine Learning:: -* Fixing time range selector in recognizer wizard {kibana-pull}160910[#160910] -* Fixes Anomaly Explorer URL for alerting context with non-default space {kibana-pull}160899[#160899] -* Hide inference stats for PyTorch models {kibana-pull}160599[#160599] -* Outlier detection results: ensure feature influence color persists on column position change {kibana-pull}160470[#160470] -* Anomaly detection: fixes time format used in query for datafeed chart {kibana-pull}160325[#160325] -* Hide `cache_miss_count` for PyTorch models {kibana-pull}160265[#160265] -* Fixes time range in link to data visualizer after file upload {kibana-pull}160189[#160189] -* Fixes links to dashboards in Lens created anomaly detection jobs {kibana-pull}160156[#160156] -* Adds warning to log pattern analysis page if data view is not time based {kibana-pull}160021[#160021] -Management:: -* Fixes creation and editing of composite runtime fields with dots in the names {kibana-pull}160458[#160458] -* Fixes decreasing network delays when cross cluster search is running by sending `ccs_minimize_roundtrips=true` for async search requests {kibana-pull}159848[#159848] -* Fixes max page search size limit for Transforms {kibana-pull}159052[#159052] -* Fixes issue with single value for fields parameters {kibana-pull}157930[#157930] -* Fixes data view timestamp validation {kibana-pull}150398[#150398] -* Fixes theming for search sessions management {kibana-pull}160182[#160182] -Maps:: -* Fixes geojson layer with joins and no left source matches stuck in loading state {kibana-pull}160222[#160222] -* Fixes size legend not indicating when min/max clamped by standard deviation range {kibana-pull}156927[#156927] -Metrics:: -* Fixes `metric_explorer` flaky test{kibana-pull}157194[#157194] -Operations:: -* Fixes Elasticsearch snapshot startup for parameters with dots in their path {kibana-pull}161022[#161022] -Platform:: -* Fixes theming for error toast messages {kibana-pull}160219[#160219] -* Fixes a bug that could cause old Kibana deployments to loose their uiSettings after an upgrade {kibana-pull}159649[#159649] -* Fixes the handling of non-existing objects in _copy_saved_objects API call {kibana-pull}158036[#158036] -Security:: -* Fixes theming of CodeEditors {kibana-pull}159638[#159638] -* Update session viewer Policy permissions to use Policy specific check {kibana-pull}160448[#160448] -* Fixes an issue when opening an endpoint exception from the Alert's summary flyout actions button, the exception did not auto-populate {kibana-pull}159908[#159908] -Uptime:: -* Fixes parsing of response check on JSON expressions {kibana-pull}161634[#161634] -* Fixes an API error stating no key exists when a user was visiting the getting started page or tried to add monitor view by enabling synthetics {kibana-pull}160360[#160360] -* Fixes copy on private locations popover {kibana-pull}159740[#159740] - -[[release-notes-8.8.2]] -== {kib} 8.8.2 - -Review the following information about the {kib} 8.8.2 release. - -[float] -[[known-issues-8.8.2]] -=== Known issues - -// tag::known-issue-161249[] -[discrete] -.Kibana can run out of memory during an upgrade when there are many {fleet} agent policies. -[%collapsible] -==== -*Details* + -Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. -This action triggers a lot of queries to the Elastic Package Registry (EPR) to fetch integration packages. As a result, -there is an increase in Kibana's resident memory usage (RSS). - -*Impact* + -Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can -cause Kibana to run out of memory during an upgrade. For example, we have observed 1GB Kibana instances run -out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. - -*Workaround* + -Two workaround options are available: - -* Increase the Kibana instance size to 2GB. So far, we are not able to reproduce the issue with 2GB instances. -* Set `xpack.fleet.setup.agentPolicySchemaUpgradeBatchSize` to `2` in the `kibana.yml` and restart the Kibana instance(s). - -In 8.9.0, we are addressing this by changing the default batch size to `2`. - -==== -// end::known-issue-161249[] - -[float] -[[fixes-v8.8.2]] -=== Bug fixes - -APM:: -* Fixes the latency graph displaying all service transactions, rather than the selected one, on the transaction detail page {kibana-pull}159085[#159085] - -Dashboard:: -* Fixes styling of top nav bar {kibana-pull}159754[#159754] -* Fixes alias redirect and update error handling {kibana-pull}159742[#159742] -* Fixes time range regression {kibana-pull}159337[#159337] - -Elastic Security:: -For the Elastic Security 8.8.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.8.2 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Fixes usage of AsyncLocalStorage for audit log {kibana-pull}159807[#159807] -* Fixing issue of returning output API key {kibana-pull}159179[#159179] - -Logs:: -* Fixes log categorization UI failure due to an infinite loop {kibana-pull}159090[#159090] - -Machine Learning:: -* Hiding pattern analysis button for non-time series data {kibana-pull}160051[#160051] -* Fixes blocking forced downgrades/installation if indices can't be deleted {kibana-pull}159814[#159814] - -Maps:: -* Fixes layer group loading indicator always on when group has non-visible layer {kibana-pull}159517[#159517] -* Fixes geo line source not loading unless the Maps application is open {kibana-pull}159432[#159432] -* Fixes Maps orphan sources on layer deletion {kibana-pull}159067[#159067] - -Monitoring:: -* Permanently hide the telemetry notice on dismissal {kibana-pull}159893[#159893] - -Observability:: -* Handle buildEsQuery error (such as leading wildcard) in status change {kibana-pull}159891[#159891] - -Platform:: -* Fixes global search crash on missing tag {kibana-pull}159196[#159196] -* Fixes a regression where the "saved_object_resolve" audit action was not being logged per object {kibana-pull}160014[#160014] - -Uptime:: -* Ensures that users can configure custom `Content-Type` headers for HTTP monitors in the Synthetics app {kibana-pull}159737[#159737] -* Fixes an issue where alerting on Synthetics monitors was sometimes delayed {kibana-pull}159511[#159511] - -[[release-notes-8.8.1]] -== {kib} 8.8.1 - -Review the following information about the {kib} 8.8.1 release. - -[float] -[[known-issues-8.8.1]] -=== Known issues - -// tag::known-issue-161249[] -[discrete] -.Kibana can run out of memory during an upgrade when there are many {fleet} agent policies. -[%collapsible] -==== -*Details* + -Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. -This action triggers a lot of queries to the Elastic Package Registry (EPR) to fetch integration packages. As a result, -there is an increase in Kibana's resident memory usage (RSS). - -*Impact* + -Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can -cause Kibana to run out of memory during an upgrade. For example, we have observed 1GB Kibana instances run -out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. - -*Workaround* + -Two workaround options are available: - -* Increase the Kibana instance size to 2GB. So far, we are not able to reproduce the issue with 2GB instances. -* Set `xpack.fleet.setup.agentPolicySchemaUpgradeBatchSize` to `2` in the `kibana.yml` and restart the Kibana instance(s). - -In 8.9.0, we are addressing this by changing the default batch size to `2`. - -==== -// end::known-issue-161249[] - -// tag::known-issue-159807[] -[discrete] -.Memory leak in {fleet} audit logging. -[%collapsible] -==== -*Details* + -{fleet} introduced audit logging for various CRUD (create, read, update, and delete) operations in version 8.8.0. - -While audit logging is not enabled by default, we have identified an off-heap memory leak in the implementation of {fleet} audit logging that can result in poor {kib} performance, and in some cases {kib} instances being terminated by the OS kernel's oom-killer. This memory leak can occur even when {kib} audit logging is not explicitly enabled (regardless of whether `xpack.security.audit.enabled` is set in the `kibana.yml` settings file). - -*Impact* + -The version 8.8.2 release includes in {kibana-pull}159807[a fix] for this problem. If you are using {fleet} integrations -and {kib} audit logging in version 8.8.0 or 8.8.1, you should upgrade to 8.8.2 or above to obtain the fix. -==== -// end::known-issue-159807[] - -[float] -[[breaking-changes-8.8.1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.8.1, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in the {kib} 8.8.1 release. - -To review the breaking changes in the previous release, check {kibana-ref-all}/8.8/release-notes-8.8.0.html#breaking-changes-8.8.0[8.8.0]. - -[float] -[[enhancement-v8.8.1]] -=== Enhancements - -Elastic Security:: -For the Elastic Security 8.8.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Add Elastic Agent UI instructions for Universal Profile {kibana-pull}158936[#158936] - -[float] -[[fixes-v8.8.1]] -=== Bug fixes - -Alerting:: -* Fixes a bug where ML embeddables, OsQuery, and IoCs attachments in a case render the wrong view {kibana-pull}158441[#158441] -* Makes alert links shorter {kibana-pull}158582[#158582] -* Throws a Mustache error when validating action message for warnings {kibana-pull}158668[#158668] -* Adds null checks when iterating through an index template list {kibana-pull}158742[#158742] - -APM:: -* Displays the size of hidden indices in storage explorer {kibana-pull}158746[#158746] -* Changes the APM latency value and latency threshold to microseconds {kibana-pull}158703[#158703] -* Fixes service transaction metrics by using `transaction.duration.histogram` for percentile aggregations {kibana-pull}158909[#158909] - -Discover:: -* Update single doc view locator to URL encode `rowId` {kibana-pull}158635[#158635] -* Fixes the display of grid row selection when in dark mode {kibana-pull}158231[#158231] - -Elastic Security:: -For the Elastic Security 8.8.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Include hidden data streams in package upgrade {kibana-pull}158654[#158654] - -Logs:: -* Fixes Log Categorization UI failure due to an infinite loop {kibana-pull}159090[#159090] - -Machine Learning:: -* Increases calendar events request limit {kibana-pull}158726[#158726] -* Disables the delete option for deployed models {kibana-pull}158533[#158533] -* Applies theme based on the User Profile settings {kibana-pull}158258[#158258] - -Maps:: -* Fixes toolbar action button not filled when selected {kibana-pull}158284[#158284] -* Fixes Maps to display dark theme when enabled {kibana-pull}158219[#158219] - -Operations:: -* Fixes configuration stacking order {kibana-pull}158827[#158827] - -Platform:: -* Fixes {kib} crashing on Safari versions prior to 16.4 {kibana-pull}158825[#158825] -* Updates all aliases with a single `updateAliases()` when relocating saved objects {kibana-pull}158940[#158940] -* Fixes a race condition that could cause intermittent upgrade migration failures when {kib} connects to a single node {es} cluster {kibana-pull}158182[#158182] -* Dynamically reduces the `migrations.batchSize` value when {kib} encounters a migration batch that's too big to process {kibana-pull}157494[#157494] - -[[release-notes-8.8.0]] -== {kib} 8.8.0 - -Review the following information about the {kib} 8.8.0 release. - -[float] -[[known-issues-8.8.0]] -=== Known issues - -// tag::known-issue-161249[] -[discrete] -.Kibana can run out of memory during an upgrade when there are many {fleet} agent policies. -[%collapsible] -==== -*Details* + -Due to a schema version update, during {fleet} setup in 8.8.x, all agent policies are being queried and deployed. -This action triggers a lot of queries to the Elastic Package Registry (EPR) to fetch integration packages. As a result, -there is an increase in Kibana's resident memory usage (RSS). - -*Impact* + -Because the default batch size of `100` for schema version upgrade of {fleet} agent policies is too high, this can -cause Kibana to run out of memory during an upgrade. For example, we have observed 1GB Kibana instances run -out of memory during an upgrade when there were 20 agent policies with 5 integrations in each. - -*Workaround* + -Two workaround options are available: - -* Increase the Kibana instance size to 2GB. So far, we are not able to reproduce the issue with 2GB instances. -* Set `xpack.fleet.setup.agentPolicySchemaUpgradeBatchSize` to `2` in the `kibana.yml` and restart the Kibana instance(s). - -In 8.9.0, we are addressing this by changing the default batch size to `2`. - -==== -// end::known-issue-161249[] - -// tag::known-issue-158940[] -[discrete] -.Failed upgrades to 8.8.0 can cause bootlooping and data loss -[%collapsible] -==== -*Details* + -The 8.8.0 release splits the `.kibana` index into multiple saved object indices. If an upgrade to 8.8.0 partially succeeds, but not all the indices are created successfully, {kib} may be unable to successfully complete the upgrade on the next restart. - -This can result in a loss of saved objects during the upgrade. This can also leave {kib} in a bootlooping state where it's unable to start due to `write_blocked` indices. - -*Impact* + -The 8.8.1 release includes in {kibana-pull}158940[a fix] for this problem. Customers affected by a failed 8.8.0 upgrade should contact Elastic support. For more information, see the {kibana-issue}158733[related issue]. -==== -// end::known-issue-158940[] - -// tag::known-issue-159807[] -[discrete] -.Memory leak in {fleet} audit logging. -[%collapsible] -==== -*Details* + -{fleet} introduced audit logging for various CRUD (create, read, update, and delete) operations in version 8.8.0. -While audit logging is not enabled by default, we have identified an off-heap memory leak in the implementation of {fleet} audit logging that can result in poor {kib} performance, and in some cases {kib} instances being terminated by the OS kernel's oom-killer. This memory leak can occur even when {kib} audit logging is not explicitly enabled (regardless of whether `xpack.security.audit.enabled` is set in the `kibana.yml` settings file). - -*Impact* + -The version 8.8.2 release includes in {kibana-pull}159807[a fix] for this problem. If you are using {fleet} integrations -and {kib} audit logging in version 8.8.0 or 8.8.1, you should upgrade to 8.8.2 or above to obtain the fix. -==== -// end::known-issue-159807[] - -// tag::known-issue-155203[] -[discrete] -.Monitors in Synthetics may stop running -[%collapsible] -==== -*Details* + -If Monitor Management was enabled prior to 8.6.0, the API key generated internally will not contain the required permissions. The Synthetics app will attempt to fix this automatically in {kibana-pull}155203[#155203] when a user with https://www.elastic.co/guide/en/observability/8.8/synthetics-role-setup.html[sufficient privileges] visits this page for the first time after upgrading to 8.8.0. - -*Impact* + -All monitors configured to run on Elastic's global managed testing infrastructure will stop running until a user with permissions has loaded the Synthetics app. -==== -// end::known-issue-155203[] - -// tag::known-issue-156798[] -[discrete] -.Network throttling disabled for browser monitors in Synthetics -[%collapsible] -==== -*Details* + -Network throttling has been temporarily disabled for browser-based Synthetics monitors running on Elastic's global managed testing infrastructure and private locations. This will be enabled again at some point in the future. We're providing frequent updates on this issue in https://github.com/elastic/synthetics/blob/main/docs/throttling.md[this document]. - -*Impact* + -With network throttling being disabled, your monitors may run more quickly (i.e. have a lower duration) than you observed previously and than when network throttling is enabled again in the future. No monitor configurations have been changed, but the network throttling settings are ignored at the moment. -==== -// end::known-issue-156798[] - -// tag::known-issue-120[] -[discrete] -.Alert failures when migrating to 8.8.0 from 8.6 or earlier -[%collapsible] -==== -*Details* + -If a cluster meets all of the following conditions, its {elastic-sec} and {observability} rules will fail and no actions will be sent: - -- The {elastic-sec} and {observability} rules were created in version 8.6 or earlier releases. -- There must be an {ref}/index-templates.html[index template] (for any index) that isn't composed of component templates. - -The following error messages in the {kib} log occur when {kib} starts or when the rules run: - -[source,sh] ----- -Error installing component template .alerts-ecs-mappings - Cannot read properties of undefined (reading 'includes') - -Error installing common resources for AlertsService. No additional resources will be installed and rule execution may be impacted. - Failure during installation. Cannot read properties of undefined (reading 'includes') ----- - -*Impact* + -If you have upgraded to 8.8.0 and your alerting rules fail, upgrade to 8.8.1. -==== -// end::known-issue-120[] - -// tag::known-issue-158447[] -[discrete] -.Incorrect attachments are added to cases -[%collapsible] -==== -*Details* + -When you attach {ml} visualizations, OsQuery, or Indicators of Compromise (IoCs) to a case, each attachment has its own view which renders in the *Activity* tab. -For these attachments, a bug was introduced in 8.8.0: - -. If you add two different attachments on a case, the view will be the same for both. -. If you add one attachment to one case and another to a different case, in the second case you will view the attachment of the first case. - -Alerts are not affected. - -*Impact* + -There are no mitigations for the first scenario, other than upgrading to 8.8.1. -For the second scenario, refreshing the case fixes the issue. -==== -// end::known-issue-158447[] - -[float] -[[breaking-changes-8.8.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.8.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Removes legacy project monitor API -[%collapsible] -==== -*Details* + -The project monitor API for Synthetics in Elastic Observability has been removed. For more information, refer to {kibana-pull}155470[#155470]. - -*Impact* + -In 8.8.0 and later, an error appears when you use the project monitor API. -==== - -[discrete] -.Changes the privileges for alerts and cases -[%collapsible] -==== -*Details* + -The privileges for attaching alerts to cases has changed. For more information, refer to {kibana-pull}147985[#147985]. - -*Impact* + -To attach alerts to cases, you must have `Read` access to an {observability} or Security feature that has alerts and `All` access to the **Cases** feature. For detailed information, check link:https://www.elastic.co/guide/en/kibana/current/kibana-privileges.html[{kib} privileges] and link:https://www.elastic.co/guide/en/kibana/current/setup-cases.html[Configure access to cases]. -==== - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.7/release-notes-8.7.0.html#breaking-changes-8.7.0[8.7.0] | {kibana-ref-all}/8.6/release-notes-8.6.0.html#breaking-changes-8.6.0[8.6.0] | {kibana-ref-all}/8.5/release-notes-8.5.0.html#breaking-changes-8.5.0[8.5.0] | {kibana-ref-all}/8.4/release-notes-8.4.0.html#breaking-changes-8.4.0[8.4.0] | {kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[deprecations-8.8.0]] -=== Deprecations - -The following functionality is deprecated in 8.8.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.8.0. - -[discrete] -[[deprecation-154275]] -.Deprecates ephemeral Task Manager settings -[%collapsible] -==== -*Details* + -The following Task Manager settings are deprecated: - -* `xpack.task_manager.ephemeral_tasks.enabled` -* `xpack.task_manager.ephemeral_tasks.request_capacity` -* `xpack.alerting.maxEphemeralActionsPerAlert` - -For more information, refer to {kibana-pull}154275[#154275]. - -*Impact* + -To improve task execution resiliency, remove the deprecated settings from the `kibana.yml` file. For detailed information, check link:https://www.elastic.co/guide/en/kibana/current/task-manager-settings-kb.html[Task Manager settings in {kib}]. -==== - -[discrete] -[[deprecation-154010]] -.Deprecates monitor schedules -[%collapsible] -==== -*Details* + -Synthetics and Uptime monitor schedules and zip URL fields are deprecated. For more information, refer to {kibana-pull}154010[#154010] and {kibana-pull}154952[#154952]. - -*Impact* + -When you create monitors in Uptime Monitor Management and the Synthetics app, unsupported schedules are automatically transfered to the nearest supported schedule. To use zip URLs, use project monitors. -==== - -[discrete] -[[deprecation-152236]] -.Deprecates Agent reassign API PUT endpoint -[%collapsible] -==== -*Details* + -The PUT endpoint for the agent reassign API is deprecated. For more information, refer to {kibana-pull}152236[#152236]. - -*Impact* + -Use the POST endpoint for the agent reassign API. -==== - -[discrete] -[[deprecation-151564]] -.Deprecates `total` in `/agent_status` Fleet API -[%collapsible] -==== -*Details* + -The `total` field in `/agent_status` Fleet API responses is deprecated. For more information, refer to {kibana-pull}151564[#151564]. - -*Impact* + -The `/agent_status` Fleet API now returns the following statuses: - -* `all` — All active and inactive -* `active` — All active -==== - -[discrete] -[[deprecation-149506]] -.Deprecates Elastic Synthetics integration -[%collapsible] -==== -*Details* + -The Elastic Synthetics integration is deprecated. For more information, refer to {kibana-pull}149506[#149506]. - -*Impact* + -To monitor endpoints, pages, and user journeys, go to **{observability}** -> **Synthetics (beta)**. -==== - -[float] -[[features-8.8.0]] -=== Features -{kib} 8.8.0 adds the following new and notable features. - -Alerting:: -* Adds Maintenance Window Task Runner Integration + New AAD/Event Log Fields {kibana-pull}154761[#154761] -* Adds support for users authenticated with API keys to manage alerting rules {kibana-pull}154189[#154189] -* Adds the ability to control allowed attached file mime types and the maximum file size {kibana-pull}154013[#154013] -* Adds query and timeframe params to RuleAction to filter alerts {kibana-pull}152360[#152360] - -APM:: -* Adds group-by feature in APM rules {kibana-pull}155001[#155001] -* Adds queues as nodes to the service map {kibana-pull}153784[#153784] -* Adds the ability to display the latest agent version in agent explorer {kibana-pull}153643[#153643] -* Adds table tabs showing summary of metrics {kibana-pull}153044[#153044] -* Adds warning to Edit Rule Flyout when publicUrl is not configured {kibana-pull}149832[#149832] - -Cases:: -* Adds support for file attachments in Cases {kibana-pull}154436[#154436] -* Adds the Cases column to the alerts table {kibana-pull}150963[#150963] -* Adds filtering and sorting for the case activity {kibana-pull}149396[#149396] -* Adds the ability to filter user activities with pagination {kibana-pull}152702[#152702] - -Dashboard:: -Pins the unified search bar and dashboard toolbar to the top of the dashboard page when scrolling {kibana-pull}145628[#145628] - -Discover:: -Adds log pattern analysis {kibana-pull}153449[#153449] - -Elastic Security:: -For the Elastic Security 8.8.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.8.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Adds audit logging for core CRUD operations {kibana-pull}152118[#152118] -* Adds modal to display versions changelog {kibana-pull}152082[#152082] - -Infrastructure:: -* Adds the logs tab to the Hosts View {kibana-pull}152995[#152995] -* Adds Alerts tab into Hosts View {kibana-pull}149579[#149579] -* Adds refactoring to the Time and Position log stream state {kibana-pull}149052[#149052] - -Machine Learning:: -* Adds ELSER config to the Trained Models UI {kibana-pull}155867[#155867] -* Adds support for custom URLs in jobs for Data Frame Analytics {kibana-pull}154287[#154287] -* Adds support to filter fields from grouping in Explain Log Rate Spikes {kibana-pull}153864[#153864] -* Adds log pattern analysis in Discover {kibana-pull}153449[#153449] - -Management:: -* Adds support for global settings {kibana-pull}148975[#148975] -* Adds Custom Branding settings to Global settings {kibana-pull}150080[#150080] - -Maps:: -Adds map.emsUrl to docker env variables {kibana-pull}153441[#153441] - -Observability:: -* Adds the ability to changes all SLO assets to managed, and indices to hidden {kibana-pull}154953[#154953] -* Adds Exploratory View to a separate app {kibana-pull}153852[#153852] - -Platform:: -Adds text {kibana-pull}151631[#151631] - -Security:: -* Adds CloudFormation agent install method {kibana-pull}155045[#155045] -* Adds Vul mgmt flyout details panel {kibana-pull}154873[#154873] -* Adds Vulnerabilities Table {kibana-pull}154388[#154388] -* Adds the ability to select a theme preference for {kib} in the User Profile {kibana-pull}151507[#151507] - -Uptime:: -Adds UUID to RuleAction {kibana-pull}148038[#148038] - -For more information about the features introduced in 8.8.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.8.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.8.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.8.0]] -=== Enhancements -Alerting:: -* Adds the ability to predefine IDs when you create connectors {kibana-pull}155392[#155392] -* Adds the ability to allow the footer added to emails sent from {kib} alerting rules to **NOT** be added {kibana-pull}154919[#154919] -* Adds conditional actions UI for timeframe {kibana-pull}153944[#153944] -* Adds a single view in the app function for rule actions variables and UI page {kibana-pull}148671[#148671] - -APM:: -* Adds error grouping key filter in error count rule type {kibana-pull}155410[#155410] -* Adds transaction name filter in failed transaction rate rule type {kibana-pull}155405[#155405] -* Replaces most used charts with the Lens embeddable {kibana-pull}155026[#155026] -* Adds transaction name filter in latency threshold rule {kibana-pull}154241[#154241] -* Adds Unified Search for APM {kibana-pull}153842[#153842] -* Adds migratation for the remaining tx-based visualizations {kibana-pull}153375[#153375] -* Adds migration for the tx latency chart and group stats to rollups/service metrics {kibana-pull}153162[#153162] -* Disables agent configuration creation for opentelemetry agents {kibana-pull}150697[#150697] - -Cases:: -* Adds the ability to set a new connector to default {kibana-pull}151884[#151884] -* Improves the design of the description markdown editor on the Cases page {kibana-pull}155151[#155151] - -Dashboard:: -* Adds support to Dashboard for searching saved objects by tags {kibana-pull}154946[#154946] -* Adds reset button {kibana-pull}154872[#154872] -* Adds unified dashboard settings {kibana-pull}153862[#153862] -* Adds the ability to scroll to a new panel {kibana-pull}152056[#152056] - -Discover:: -* Adds the ability to allow wildcards in field search {kibana-pull}155540[#155540] -* Adds a loading indicator during Discover table updates {kibana-pull}155505[#155505] -* Adds drag & drop capabilities for adding columns to the table {kibana-pull}153538[#153538] -* Adds a progress indicator when a saved search embeddable is updating {kibana-pull}152342[#152342] -* Adds inline data fetching errors {kibana-pull}152311[#152311] -* Adds a loading indicator for the classic table embeddable {kibana-pull}152072[#152072] -* Adds the ability to suppress "Missing index" toasts {kibana-pull}149625[#149625] - -Elastic Security:: -For the Elastic Security 8.8.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.8.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Adds support for fields of type aggregate_metric_double {kibana-pull}154920[#154920] -* Adds overview dashboards in fleet {kibana-pull}154914[#154914] -* Adds raw status to Agent details UI {kibana-pull}154826[#154826] -* Adds support for dynamic_namespace and dynamic_dataset {kibana-pull}154732[#154732] -* Adds the ability to show pipelines and mappings editor for input packages {kibana-pull}154077[#154077] -* Adds placeholder to integration select field {kibana-pull}153927[#153927] -* Adds the ability to show integration subcategories {kibana-pull}153591[#153591] -* Adds the ability to create and update the package policy API return 409 conflict when names are not unique {kibana-pull}153533[#153533] -* Adds the ability to display policy changes in Agent activity {kibana-pull}153237[#153237] -* Adds the ability to display errors in Agent activity with link to Logs {kibana-pull}152583[#152583] -* Adds support for select type in integrations {kibana-pull}152550[#152550] -* Adds the ability to make spaces plugin optional {kibana-pull}152115[#152115] -* Adds proxy ssl key and certificate to agent policy {kibana-pull}152005[#152005] -* Adds `_meta` field `has_experimental_data_stream_indexing_features` {kibana-pull}151853[#151853] -* Adds the ability to create templates and pipelines when updating package of a single package policy from type integration to input {kibana-pull}150199[#150199] -* Adds user's secondary authorization to Transforms {kibana-pull}154665[#154665] - -Infrastructure:: -Adds Memory Available Graph To Hosts View {kibana-pull}151863[#151863] - -Lens & Visualizations:: -* Adds the ability to sync the partition legend order with the filters order in *Lens* {kibana-pull}154820[#154820] -* Adds support for icons in the new *Lens* metric {kibana-pull}154210[#154210] -* Adds the ability to share with reports in *Lens* {kibana-pull}153429[#153429] -* Adds show and hide heatmap ticks in *Lens* {kibana-pull}153425[#153425] -* Adds the ability to remove empty headers when there is no x-axis in *Lens* {kibana-pull}153420[#153420] -* Adds improvements to the Metric formatter to support bit format in *Lens* {kibana-pull}153389[#153389] -* Adds the ability to prevent default behaviour from action callback in *Lens* {kibana-pull}152842[#152842] -* Adds Random Sampling to *Lens* {kibana-pull}151749[#151749] - -Machine Learning:: -* Data Frame Analytics creation wizard: add ability to add time field to result data view {kibana-pull}155669[#155669] -* Display info when no datafeed preview results are found {kibana-pull}155650[#155650] -* Adding ignore unavailable indices option to anomaly detection job wizards {kibana-pull}155527[#155527] -* Support multiple model deployments {kibana-pull}155375[#155375] -* Uses two weeks before now for default start time in job start date picker {kibana-pull}155312[#155312] -* AIOps: Adds filter action for the Change point detection results {kibana-pull}155256[#155256] -* Adds search links for AIOps Labs pages {kibana-pull}155202[#155202] -* AIOps: Adds field stats for metric and split fields {kibana-pull}155177[#155177] -* AIOps: Link from Explain Log Rate Spikes to Log Pattern Analysis {kibana-pull}155121[#155121] -* Explain Log Rate Spikes: adds popover to analysis table for viewing other field values {kibana-pull}154689[#154689] -* Explain Log Rate Spikes: Makes use of random sampling for overall histogram chart {kibana-pull}154520[#154520] -* Explain Log Rate Spikes: Adds table action to copy filter to clipboard {kibana-pull}154311[#154311] -* Change point detection: support for multiple metric and split fields {kibana-pull}154237[#154237] -* Enhances support for counter fields in data visualizer / field statistics {kibana-pull}153893[#153893] -* Custom sorting by message level on Notifications page {kibana-pull}153462[#153462] -* Adds log pattern analysis in Discover {kibana-pull}153449[#153449] -* Explain Log Rate Spikes: Improves grouping using the `include` option of the `frequent_item_sets` agg {kibana-pull}153091[#153091] -* Data Frame Analytics exploration: adds actions column with link to discover {kibana-pull}151482[#151482] -* Allows row expansion for blocked anomaly detection jobs {kibana-pull}151351[#151351] -* Enhances job and datafeed config editors in the Advanced anomaly detection job wizard to provide suggestions and documentation {kibana-pull}146968[#146968] - -Management:: -* Adds timezone support for Transforms date histogram pivot configuration {kibana-pull}155535[#155535] -* Adds more system indices to store internal data when you upgrade to 8.8.0 {kibana-pull}154888[#154888] -* Adds improvements for supporting counter fields in Transforms {kibana-pull}154171[#154171] -* Adds `_schedule_now` action to transform list {kibana-pull}153545[#153545] -* Adds link to Discover from Index Management so users can directly look at documents of their indices {kibana-pull}152640[#152640] -* Adds health information for alerting rule in Transforms{kibana-pull}152561[#152561] -* Adds improvements for index pattern input in the data view flyout {kibana-pull}152138[#152138] -* Adds a new description for the metadata field in ingest pipelines {kibana-pull}150935[#150935] -* Adds a _meta field to the Ingest pipelines form {kibana-pull}149976[#149976] -* Adds option to Reauthorize transform on Management page {kibana-pull}154736[#154736] - -Maps:: -Adds metrics mask {kibana-pull}154983[#154983] - -Observability:: -* Adds invalid license page {kibana-pull}154866[#154866] -* Adds empty state page links {kibana-pull}154678[#154678] -* Adds upload symbols instructions to add data page {kibana-pull}154670[#154670] -* Adds new CPU incl and CPU excl names {kibana-pull}154560[#154560] -* Adds symbols callout on frame information window {kibana-pull}154478[#154478] -* Adds Co2 and dollar cost columns and show more information action to functions table {kibana-pull}154097[#154097] -* Adds improvements to functions {kibana-pull}153873[#153873] -* Adds improvements to Flamegraph {kibana-pull}153598[#153598] -* Adds the ability to open the Traces view when you click on a series in stacked charts {kibana-pull}153325[#153325] -* Adds CPU usage column to replace CPU count column {kibana-pull}151696[#151696] - -Querying & Filtering:: -* Adds the ability to avoid duplicate host IP mapping {kibana-pull}155353[#155353] -* Adds improvements to the saved query terminology {kibana-pull}154517[#154517] - -[float] -[[fixes-v8.8.0]] -=== Bug fixes -Alerting:: -* Fixes Delete Schedule button padding issue {kibana-pull}154503[#154503] -* Fixes error message flash and throttle value reset {kibana-pull}154497[#154497] -* Fixes broken custom snooze recurrences with monthly frequency {kibana-pull}154251[#154251] -* Fixes an issue where you were unable to use retry on updateAPIKey conflict {kibana-pull}151802[#151802] - -APM:: -* Fixes an issue where you were uneable to enable framework alerts as data by default {kibana-pull}154076[#154076] -* Upgraded EUI to v76.0.0 {kibana-pull}152506[#152506] -* Fixes an issue where the OpenTelemetry process and system metrics were unsupported {kibana-pull}151826[#151826] - -Canvas:: -Fixes `createElement` callback {kibana-pull}154398[#154398] - -Cases:: -Fixes the Lens visualization in the comment and description markdown on the New Case page {kibana-pull}155897[#155897] - -Dashboard:: -* Fixes unsaved changes bug on empty dashboard {kibana-pull}155648[#155648] -* Removed Reload on Clone and Replace Panel {kibana-pull}155561[#155561] -* Fixes z index of toolbar items {kibana-pull}154501[#154501] -* Fixes inherited input race condition {kibana-pull}154293[#154293] -* Fixes Changing label of a geospatial filter causes filter disappear from map {kibana-pull}154087[#154087] - -Discover:: -* Adds a "Temporary" badge for temporary data views in the Alerts flyout {kibana-pull}155717[#155717] -* Adds the ability to exclude counter fields from Breakdown options {kibana-pull}155532[#155532] -* Adds the ability to skip requests for the time series metric counter field {kibana-pull}154319[#154319] -* Fixes KQL autocomplete suggestions, which now support IP-type fields when the `autocomplete:valueSuggestionMethod advanced setting is set to terms_enum {kibana-pull}154111[#154111] -* Fixes an issue where saved search "Manage searches" button was unable to apply the "search" type filter {kibana-pull}152565[#152565] - -Elastic Security:: -For the Elastic Security 8.8.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.8.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Fixes package license check to use new `conditions.elastic.subscription` field {kibana-pull}154831[#154831] -* Fixes the OpenAPI spec from `/agent/upload` to `/agent/uploads` for Agent uploads API {kibana-pull}151722[#151722] - -Infrastructure:: -Adds a 404 page for metrics and logs {kibana-pull}153005[#153005] - -Integrations:: -Fixes the slow process event for queries + xterm.js {kibana-pull}155326[#155326] - -Kibana Home & Add Data:: -Fixes the guided onboarding API prefix to indicate that it's intended for internal use {kibana-pull}155643[#155643] - -Lens & Visualizations:: -* Adds a default label on field changes for counter rate in *Lens* {kibana-pull}155509[#155509] -* Panel titles and descriptions are now transferred to the converted Lens panels in *TSVB* {kibana-pull}154713[#154713] -* Adds the ability to use the empty label for `/` terms in *TSVB* {kibana-pull}154647[#154647] -* Fixes the formatting for the legend actions title {kibana-pull}153747[#153747] -* Adds support for negative filter ratios in *TSVB* {kibana-pull}152053[#152053] -* Adds the ability to always retain source order for multi-metric partition chart layers in *Lens* {kibana-pull}151949[#151949] - -Machine Learning:: -* Data Frame Analytics/Anomaly Detection: Custom URLs - entity dropdown reflects Data View update {kibana-pull}155096[#155096] -* AIOps: Fix race condition where stale url state would reset search bar {kibana-pull}154885[#154885] -* Fixes anomalies table drilldown time range for longer bucket spans {kibana-pull}153678[#153678] -* Do not match time series counter fields with aggs in wizards {kibana-pull}153021[#153021] -* Anomaly Detection datafeed chart: ensure chart y axis minimum set correctly {kibana-pull}152051[#152051] - -Management:: -* Improves the display when there are many columns {kibana-pull}155119[#155119] -* Fixes stale submit handler ref update {kibana-pull}154242[#154242] -* Fixes terms aggregation support in wizard for Transforms {kibana-pull}151879[#151879] -* Fixes an issue where you were unable to accept additional dynamic field values for an index template {kibana-pull}150543[#150543] - -Maps:: -* Fixes raster layer is missing in pdf/png exports {kibana-pull}154686[#154686] -* Fixes RegionMap chart type does not work with reporting {kibana-pull}153492[#153492] -* Fixes layers are not displayed in offline environment and map.includeElasticMapsService not set to false {kibana-pull}152396[#152396] - -Monitoring:: -Removes usage for the stats endpoint {kibana-pull}151082[#151082] - -Observability:: -* Adds space-specific feature privileges {kibana-pull}154734[#154734] -* Adds the ability to properly handle NO DATA with multiple conditions with a mix of aggregations and document count thresholds {kibana-pull}154690[#154690] -* Adds additional types to the fields to be use with cardinality aggregation for Metric Threshold Rule {kibana-pull}154197[#154197] -* Adds persistent normalization mode {kibana-pull}153116[#153116] -* Fixes refresh every in the alert search bar {kibana-pull}152246[#152246] - -Platform:: -Fixes badge counter for global settings {kibana-pull}150869[#150869] - -Querying & Filtering:: -* Adds the ability to unload a selected query when it is deleted {kibana-pull}154644[#154644] -* Removes failures in wrong custom timerange {kibana-pull}154643[#154643] - -Reporting:: -* Fixes report generation when image panel is in the end of the layout {kibana-pull}153846[#153846] -* Updates Chromium to 111.0.5555.0 (r1095492) and Puppeteer to 19.7.2 {kibana-pull}153033[#153033] - -Uptime:: -* Fixes default date range on errors page {kibana-pull}155661[#155661] -* Removes the "Beta" labels in Synthetics {kibana-pull}155589[#155589] -* Fixes ML job/rule edit error {kibana-pull}155212[#155212] - -[[release-notes-8.7.1]] -== {kib} 8.7.1 - -Review the following information about the {kib} 8.7.1 release. - -[float] -[[breaking-changes-8.7.1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.7.1, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in the {kib} 8.7.1 release. - -To review the breaking changes in the previous release, check {kibana-ref-all}/8.7/release-notes-8.7.0.html#breaking-changes-8.7.0[8.7.0]. - -[float] -[[enhancement-v8.7.1]] -=== Enhancement -Fleet:: -The agent policy "Host name format" selector is now enabled by default {kibana-pull}154563[#154563] - -[float] -[[fixes-v8.7.1]] -=== Bug fixes -APM:: -* Scoring is now applied by ES {kibana-pull}154627[#154627] -* Fixes the APM Java Agent download link {kibana-pull}154023[#154023] -* Improves the overflow message text {kibana-pull}153676[#153676] - -Canvas:: -* Disables the Edit in Lens action for the legacy savedVisualization function {kibana-pull}154656[#154656] -* Fixes the home page redirect loop {kibana-pull}154568[#154568] -* Fixes an issue where the image upload component was unable to load for image elements {kibana-pull}154385[#154385] - -Dashboard:: -Improves controls flyout performance for data views with a large number of fields {kibana-pull}154004[#154004] - -Discover:: -Fixes aborted request handling in the saved search embeddable {kibana-pull}153822[#153822] - -Elastic Security:: -For the Elastic Security 8.7.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Fixes an issue where the Advanced options toggle in the policy editor was always showing {kibana-pull}154612[#154612] -* Fixes an issue where the warning icon was unable to display in 8.7 {kibana-pull}154119[#154119] -* Adds updates to output logic {kibana-pull}153226[#153226] - -Infrastructure:: -Fixes the inventory table pagination navigation {kibana-pull}153849[#153849] - -Lens & Visualizations:: -Fixes the timezone that *Lens* uses in normalize by unit {kibana-pull}154472[#154472] - -Machine Learning:: -* Change point detection: Fixes applied filters and queries to the charts {kibana-pull}154707[#154707] -* Change point detection: Fixes support for running over relative time range {kibana-pull}154313[#154313] -* Reinstates cold and frozen tier filters for Linux and Windows security modules {kibana-pull}153222[#153222] - -Maps:: -Fixes an issue where geographic filters were unable to work when courier:ignoreFilterIfFieldNotInIndex was enabled {kibana-pull}153816[#153816] - -Monitoring:: -Fixes the CCR read_exceptions alert {kibana-pull}153888[#153888] - -Querying & Filtering:: -Fixes the ability to copy and paste the comma delimeter for multifields {kibana-pull}153772[#153772] - -[[release-notes-8.7.0]] -== {kib} 8.7.0 - -Review the following information about the {kib} 8.7.0 release. - -[float] -[[known-issues-8.7.0]] -=== Known issues - -// tag::known-issue-151698[] -[discrete] -.Observability Overview shows empty User Experience panel -[%collapsible] -==== -*Details* + -Release 8.7.0 has a bug causing the Observability Overview page to show an empty User Experience panel, even when there is RUM data (fixed in {kibana-pull}154419[#154419]). - -*Impact* + -While the User Experience panel on the Observability Overview page is empty, any RUM data will still be available from the User Experience Dashboard. -==== -// end::known-issue-151698[] - -[float] -[[breaking-changes-8.7.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.7.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Removes the fields list sampling setting -[%collapsible] -==== -*Details* + -`lens:useFieldExistenceSampling` has been removed from *Advanced Settings*. The setting allowed you to enable document sampling to determine the fields that are displayed in *Lens*. For more information, refer to {kibana-pull}149482[#149482]. - -*Impact* + -In 8.1.0 and later, {kib} uses the field caps API, by default, to determine the fields that are displayed in *Lens*. -==== - -[discrete] -.Removes the legacy pie chart visualization setting -[%collapsible] -==== -*Details* + -`visualization:visualize:legacyPieChartsLibrary` has been removed from *Advanced Settings*. The setting allowed you to create aggregation-based pie chart visualizations using the legacy charts library. For more information, refer to {kibana-pull}146990[#146990]. - -*Impact* + -In 7.14.0 and later, the new aggregation-based pie chart visualization is available by default. For more information, check <>. -==== - -[discrete] -.Removes the current_upgrades endpoint -[%collapsible] -==== -*Details* + -The `api/fleet/current_upgrades` endpoint has been removed. For more information, refer to {kibana-pull}147616[#147616]. - -*Impact* + -When you upgrade to 8.7.0, use the `/action_status` endpoint. -==== - -[discrete] -.Removes the preconfiguration API route -[%collapsible] -==== -*Details* + -The `/api/fleet/setup/preconfiguration` API, which was released as generally available by error, has been removed. For more information, refer to {kibana-pull}147199[#147199]. - -*Impact* + -Do not use `/api/fleet/setup/preconfiguration`. To manage preconfigured agent policies, use kibana.yml. For more information, check link:https://www.elastic.co/guide/en/kibana/current/fleet-settings-kb.html#_preconfiguration_settings_for_advanced_use_cases[Preconfigured settings]. -==== - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.6/release-notes-8.6.0.html#breaking-changes-8.6.0[8.6.0] | {kibana-ref-all}/8.5/release-notes-8.5.0.html#breaking-changes-8.5.0[8.5.0] | {kibana-ref-all}/8.4/release-notes-8.4.0.html#breaking-changes-8.4.0[8.4.0] | {kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[features-8.7.0]] -=== Features -{kib} 8.7.0 adds the following new and notable features. - -Alerting:: -* Alerts Table- Inspect Modal {kibana-pull}149586[#149586] -* Updates Rule Action Form to add Summary UX {kibana-pull}149367[#149367] -* Users can now search for Cases by ID {kibana-pull}149233[#149233] -* Alerts table row loading state {kibana-pull}148874[#148874] -* Adds default summary message {kibana-pull}148749[#148749] -* AlertsTable - Add persistent controls that show even on empty state {kibana-pull}148735[#148735] -* Connector logs view {kibana-pull}148291[#148291] -* Make action retries configurable {kibana-pull}147876[#147876] -* Adds summary capabilities to the API and execution logic {kibana-pull}147360[#147360] -* Adds flapping state to alert context for action parameters {kibana-pull}147136[#147136] -* Adds triggered actions list in task state {kibana-pull}146183[#146183] -* Moves “Notify When” and throttle from rule to action {kibana-pull}145637[#145637] - -APM:: -* Increases maxTraceItems {kibana-pull}149062[#149062] -* Disables navigation to _other bucket and show warning tooltip {kibana-pull}148641[#148641] -* Show warning if transaction groups are dropped {kibana-pull}148625[#148625] -* Show alert indicator on alerts tab {kibana-pull}148048[#148048] -* Adds latency alert history chart on the Alert details page for APM {kibana-pull}148011[#148011] -* Adds alert annotation and threshold shade on the APM latency chart on the Alert Details page {kibana-pull}147848[#147848] -* Errors group sampler {kibana-pull}147571[#147571] -* Show alert indicator on service inventory page {kibana-pull}147511[#147511] -* Adds alertDetailAppSection to the APM Rule Details page {kibana-pull}143298[#143298] - -Dashboard:: -* Adds the ability to load more options list suggestions when you scroll {kibana-pull}148331[#148331] -* Adds alert filters to the Detection page {kibana-pull}146989[#146989] -* Adds the image embeddable {kibana-pull}146421[#146421] -* Adds the "Convert to lens" action to Dashboard {kibana-pull}146363[#146363] -* Adds a step size to the time slider control {kibana-pull}145033[#145033] -* Adds the ability to sort the options list suggestions {kibana-pull}144867[#144867] - -Elastic Security:: -For the Elastic Security 8.7.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.7.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Adds ability to show FQDN of agents {kibana-pull}150239[#150239] -* Adds `getStatusSummary` query parameter to `GET /api/fleet/agents` API {kibana-pull}149963[#149963] -* Enabling diagnostics feature flag and changed query for files to use upload_id {kibana-pull}149575[#149575] -* Experimental toggles for doc-value-only {kibana-pull}149131[#149131] -* We now display agent metrics, CPU and memory in the agent list table and agent details page {kibana-pull}149119[#149119] -* Implement subcategories in integrations UI {kibana-pull}148894[#148894] -* Added rollout period to upgrade action {kibana-pull}148240[#148240] -* Adds per-policy inactivity timeout + use runtime fields for agent status {kibana-pull}147552[#147552] -* Show dataset combo box for input packages {kibana-pull}147015[#147015] -* Adds UI controls to setting/outputs to configure new shipper {kibana-pull}145755[#145755] - -Infrastructure:: -* Adds link to ingest pipeline dashboard from Stack Monitoring {kibana-pull}149721[#149721] - -Integrations:: -* User friendly UX added alongside advanced yaml editor {kibana-pull}147900[#147900] -* Custom fleet policy UX for new integration (cloud defend v1) {kibana-pull}147300[#147300] - -Kibana Home & Add Data:: -Self-managed {kib} instances now have a link to instructions for migrating self-managed clusters to Elastic Cloud {kibana-pull}145523[#145523] - -Lens & Visualizations:: -Adds the share link feature in *Lens* {kibana-pull}148829[#148829] - -Machine Learning:: -* Adds change point detection feature {kibana-pull}150308[#150308] -* Remove Technical Preview label from the Trained Models UI {kibana-pull}149715[#149715] -* Adds a new memory usage by job and by model view {kibana-pull}149419[#149419] -* Allow Anomaly Detection geo jobs to be created from maps dashboard {kibana-pull}147797[#147797] -* Adds geo fields support for Unified field list, add statistics flyover to Anomaly detection job creation wizards {kibana-pull}147322[#147322] -* Anomaly Detection wizards: adds geo job wizard {kibana-pull}147043[#147043] - -Management:: -* Adds field statistics popovers for Data Frame Analytics & Transform creation wizards {kibana-pull}149879[#149879] -* Transforms: Shows health status of transform in UI {kibana-pull}150359[#150359] - -Monitoring:: -* Adds duration configuration to Stack Monitoring Cluster Health rule {kibana-pull}147565[#147565] - -Observability:: -* Adds alert summary widget to overview page {kibana-pull}149581[#149581] -* Adds AlertSummaryWidget full-size on the Alerts page {kibana-pull}148539[#148539] -* Additional context for log threshold rule {kibana-pull}148503[#148503] -* Adds charts to Alert Summary Widget {kibana-pull}148143[#148143] -* Adds rule details locator and make AlertSummaryWidget clickable {kibana-pull}147103[#147103] -* Adds groupByKeys context to recovered alerts for Log Threshold Rule and Metric Threshold Rule {kibana-pull}146874[#146874] -* Adds new context variable called groupByKeys {kibana-pull}146633[#146633] -* Adds new context variable for group by keys {kibana-pull}145654[#145654] -* Adds Platinum license check for SLO APIs and SLO pages {kibana-pull}149055[#149055] -* Create SLO / Edit SLO Form - Custom KQL {kibana-pull}147843[#147843] -* SLO List {kibana-pull}147447[#147447] - -Platform:: -New trigger actions for chart legends and table cell actions {kibana-pull}146779[#146779] - -Querying & Filtering:: -* Insight filter builder form as markdown plugin {kibana-pull}150363[#150363] -* Adds the ability to support complex filters with AND/OR relationships {kibana-pull}143928[#143928] - -Security:: -* Adds the ability to allow administrators to limit the number of concurrent user sessions with `xpack.security.session.сoncurrentSessions.maxSessions` {kibana-pull}147442[#147442] -* API Keys can now be updated with new role descriptors and metadata in the API Keys Management screen {kibana-pull}146237[#146237] - -For more information about the features introduced in 8.7.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.7.0]] -=== Enhancements and bug fixes -For detailed information about the 8.7.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.7.0]] -=== Enhancements -Alerting:: -* Bring flapping status and settings in o11y {kibana-pull}150483[#150483] -* RenderCustomActionsRow with named params instead of args {kibana-pull}149304[#149304] -* Ram 145739 use bulk enable disable in UI {kibana-pull}145928[#145928] -* Create generic retry if function {kibana-pull}145713[#145713] -* Return rules from bulk enable {kibana-pull}145391[#145391] -* Create bulk disable endpoint {kibana-pull}145179[#145179] -* Adding group by options to ES query rule type {kibana-pull}144689[#144689] - -APM:: -* Adds APM alert status to the alerts table {kibana-pull}150500[#150500] -* Promotes the Alerts tab in the APM UI to GA {kibana-pull}150528[#150528] -* Switches get environment function to use `terms_enum` api {kibana-pull}150175[#150175] -* Uses (rolled up) service metrics for service inventory {kibana-pull}149938[#149938] -* Adds KQL filter bar to the service map page {kibana-pull}149900[#149900] -* Integrates Alert search bar in the alerts tab {kibana-pull}149610[#149610] -* Adds Azure Functions support in the APM UI {kibana-pull}149479[#149479] -* Adds a 404 page {kibana-pull}149471[#149471] -* Adds single-click setup from Kibana {kibana-pull}148959[#148959] -* Updates sparklines to support the bar chart graph style {kibana-pull}148702[#148702] -* Adds a flamegraph legend {kibana-pull}147910[#147910] -* Adds API keys to APM package policies {kibana-pull}147650[#147650] -* Only renders waterfall items up until 3 levels {kibana-pull}147569[#147569] -* Improves span links navigation {kibana-pull}147426[#147426] -* Updates default refresh interval to 60 seconds {kibana-pull}146791[#146791] -* Adds pagination to source map API {kibana-pull}145959[#145959] -* Adds ability to offset point labels on maps {kibana-pull}145773[#145773] - -Cases:: -* Adds new column `Updated on ` in `all cases list ` table. This column can be sorted and can persist sorting options {kibana-pull}149116[#149116] -* Improves the design of all cases list select modal {kibana-pull}149851[#149851] -* Adds a button to Case Detail and All Cases List to copy case UUIDs to the clipboard {kibana-pull}148962[#148962] -* Adds the ability to persist sorting, severity filter, and status filter in the URL and local storage for the all cases list {kibana-pull}148549[#148549] -* Adds the ability to allow sorting by status, severity, and title in the all-cases list {kibana-pull}148193[#148193] -* See "My recently assigned cases" to the recent cases widget of Overview dashboard in Security {kibana-pull}147763[#147763] -* Adds the ability to bulk edit assignees on multiple cases {kibana-pull}146907[#146907] -* Adds the ability to save draft comments {kibana-pull}146327[#146327] - -Dashboard:: -* Add new panel settings option to change the title, description, and time range for panels {kibana-pull}148301[#148301] -* Anchor time slider to start {kibana-pull}148028[#148028] -* Show document count beside options list suggestions {kibana-pull}146241[#146241] - -Discover:: -* Show "Copy value" button for any grid cell {kibana-pull}149525[#149525] -* Align field list filters UI between Discover and Lens {kibana-pull}148547[#148547] -* Persist field list sections state in local storage {kibana-pull}148373[#148373] -* Enable adhoc data views creation from no data views state {kibana-pull}147850[#147850] -* Adds a way to quickly expand time range from "No results" screen {kibana-pull}147195[#147195] -* Optimize checking for multifields during grid rendering {kibana-pull}145698[#145698] -* Align field list sections between Discover and Lens {kibana-pull}144412[#144412] -* Update Discover's histogram to use Lens, and add support for breaking down the histogram by top values of a selected field {kibana-pull}143117[#143117] - -Elastic Security:: -For the Elastic Security 8.7.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.7.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Fixes discard changes link taking user to "page not found" {kibana-pull}150174[#150174] -* Adds filebeat_input index to agent policy default {kibana-pull}149974[#149974] -* Bugfix: Apply namespace from agent policy if there is one when adding integration {kibana-pull}149949[#149949] -* Agent List: Inform users when agents have become inactive since last page view {kibana-pull}149226[#149226] -* Experimental toggles for doc-value-only {kibana-pull}149131[#149131] -* Create index templates and ingest pipeline at package policy creation time for input packages {kibana-pull}148772[#148772] -* Do not allow namespace or dataset to be edited for input only package policies {kibana-pull}148422[#148422] -* Adds deprecation warning to unenrollment timeout agent policy setting {kibana-pull}147963[#147963] -* Adds active filter count to agent status filter {kibana-pull}147821[#147821] - -Kibana UI:: -The list view in Dashboard, Visualize Library, Maps, and Graph now stores the latest state of the table in the URL {kibana-pull}145517[#145517] - -Lens & Visualizations:: -* Enable nice rounding for scalar axis in *Lens* {kibana-pull}149388[#149388] -* Don't block render on missing field in *Lens* {kibana-pull}149262[#149262] -* Enable previous time shift when using a date histogram in *Lens* {kibana-pull}149126[#149126] -* Displays the annotation icon on the annotation dimension label in *Lens* {kibana-pull}147686[#147686] -* Extend explore data in Discover/open in Discover drilldown to visualizations with annotations and reference lines in *Lens* {kibana-pull}147541[#147541] -* Moves the mosaic/waffle charts into GA in *Lens* {kibana-pull}146261[#146261] -* Color by slice for multi-metric partition chart in *Lens* {kibana-pull}145948[#145948] -* Save function to integrate listing Inspector {kibana-pull}145381[#145381] -* Absolute time shift support in formula in *Lens* {kibana-pull}144564[#144564] - -Machine Learning:: -* Adding multi-modal distribution to the explain anomaly results {kibana-pull}150014[#150014] -* Adding anomaly explanation help link {kibana-pull}149674[#149674] -* Data Frame Analytics results view: add link to custom visualizations for viewing scatterplot charts {kibana-pull}149647[#149647] -* Explain Log Rate Spikes: highlight field pairs unique to groups in expanded row {kibana-pull}148601[#148601] -* Adds delete annotations option to delete and reset job modals {kibana-pull}147537[#147537] -* Adds override for data which doesn't contain a time field {kibana-pull}147504[#147504] -* Adds responsive layout to Index data visualizer, fix doc count chart margin {kibana-pull}147137[#147137] -* Use anomaly score explanation for chart tooltip multi-bucket impact {kibana-pull}146866[#146866] -* Remove beta badge for Field statistics table in Discover {kibana-pull}140991[#140991] - -Management:: -* Transforms: Adds "Use full data" button to transform creation wizard {kibana-pull}150030[#150030] -* Adds override field to Dot expander processor form {kibana-pull}149599[#149599] -* Adds fields to Append Ingest Pipeline processor form {kibana-pull}149520[#149520] -* Adds support for S3 intelligent tiering in Snapshot and Restore {kibana-pull}149129[#149129] -* Transforms: Adds date picker to transform wizard for data view with time fields {kibana-pull}149049[#149049] -* Use data view formatter for fields preview in Edit field flyout {kibana-pull}148446[#148446] -* Adds a new global ui settings client {kibana-pull}146270[#146270] -* Update Transform installation mechanism to support upgrade paths {kibana-pull}142920[#142920] - -Maps:: -Adds support for hex aggregation with geo_shape field {kibana-pull}143890[#143890] - -Monitoring:: -* Link to individual host page on hosts view {kibana-pull}147380[#147380] -* Adds support for beats datastream patterns {kibana-pull}146184[#146184] - -Observability:: -* Custom equation editor for Metric Threshold Rule {kibana-pull}148732[#148732] -* Adds context.originalAlertState to the Metric Threshold and Inventory Threshold recovery context {kibana-pull}147928[#147928] - -Querying & Filtering:: -Allows case sensitive option on multiselection filters input {kibana-pull}149570[#149570] - -Security:: -* The default `csp.disableUnsafeEval` value is now `true`, so now the `unsafe-eval` source expression isn't present by default in the Kibana Content Security Policy (CSP) {kibana-pull}150157[#150157] -* Adds client IP address to Kibana audit log {kibana-pull}148055[#148055] -* Adds `Cross-Origin-Opener-Policy: same-origin` HTTP header to Kibana default response headers {kibana-pull}147874[#147874] - -Sharing:: -Enables multiple values filtering on tooltip actions {kibana-pull}148372[#148372] - -Uptime:: -TLS rule allow monitors filtering {kibana-pull}150339[#150339] - -[float] -[[fixes-v8.7.0]] -=== Bug fixes -Alerting:: -* Event log failure message {kibana-pull}149355[#149355] -* Optimize alerting task runner for persistent (non-lifecycle rule types) {kibana-pull}149043[#149043] -* Failed test x-pack/plugins/triggers_actions_ui/public/application/lib/transformActionVariables {kibana-pull}147579[#147579] -* Rule create/update form re-render {kibana-pull}147221[#147221] -* Hiding all features in a space causes rules to stop running {kibana-pull}146188[#146188] -* Send complete test data to xMatters, so it can create an alert {kibana-pull}145431[#145431] -* Hiding all features in a space causes rules to stop running {kibana-pull}145372[#145372] - -APM:: -* Latency threshold rule's threshold context variable should use milliseconds instead of microseconds {kibana-pull}150234[#150234] -* Cannot read/write APM Settings Indices page with minimally-privileged user {kibana-pull}150107[#150107] -* Adds `service.environment` log correlation {kibana-pull}150065[#150065] -* Remove `host.name` correlation {kibana-pull}150005[#150005] -* Fixes display of stacktrace with EuiCodeBlocks {kibana-pull}149911[#149911] -* Alert rules: The transaction type and environment options are not filtered by the selected service {kibana-pull}149849[#149849] -* Unable to create Latency threshold rule for All services or All Transaction types {kibana-pull}149735[#149735] -* Adds language specific headers {kibana-pull}149400[#149400] -* Adds stacktrace support for php {kibana-pull}149122[#149122] -* Tech preview feature on General settings {kibana-pull}148996[#148996] -* Fixes APM sourcemap upload route {kibana-pull}148508[#148508] -* Change order of tabs {kibana-pull}147518[#147518] -* Show values of highlighted sample in TopN chart {kibana-pull}147431[#147431] -* Synchronous Anomaly detection jobs creation {kibana-pull}145969[#145969] -* Change default refresh interval to 60 seconds {kibana-pull}144389[#144389] - -Dashboard:: -* Retain maximized panel on link/unlink from library {kibana-pull}150405[#150405] -* Fixes Unlink from Library / Save to Library for Maximized Panel {kibana-pull}150338[#150338] -* Fixes Darktheme is missing from add drilldowns panel {kibana-pull}147270[#147270] -* Removes options list `"Allow "` toggles {kibana-pull}147216[#147216] - -Design:: -* Fixes a11y issue with dev tool tabs {kibana-pull}149349[#149349] -* Fixes a11y issues with cross cluster replication flyouts {kibana-pull}149069[#149069] -* Fixes a11y for snapshot policy flyout {kibana-pull}148972[#148972] - -Discover:: -* Fixes Phrase_filter query for scripted fields {kibana-pull}148943[#148943] -* Use Discover locator for alert results link {kibana-pull}146403[#146403] -* Validate if Data View time field exists on Alert creation / editing {kibana-pull}146324[#146324] - -Elastic Security:: -For the Elastic Security 8.7.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.7.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Truncate long names in agents table {kibana-pull}150069[#150069] -* Update kubernetes templates for elastic-agent {kibana-pull}143275[#143275] - -Infrastructure:: -Remove ts-ignore annotation {kibana-pull}145759[#145759] - -Lens & Visualizations:: -* Always use resolved time range when computing Top values order agg with timeshifts in *Lens* {kibana-pull}150418[#150418] -* Fixes problem with timeshift in formula and breakdown in *Lens* {kibana-pull}150406[#150406] -* Fixes nested other bucket for empty string {kibana-pull}150321[#150321] -* Fixes chart padding on reference lines/annotations icon on the left side in *Lens* {kibana-pull}149573[#149573] -* Fixes the partition legend actions header format problem in *Lens* {kibana-pull}149114[#149114] -* Automatically enable show array values for non-numeric runtime fields in *Lens* {kibana-pull}149025[#149025] -* Always display the major label {kibana-pull}148999[#148999] -* Adds multi fields support to selected fields list in *Lens* {kibana-pull}148899[#148899] -* Allows cleaning up of the filters aggregatiob custom label in *Lens* {kibana-pull}148535[#148535] -* Order date fields first on discover drilldown in *Lens* {kibana-pull}146786[#146786] -* Fixes the syncing of other series color in *Lens* {kibana-pull}146785[#146785] - -Machine Learning:: -* Data Frame Analytics creation wizard: ensure includes table is populated correctly on job type change {kibana-pull}150112[#150112] -* Data Frame Analytics maps view: Fix update of map when selecting results index node {kibana-pull}149993[#149993] -* Fixes Typical to actual connector lines in AnomalyLayer have dot halfway {kibana-pull}149270[#149270] -* Fixes responsive behaviour of page header with date picker {kibana-pull}149073[#149073] -* Delayed data visualization: ensure y-axis count is visible {kibana-pull}148982[#148982] -* Allow dedicated index override in JSON editor {kibana-pull}148887[#148887] -* Anomaly Detection: Fix button switch issue with unmounted component {kibana-pull}148239[#148239] -* Anomaly Detection: Fix Anomaly Explorer context handling {kibana-pull}148231[#148231] -* Fixes modal titles {kibana-pull}147855[#147855] - -Management:: -* Replace global `GET /_mapping` request with `GET /_mapping` {kibana-pull}147770[#147770] -* Fixes form validation UX when the same data view name already exists {kibana-pull}146126[#146126] -* The field preview in the data view field editor now works for all fields, whether or not they are in the document's `_source` {kibana-pull}145943[#145943] - -Maps:: -* Fixes Kibana maps should not override the sort field if not provided by the user {kibana-pull}150400[#150400] -* Show embeddable filters in spatial layer {kibana-pull}150078[#150078] -* Fixes Kibana Maps UI upload geojson failure should be received as such {kibana-pull}149969[#149969] -* Verify CRS for geojson upload {kibana-pull}148403[#148403] - -Monitoring:: -Use UI time range filter in logstash pipeline details query {kibana-pull}150032[#150032] - -Observability:: -Adds ALERT_RULE_PARAMETERS to the common fields in Rule Registry {kibana-pull}147458[#147458] - -Platform:: -Support cgroup v2 in core metric collection {kibana-pull}147082[#147082] - -[[release-notes-8.6.1]] -== {kib} 8.6.1 - -Review the following information about the {kib} 8.6.1 release. - -[float] -[[breaking-changes-8.6.1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.6.1. - -{kibana-ref-all}/8.5/release-notes-8.5.0.html#breaking-changes-8.5.0[8.5.0] | {kibana-ref-all}/8.4/release-notes-8.4.0.html#breaking-changes-8.4.0[8.4.0] | {kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[enhancement-v8.6.1]] -=== Enhancements -Alerting:: -* Create OAS for get rule types and get alerting framework health {kibana-pull}148774[#148774] -* Create open API specification for create/update connector {kibana-pull}148691[#148691] -* Create open API specification for disable/enable rule and mute/unmute all alerts {kibana-pull}148494[#148494] - -Elastic Security:: -For the Elastic Security 8.6.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -[float] -[[fixes-v8.6.1]] -=== Bug fixes -Canvas:: -Replaces React.lazy and withSuspense with async imports in expressions plugins {kibana-pull}147693[#147693] - -Dashboard:: -* Adds styling to allow clickable *TSVB* markdown images {kibana-pull}147802[#147802] -* Changes the visibility of the panel filters action {kibana-pull}146335[#146335] - -Discover:: -* Adds support for case-insensitive search in Document Viewer {kibana-pull}148312[#148312] -* Fixes the field stats for the epoch time format {kibana-pull}148288[#148288] - -Elastic Security:: -For the Elastic Security 8.6.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Fixes missing policy Id in installation URL for cloud integrations {kibana-pull}149243[#149243] -* Fixes package installation APIs to install packages without a version {kibana-pull}149193[#149193] -* Fixes issue where the latest GA version could not be installed if there was a newer prerelease version in the registry {kibana-pull}149133[#149133] and {kibana-pull}149104[#149104] - -Infrastructure:: -Fixes an issue where the summary request piled up {kibana-pull}148670[#148670] - -Machine Learning:: -* Fixes the bucket span estimator in the advanced wizard {kibana-pull}149008[#149008] -* Fixes the transforms JSON display {kibana-pull}147996[#147996] - -Management:: -* Fixes the runtime field format editor {kibana-pull}148497[#148497] -* Improves the check for response size in the `/autocomplete_entities` endpoint {kibana-pull}148328[#148328] - -Maps:: -Fixes an issue where Maps was unable to initialize the time range from URLs {kibana-pull}148465[#148465] - -Platform:: -Fixes the server-side import of the contract `CloudStart` {kibana-pull}149203[#149203] - -Uptime:: -* ssl fields are now omitted when ssl is disabled {kibana-pull}149087[#149087] -* Adds the ability to disable throttling for project monitors {kibana-pull}148669[#148669] - -[[release-notes-8.6.0]] -== {kib} 8.6.0 - -Review the following information about the {kib} 8.6.0 release. - -[float] -[[known-issues-8.6.0]] -=== Known issues - -[discrete] -[[known-issue-146020]] -.Attempting to create APM latency threshold rules from the Observability rules page fail -[%collapsible] -==== -*Details* + -When you attempt to create an APM latency threshold rule in **Observability** > **Alerts** > **Rules** for all services or all transaction types, the request will fail with a `params invalid` error. - -*Impact* + -This known issue only impacts the Observability Rules page. To work around this issue, create APM latency threshold rules in the APM Alerts and Rules dialog. See {observability-guide}/apm-alerts.html[Alerts and rules] for detailed instructions. -==== - -[float] -[[breaking-changes-8.6.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.6.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Changes the `histogram:maxBars` default setting -[%collapsible] -==== -*Details* + -To configure higher resolution data histogram aggregations without changing the *Advanced Settings*, the default histogram:maxBars setting is now 1000 instead of 100. For more information, refer to {kibana-pull}143081[#143081]. - -*Impact* + -For each {kibana-ref}/xpack-spaces.html[space], complete the following to change *histogram:maxBars* to the previous default setting: - -. Open the main menu, then click *Stack Management > Advanced Settings*. -. Scroll or search for *histogram:maxBars*. -. Enter `100`, then click *Save changes*. -==== - -[discrete] -.CSV reports use PIT instead of Scroll -[%collapsible] -==== -*Details* + -CSV reports now use PIT instead of Scroll. Previously generated CSV reports that used an index alias with alias-only privileges, but without privileges on the alias referenced-indices will no longer generate. For more information, refer to {kibana-pull}158338[#158338]. - -*Impact* + -To generate CSV reports, grant `read` privileges to the underlying indices. -==== - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.5/release-notes-8.5.0.html#breaking-changes-8.5.0[8.5.0] | {kibana-ref-all}/8.4/release-notes-8.4.0.html#breaking-changes-8.4.0[8.4.0] | {kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[features-8.6.0]] -=== Features -{kib} 8.6.0 adds the following new and notable features. - -Alerting:: -* Notify users by email when assigned to a case {kibana-pull}144391[#144391] -* Adds flapping state object and interface in AAD index and Event Log {kibana-pull}143920[#143920] -* Change Alerts > Actions execution order {kibana-pull}143577[#143577] -* Adds the ability to remove alerts attached to a case {kibana-pull}143457[#143457] -* This feature allows users to create and close alerts within Opsgenie {kibana-pull}142411[#142411] -* Adds filter field to index threshold rule type {kibana-pull}142255[#142255] -* Allow users to see event logs from all spaces they have access to {kibana-pull}140449[#140449] - -Elastic Security:: -For the Elastic Security 8.6.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.6.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Differentiate kubernetes integration multipage experience {kibana-pull}145224[#145224] -* Adds prerelease toggle to Integrations list {kibana-pull}143853[#143853] -* Adds link to allow users to skip multistep add integration workflow {kibana-pull}143279[#143279] - -Infrastructure:: -Adds support for the the Unified Search Bar for Query input {kibana-pull}143222[#143222] - -Lens & Visualizations:: -Adds support for trend lines in *Lens* metric visualizations {kibana-pull}141851[#141851] - -Machine Learning:: -* Trained model testing with index data {kibana-pull}144629[#144629] -* Adding anomaly score explanations {kibana-pull}142999[#142999] - - -Monitoring:: -Collect metrics about the active/idle connections to ES nodes {kibana-pull}141434[#141434] - -Observability:: -* Integrate alert search bar on rule details page {kibana-pull}144718[#144718] -* Adds additional context to recovered alerts of Infrastructure rules {kibana-pull}144683[#144683] -* Adds list of containers in context variable of Inventory rule {kibana-pull}144526[#144526] -* Adds new contextual attributes to Infrastructure - Metric threshold rule {kibana-pull}143001[#143001] -* Adds alert details page feature flag by App {kibana-pull}142839[#142839] -* Adds new contextual attributes to Infrastructure - Inventory Rule {kibana-pull}140598[#140598] - -Osquery:: - -Allows users to deploy Osquery across all {agent} policies or on specified policies only {kibana-pull}143948[#143948] - -Platform:: -Adds notifications plugin, offering basic email service {kibana-pull}143303[#143303] - -Security:: -Adds the ability to show sub-feature privileges when using the Basic license {kibana-pull}142020[#142020] - -Uptime:: -Adds `created_at` field in saved objects {kibana-pull}143507[#143507] - -For more information about the features introduced in 8.6.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.6.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.6.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.6.0]] -=== Enhancements -Alerting:: -* Clone rule {kibana-pull}144741[#144741] -* Remove errors and warning in triggers_actions_ui jest test {kibana-pull}144443[#144443] - -* Increase the default table size of the cases table to 10 {kibana-pull}144228[#144228] -* Bulk enable rules api {kibana-pull}144216[#144216] -* Create bulk delete on rules front {kibana-pull}144101[#144101] -* Improve Task Manager’s retry logic for ad-hoc tasks {kibana-pull}143860[#143860] -* Increases the max length limit of the case title to 160 characters {kibana-pull}143664[#143664] -* Adds the ability to bulk edit tags in the cases table {kibana-pull}143450[#143450] -* Filter cases without assignees {kibana-pull}143390[#143390] -* Make actions retry when encountering failures {kibana-pull}143224[#143224] -* Adds a backlink to cases when pushing in external services {kibana-pull}143174[#143174] -* Move Connectors to own page {kibana-pull}142485[#142485] -* 142183 create bulk delete on rules {kibana-pull}142466[#142466] -* Allow `_source` field for ES DSL query rules {kibana-pull}142223[#142223] -* Update rule status {kibana-pull}140882[#140882] - -APM:: -* Adds pie charts displaying the most used mobile devices, operating systems, etc. {kibana-pull}144232[#144232] -* Adds the ability to filter mobile APM views {kibana-pull}144172[#144172] -* Adds average latency map to the mobile service overview {kibana-pull}144127[#144127] -* Adds new options to APM central configuration {kibana-pull}143668[#143668] -* Adds a trace waterfall to the dependency operation detail view {kibana-pull}143257[#143257] -* Adds a configuration table above code sample in getting started guide {kibana-pull}143178[#143178] -* Adds improvements to the AWS Lambda metrics view {kibana-pull}143113[#143113] -* Adds total APM size and perecent of disk space used to storage explorer {kibana-pull}143179[#143179] -* [Technical preview] Adds the ability to display a critical path for a single trace {kibana-pull}143735[#143735] -* [Technical preview] Adds the agent explorer inventory and detail page {kibana-pull}143844[#143844] - -Dashboard:: -* Adds unmapped runtime field support to options list {kibana-pull}144947[#144947] -* Adds "Exists" functionality to options list {kibana-pull}143762[#143762] -* Adds `excludes` toggle to options list {kibana-pull}142780[#142780] -* Adds support for IP field to options list {kibana-pull}142507[#142507] -* Adds option to disable cursor sync on dashboards {kibana-pull}143355[#143355] - -Discover:: -* Adds the ability to edit ad hoc data views without permissions {kibana-pull}142723[#142723] -* Enables `esQuery` alert for adhoc data views {kibana-pull}140885[#140885] - -Elastic Security:: -For the Elastic Security 8.6.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.6.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -Adds `?full` option to get package info endpoint to return all package fields {kibana-pull}144343[#144343] - -Infrastructure:: -* Use the Unified Search Bar for date range selection {kibana-pull}144351[#144351] -* Adds network traffic to Hosts table {kibana-pull}142137[#142137] - -Kibana UI:: -Updates the Dashboard, Visualize Library, and Maps pages with enhanced tag filtering {kibana-pull}142108[#142108] - -Lens & Visualizations:: -* Rearranges the options in *Lens* {kibana-pull}144891[#144891] -* Adds the ability to open *TSVB* tables in *Lens* {kibana-pull}143946[#143946] -* Adds advanced params to the formula API in *Lens* {kibana-pull}143876[#143876] -* Adds the ability to display value labels on histogram and stacked charts in *Lens* {kibana-pull}143635[#143635] -* Distinguishes the adhoc data views from the permanent data views in the dropdowns {kibana-pull}143525[#143525] -* Adds the ability to filter metrics in the *Lens* data table {kibana-pull}143219[#143219] -* Adds support for navigate Variance aggregations in *Lens* {kibana-pull}143209[#143209] -* Adds selected field accordion to the fields list in *Lens* {kibana-pull}143175[#143175] -* Adds the ability to open aggregation-based xy charts in *Lens* {kibana-pull}142936[#142936] -* Adds the ability to open aggregation-based Gauge and Goal visualizations in *Lens* {kibana-pull}142838[#142838] -* Enables cursor syncronization in *Lens* heatmaps {kibana-pull}142821[#142821] -* Adds a reduced time range option for formula in *Lens* {kibana-pull}142709[#142709] -* Adds the ability to open aggregation-based metric visualization in *Lens* {kibana-pull}142561[#142561] -* Adds the ability to edit data views in the *Lens* flyout {kibana-pull}142362[#142362] -* Adds conditional operations in the *Lens* formula {kibana-pull}142325[#142325] -* Adds the ability to explore fields in Discover from *Lens* {kibana-pull}142199[#142199] -* Adds the ability to open *TSVB* Gauge visualizations in *Lens* {kibana-pull}142187[#142187] -* Adds new defaults function in *Lens* {kibana-pull}142087[#142087] -* Adds support for mustache context variables with periods {kibana-pull}143703[#143703] -* Adds explore matching indices to data view menu {kibana-pull}141807[#141807] -* Adds control in the *Lens* annotations layer menu for global filters {kibana-pull}141615[#141615] -* Adds field filter to popover in *Lens* {kibana-pull}141582[#141582] -* Improves the performance for large formulas in *Lens* {kibana-pull}141456[#141456] -* Improves the Quick function in-product assistance in *Lens* {kibana-pull}141399[#141399] -* Adds bit formatter in *Lens* {kibana-pull}141372[#141372] -* Adds the ability to open aggregation-based pie visualizations in *Lens* {kibana-pull}140879[#140879] -* Adds the ability to open *TSVB* metric visualizations in *Lens* {kibana-pull}140878[#140878] -* Adds the ability to open aggregation-based table visualizations in *Lens* {kibana-pull}140791[#140791] -* Adds the ability to allow date functions in formula {kibana-pull}143632[#143632] - -Machine Learning:: -* Data Frame Analytics: Highlight filtered data in scatterplot charts {kibana-pull}144871[#144871] -* Allow updates for number of allocations and priority for trained model deployments {kibana-pull}144704[#144704] -* Switch from normal sampling to random sampler for Index data visualizer table {kibana-pull}144646[#144646] -* Explain Log Rate Spikes: Replace chunks of queries with concurrent queue {kibana-pull}144220[#144220] -* Explain Log Rate Spikes: Allow to continue failed stream {kibana-pull}143301[#143301] -* Entity filter for the Notifications page {kibana-pull}142778[#142778] -* Show an info callout for new notifications {kibana-pull}142245[#142245] -* Adding dashboard custom url to lens created jobs {kibana-pull}142139[#142139] -* Adds ML open API output to appendix {kibana-pull}141556[#141556] - -Management:: -Adds missing geo aggs to autocomplete in Console {kibana-pull}141504[#141504] - -Maps:: -* Adds the ability to invert color ramp and size {kibana-pull}143307[#143307] -* Adds layer groups {kibana-pull}142528[#142528] -* Adds the ability to hide or show all layers {kibana-pull}141495[#141495] - -Observability:: -* Adds kibana.alert.time_range field to Alert-As-Data mappings and populate it {kibana-pull}141309[#141309] -* Alert summary widget new design {kibana-pull}141236[#141236] -* Adds histogram support for avg, max, min, sum and percentiles {kibana-pull}139770[#139770] - -Platform:: -Adds maxIdleSockets and idleSocketTimeout to Elasticsearch config {kibana-pull}142019[#142019] - -Security:: -* Adds a read-only mode to the User management screen for users with `read_security` cluster privilege {kibana-pull}143438[#143438] -* Adds a read-only mode to the API keys management screen for users with `read_security` cluster privilege {kibana-pull}144923[#144923] -* Adds `user.id` field to Kibana audit log {kibana-pull}141092[#141092] - -Uptime:: -* Allow using AND for tags filtering {kibana-pull}145079[#145079] -* Adds monitor detail flyout {kibana-pull}136156[#136156] - -[float] -[[fixes-v8.6.0]] -=== Bug fixes -Alerting:: -* Fixes logger text and fix bulk error type {kibana-pull}144598[#144598] -* Flaky bulkDisable tasks functional test {kibana-pull}144405[#144405] -* Adding back unknown outcome filter {kibana-pull}143546[#143546] -* Fixing flaky test in x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list·ts {kibana-pull}142855[#142855] -* Rule run history displays success with a message when the rule status is warning {kibana-pull}142645[#142645] -* Elasticsearch query rule type allows SIZE: 0, but flags as error on re-edit {kibana-pull}142225[#142225] -* Rules and Connectors: Current page in breadcrumbs shows as link {kibana-pull}141838[#141838] -* Move save button into connector config form {kibana-pull}141361[#141361] - -APM:: -* Show a recommended minimum size when going below 5 minutes {kibana-pull}144170[#144170] -* Fixes ML permissions by removing usage of `canAccessML` {kibana-pull}143631[#143631] -* Fallback to terms aggregation search if terms enum doesn’t return results {kibana-pull}143619[#143619] -* Fixes bug that causes alert expression to not close {kibana-pull}143531[#143531] -* Fixes `apm.transaction_duration` alert to aggregrate over service environment {kibana-pull}143238[#143238] -* Fixes broken latency and services layout {kibana-pull}143453[#143453] -* Fixes metadata API environment filter {kibana-pull}144472[#144472] - -Dashboard:: -* The extra reload caused by Controls is now skipped {kibana-pull}142868[#142868] -* Modifies the state shared in dashboard permalinks {kibana-pull}141985[#141985] - -Discover:: -* Fixes theme for Alerts popover {kibana-pull}145390[#145390] -* Improves the no data views state for `esQuery` alert {kibana-pull}145052[#145052] -* Updates the data view id on adhoc data view change {kibana-pull}142069[#142069] -* Improves the error and fix app state when updating data view ID in the URL to an invalid ID {kibana-pull}141540[#141540] - -Elastic Security:: -For the Elastic Security 8.6.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.6.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -Only show fleet managed data streams on data streams list page {kibana-pull}143300[#143300] - -Infrastructure:: -Adds disk latency metrics to hosts table {kibana-pull}144312[#144312] - -Kibana Home & Add Data:: -* Updates the examples {kibana-pull}141265[#141265] - -Lens & Visualizations:: -* All saved queries are now returned on the list instead of only the first 50 {kibana-pull}145554[#145554] -* The baseTheme is now always included with the charts theme {kibana-pull}145401[#145401] -* Do not throw on undefined sorting column referenced in *Lens* {kibana-pull}144716[#144716] -* Fixes suggestion bug in *Lens* {kibana-pull}144708[#144708] -* The reference layer is now excluded from the cursor sync hook in *Lens* {kibana-pull}144384[#144384] -* Improves the embeddable warnings placement in *Lens* metric visualizations {kibana-pull}144368[#144368] -* Fixes the list control popover background color in dark mode {kibana-pull}144204[#144204] -* The unused dimension label from the tooltip in *Lens* is now hidden {kibana-pull}143721[#143721] -* Improves the default text for the controls options list {kibana-pull}143413[#143413] -* Fixes styling issues in *Vega* {kibana-pull}143168[#143168] -* Fixes an issue where the shard failure notices made *Lens* unusable {kibana-pull}142985[#142985] -* Fixes the syncing for colors and tooltips {kibana-pull}142957[#142957] -* Updates the label for Time field annotations in *TSVB* {kibana-pull}142452[#142452] -* Fixes an issue where empty annotation query strings in *TSVB* and *Lens* displayed different results {kibana-pull}142197[#142197] -* Drag and drop capabilities of a single element in *Lens* is no longer allowed {kibana-pull}141793[#141793] -* Fixes the ability to close the settings popover with a click in *Lens* {kibana-pull}141272[#141272] - -Machine Learning:: -* Fixes the default time range on the Notifications page {kibana-pull}145578[#145578] -* Data Frame Analytics maps view: ensure nodes reload correctly after using timepicker refresh {kibana-pull}145265[#145265] -* Explain Log Rate Spikes: Fix applying overall params to histogram queries {kibana-pull}144219[#144219] -* Calculate model memory limit for Lens created jobs {kibana-pull}143456[#143456] -* Explain Log Rate Spikes: fix chart showing as empty when filter matches field/value pair in hovered row {kibana-pull}142693[#142693] - -Management:: -* Fixes nested formatter for terms {kibana-pull}144543[#144543] -* Cache ad-hoc data views to avoid repeated field list calls {kibana-pull}144465[#144465] -* In the case of 2 or more panels on the dashboard, TSVB renderComplete fires 2 times {kibana-pull}143999[#143999] -* Shard failure notifications have been reduced when many queries fail at the same time {kibana-pull}131776[#131776] - -Maps:: -* Fixes an issue where the Time Slider text was not working properly with Dark Mode {kibana-pull}145612[#145612] -* Adds ungroup layers action {kibana-pull}144574[#144574] - -Observability:: -Fixes alerts' blank page in case of invalid query string {kibana-pull}145067[#145067] - -Observability Home:: -* Use bucketSize from request options for overview query {kibana-pull}145032[#145032] -* Solution nav with no data page {kibana-pull}144280[#144280] - -Querying & Filtering:: -* Fixes an issue with autocomplete value suggestions where the date range was sometimes incorrectly applied {kibana-pull}144134[#144134] -* Fixes Moment.js timezone error when defining a range filter {kibana-pull}143213[#143213] - -Reporting:: -* Fixed a bug with CSV export in Discover, where searching over hundreds of shards would result in an incomplete CSV file {kibana-pull}144201[#144201] -* Fixes an issue where downloading a report caused a new browser tab to open with the report content, rather than receiving a downloaded file {kibana-pull}144136[#144136] -* Fixed an issue with CSV exports from Discover, where using the `_id` field in an export, when `_id` is a very high numeric value, the value could lose precision {kibana-pull}143807[#143807] - -[[release-notes-8.5.2]] -== {kib} 8.5.2 - -Review the following information about the {kib} 8.5.2 release. - -[float] -[[breaking-changes-8.5.2]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.5.2. - -{kibana-ref-all}/8.4/release-notes-8.4.0.html#breaking-changes-8.4.0[8.4.0] | {kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[enhancement-v8.5.2]] -=== Enhancement -Security:: -* Adds a `Content-Security-Policy` header to all `/api/*` responses {kibana-pull}144902[#144902] - -[float] -[[fixes-v8.5.2]] -=== Bug fixes -APM:: -* Limits the number of source map artifacts {kibana-pull}144963[#144963] -* Fixes an incorrect documentation link {kibana-pull}145077[#145077] -* Suppresses error toast when data view cannot be created {kibana-pull}143639[#143639] - -Dashboard:: -Fixes unexpected suggestions for text/keyword multi-fields {kibana-pull}145177[#145177] - -Discover:: -Fixes % for field stats calculations (edge cases) {kibana-pull}144962[#144962] - -Management:: -Fixes autocomplete_entities API crash when response size is too big {kibana-pull}140569[#140569] - -Uptime:: -Adjust formula for synthetics monitor availability {kibana-pull}144868[#144868] - -[[release-notes-8.5.1]] -== {kib} 8.5.1 - -Review the following information about the {kib} 8.5.1 release. - -[float] -[[breaking-changes-8.5.1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.5.1. - -{kibana-ref-all}/8.4/release-notes-8.4.0.html#breaking-changes-8.4.0[8.4.0] | {kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[enhancement-v8.5.1]] -=== Enhancements -Elastic Security:: -For the Elastic Security 8.5.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -[float] -[[fixes-v8.5.1]] -=== Bug fixes -APM:: -* Fixes a bug where Metadata API does not filter by environment {kibana-pull}144472[#144472] -* Fixes a bug where AWS lambda checks for an undefined value {kibana-pull}143987[#143987] -* Limits the number of source map artifacts {kibana-pull}144963[#144963] -* Fixes an incorrect documentation link {kibana-pull}145077[#145077] - -Dashboard:: -* Removes support for scripted fields in options list {kibana-pull}144643[#144643] -* Fixes help documentation link for dashboard {kibana-pull}143894[#143894] - -Elastic Security:: -For the Elastic Security 8.5.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -Make asset tags space aware {kibana-pull}144066[#144066] - -Machine Learning:: -* Correcting the size of the free ML node on cloud {kibana-pull}144512[#144512] -* Fixes model testing flyout reload {kibana-pull}144318[#144318] -* Explain Log Rate Spikes: Wrap analysis in try/catch block {kibana-pull}143651[#143651] -* Explain Log Rate Spikes: Fix uncompressed streams and backpressure handling {kibana-pull}142970[#142970] - -Osquery:: -* Fixes a bug that prevented users from viewing Osquery results if they were in a non-default {kib} space {kibana-pull}144210[#144210] - -Platform:: -Fixes the execution pipeline not to stop on a flaky subexpression {kibana-pull}143852[#143852] - -Uptime:: -* Adjust forumla for synthetics monitor availability {kibana-pull}144868[#144868] -* TLS alert - do not alert when status cannot be determined {kibana-pull}144767[#144767] - -[[release-notes-8.5.0]] -== {kib} 8.5.0 - -Review the following information about the {kib} 8.5.0 release. - -[float] -[[known-issues-8.5.0]] -=== Known issues - -Due to a recent change in the Red Hat scan verification process, -{kib} 8.5.0 is not available in the Red Hat Ecosystem Catalog. -This known issue will be fixed in the next release. -To download the {kib} 8.5.0 image, use the https://www.docker.elastic.co/r/kibana/kibana[Elastic docker registry]. - -[float] -[[breaking-changes-8.5.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.5.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Updates bulk action API to return actionId instead of agent success -[%collapsible] -==== -*Details* + -To make bulk action responses consistent, returns `actionId` instead of agent ids with `success: True` or `success: False` results. For more information, refer to {kibana-pull}141757[#141757]. - -*Impact* + -When you use `FleetBulkResponse`, you now receive only `actionId` responses. -==== - -[discrete] -.Removes filter validation for ad-hoc data views -[%collapsible] -==== -*Details* + -Filters associated with unknown data views, such as deleted data views, are no longer automatically disabled. For more information, refer to {kibana-pull}139431[#139431]. - -*Impact* + -Filters associated with unknown data views now display a warning message instead of being automatically disabled. -==== - -[discrete] -.Removes the `package_policies` field from the agent policy saved object -[%collapsible] -==== -*Details* + -The bidirectional foreign key between agent policy and package policy has been removed. For more information, refer to {kibana-pull}138677[#138677]. - -*Impact* + -The agent policy saved object no longer includes the `package_policies` field. -==== - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.4/release-notes-8.4.0.html#breaking-changes-8.4.0[8.4.0] | {kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[features-8.5.0]] -=== Features -{kib} 8.5.0 adds the following new and notable features. - -Alerting:: -* Adds dynamic field selection to the alerts table {kibana-pull}140516[#140516] -* Show alerts count {kibana-pull}140473[#140473] -* Adds the ability to allows users to assign other users to cases {kibana-pull}140208[#140208] -* Ability run a rule on-demand {kibana-pull}139848[#139848] -* Ability to bulk update API keys for alerting rules {kibana-pull}139036[#139036] -* Index threshold alert can't use unsigned long data type {kibana-pull}138452[#138452] -* Category fields endpoint {kibana-pull}138245[#138245] -* Index threshold alert UI does not fill index picker with data streams {kibana-pull}137584[#137584] - -APM:: -* Display kubernetes metadata in service icons popup and instance accordion {kibana-pull}139612[#139612] -* AWS lambda metrics api {kibana-pull}139041[#139041] - -Discover:: -* Adds support for storing time with saved searches {kibana-pull}138377[#138377] -* Enables tags for saved searches {kibana-pull}136162[#136162] - -Elastic Security:: -For the Elastic Security 8.5.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Adds agent activity flyout {kibana-pull}140510[#140510] -* Adds a new event toggle to capture terminal output in endpoint {kibana-pull}139421[#139421] -* Makes batch actions asynchronous {kibana-pull}138870[#138870] -* Adds ability to tag integration assets {kibana-pull}137184[#137184] -* Adds support for input only packages {kibana-pull}140035[#140035] - -Infrastructure:: -Inital hosts page {kibana-pull}138173[#138173] - -Lens & Visualizations:: -* Adds query-based annotations in *Lens* {kibana-pull}138753[#138753] -* Enables ad-hoc data views in *Lens* {kibana-pull}138732[#138732] - -Machine Learning:: -* Notifications page {kibana-pull}140613[#140613] -* Explain Log Rate Spikes: Add option to view grouped analysis results {kibana-pull}140464[#140464] -* Stubs out UI for the ML Inference Pipeline panel {kibana-pull}140456[#140456] -* Attach the anomaly charts embeddable to Case {kibana-pull}139628[#139628] -* Log pattern analysis UI {kibana-pull}139005[#139005] -* Attach the anomaly swim lane embeddable to Case {kibana-pull}138994[#138994] - -Management:: -* Adds the ability to allow variables in URL Drilldown titles {kibana-pull}140076[#140076] -* Enables time series downsampling action in ILM configurations {kibana-pull}138748[#138748] -* Adds the composite runtime field editor {kibana-pull}136954[#136954] - -Observability:: -Feat(slo): Create basic SLO route {kibana-pull}139490[#139490] - -Osquery:: -* Adds Osquery results to cases {kibana-pull}139909[#139909] -* Add support for differential logs {kibana-pull}140660[#140660] - -Security:: -Adds the ability to set a default Access Agreement for all `xpack.security`-level authentication providers {kibana-pull}139217[#139217] - -For more information about the features introduced in 8.5.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.5.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.5.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.5.0]] -=== Enhancements -Alerting:: -* 141189 alerts table performance {kibana-pull}141385[#141385] -* 141119 remove visibility toogle + use_columns refactor {kibana-pull}141250[#141250] -* Adds Stats on top of execution logs {kibana-pull}140883[#140883] -* Adds the Logs tab to Rules and Connectors UI {kibana-pull}138852[#138852] -* Adds "exclude previous hits" check box to ESQuery rule form {kibana-pull}138781[#138781] -* The ES Query Rule Type now supports Runtime Mappings and the Fields parameters when using an Elasticsearch DSL query {kibana-pull}138427[#138427] - -APM:: -* Adds option to power APM inventory with service metrics {kibana-pull}140868[#140868] -* Adds a sort order to the trace samples on the transaction details page {kibana-pull}140589[#140589] -* Adds a tail-based sampling storage limit (APM integration) {kibana-pull}140567[#140567] -* Adds AWS Lambda metrics to the "Metrics" tab {kibana-pull}140550[#140550] -* Adds an experimental mode to the APM app {kibana-pull}139553[#139553] -* Renames JVMs to Metrics {kibana-pull}138437[#138437] -* Changes how partial data buckets are displayed {kibana-pull}137533[#137533] - -Dashboard:: -Adds the ability to view panel-level filters and queries {kibana-pull}136655[#136655] - -Discover:: -* Enables `Explore in Discover` for adhoc data views in *Lens* {kibana-pull}140726[#140726] -* Adds the ability to show actions inline in the Expanded Document view for quick access {kibana-pull}140085[#140085] -* Updates the layout for unified histogram {kibana-pull}139446[#139446] -* Adds new field stats in sidebar popover {kibana-pull}139072[#139072] -* Adds ad-hoc data views {kibana-pull}138283[#138283] -* Updates the formatter for aggregate_metric_double field values {kibana-pull}138205[#138205] - -Elastic Security:: -For the Elastic Security 8.5.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Adds toggle for experimental synthetic `_source` support in Fleet data streams {kibana-pull}140132[#140132] -* Enhances the package policy API to create or update a package policy API with a simplified way to define inputs {kibana-pull}139420[#139420] -* Support new subscription and license fields {kibana-pull}137799[#137799] - -Infrastructure:: -* Adds log-* index pattern support on SM UI {kibana-pull}139121[#139121] -* Adds health API support for data ingested from package {kibana-pull}138964[#138964] -* Adds support for integration package {kibana-pull}138224[#138224] -* Adds the inital hosts page {kibana-pull}138173[#138173] - -Lens & Visualizations:: -* Adds the Collapse by option for partition charts in *Lens* {kibana-pull}140336[#140336] -* Adds the ability to show the metric name if there are multiple layers with breakdowns in *Lens* {kibana-pull}140314[#140314] -* Adds time scaling without date histogram in *Lens* {kibana-pull}140107[#140107] -* Improves the field drag defaults in *Lens* {kibana-pull}140050[#140050] -* Adds the time marker setting for time axis in *Lens* {kibana-pull}139950[#139950] -* Adds the ability to make sure shard size stays stable for low number of sizes in *TSVB*, *Lens*, and Agg based visualizations {kibana-pull}139791[#139791] -* Adds the one click filter to the *Lens* table {kibana-pull}139701[#139701] -* Improves the metric palette behavior in *Lens* {kibana-pull}139596[#139596] -* Adds separate dimension groups for mosaic rows and columns in *Lens* {kibana-pull}139214[#139214] -* Adds display-infinity option to custom palette editor in *Lens* {kibana-pull}139061[#139061] -* Adds TSDB support for *Lens*, *TSVB* and *Timelion* {kibana-pull}139020[#139020] -* Adds the format selector to the new metric visualization in *Lens* {kibana-pull}139018[#139018] -* Shows the edit/delete button while field stats are loading in *Lens* {kibana-pull}138899[#138899] -* Adds auto mode for secondary metric prefix in *Lens* {kibana-pull}138167[#138167] -* Adds open in *Lens* extendability {kibana-pull}136928[#136928] -* Adds TSDB warning handling support for *Lens*, Agg based, and *TSVB* {kibana-pull}136833[#136833] -* Adds reduced time range option in *Lens* {kibana-pull}136706[#136706] -* Migrates xy visualization type to new unified xy expression {kibana-pull}136475[#136475] -* Adds the ability to duplicate layers in *Lens* {kibana-pull}140603[#140603] - -Machine Learning:: -* Explain Log Rate Spikes: add main chart sync on row hover at group level {kibana-pull}141138[#141138] -* Show "No anomalies found" message instead of empty swim lane {kibana-pull}141098[#141098] -* Explain Log Rate Spikes: Group results API {kibana-pull}140683[#140683] -* Match Data Visualizer/Field stats table content with the popover {kibana-pull}140667[#140667] -* Explain Log Rate Spikes: Adds discover link to analysis table {kibana-pull}139877[#139877] -* Adding ecs_compatibility setting for find structure calls {kibana-pull}139708[#139708] -* Improves messaging when an anomaly detection forecast errors {kibana-pull}139345[#139345] -* Anomaly Detection: adds maps link when source data contains geo fields {kibana-pull}139333[#139333] -* Quickly create ML jobs from lens visualizations {kibana-pull}136421[#136421] - -Management:: -* Url drilldown `date` helper now allows rounding up relative dates {kibana-pull}137874[#137874] -* In CSV reports, an error message now appears on the job when fewer CSV rows are generated than expected {kibana-pull}137800[#137800] - -Maps:: -* Adds support for adhoc data views {kibana-pull}140858[#140858] -* Timeslider control {kibana-pull}139228[#139228] -* Support Vector tile runtime geo_point fields {kibana-pull}139047[#139047] -* Show data view name in UI {kibana-pull}138928[#138928] -* Adds ability to disable tooltips for layer {kibana-pull}138275[#138275] -* Cancel button when editing by value from dashboard {kibana-pull}137880[#137880] - -Security:: -Adds audit events to "login-less" authentication flows (e.g. PKI, Kerberos) {kibana-pull}139492[#139492] - -Uptime:: -* Project monitors - support lightweight project monitors {kibana-pull}141066[#141066] -* Adds Actions popover menu {kibana-pull}136992[#136992] - -[float] -[[fixes-v8.5.0]] -=== Bug fixes -Alerting:: -* Render the grid only if we have alerts {kibana-pull}142481[#142481] -* Alerts Table browser field - fix siem browser fields call {kibana-pull}141431[#141431] -* Adds getActionsHealth method to return permanent encryption key existence {kibana-pull}140535[#140535] -* Clarify rule notification values {kibana-pull}140457[#140457] -* Actions are not able to configure a max number of attempts {kibana-pull}138845[#138845] -* Elasticsearch Query Rule doesn't have 'dark mode' view for query {kibana-pull}138631[#138631] -* Getting error about secrets not being saved when import a SO (Connector Saved Object) {kibana-pull}138019[#138019] -* Provide indication of how many rules are using connector on Connector List view {kibana-pull}137181[#137181] - -APM:: -* Remove check for infra data {kibana-pull}142835[#142835] -* Prefer span metrics over span events {kibana-pull}141519[#141519] -* Fixes search bar suggestions {kibana-pull}141101[#141101] -* Sort trace samples {kibana-pull}140589[#140589] - -Dashboard:: -Fixes pinned filters that backed up in Session Storage {kibana-pull}142262[#142262] - -Discover:: -* Adds support for line breaks in Document explorer {kibana-pull}139449[#139449] -* Cancelled request errors for embeddables are now hidden {kibana-pull}137690[#137690] -* Fixes legacy sort saved search stored in Dashboard saved objects {kibana-pull}137488[#137488] -* Fixes column width handling {kibana-pull}137445[#137445] - -Elastic Security:: -For the Elastic Security 8.5.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.5.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Refresh search results when clearing category filter {kibana-pull}142853[#142853] -* Respect `default_field: false` when generating index settings {kibana-pull}142277[#142277] -* Fixes repeated debug logs when bundled package directory does not exist {kibana-pull}141660[#141660] - -Graph:: -Fixes query bar autocomplete {kibana-pull}140963[#140963] - -Infrastructure:: -* Adds support for Logstash datastream in standalone query {kibana-pull}138689[#138689] - -Lens & Visualizations:: -* Fixes the detailed tooltip wrap problem {kibana-pull}142818[#142818] -* Fixes an issue where columns normalized by unit were unable to display properly on Dashboards for *Lens* metric visualizations {kibana-pull}142741[#142741] -* Adds back ticks on bands in *Lens* {kibana-pull}142702[#142702] -* Fixes guidance panel appearing for a moment when saving Graph {kibana-pull}141228[#141228] -* Fixes pie filter without slice {kibana-pull}141227[#141227] -* Fixes an issue where using annotations from different data views than the visualizations created panel breaks in *TSVB* {kibana-pull}141104[#141104] -* Fixes drilldown url templates for sample data {kibana-pull}141079[#141079] -* Fixes time shift with reduced time range tabification in *Lens* {kibana-pull}141076[#141076] -* Fixes the time shifted pipeline agg in *Lens* {kibana-pull}140723[#140723] -* Fixes an A11y issue where the query input doesn't react to `escape` button in *Lens* {kibana-pull}140382[#140382] -* Boolean values are now correctly formatted by default in *TSVB* {kibana-pull}140308[#140308] -* All data views are no longer loaded on broken data view reference in *Lens* {kibana-pull}139690[#139690] -* Removes the exclamation circle icon in *TSVB* {kibana-pull}139686[#139686] -* Theme is now passed to visualize save modal {kibana-pull}139685[#139685] -* Push-out behavior is now preserved for table cells when possible in *Lens* {kibana-pull}139619[#139619] -* The metric visualization state is now cleared in *Lens* {kibana-pull}139154[#139154] -* Adds the ability to set minimum table width for column split tables {kibana-pull}139004[#139004] -* Adds the ability to scroll tall metric visualizations in *Lens* {kibana-pull}138178[#138178] - -Machine Learning:: -* Explain Log Rate Spikes: update more groups badge for clarity {kibana-pull}142793[#142793] -* Fixes Index data visualizer doc count when time field is not defined {kibana-pull}142409[#142409] -* Explain Log Rate Spikes: Fix error handling {kibana-pull}142047[#142047] -* Fixes date picker not allowing unpause when refresh interval is 0 {kibana-pull}142005[#142005] -* Fixes expanded row layout in the Nodes table {kibana-pull}141964[#141964] -* Fixes links to Discover and Maps and custom URLs for jobs with a query in the datafeed {kibana-pull}141871[#141871] - -Management:: -* The progress bar is now visible in Expression renderer {kibana-pull}142699[#142699] -* Transforms: Preserves the `field` for unsupported aggs {kibana-pull}142106[#142106] -* Removes unnecessary time units in ILM policy dialog {kibana-pull}140815[#140815] -* Fixes search query builder to generate wildcard query for keyword fields {kibana-pull}140629[#140629] -* Updates "Copy as cURL" to interpolate variables and strip request-body comments {kibana-pull}140262[#140262] -* Fixes previewing data streams in template editor {kibana-pull}140189[#140189] -* Fixes an issue where selecting requests with characters ending with '{}' was not possible {kibana-pull}140068[#140068] -* Filters that are associated with an unknown data view, such as deleted data views, are no longer automatically disabled, but now instead display a warning message {kibana-pull}139431[#139431] -* Watches no longer get stuck in a "Firing" state in Watcher {kibana-pull}138563[#138563] -* Fixes an issue where data view search results were not showing the value of mapped fields that shared a name with a runtime field {kibana-pull}138471[#138471] - -Maps:: -* Fixes Go To - lat/long values outside expected range cause blank Maps app {kibana-pull}141873[#141873] -* Fixes scaling and term join in product help popover width {kibana-pull}139120[#139120] -* Fixes legacy tile_map and region_map visualizations do not display title in Map embeddable action modals {kibana-pull}139054[#139054] -* Fixes Filters applied to map visualization not preserved when added to dashboard {kibana-pull}138188[#138188] - -Monitoring:: -Health api: account for ccs in indices regex {kibana-pull}137790[#137790] - -Observability:: -* Fixes Alert tab goes blank in APM because of Alert Details page feature flag {kibana-pull}142188[#142188] -* Update links to Observability rule management {kibana-pull}140009[#140009] - -Platform:: -* Fixes an issue where the expressions executor stopped on failing partially emitted results {kibana-pull}142105[#142105] -* A 0 is now returned when there are no overdue tasks for capacity estimation {kibana-pull}140720[#140720] -* The task health calculation now never returns Error or Warning, but logs the HealthStatus {kibana-pull}139274[#139274] - -Uptime:: -* Fixes Next and Previous button on step screenshot carousel {kibana-pull}141422[#141422] -* Fixes disrupted UI on Browser Test Results` step screenshots {kibana-pull}139017[#139017] - -[[release-notes-8.4.3]] -== {kib} 8.4.3 - -Review the following information about the {kib} 8.4.3 release. - -[float] -[[breaking-changes-8.4.3]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.4.3. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[fixes-v8.4.3]] -=== Bug fixes -Cases:: -Fixes an issue where the recent cases widget shows cases from other solutions {kibana-pull}141221[#141221] - -Discover:: -* Fixes scrolling prevented by saved search embeddable on touch devices {kibana-pull}141718[#141718] -* Fixes columns management for saved search embeddable {kibana-pull}140799[#140799] - -Elastic Security:: -For the Elastic Security 8.4.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Graph:: -* Fixes the position of Add fields popover {kibana-pull}141040[#141040] -* Fixes responsive styles of field manager {kibana-pull}140948[#140948] - -Machine Learning:: -Fixes an issue where Data visualizer was unable to update distribution when changing shard size, forbidden error with recognize modules on basic license {kibana-pull}141313[#141313] - -Management:: -Fixes the removal of a single field formatter {kibana-pull}141078[#141078] - -Observability:: -Fixes an alert summary widget issue in non-default space {kibana-pull}140842[#140842] - -[[release-notes-8.4.2]] -== {kib} 8.4.2 - -Review the following information about the {kib} 8.4.2 release. - -[float] -[[breaking-changes-8.4.2]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.4.2. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[enhancement-v8.4.2]] -=== Enhancements -Security:: -Logs a hash of the saved objects encryption key (`xpack.encryptedSavedObjects.encryptionKey`) when {kib} starts to assist in identifying mismatched encryption keys {kibana-pull}139874[#139874] - -[float] -[[fixes-v8.4.2]] -=== Bug fixes -Connectors:: -The connectors table now uses "compatibility" rather than "availability" {kibana-pull}139024[#139024] - -Discover:: -Fixes saved search embeddable rendering {kibana-pull}140264[#140264] - -Elastic Security:: -For the Elastic Security 8.4.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Applies fixes for package policy upgrade API with multiple ids {kibana-pull}140069[#140069] -* Improves performance for many integration policies {kibana-pull}139648[#139648] - -Lens & Visualizations:: -* Fixes "Collapse by" for table and XY visualizations with multiple metrics in *Lens* {kibana-pull}140381[#140381] -* Fixes action menu in *Lens* {kibana-pull}139588[#139588] - -Machine Learning:: -* Explain Log Rate Spikes: Histogram fixes {kibana-pull}139933[#139933] -* Explain Log Rate Spikes: Improve streaming headers for certain proxy configs {kibana-pull}139637[#139637] -* Fixes navigation for the Basic licence {kibana-pull}139469[#139469] -* Corrects file.path field name in v3_windows_anomalous_script job {kibana-pull}139109[#139109] - -Management:: -Bfetch` response headers now include `X-Accel-Buffering: no` {kibana-pull}139534[#139534] - -Maps:: -* Fixes issue where percentile aggregation was not working with vector tiles {kibana-pull}140318[#140318] -* Fixes Map app crashing on file upload request timeout {kibana-pull}139760[#139760] - -Monitoring:: -* Ensures GlobalState class has it's destroy() method called on unmount {kibana-pull}139908[#139908] -* Adds KibanaThemeProvider to Stack Monitoring UI {kibana-pull}139839[#139839] - -Uptime:: -Fixes an issue where decryption errors caused the entire suite of monitors to fail syncing {kibana-pull}140549[#140549] - -[[release-notes-8.4.1]] -== {kib} 8.4.1 - -Review the following information about the {kib} 8.4.1 release. - -[float] -[[breaking-changes-8.4.1]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.4.1. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[fixes-v8.4.1]] -=== Bug fixes - -Alerting:: -* Fixes alert tab crash on rule details page {kibana-pull}139372[#139372] -* Fixes issue where some 8.3.x clusters failed to upgrade with a saved object migration failure {kibana-pull}139427[#139427] - -Lens & Visualizations:: -* Fixes table pagination in *Lens* and *Aggregation-based* visualization editors {kibana-pull}139160[#139160] - -[[release-notes-8.4.0]] -== {kib} 8.4.0 - -Review the following information about the {kib} 8.4.0 release. - -[float] -[[known-issue-8.4.0]] -=== Known issues - -If you have alerting rules that have been snoozed, do not upgrade {kib} to 8.4.0. Upgrade to 8.4.1 instead. - -To determine if you have snoozed alerting rules, open the main menu, then click -**{stack-manage-app}** -> **{rac-ui}**. Filter the rule list by selecting -**View** -> **Snoozed**. If you must upgrade to 8.4.0, for each space, cancel -the snooze for all affected rules before you upgrade. - -To identify snoozed rules in all Spaces using **Dev Tools**, run the following -query: - -[source,console] ----- -GET /.kibana/_search -{ - "query": { - "exists": { - "field": "alert.isSnoozedUntil" - } - } -} ----- - -If you upgraded {kib} to 8.4.0 and you have alerting rules configured to -snooze notifications, you will receive the following error message: - -[source,text] ----- -FATAL Error: Unable to complete saved object migrations for the [.kibana] index. ----- - -To fix that problem, restore your previous version, then upgrade to 8.4.1 instead. - -[float] -[[breaking-changes-8.4.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.4.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.xpack.agents.* are uneditable in UI when defined in kibana.yml -[%collapsible] -==== -*Details* + -When you configure `setxpack.fleet.agents.fleet_server.hosts` and `xpack.fleet.agents.elasticsearch.hosts` in kibana.yml, you are unable to update the fields on the Fleet UI. - -For more information, refer to {kibana-pull}135669[#135669]. - -*Impact* + -To configure `setxpack.fleet.agents.fleet_server.hosts` and `xpack.fleet.agents.elasticsearch.hosts` on the Fleet UI, avoid configuring the settings in kibana.yml. -==== - -[discrete] -.Removes the legacy charts library -[%collapsible] -==== -*Details* + -The legacy implementation of the *Timelion* visualization charts library has been removed. All *Timelion* visualizations now use the elastic-charts library, which was introduced in 7.15.0. - -For more information, refer to {kibana-pull}134336[#134336]. - -*Impact* + -In 8.4.0 and later, you are unable to configure the *Timelion* legacy charts library advanced setting. For information about visualization Advanced Settings, check link:https://www.elastic.co/guide/en/kibana/8.4/advanced-options.html#kibana-visualization-settings[Visualization]. -==== - -{kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[deprecations-8.4.0]] -=== Deprecations - -The following functionality is deprecated in 8.4.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.4.0. - -[discrete] -[[deprecation-136422]] -.Deprecates ApiKey authentication for interactive users -[%collapsible] -==== -*Details* + -The ability to authenticate interactive users with ApiKey via a web browser has been deprecated, and will be removed in a future version. - -For more information, refer to {kibana-pull}136422[#136422]. - -*Impact* + -To authenticate interactive users via a web browser, use <>. Use API keys only for programmatic access to {kib} and {es}. -==== - -[float] -[[features-8.4.0]] -=== Features -{kib} 8.4.0 adds the following new and notable features. - -Alerting:: -* Adds the "updated at" feature in new alerts table {kibana-pull}136949[#136949] -* Adds a rule detail table with bulk actions {kibana-pull}136601[#136601] -* Adds bulk Actions for Alerts Table {kibana-pull}135797[#135797] -* Adds the Alerting stack-monitoring PoC {kibana-pull}135365[#135365] -* Adds custom inline/row actions for alerts table {kibana-pull}134015[#134015] - -Cases:: -Adds the ability to customize permissions to prevent users from deleting Cases entities, such as Cases themselves, attachments, and comments {kibana-pull}135487[#135487] - -Connectors:: -The {webhook-cm} connector allows users to build a custom connector for any third-party case/ticket management system {kibana-pull}131762[#131762] - -Discover:: -Adds the ability to add a custom number of rows in the results and save the specified number with a Saved Search {kibana-pull}135726[#135726] - -Elastic Security:: -For the Elastic Security 8.4.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Enables package signature verification feature {kibana-pull}137239[#137239] -* Modal to allow user to force install an unverified package {kibana-pull}136108[#136108] -* Display package verification status {kibana-pull}135928[#135928] -* Tag rename and delete feature {kibana-pull}135712[#135712] -* Bulk update agent tags ui {kibana-pull}135646[#135646] -* Adds API to bulk update tags {kibana-pull}135520[#135520] -* Adds and remove agent tags {kibana-pull}135320[#135320] -* Support sorting agent list {kibana-pull}135218[#135218] -* Promote Logstash output support to GA {kibana-pull}135028[#135028] -* Create new API to manage download_source setting {kibana-pull}134889[#134889] - -Machine Learning:: -* Adds random sampler to Data visualizer document count chart {kibana-pull}136150[#136150] -* Adds explain log rate spikes feature to the ML plugin {kibana-pull}135948[#135948] - -Management:: -* Run packs live {kibana-pull}132198[#132198] -* Ability to set human readable title of data view & ability to edit data view {kibana-pull}124191[#124191] - -Monitoring:: -Adds stale status reporting for Kibana {kibana-pull}132613[#132613] - -Observability:: -* Adds Beta label to Infrastructure tab {kibana-pull}136710[#136710] -* Creates and adds Rule Alerts Summary as a sharable component to the O11y Rule Details {kibana-pull}135805[#135805] -* Rule Details Page - Use RuleStatusPanel from triggersActionsUI {kibana-pull}135643[#135643] -* Adds Top erroneous transactions to errors details page {kibana-pull}134929[#134929] -* Introduces Alerts tab on service overview page {kibana-pull}134350[#134350] -* Adds single metric report type {kibana-pull}132446[#132446] - -Platform:: -Adds new bulkUpdatesSchedules method to Task Manager {kibana-pull}132637[#132637] - -Security:: -Adds the ability to create personal avatars {kibana-pull}132522[#132522] - -Sharing:: -Adds the new metric visualization {kibana-pull}136567[#136567] - -For more information about the features introduced in 8.4.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.4.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.4.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.4.0]] -=== Enhancements -Alerting:: -* Adds snooze state UI to Rule Details page {kibana-pull}135146[#135146] -* Adds Snooze Scheduling UI and API {kibana-pull}134512[#134512] -* Adds recovery context for ES query rule type {kibana-pull}132839[#132839] -* Visualize alerting metrics in Stack Monitoring {kibana-pull}123726[#123726] - -Canvas:: -* Markdown element auto-applies text changes {kibana-pull}133318[#133318] -* Lines operations keybindings {kibana-pull}132914[#132914] -* Detailed tooltip {kibana-pull}131116[#131116] - -Cases:: -* Improved the cases search bar functionality. The search functionality will only consider the title and description fields {kibana-pull}136776[#136776] -* Performance improvements were made to reduce the time required to create, update, and delete cases and comments. In our testing we saw around a half second reduction in the round trip time for the UI requests {kibana-pull}136452[#136452] - -Dashboard:: -Hide controls callout when the `hideAnnouncements` setting is `true` {kibana-pull}136410[#136410] - -Design:: -* Adds an H1 tag with the workpad title when viewing workpads {kibana-pull}135504[#135504] -* Improve keyboard navigation in Discover top nav menu {kibana-pull}134788[#134788] - -Discover:: -* Improves the HTML formatting of fields with a list of values {kibana-pull}136684[#136684] -* Adds support for accessing the edit field flyout from the document explorer column popover {kibana-pull}135277[#135277] -* Adds support for copying the query from the add rule flyout {kibana-pull}135098[#135098] -* Adds focus to h1 on navigate for single document and surrounding document views {kibana-pull}134942[#134942] -* Improves the creation and editing of "Elasticsearch query" rule in Management {kibana-pull}134763[#134763] -* Adds data view changed warning after alert rule created {kibana-pull}134674[#134674] -* Make 'Test query' button pretty {kibana-pull}134605[#134605] -* Improves the document explorer timestamp tooltip accessibility {kibana-pull}134411[#134411] -* Adds focus to h1 element when client side routing is executed {kibana-pull}133846[#133846] -* Adds an option to copy column values to Clipboard in Document Explorer {kibana-pull}132330[#132330] - -Elastic Security:: -For the Elastic Security 8.4.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Remove Kubernetes Package granularity {kibana-pull}136622[#136622] -* Elastic-agent manifests: align with elastic-agent repo; add comments {kibana-pull}136394[#136394] -* Configure source URI in global settings and in agent policy settings {kibana-pull}136263[#136263] -* Adds Kubernetes in platforms selection list && update managed agent installation steps {kibana-pull}136109[#136109] -* That PR will enable user to write custom ingest pipeline for Fleet installed datastream {kibana-pull}134578[#134578] -* Update manifests for agent on kubernetes with new permissions {kibana-pull}133495[#133495] -* Adds support for a textarea type in integrations {kibana-pull}133070[#133070] - -Kibana Home & Add Data:: -Adds AnalyticsNoDataPage {kibana-pull}134172[#134172] - -Lens & Visualizations:: -* Adds supports for include and exclude terms in *Lens* {kibana-pull}136179[#136179] -* Adds the ability to set top values limit to 10,000 in *Lens* {kibana-pull}136399[#136399] -* Addss value count to *Lens* {kibana-pull}136385[#136385] -* Adds standard deviation function in *Lens* {kibana-pull}136323[#136323] -* Adds the ability to set the font size for mosaic outer level in *Lens* {kibana-pull}135911[#135911] -* Adds the ability to rank top values by custom metric in *Lens* {kibana-pull}134811[#134811] -* Adds the ability to convert TSVB series agg to *Lens* configuration {kibana-pull}134681[#134681] -* Adds the ability to allow multiple split accessors {kibana-pull}134566[#134566] -* Adds the ability to render newlines in data table in *Lens* {kibana-pull}134441[#134441] -* Extends Axis bounds for XY chart when using Interval operation in *Lens* {kibana-pull}134020[#134020] -* Adds the ability to use pick_max instead of clamp for positive only {kibana-pull}133460[#133460] -* Adds a new pick_min/max operation and clamp fixes in *Lens* {kibana-pull}132449[#132449] -* Adds support for percentile_ranks aggregation in *Lens* {kibana-pull}132430[#132430] -* Implements the ability to drag and drop between layers in *Lens* {kibana-pull}132018[#132018] -* Adds optimization for percentiles fetching in *Lens* {kibana-pull}131875[#131875] - -Machine Learning:: -* Adds tooltips for disabled actions in the Trained Models list {kibana-pull}137176[#137176] -* Data visualizer: Add field types in-product help {kibana-pull}137121[#137121] -* Plot zero scores on the Overall anomaly swim lane {kibana-pull}136951[#136951] -* One-way cursor sync added from Anomaly detection swimlane to other charts {kibana-pull}136775[#136775] -* Adds action to view datafeed counts chart to jobs list rows {kibana-pull}136274[#136274] -* Data Visualizer: Remove duplicated geo examples, support 'version' type, add filters for boolean fields, and add sticky header to Discover {kibana-pull}136236[#136236] -* Adds a link to ML trained models list from ID in Stack Management app table {kibana-pull}135700[#135700] -* Adds information callouts to trained model testing flyout {kibana-pull}135566[#135566] -* Limit Use full data button in anomaly detection job wizards to past data only {kibana-pull}135449[#135449] -* Replace a fixed Y-axis width with a max width setting for Anomaly Swim Lane Embeddable {kibana-pull}135436[#135436] -* Adds support for setting threading params when starting a trained model deployment {kibana-pull}135134[#135134] -* Refactors Management page to focus on space management tasks {kibana-pull}134893[#134893] -* Disable the Single Metric Viewer button for not viewable jobs {kibana-pull}134048[#134048] -* Anomaly Detection: allow snapshot to be reverted from the view datafeed flyout {kibana-pull}133842[#133842] - -Management:: -* Transforms: Adds per-transform setting for num_failure_retries to creation wizard and edit flyout and authorization info {kibana-pull}135486[#135486] -* Transforms: Adds sorting to audit messages tab {kibana-pull}135047[#135047] -* Console now supports saving the state of folding/unfolding of commands {kibana-pull}134810[#134810] -* Render most severe response status code from Console response pane {kibana-pull}134627[#134627] -* You can now create variables in Console {kibana-pull}134215[#134215] -* Make index template previews copyable {kibana-pull}134060[#134060] -* Console now supports adding comments in the body of a request by using `//` for a single line and `/*....*/` for multiline comments {kibana-pull}133852[#133852] -* Surface HTTP status badges next to each response in Console {kibana-pull}132494[#132494] -* Adds updated `essql` expression function {kibana-pull}132332[#132332] -* Adds a "get all" REST API for data views: `GET /api/data_views` {kibana-pull}131683[#131683] - -Maps:: -* Automatically display the maps legend {kibana-pull}136872[#136872] -* Custom raster source example plugin {kibana-pull}136761[#136761] -* Label zoom range style property {kibana-pull}136690[#136690] -* Adjust icon size when cluster resolution changes {kibana-pull}136573[#136573] -* Adds context for 'No longer contained' geo-containment alert {kibana-pull}136451[#136451] -* Keydown+scroll to zoom {kibana-pull}135330[#135330] -* Synchronize map views in dashboard and canvas {kibana-pull}134272[#134272] -* Adds spatial filter from cluster {kibana-pull}133673[#133673] -* Customizable colors in basemaps {kibana-pull}131576[#131576] - -Observability:: -* Prefer DataView client over SavedObjects client when possible {kibana-pull}136694[#136694] -* Use proper header nesting {kibana-pull}136559[#136559] -* Removes "no data" redirects for observability overview {kibana-pull}136442[#136442] -* Allow connectors to explicitly register which features they will be available in {kibana-pull}136331[#136331] -* Display node details metrics for kubernetes containers {kibana-pull}135585[#135585] -* Replace sourceId with mandatory logView prop in LogStream component {kibana-pull}134850[#134850] -* Backend operation distribution chart {kibana-pull}134561[#134561] -* Display top spans for operation {kibana-pull}134179[#134179] -* Show descriptive loading, empty and error states in the metrics table {kibana-pull}133947[#133947] -* Backend operations detail view + metric charts {kibana-pull}133866[#133866] -* Backend operations list view {kibana-pull}133653[#133653] - -Platform:: -* Upgrade Kibana logs to ECS 8.4 {kibana-pull}136362[#136362] -* Adds error messaging to the report contents when there is a timeout in page setup {kibana-pull}134868[#134868] -* Adds migrations.discardCorruptObjects flag {kibana-pull}132984[#132984] -* Adds migrations.discardUnknownObjects flag {kibana-pull}132167[#132167] - -Querying & Filtering:: -Hides the tour component when the hideAnnouncements uiSetting is on {kibana-pull}135990[#135990] - -Security:: -Eliminates the need for a full page reload when navigating to a user profile page {kibana-pull}135543[#135543] - -[float] -[[fixes-v8.4.0]] -=== Bug fixes -Alerting:: -* Error message hidden after closing action accordion {kibana-pull}136570[#136570] -* Allow wildcard search on rule's name and tags {kibana-pull}136312[#136312] - -Canvas:: -* Fixes Filter not saving the selected Sort field option {kibana-pull}136085[#136085] -* Fixes "Element status" is inaccurate for grouped elements {kibana-pull}135829[#135829] -* Fixes Canvas filter behaviour on table {kibana-pull}134801[#134801] -* Fixes Uploaded asset not being saved {kibana-pull}133166[#133166] -* Lines operations keybindings {kibana-pull}132914[#132914] -* Fixes pointseries don't get updated on datasource change {kibana-pull}132831[#132831] - -Connectors:: -* Fixing ES index connector so that it can index into data streams as well as indices {kibana-pull}136011[#136011] -* Verify emails when creating an email connector, even if allowedDomain {kibana-pull}133859[#133859] - -Dashboard:: -Fixes Copy to dashboard includes filters {kibana-pull}136275[#136275] - -Discover:: -* Fixes hiding histogram for rollup data views {kibana-pull}137157[#137157] -* Fixes filtering out custom meta fields of Elasticsearch plugins enhanced documents {kibana-pull}137147[#137147] -* Disables refresh interval for data views without time fields and rollups {kibana-pull}137134[#137134] -* Fixes Discover breadcrumb losing context after page refresh or when opening in a new tab {kibana-pull}136749[#136749] -* Improves support for pinned filters in surrounding documents {kibana-pull}135722[#135722] -* Fixes flaky accessibility functional tests {kibana-pull}135596[#135596] -* Improves alerts popover accessibility and semantics {kibana-pull}135270[#135270] -* Migrate from savedObjectsClient to dataViews and fix the displayed data view name {kibana-pull}135142[#135142] -* Removes _type of metaFields to remove of sidebar {kibana-pull}134453[#134453] -* Fixes legacy sort parameter provided by URL {kibana-pull}134447[#134447] -* Make footer visible under Document Explorer if sample size is less than hits number {kibana-pull}134231[#134231] -* Do not update defaultIndex in case of insufficient permissions {kibana-pull}134202[#134202] -* Fixes flaky test for "allows editing of a newly created field" {kibana-pull}132812[#132812] -* Fixes flaky test for "context encoded URL params" {kibana-pull}132808[#132808] - -Elastic Security:: -For the Elastic Security 8.4.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -Using point in time for agent status query to avoid discrepancy {kibana-pull}135816[#135816] - -Lens & Visualizations:: -* Make reference line chart more robust in *Lens* {kibana-pull}137101[#137101] -* Format the label with the right default formatter in *TSVB* {kibana-pull}136934[#136934] -* Removes extra space from the legend when it is positioned on top/bottom {kibana-pull}135982[#135982] -* Display Y-axis tick labels {kibana-pull}135976[#135976] -* Fixes Date histogram bounds calculation doesn't update "now" {kibana-pull}135899[#135899] -* Fixes internal links in *Vega* {kibana-pull}135890[#135890] -* Do not set non-unique id for legend action popovers in *Lens* {kibana-pull}135656[#135656] -* Fixes non-editable Lens panel when using prefix wildcard in *Lens* {kibana-pull}135654[#135654] -* Removes saved search references from all places on unlink in *Visualize* {kibana-pull}135628[#135628] -* Fix multi-field top values for Heatmap visualizations in *Lens* {kibana-pull}135581[#135581] -* Fixes styling issues in *Lens* {kibana-pull}135406[#135406] -* Show badge for read-only in *Lens* {kibana-pull}135313[#135313] -* Don't let reference line fills on different axes collide in *Lens* {kibana-pull}135299[#135299] -* Fixes css specificity issue in *TSVB* {kibana-pull}135245[#135245] -* Always show palette on first dimension for mosaic in *Lens* {kibana-pull}135198[#135198] -* Wrong `visType` for `horizontal_bar` visualization {kibana-pull}135013[#135013] -* Unlinking Some Agg Based Visualizations Results in Unsaved Changes in *Visualize* {kibana-pull}134229[#134229] -* Fixes an issue where ellipsis truncation is not visible in table visualization cells, and letters are cut off in *Lens* {kibana-pull}134065[#134065] -* Switching dashboard mode doesn't update missing data view prompt in *Visualize* {kibana-pull}133873[#133873] -* Fixes application of suffix formats in *Lens* {kibana-pull}133780[#133780] -* Error messages not centered in *TSVB* {kibana-pull}133288[#133288] -* Use correct time zone for time shifting {kibana-pull}133141[#133141] - -Machine Learning:: -* Lock the delete annotation button on click {kibana-pull}137306[#137306] -* Fixes globally pinned filters in Data visualizer and query search bar not clearing properly for saved searches {kibana-pull}136897[#136897] -* Fixes overflow in start datafeed modal {kibana-pull}136292[#136292] -* Fixes error in categorization wizard summary step {kibana-pull}134228[#134228] -* Fixes flaky job selection on the Anomaly Explorer page {kibana-pull}137596[#137596] -* Fixes query in the Anomaly Explorer when viewing a job with no influencers {kibana-pull}137670[#137670] -* Fixes the Dashboard saving indicator with Anomaly Swim Lane embeddable {kibana-pull}137989[#137989] -* Anomaly detection job wizards now use data view names {kibana-pull}138255[#138255] - -Management:: -* The ILM UI now supports configuring policies with rollover based on `max_primary_shard_docs` {kibana-pull}137364[#137364] -* Fixes a bug in Console when sending a request with encoded characters resulted in an error {kibana-pull}136788[#136788] -* Fixes a bug where the autocomplete popup remains open when navigating away from Console {kibana-pull}136268[#136268] -* Fixes a bug in Index Management where the number of documents for an index could appear wrong {kibana-pull}135748[#135748] -* Fixes a bug in the Painless code editor that was incorrectly handling expressions with multiple division operators {kibana-pull}135423[#135423] -* Transforms: Fixes unsupported boolean filter when cloning {kibana-pull}137773[#137773] -* Transforms: Fixes restoring a field name with the exists filter aggregation {kibana-pull}138630[#138630] -* Transforms: Fixes data view error on cloning due to missing indices {kibana-pull}138756[#138756] -* Fixes Watcher stuck firing state {kibana-pull}138563[#138563] - -Maps:: -* Fixes "other" is always shown in legend for category styling rules {kibana-pull}137008[#137008] -* Fixes Tooltip loses pages on refresh {kibana-pull}135593[#135593] -* Fixes Pinned filters should be visible on new maps without user having to do any action on layers {kibana-pull}135465[#135465] -* Keep timeframe when editing a map from a dashboard {kibana-pull}135374[#135374] -* Reduce precision of coordinates for geo imports {kibana-pull}135133[#135133] -* Fixes onDataLoadEnd and onDataLoadError event handler callbacks only called for source data requests {kibana-pull}134786[#134786] -* Fixes sort not applied to vector tile search request {kibana-pull}134607[#134607] -* Fixes array values out of order in tooltips {kibana-pull}134588[#134588] - -Monitoring:: -Removes beta icon from logstash pipeline {kibana-pull}131752[#131752] - -Observability:: -* Invalid array value is permitted in Origin Headers for RUM configuration {kibana-pull}137228[#137228] -* When comparison feature is disabled, we still see the shaded area {kibana-pull}137223[#137223] -* Fixes responsivity Alert Summary chart in the Rule details page {kibana-pull}137175[#137175] -* Fixes custom link filter select value {kibana-pull}137025[#137025] -* Fixes Spark plots loading state when there are no data {kibana-pull}136817[#136817] -* Adds support for metrics for latency distribution histogram {kibana-pull}136594[#136594] -* Navigation from Span to Service breaks due to wrong transactionType {kibana-pull}136569[#136569] -* Breadcrumbs not updating from service jump on service map {kibana-pull}136144[#136144] -* Adds support for metrics for latency distribution histogram {kibana-pull}136083[#136083] -* Disallow spaces in index pattern {kibana-pull}135977[#135977] -* WrappedElasticsearchClientError: Request aborted {kibana-pull}135752[#135752] -* Fixes waterfall skew due to accordion left margins {kibana-pull}135544[#135544] -* Service inventory: detailed stats fetched for all services {kibana-pull}134844[#134844] -* Update network fields {kibana-pull}134471[#134471] -* Fixes Metrics Table Pod link to Details Page {kibana-pull}134354[#134354] -* Adds last updated at label and fix started at label {kibana-pull}134254[#134254] -* Adds event module filter to metrics table {kibana-pull}133872[#133872] -* APM Correlations: Fixes chart errors caused by inconsistent histogram range steps {kibana-pull}138259[#138259] - -Platform:: -* Migrations wait for index status green if create index returns acknowledged=false or shardsAcknowledged=false {kibana-pull}136605[#136605] -* Fixes CSV generator to include unmapped fields in the search source {kibana-pull}132972[#132972] -Security:: -Fixes keyboard and screen reader navigation for the spaces selector {kibana-pull}134454[#134454] - -[[release-notes-8.3.3]] -== {kib} 8.3.3 - -Review the following information about the {kib} 8.3.3 release. - -[float] -[[breaking-changes-8.3.3]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.3.3. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[fixes-v8.3.3]] -=== Bug fixes -Dashboard:: -* Fixes `z-index` of `embPanel__header--floater` {kibana-pull}136463[#136463] -* Filter out experimental visualizations when labs setting is disabled {kibana-pull}136332[#136332] - -Discover:: -Fixes filter in / filter out buttons for empty values {kibana-pull}135919[#135919] - -Elastic Security:: -For the Elastic Security 8.3.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Pass start_time to actions when the maintenance window for rolling upgrades is set to immediately {kibana-pull}136384[#136384] -* Allow agent bulk actions without specific licence restrictions {kibana-pull}136334[#136334] -* Adds reinstall button to integration settings page {kibana-pull}135590[#135590] - -Lens & Visualizations:: -Fixes normalizeTable performance bottleneck in *Lens* {kibana-pull}135792[#135792] - -[[release-notes-8.3.2]] -== {kib} 8.3.2 - -Review the following information about the {kib} 8.3.2 release. - -[float] -[[breaking-changes-8.3.2]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.3.2. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[fixes-v8.3.2]] -=== Bug fixes -Alerting:: -Fixes an issue where alerting rules that were created or edited in 8.2.0 stopped running when you upgraded {kib} to 8.3.0 or 8.3.1 {kibana-pull}135663[#135663] - -Discover:: -* Hide Alerts menu item when user does not have access to Stack Rules {kibana-pull}135655[#135655] -* Fixes loading of a single doc JSON when using index alias based data views {kibana-pull}135446[#135446] - -Elastic Security:: -For the Elastic Security 8.3.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -Keep all agents selected in query selection mode {kibana-pull}135530[#135530] - -Machine Learning:: -* Fixes put anomaly detection job endpoint when payload contains datafeed {kibana-pull}134986[#134986] -* Fixes trained model map associating wrong model to job {kibana-pull}134849[#134849] -* Use time range when validating datafeed preview {kibana-pull}134073[#134073] - -Maps:: -* Do not show layer error for term joins when terms aggregation does not return results {kibana-pull}135564[#135564] -* Fixes Vector map layers will not render when runtime field has '%' {kibana-pull}135491[#135491] - -[[release-notes-8.3.1]] -== {kib} 8.3.1 - -Review the following information about the {kib} 8.3.1 release. - -[float] -[[known-issues-8.3.1]] -=== Known issues - -[discrete] -[[known-issue-133965]] -.URL arguments cause API requests in Dev Tools to fail -[%collapsible] -==== -*Details* + -When you add any URL arguments, such as `?v` or `?pretty`, to API requests, the requests fail {kibana-issue}133965[#133965] - -*Impact* + -The known issue only impacts {kib} Dev Tools. All other sources of API requests are unaffected, such as curl and Elastic Cloud API console. -==== - -[discrete] -[[known-issue-alerting-rule]] -.Alerting rules stop running when you upgrade to 8.3.0 or 8.3.1 -[%collapsible] -==== -*Details* + -In 8.3.0 and 8.3.1, there is a known issue where alerting rules that were created or edited in 8.2.0 stop running when you upgrade {kib}. When you upgrade to 8.3.0 or 8.3.1, and your alerting rules have stopped running, the following error appears: - -[source,text] ----- -:: execution failed - security_exception: [security_exception] Reason: missing authentication credentials for REST request [/_security/user/_has_privileges], caused by: "" ----- - -*Impact* + -If you have upgraded to 8.3.0 or 8.3.1, and your alerting rules fail, reset the rules by disabling, then re-enabling them. When you disable, then re-enable your alerting rules, {kib} generates a new API key using the credentials of the user that manages the rules. - -To disable, then re-enable your alerting rules: - -. Open the main menu, then click *{stack-manage-app} > {rac-ui}*. - -. Select the failed alerting rules. - -. Click **Manage rules > Disable**, then click **Manage rules > Enable**. - -For more details about API key authorization, refer to <>. -==== - -[float] -[[breaking-changes-8.3.1]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.3.1. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[enhancement-v8.3.1]] -=== Enhancements -Operations:: -Adds EsArchiver datastream support {kibana-pull}132853[#132853] - -[float] -[[fixes-v8.3.1]] -=== Bug fixes -Alerting:: -Prevent negative snooze intervals {kibana-pull}134935[#134935] - -Elastic Security:: -For the Elastic Security 8.3.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Fixes dropping select all {kibana-pull}135124[#135124] -* Improves bulk actions for more than 10k agents {kibana-pull}134565[#134565] - -Infrastructure:: -Query persistent queue size for metricbeat documents {kibana-pull}134569[#134569] - -Observability:: -* Fixes a bug that displayed a toast error when deleting a rule {kibana-pull}135132[#135132] -* Fixes viewInAppUrl for custom metrics for Inventory Threshold Rule {kibana-pull}134114[#134114] - -Platform:: -* Fixes an issue where importing/copying the same saved object to the same space multiple times using the "Check for existing objects" option could fail or cause duplicates to be created {kibana-pull}135358[#135358] -* Fixes a bug where {es} nodes that stopped, then started again, were unreachable by {kib} for a given amount of requests when {kib} was configured to connect to multiple {es} nodes {kibana-pull}134628[#134628] - -[[release-notes-8.3.0]] -== {kib} 8.3.0 - -Review the following information about the {kib} 8.3.0 release. - -[float] -[[known-issues-8.3.0]] -=== Known issues - -Alerting users who are running 8.2 should not upgrade to either 8.3.0 or 8.3.1. -Both 8.3.0 and 8.3.1 have a bug where alerting rules that were created or edited -in 8.2 will stop running on upgrade. If you have upgraded to 8.3.0 or 8.3.1 and -your alerting rules have stopped running with an error similar to the following -example, you will need to go to *{stack-manage-app} > {rac-ui}*, multi-select -the failed rules, click on **Manage rules > Disable** and then click on **Manage -rules > Enable**. Disabling and re-enabling the rule will generate a new API key -using the credentials of the user performing these actions and reset the rule -state. For more details about API key authorization, refer to -<>. - -Example error message:: - -[source,text] ----- -:: execution failed - security_exception: [security_exception] Reason: missing authentication credentials for REST request [/_security/user/_has_privileges], caused by: "" ----- - -[float] -[[breaking-changes-8.3.0]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -[discrete] -.Removes Quandl and Graphite integrations -[%collapsible] -==== -*Details* + -The experimental `.quandl` and `.graphite` functions and advanced settings are removed from *Timelion*. For more information, check {kibana-pull}129581[#129581]. - -*Impact* + -When you use the `vis_type_timelion.graphiteUrls` kibana.yml setting, {kib} successfully starts, but logs a `[WARN ][config.deprecation] You no longer need to configure "vis_type_timelion.graphiteUrls".` warning. - -To leave your feedback about the removal of `.quandl` and `.graphite`, go to the link:https://discuss.elastic.co/c/elastic-stack/kibana/7[discuss forum]. -==== - -[discrete] -.Makes Osquery All with All base privilege -[%collapsible] -==== -*Details* + -The Osquery {kib} privilege has been updated, so that when the *Privileges for all features level* is set to *All*, this now applies *All* to Osquery privileges as well. Previously, users had to choose the *Customize* option to grant any access to Osquery. For more information, refer to {kibana-pull}130523[#130523]. - -*Impact* + -This impacts user roles that have *Privileges for all features* set to *All*. After this update, users with this role will have access to the Osquery page in {kib}. However, to use the Osquery feature fully, these requirements remain the same: users also need Read access to the logs-osquery_manager.result* index and the Osquery Manager integration must be deployed to Elastic Agents. -==== - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[deprecations-8.3.0]] -=== Deprecations - -The following functionality is deprecated in 8.3.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.3.0. - -[discrete] -[[deprecation-132790]] -.Removes `apm_user` -[%collapsible] -==== -*Details* + -Removes the `apm_user` role. For more information, check {kibana-pull}132790[#132790]. - -*Impact* + -The `apm_user`role is replaced with the `viewer` and `editor` built-in roles. -==== - -[discrete] -[[deprecation-132562]] -.Deprecates input controls -[%collapsible] -==== -*Details* + -The input control panels, which allow you to add interactive filters to dashboards, are deprecated. For more information, check {kibana-pull}132562[#132562]. - -*Impact* + -To add interactive filters to your dashboards, use the link:https://www.elastic.co/guide/en/kibana/8.3/add-controls.html[new controls]. -==== - -[discrete] -[[deprecation-131636]] -.Deprecates anonymous authentication credentials -[%collapsible] -==== -*Details* + -The apiKey, including key and ID/key pair, and `elasticsearch_anonymous_user` credential types for anonymous authentication providers are deprecated. For more information, check {kibana-pull}131636[#131636]. - -*Impact* + -If you have anonymous authentication provider configured with apiKey or `elasticsearch_anonymous_user` credential types, a deprecation warning appears, even when the provider is not enabled. -==== - -[discrete] -[[deprecation-131166]] -.Deprecates v1 and v2 security_linux and security_windows jobs -[%collapsible] -==== -*Details* + -The v1 and v2 job configurations for security_linux and security_windows are deprecated. For more information, check {kibana-pull}131166[#131166]. - -*Impact* + -The following security_linux and security_windows job configurations are updated to v3: - -* security_linux: - -** v3_linux_anomalous_network_activity -** v3_linux_anomalous_network_port_activity_ecs -** v3_linux_anomalous_process_all_hosts_ecs -** v3_linux_anomalous_user_name_ecs -** v3_linux_network_configuration_discovery -** v3_linux_network_connection_discovery -** v3_linux_rare_metadata_process -** v3_linux_rare_metadata_user -** v3_linux_rare_sudo_user -** v3_linux_rare_user_compiler -** v3_linux_system_information_discovery -** v3_linux_system_process_discovery -** v3_linux_system_user_discovery -** v3_rare_process_by_host_linux_ecs - -* security_windows: - -** v3_rare_process_by_host_windows_ecs -** v3_windows_anomalous_network_activity_ecs -** v3_windows_anomalous_path_activity_ecs -** v3_windows_anomalous_process_all_hosts_ecs -** v3_windows_anomalous_process_creation -** v3_windows_anomalous_script -** v3_windows_anomalous_service -** v3_windows_anomalous_user_name_ecs -** v3_windows_rare_metadata_process -** v3_windows_rare_metadata_user -** v3_windows_rare_user_runas_event -** v3_windows_rare_user_type10_remote_login -==== - -[discrete] -[[deprecation-130336]] -.Updates the default legend size -[%collapsible] -==== -*Details* + -In the *Lens* visualization editor, the *Auto* default for *Legend width* has been deprecated. For more information, check {kibana-pull}130336[#130336]. - -*Impact* + -When you create *Lens* visualization, the default for the *Legend width* is now *Medium*. -==== - -[discrete] -[[deprecation-122075]] -.Deprecates `xpack.data_enhanced.*` -[%collapsible] -==== -*Details* + -In kibana.yml, the `xpack.data_enhanced.*` setting is deprecated. For more information, check {kibana-pull}122075[#122075]. - -*Impact* + -Use the `data.*` configuration parameters instead. -==== - -[float] -[[features-8.3.0]] -=== Features - -{kib} 8.3.0 adds the following new and notable features. - -Alerting:: -* Adds circuit breaker for max number of actions by connector type {kibana-pull}128319[#128319] -* Adds `bulkEdit` method to alerting rulesClient and internal _bulk_edit API, that allow bulk editing of rules {kibana-pull}126904[#126904] - -Cases:: -* Adds average time to close metric in Cases {kibana-pull}131909[#131909] -* View all alerts attached to a case in the alerts table. The feature is experimental {kibana-pull}131883[#131883] -* Adds severity field to Cases {kibana-pull}131626[#131626] -* Adds the ability to delete comments in Cases {kibana-pull}130254[#130254] - -Dashboard:: -Enables the new controls by default {kibana-pull}131341[#131341] - -Discover:: -* To enable Threshold Alerts, adds the ability to edit dataView, query, & filters {kibana-pull}131688[#131688] -* To enable Threshold Alerts, extended the {es} query rule with search source-based data fetching {kibana-pull}124534[#124534] - -Elastic Security:: -For the Elastic Security 8.3.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -Changes to agent upgrade modal to allow for rolling upgrades {kibana-pull}132421[#132421] - -Lens & Visualizations:: -* Adds method to re-link visualizations with missing `SavedSearch` {kibana-pull}132729[#132729] -* Adds support of Data View switching for Agg-Based visualizations {kibana-pull}132184[#132184] - -Machine Learning:: -* Adds the ability to create anomaly detection jobs from Lens visualizations {kibana-pull}129762[#129762] -* Adds trained model testing for additional pytorch models {kibana-pull}129209[#129209] - -Management:: -* Adds saved object relationships to data view management {kibana-pull}132385[#132385] -* Adds support for feature_states {kibana-pull}131310[#131310] - -Monitoring:: -Adds the Stack monitoring health API {kibana-pull}132705[#132705] - -Observability:: -* Adds the ability to bulk attach multiple alerts to a Case {kibana-pull}130958[#130958] -* Adds rule details page {kibana-pull}130330[#130330] -* Adds span link {kibana-pull}126630[#126630] -* Adds ML expected model bounds as an option to Comparison controls {kibana-pull}132456[#132456] - -Platform:: -Adds `xyVis` and `layeredXyVis` {kibana-pull}128255[#128255] - -Querying & Filtering:: -Improves the current filter/search experience {kibana-pull}128401[#128401] - -Sharing:: -Adds method to re-link visualizations with missing index-pattern {kibana-pull}132336[#132336] - -For more information about the features introduced in 8.3.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.3.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.3.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.3.0]] -=== Enhancements -Alerting:: -* Adds helper text in the edit rule form about the change in privileges when saving the rule {kibana-pull}131738[#131738] -* Display rule API key owner to users who can manage API keys {kibana-pull}131662[#131662] - -Canvas:: -Fixes reference line overlay {kibana-pull}132607[#132607] - -Cases:: -* Show a warning for deprecated preconfigured connectors {kibana-pull}132237[#132237] -* Reduce space taken by the reporter column in the all cases table {kibana-pull}132200[#132200] -* Adds a tooltip to show truncate tags in Cases {kibana-pull}132023[#132023] -* Adds the ability to create a case from within the selection case modal {kibana-pull}128882[#128882] - -Content Management:: -The list view for Dashboard, Visualize Library, Maps, and Graph has a new "Last updated" column to easily access content that has been recently modified {kibana-pull}132321[#132321] - -Dashboard:: -* Improves the banner {kibana-pull}132301[#132301] -* Adds Analytics No Data Page {kibana-pull}132188[#132188] -* Adds field first control creation {kibana-pull}131461[#131461] -* Make text field based Options list controls case Insensitive {kibana-pull}131198[#131198] -* Allow existing controls to change type {kibana-pull}129385[#129385] - -Discover:: -* Adds an option to hide specified filter actions from SearchBar filter panels {kibana-pull}132037[#132037] -* Adds Analytics No Data Page {kibana-pull}131965[#131965] -* Adds close button to field popover using Document Explorer {kibana-pull}131899[#131899] -* Adds monospace font in Document Explorer {kibana-pull}131513[#131513] -* Adds a tour for Document Explorer {kibana-pull}131125[#131125] -* Display current interval setting {kibana-pull}130850[#130850] -* Adds a direct link from sample data to Discover {kibana-pull}130108[#130108] - -Elastic Security:: -For the Elastic Security 8.3.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Move integration labels below title and normalise styling {kibana-pull}134360[#134360] -* Adds First Integration Multi Page Steps Flow MVP (cloud only) {kibana-pull}132809[#132809] -* Optimize package installation performance, phase 2 {kibana-pull}131627[#131627] -* Adds APM instrumentation for package install process {kibana-pull}131223[#131223] -* Adds "Label" column + filter to Agent list table {kibana-pull}131070[#131070] -* Adds `cache-control` headers to key `/epm` endpoints in Fleet API {kibana-pull}130921[#130921] -* Optimize package installation performance, phase 1 {kibana-pull}130906[#130906] -* Adds experimental features (feature flags) config to fleet plugin {kibana-pull}130253[#130253] -* Adds redesigned Fleet Server flyout {kibana-pull}127786[#127786] - -Lens & Visualizations:: -* Renders no data component if there is no {es} data or dataview in *Visualize* {kibana-pull}132223[#132223] -* Swaps dimensions for mosaic in *Lens* {kibana-pull}131945[#131945] -* Adds log and sqrt scale in *Lens* {kibana-pull}131940[#131940] -* Adds collapse fn to table and xy chart in *Lens* {kibana-pull}131748[#131748] -* Allow filtering on metric vis in *Lens* {kibana-pull}131601[#131601] -* Improved interval input in *Lens* {kibana-pull}131372[#131372] -* Adds the Discover drilldown to *Lens* {kibana-pull}131237[#131237] -* Update defaults for metric vis in *Lens* {kibana-pull}129968[#129968] -* Adds range event annotations in *Lens* {kibana-pull}129848[#129848] -* Adds accuracy mode for Top Values in *Lens* {kibana-pull}129220[#129220] -* Adds type murmur3 into the *Lens* fields list {kibana-pull}129029[#129029] - -Machine Learning:: -* Optimize resize behaviour for the Anomaly Explorer page {kibana-pull}132820[#132820] -* Wizard validation improvements {kibana-pull}132615[#132615] -* Support version fields in anomaly detection wizards {kibana-pull}132606[#132606] -* Context for recovered alerts {kibana-pull}132496[#132496] -* Adding UI for question_answering model testing {kibana-pull}132033[#132033] -* Adds recognized modules links for Index data visualizer {kibana-pull}131342[#131342] -* Anomaly Detection: Adds View in Maps item to Actions menu in the anomalies table {kibana-pull}131284[#131284] -* Adding v3 modules for Security_Linux and Security_Windows and Deprecating v1 + v2 {kibana-pull}131166[#131166] -* Data Frame Analytics creation wizard: add support for filters in saved searches {kibana-pull}130744[#130744] -* Edit job selection on data frame analytics results and map pages {kibana-pull}130419[#130419] -* Resizable/Collapsible Top Influencers section {kibana-pull}130018[#130018] - -Management:: -* Adds context for recovered alerts {kibana-pull}132707[#132707] -* Adds warnings for managed system policies {kibana-pull}132269[#132269] -* Skip empty prompt screen {kibana-pull}130862[#130862] -* Console now supports properly handling multiple requests. For es errors such as `400`, `405` exception results are displayed with successful request results in the order they called {kibana-pull}129443[#129443] -* Display vector tile API response in Console {kibana-pull}128922[#128922] -* Adds option to disable keyboard shortcuts {kibana-pull}128887[#128887] - -Maps:: -* Show marker size in legend {kibana-pull}132549[#132549] -* Fixes marker size scale issue for counts {kibana-pull}132057[#132057] -* Scale marker size by area {kibana-pull}131911[#131911] -* Localized basemaps {kibana-pull}130930[#130930] -* Support term joins for Elasticsearch document source with vector tile scaling {kibana-pull}129771[#129771] -* Allow feature editing for document layers with "applyGlobalTime", "applyGlobalQuery", and joins {kibana-pull}124803[#124803] - -Observability:: -* Bumps synthetics integration package to 0.9.4 {kibana-pull}133423[#133423] -* Immediately re-run monitors in the synthetics service when they're edited {kibana-pull}132639[#132639] -* Enables log flyouts on APM logs tables {kibana-pull}132617[#132617] -* Adds logging to Metric Threshold Rule {kibana-pull}132343[#132343] -* Adds Page load distribution chart to overview page {kibana-pull}132258[#132258] -* Show experimental locations only when a particular flag is enabled {kibana-pull}132063[#132063] -* Trace explorer {kibana-pull}131897[#131897] -* Static Java agent version list becomes stale quickly {kibana-pull}131759[#131759] -* Adds recovery context to Log Threshold rule {kibana-pull}131279[#131279] -* Adds recovery context to the Metric Threshold rule {kibana-pull}131264[#131264] -* Adds context variables to recovery alerts for Inventory Threshold rule {kibana-pull}131199[#131199] -* Copy alert state to alert context and implement alert recovery {kibana-pull}128693[#128693] -* Progressive fetching (experimental) {kibana-pull}127598[#127598] -* Replace environment dropdown with SuggestionsSelect in landing pages and service overview page {kibana-pull}126679[#126679] -* Store Logs UI settings in a dedicated `infrastructure-monitoring-log-view` saved object {kibana-pull}125014[#125014] - -Platform:: -* The saved objects management table has a new "Last updated" column to easily access recently modified saved objects {kibana-pull}132525[#132525] -* Sync panels tooltips on dashboard level {kibana-pull}130449[#130449] - -Querying & Filtering:: -* Support fields custom label on filter editor {kibana-pull}130533[#130533] -* Allows comma delimiter on the filters multiple selections dropdowns {kibana-pull}130266[#130266] - -Security:: -* Disallows creating a role with an existing name in the role management page. Introduces an optional boolean `createOnly` parameter in the create role API to prevent overwriting existing roles; the default value is false, preserving the original API behavior {kibana-pull}132218[#132218] -* Adds experimental `csp.disableUnsafeEval` config option. Set this to `true` to remove the link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions[`unsafe-eval`] source expression from the `script-src` Content Security Policy (CSP) directive. The default value is `false`, which is identical to the original Kibana behavior {kibana-pull}124484[#124484] - -[float] -[[fixes-v8.3.0]] -=== Bug fixes -Alerting:: -* Don't load connectors and connector types when there isn't an encryptionKey {kibana-pull}133335[#133335] -* Adds cloud icon "ess-icon" at the end of the config keys in "alerting" {kibana-pull}131735[#131735] -* Fixes optional spaceId in rules_client {kibana-pull}130704[#130704] - -Content Managment:: -Fixes the listingLimit settings url {kibana-pull}129701[#129701] - -Dashboard:: -* Adds Fatal Error Handling {kibana-pull}133579[#133579] -* Hide in Print Mode {kibana-pull}133446[#133446] -* Send Control State to Reporting Via Locator {kibana-pull}133425[#133425] -* Fixes new controls causing unsaved changes bug {kibana-pull}132850[#132850] - -Design:: -* Keyboard shortcut popup {kibana-pull}133069[#133069] -* Adding aria-label for discover data grid select document checkbox {kibana-pull}131277[#131277] -* Adds item descriptions to edit button screen reader labels in TableListView {kibana-pull}125334[#125334] - -Discover:: -* Hide "Add a field", "Edit" and "Create a data view" buttons in viewer mode {kibana-pull}134582[#134582] -* Unify definition of field names and field descriptions {kibana-pull}134463[#134463] -* Address "Don't call Hooks" React warnings {kibana-pull}134339[#134339] -* Include current filters into "Test query" request {kibana-pull}134184[#134184] -* Prevent rule flyout from being open simultaneously with other popovers like search suggestions {kibana-pull}132108[#132108] -* Fixes link to open new window {kibana-pull}131930[#131930] -* Discover Classic View Filter In/Out placement when `truncate:maxHeight` is set to 0 {kibana-pull}129942[#129942] -* Fixes inconsistent usage of arrow icons on Surrounding documents page {kibana-pull}129292[#129292] -* Show a fallback empty message when no results are found {kibana-pull}128754[#128754] - -Elastic Security:: -For the Elastic Security 8.3.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Bulk reassign kuery optimize {kibana-pull}134673[#134673] -* Fixes flickering tabs layout in add agent flyout {kibana-pull}133769[#133769] -* Adds $ProgressPreference to windows install command in flyout {kibana-pull}133756[#133756] -* Fixes sorting by size on data streams table {kibana-pull}132833[#132833] - -Infrastructure:: -Pass decorated server to routes {kibana-pull}133264[#133264] - -Lens & Visualizations:: -* Hide null cells in Heatmap {kibana-pull}134450[#134450] -* Fixes formula generate error in *Lens* {kibana-pull}134434[#134434] -* Better default for date_range agg in *Visualize* {kibana-pull}134220[#134220] -* Keep suggestions stable in *Lens* {kibana-pull}134212[#134212] -* Fixes voiceover drag and drop in *Lens* {kibana-pull}134196[#134196] -* Fixes palette bug {kibana-pull}134159[#134159] -* Fixes multi index pattern load on the server in *TSVB* {kibana-pull}134091[#134091] -* Fixes axis title visibility bug in *Lens* {kibana-pull}134082[#134082] -* Fixes broken drilldowns for gauges and heatmaps in *Lens* {kibana-pull}134008[#134008] -* Fixes application of suffix formats in *Lens* {kibana-pull}133780[#133780] -* Do not show edit field for record field in *Lens* {kibana-pull}133762[#133762] -* Fixes discover drilldown for non-time field case in *Lens* {kibana-pull}133334[#133334] -* Do not reset session on Lens load with filters in *Lens* {kibana-pull}133191[#133191] -* Fixes transition issue in *Lens* {kibana-pull}132956[#132956] -* Escape label in lodash set command in *TSVB* {kibana-pull}132932[#132932] -* Changing the `Data View` logic with an initially missed `Data View` does not work in *TSVB* {kibana-pull}132796[#132796] -* Terms with keyword field with "numbers" is displayed with a weird date in *TSVB* {kibana-pull}132226[#132226] -* TSVB] Chart is failing when the user tries to add a percentile_rank {kibana-pull}132105[#132105] -* Fixes metric label font size in *Visualize* {kibana-pull}132100[#132100] -* Datatable: Do not apply truncation in value popover in *Lens* {kibana-pull}132005[#132005] -* Fixes percentile rank math in *TSVB* {kibana-pull}132003[#132003] -* Fixes timezone bucket shift in *Timelion* {kibana-pull}131213[#131213] -* Fixes vega controls layout in *Vega* {kibana-pull}130954[#130954] -* Fixes requesting not permitted or used data views in *Timelion* {kibana-pull}130899[#130899] -* Fixed bugs when using `include/exclude` options for Terms in *TSVB* {kibana-pull}130884[#130884] -* Make series agg work after math in *TSVB* {kibana-pull}130867[#130867] -* Use elastic-charts axis calculation in *Lens* {kibana-pull}130429[#130429] -* Make suggestions depend on active data in *Lens* {kibana-pull}129326[#129326] -* Adds back setMapView function in *Vega* {kibana-pull}128914[#128914] -* Fixes the Order by setting for split chart in metric and custom metric visualizations {kibana-pull}128185[#128185] - -Machine Learning:: -* Fixes creation of the custom URLs for Kibana Dashboard {kibana-pull}134248[#134248] -* Fixes expanded row stats not loading all correctly whenever sort by cardinality {kibana-pull}134113[#134113] -* Fixes Data visualizer showing 0 count in the doc count chart even though documents do exist {kibana-pull}134083[#134083] -* Fixes querying anomalies for the Single Metric Viewer {kibana-pull}133419[#133419] -* Fixes Anomaly Charts filtering based on the swim lane job selection {kibana-pull}133299[#133299] -* Fixes handling of unrecognised URLs {kibana-pull}133157[#133157] -* Prevent duplicate field selection in detector modal {kibana-pull}133018[#133018] -* Fixes single metric job with doc_count field {kibana-pull}132997[#132997] -* Hide job messages clear notifications tooltip on click {kibana-pull}132982[#132982] -* Filtering category runtime fields in advanced wizard {kibana-pull}132916[#132916] -* Fixes trained model testing so it is available for users with ML read permissions {kibana-pull}132698[#132698] -* Adding type for job summary state {kibana-pull}131643[#131643] - -Management:: -* Fixes linebreaks (\r\n) mis-applied from history {kibana-pull}131037[#131037] -* Fixes Kibana DevTool Copy as CURL does not url encode special chars in indice date math {kibana-pull}130970[#130970] -* Fixes cat APIs returning as escaped string {kibana-pull}130638[#130638] -* Fixes Elasticsearch doc VIEW IN CONSOLE will clean local Kibana console form history {kibana-pull}127430[#127430] - -Maps:: -* Fixes icon markers fail to load when browser zoomed out {kibana-pull}134367[#134367] -* Hide create filter UI in canvas {kibana-pull}133943[#133943] -* Use label features from ES vector tile search API to fix multiple labels {kibana-pull}132080[#132080] -* Fixes Map panels should not show the user controls in a dashboard report {kibana-pull}131970[#131970] -* Show "no results" found for vector tile aggregations when there are no results {kibana-pull}130821[#130821] - -Monitoring:: -* Prevent exceptions in rule when no data present {kibana-pull}131332[#131332] -* Fixes displaying ES version for external collection {kibana-pull}131194[#131194] -* Fixes node type detection for external collection {kibana-pull}131156[#131156] -* Use server.publicBaseUrl in Alert links {kibana-pull}131154[#131154] - -Observability:: -* Fixes x-axis on error charts {kibana-pull}134193[#134193] -* Display ENVIRONMENT_ALL label instead of value {kibana-pull}133616[#133616] -* Fixes normalizers to not parse list values if they are already parsed {kibana-pull}133563[#133563] -* Change bucket_scripts to use params for thresholds {kibana-pull}133214[#133214] -* Use Observability rule type registry for list of rule types {kibana-pull}132484[#132484] -* APM anomaly rule type should appear in observability rules page {kibana-pull}132476[#132476] -* Fixes monitors details page errors {kibana-pull}132196[#132196] -* Set a valid `service_name` for python APM onboarding {kibana-pull}131959[#131959] -* Rename service groups template titles and links {kibana-pull}131381[#131381] -* Show service group icon only for service groups {kibana-pull}131138[#131138] -* Refactor Metric Threshold rule to push evaluations to Elasticsearch {kibana-pull}126214[#126214] -* Ellipsis truncation issue - dependencies and service section {kibana-pull}122203[#122203] -* Fixes lookback window for anomalies for anomaly alert {kibana-pull}93389[#93389] - -Operations:: -Fixes error handling on precommit hook {kibana-pull}132998[#132998] - -Platform:: -Prevents Kibana from bootlooping during migrations when Elasticsearch routing allocation settings are incompatible {kibana-pull}131809[#131809] - -Querying & Filtering:: -Allows the negative character on the number type fields {kibana-pull}130653[#130653] - -Reporting:: -Remove controls from reports {kibana-pull}134240[#134240] - -Security:: -* Session view process events index will now match on prefixed index {kibana-pull}133984[#133984] -* Timestamp issue fix + updated Jest to include mock date format {kibana-pull}132290[#132290] -* Session view alerts loading improvements, and other polish / bug fixes {kibana-pull}131773[#131773] - -[[release-notes-8.2.3]] -== {kib} 8.2.3 - -Review the following information about the {kib} 8.2.3 release. - -[float] -[[breaking-changes-8.2.3]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.2.2. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[fixes-v8.2.3]] -=== Bug fixes -Elastic Security:: -For the Elastic Security 8.2.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Elastic Agent integration now installs automatically if agent monitoring is turned on in the agent policy {kibana-pull}133530[#133530] -* Removes {beats} tutorials from the Elastic Stack category {kibana-pull}132957[#132957] -Management:: -Fixes an edge case in the Inspector request selector where duplicate request names could result in a UI bug {kibana-pull}133511[#133511] -Operations:: -Fixes an issue where `node.options` was reset between upgrades in deb and rpm packages {kibana-pull}133249[#133249] -Platform:: -defaultIndex attribute was migrated for config saved object {kibana-pull}133339[#133339] - -[[release-notes-8.2.2]] -== {kib} 8.2.2 - -Review the following information about the {kib} 8.2.2 release. - -[float] -[[breaking-changes-8.2.2]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.2.2. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[fixes-v8.2.2]] -=== Bug fix -Machine Learning:: -Fixes width of icon column in Messages table {kibana-pull}132444[#132444] - -[[release-notes-8.2.1]] -== {kib} 8.2.1 - -Review the following information about the {kib} 8.2.1 release. - -[float] -[[breaking-changes-8.2.1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.2.1. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[enhancement-v8.2.1]] -=== Enhancements -Elastic Security:: -For the Elastic Security 8.2.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Monitoring:: -* Adds the ability collect Telemetry {kibana-pull}130498[#130498] -* Adds the ability to report panels in dashboards by type {kibana-pull}130166[#130166] - -[float] -[[fixes-v8.2.1]] -=== Bug fixes -Discover:: -* Fixes Document Explorer infinite height growth {kibana-pull}131723[#131723] -* Fixes links in helper callouts {kibana-pull}130873[#130873] -Elastic Security:: -For the Elastic Security 8.2.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Removes legacy component templates on package install {kibana-pull}130758[#130758] -Lens & Visualizations:: -* Fixes time shift bug in *Lens* {kibana-pull}132000[#132000] -* Fixes single color palette configuration {kibana-pull}131128[#131128] -Machine Learning:: -* Removes alerting_rules from general job list items {kibana-pull}131936[#131936] -* Fixes management app docs links {kibana-pull}130776[#130776] -Management:: -* Restores data view management field type conflict detail modal {kibana-pull}132197[#132197] -* Fixes test data for import and export between versions tests {kibana-pull}131470[#131470] -* Fixes condition auto-completion for templates in Console {kibana-pull}126881[#126881] -Maps:: -* Fixes background tiles in a map panel might not load in a screenshot report {kibana-pull}131185[#131185] -Observability:: -* Services without application metrics display an error {kibana-pull}131347[#131347] -* Correctly interprets the `resetting` and `reverting` job states {kibana-pull}129570[#129570] -Platform:: -* Migrations incorrectly detects cluster routing allocation setting as incompatible {kibana-pull}131712[#131712] -* Fixes resetting image values {kibana-pull}131610[#131610] -* Fixes a bug causing the newsfeed to not be properly displayed in locales other than english {kibana-pull}131315[#131315] - -[[release-notes-8.2.0]] -== {kib} 8.2.0 - -Review the following information about the {kib} 8.2.0 release. - -[float] -[[known-issue-v8.2.0]] -=== Known issue - -Lens & visualizations:: -A change in the Markdown library that {kib} uses to create *TSVB* *Markdown* visualizations and *Text* dashboard panels renders some tables differently. For more information, check out link:https://github.com/markdown-it/markdown-it/pull/767[#767]. - -[float] -[[breaking-changes-8.2.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.2.0. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[features-8.2.0]] -=== Features -{kib} 8.2.0 adds the following new and notable features. - -Alerting:: -* Keep the number_of_scheduled_actions in event log {kibana-pull}128438[#128438] -* Remove defaultRuleTaskTimeout and set ruleType specific timeout from kibana.yml {kibana-pull}128294[#128294] -* Limit the executable actions per rule execution {kibana-pull}128079[#128079] and {kibana-pull}126902[#126902] - -Cases:: -* Adds Cases to the Stack Management page as a technical preview feature {kibana-pull}125224[#125224] - -Dashboard:: -* Adds time slider control {kibana-pull}128305[#128305] -* Adds Control group search settings {kibana-pull}128090[#128090] -* Adds hierarchical chaining setting to Controls {kibana-pull}126649[#126649] -* Adds options list API and validation system {kibana-pull}123889[#123889] - -Discover:: -* Enables document explorer by default {kibana-pull}125485[#125485] -* Adds `Copy to clipboard` ability for column name of Document Explorer {kibana-pull}123892[#123892] - -Elastic Security:: -For the Elastic Security 8.2.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Lens & Visualizations:: -* Adds manual annotations in *Lens* {kibana-pull}126456[#126456] -* Adds multi-field group by in *TSVB* {kibana-pull}126015[#126015] -* Adds ability to navigate to *Lens* with your current *TSVB* configuration {kibana-pull}114794[#114794] - -Machine Learning:: -* Add link to maps in charts section of Anomaly Explorer {kibana-pull}128697[#128697] -* Testing trained models in UI {kibana-pull}128359[#128359] -* Space aware trained models {kibana-pull}123487[#123487] - -Management:: -* Adds support for auto-complete for data streams {kibana-pull}126235[#126235] -* Adds ability to filter Data View UI for runtime fields {kibana-pull}124114[#124114] -* Adds ability to share data views across spaces via data view management {kibana-pull}123991[#123991] - -Observability:: -* Adds button which allows users to signup for the Synthetics service public beta {kibana-pull}128798[#128798] -* Adds "View in App URL" {{context.viewInAppUrl}} variable to the rule templating language {kibana-pull}128281[#128281] -* Adds "View in App URL" {{context.viewInAppUrl}} variable to the rule templating language {kibana-pull}128243[#128243] -* Adds "View in App URL" {{context.viewInAppUrl}} variable to the rule templating language {kibana-pull}127890[#127890] -* Adds view in app url as an action variable in the alert message for uptime app {kibana-pull}127478[#127478] - -For more information about the features introduced in 8.2.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.2.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.2.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.2.0]] -=== Enhancements -Alerting:: -* Adds error logs in rule details page {kibana-pull}128925[#128925] -* Simplify error banner on rules {kibana-pull}128705[#128705] -* Adds Previous Snooze button {kibana-pull}128539[#128539] -* Adds Snooze UI and Unsnooze API {kibana-pull}128214[#128214] -* Adds aggs to know how many rules are snoozed {kibana-pull}128212[#128212] -* Adds a connector for xMatters {kibana-pull}122357[#122357] - -Dashboard:: -* Adds option to open dashboard drilldowns in new tab or window {kibana-pull}125773[#125773] -* Adds range slider Control {kibana-pull}125584[#125584] - -Discover:: -Adds ability to edit histogram as vis {kibana-pull}125705[#125705] - -Elastic Security:: -For the Elastic Security 8.2.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.2.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Merge settings & mappings component template in @package {kibana-pull}128498[#128498] -* Redesign agent flyout {kibana-pull}128381[#128381] -* Adds a link from agent details page to agent dashboard {kibana-pull}127882[#127882] -* Update add agent instructions in fleet managed mode for Kubernetes {kibana-pull}127703[#127703] -* Added time_series_metric mapping for metric_type package field {kibana-pull}126322[#126322] -* Added support for dimension field {kibana-pull}126257[#126257] -* Refactor auto upgrade package policies logic {kibana-pull}125909[#125909] -* Move mappings from index template to component template {kibana-pull}124013[#124013] - -Lens & Visualizations:: -* Adds normalize_by_unit function and option in *Lens* {kibana-pull}128303[#128303] -* Adds suffix formatter in *Lens* {kibana-pull}128246[#128246] -* Adds Xy gap settings in *Lens* {kibana-pull}127749[#127749] -* Implements null instead of zero switch in *Lens* {kibana-pull}127731[#127731] -* Adds ability to include empty rows setting for date histogram in *Lens* {kibana-pull}127453[#127453] -* Adds support for multi rows headers for the table visualization in *Lens* {kibana-pull}127447[#127447] -* Adds ability to open *Lens* visualizations in *Discover* from dashboards {kibana-pull}127355[#127355] -* Auto-set exists filtering for last value in *Lens* {kibana-pull}127251[#127251] -* Adds ability to include number of values in default terms field label in *lens* {kibana-pull}127222[#127222] -* Adds ability to drop partial buckets option in *Lens* {kibana-pull}127153[#127153] -* Addds ability to allow top metric for last value in *Lens* {kibana-pull}127151[#127151] -* Improves Datatable content height with custom row height in *Lens* {kibana-pull}127134[#127134] -* Adds ability to set legend pixel width in *Lens* {kibana-pull}126018[#126018] -* Adds underlying data editor navigation in *Lens* {kibana-pull}125983[#125983] -* Adds top metrics aggregation to AggConfigs, Expressions, and Visualize {kibana-pull}125936[#125936] -* Adds the ability to detach from global time range in *Lens* {kibana-pull}125563[#125563] -* Adds last value, min and max on dates, allow last value on ip_range, number_range, and date_range in *Lens* {kibana-pull}125389[#125389] -* Adds version-aware sorting to data table in *Lens* {kibana-pull}125361[#125361] -* Cancel discarded searches in *Timelion* {kibana-pull}125255[#125255] -* Cancel discarded searches in *TSVB* {kibana-pull}125197[#125197] -* Adds the ability to allow users to disable auto-apply in *Lens* {kibana-pull}125158[#125158] -* Adds Filter custom label for kibanaAddFilter in *Vega* {kibana-pull}124498[#124498] -* Adds metric Viz config options, title position, and sizing in *Lens* {kibana-pull}124124[#124124] -* Adds the ability to make graph edges easier to click {kibana-pull}124053[#124053] -* Adds "Show empty rows" options to intervals function in *Lens* {kibana-pull}118855[#118855] - -Machine Learning:: -* Combines annotations into one block if multiple annotations overlap {kibana-pull}128782[#128782] -* Adds `throughput_last_minute` to the deployment stats {kibana-pull}128611[#128611] -* Adds new API endpoint to improve anomaly chart performance {kibana-pull}128165[#128165] -* Utilize ML memory stats endpoint for the memory overview chart {kibana-pull}127751[#127751] -* Deleting trained model space checks {kibana-pull}127438[#127438] -* Show at least one correlation value and consolidate correlations columns {kibana-pull}126683[#126683] -* Include fields not in docs in Data Visualizer field name control {kibana-pull}126519[#126519] -* Anomaly Explorer performance enhancements {kibana-pull}126274[#126274] -* Fixes Index data visualizer reaching Elasticsearch rate request limits {kibana-pull}124898[#124898] -* Adds cache for data recognizer module configs to reduce number of privilege checks {kibana-pull}126338[#126338] - -Management:: -* Extend Transform Health alerting rule with error messages check {kibana-pull}128731[#128731] -* Enable opening queries from any UI {kibana-pull}127461[#127461] -* No Data Views Component {kibana-pull}125403[#125403] - -Maps:: -* Remove usage of max file size advanced setting 1GB limit in geo file upload {kibana-pull}127639[#127639] -* Adds support for geohex_grid aggregation {kibana-pull}127170[#127170] -* Lens choropleth chart {kibana-pull}126819[#126819] -* Register GeoJson upload with integrations page {kibana-pull}126350[#126350] -* Support custom icons in maps {kibana-pull}113144[#113144] - -Observability:: -* Guided setup progress {kibana-pull}128382[#128382] -* Enable check for public beta {kibana-pull}128240[#128240] -* Guided setup button on the overview page {kibana-pull}128172[#128172] -* Show warning when users exceed a Synthetics Node throttling limits {kibana-pull}127961[#127961] -* Adds logging to Inventory Threshold Rule {kibana-pull}127838[#127838] -* O11y rules page {kibana-pull}127406[#127406] -* Enrich documents generated by the synthetics service with `port` information {kibana-pull}127180[#127180] -* Make UI indices space aware (support for spaces) {kibana-pull}126176[#126176] -* Setting for default env for service inventory {kibana-pull}126151[#126151] -* Alerts in overview page {kibana-pull}125337[#125337] -* Adds log rate to Exploratory View {kibana-pull}125109[#125109] -* Support switching between log source modes {kibana-pull}124929[#124929] -* Overview style updates {kibana-pull}124702[#124702] -* Adds full screen/copy button ability in browser inline script editing {kibana-pull}124500[#124500] -* Update position of legend and it's controls {kibana-pull}115854[#115854] - -Platform:: -Allow customizing {es} client maxSockets {kibana-pull}126937[#126937] - -[float] -[[fixes-v8.2.0]] -=== Bug fixes -Alerting:: -* Fixes bug when providing a single value to the `fields` query parameter of the Cases find API {kibana-pull}128143[#128143] -* Fixes the count of alerts in the cases table. Only unique alerts are being counted {kibana-pull}127721[#127721] -* Do not show the lens action if Visualize feature is not enabled {kibana-pull}127613[#127613] - -Dashboard:: -* Fixes control removal {kibana-pull}128699[#128699] -* Select televant data view ID {kibana-pull}128440[#128440] -* Close controls flyouts on unmount, save, and view mode change {kibana-pull}128198[#128198] - -Discover:: -* Account for hidden time column in default sort {kibana-pull}129659[#129659] -* Make field icons consistent across field list and doc tables {kibana-pull}129621[#129621] -* Fixes `Filter for field present` in expanded document view of Document Explorer {kibana-pull}129588[#129588] -* Cancel long running request after navigating out from Discover {kibana-pull}129444[#129444] -* Fixes height of JSON tab in Document flyout when using Document explorer in Safari {kibana-pull}129348[#129348] -* Fixes stuck action menu in expanded document sidebar {kibana-pull}127588[#127588] - -Elastic Security:: -For the Elastic Security 8.2.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Lens & Visualizations:: -* Fixes multi index pattern load bug in *TSVB* {kibana-pull}130428[#130428] -* Handle empty values for range formatters {kibana-pull}129572[#129572] -* Apply pinned filters to *Lens* {kibana-pull}129503[#129503] -* Imported vislib pie triggers unsaved viz warning when embedded on a dashboard in *Visualize* {kibana-pull}129336[#129336] -* Fixes auto session-renewal on non-timebased data views in *Lens* {kibana-pull}129313[#129313] -* Fixes steps behavior to happen at the change point in *TSVB* {kibana-pull}128741[#128741] -* Improve check for 0 opacity in *TSVB* {kibana-pull}128630[#128630] -* Fixes firefox scrollbars in *Vega* {kibana-pull}128515[#128515] -* Log data tables properly in *Lens* {kibana-pull}128297[#128297] -* Fixes annotation bounds bug in *TSVB* {kibana-pull}128242[#128242] -* Make sure x axis values are always strings in *Lens* {kibana-pull}128160[#128160] -* Use default number formatter as fallback if nothing else is specified in *Timelion* {kibana-pull}128155[#128155] -* Enable Save&Return button for canvas when dashboard permissions are off in *Visualize* {kibana-pull}128136[#128136] -* Fixes permission problem for "Save and return" button in *Lens* {kibana-pull}127963[#127963] -* Restore operation auto switch based on field type in *Lens* {kibana-pull}127861[#127861] -* Fixes mosaic color syncing in *Lens* {kibana-pull}127707[#127707] -* Make edge selection work {kibana-pull}127456[#127456] -* Remove opacity for fitting line series {kibana-pull}127176[#127176] -* Handle line/area fitting function when the editor has started with bar configuration in *Visualize* {kibana-pull}126891[#126891] -* Preserve custom label when changing with multi-terms settings in *Lens* {kibana-pull}126773[#126773] -* Fixes multi terms fields validation in *Lens* {kibana-pull}126618[#126618] -* Make Embeddable resilient when toggling actions in *Lens* {kibana-pull}126558[#126558] -* Make graph app resilient to no fields or missing data views {kibana-pull}126441[#126441] -* Fixes Formula to Quick functions does not preserve custom formatting in *Lens* {kibana-pull}124840[#124840] -* Inspector displays only visible content {kibana-pull}124677[#124677] -* Coloring tooltips in Heatmap are not properly positioned in *Visualize* {kibana-pull}124507[#124507] -* Adds rison helper and URL encoding for drilldown urls in *TSVB* {kibana-pull}124185[#124185] - -Machine Learning:: -* Fixes alignment of Anomaly Explorer swim lane annotations label on Firefox {kibana-pull}130274[#130274] -* Fixes Single Metric Viewer chart failing to load if no points during calendar event {kibana-pull}130000[#130000] -* Fixes Single Metric Viewer for jobs that haven't been run {kibana-pull}129063[#129063] -* Fix outlier detection results exploration color legend display {kibana-pull}129058[#129058] -* Fixes new anomaly detection job from saved search with no query filter {kibana-pull}129022[#129022] -* Fixes data frame analytics map saved object sync warning {kibana-pull}128876[#128876] -* Adds error toast to Data visualizer when using unpopulated time field {kibana-pull}127196[#127196] - -Management:: -* Transforms: Fix to not pass on default values in configurations {kibana-pull}129091[#129091] -* Encode + sign in ISO8601 time range in query {kibana-pull}126660[#126660] - -Maps:: -* Fixes lens region map visualization throws a silent error {kibana-pull}129608[#129608] -* Fixes double click issue when deleting a shape {kibana-pull}124661[#124661] - -Monitoring:: -* Exclude Malwarescore + Ransomware EP alerts from DRule telemetry {kibana-pull}130233[#130233] -* Rename "APM & Fleet Server" to "Integrations Server" {kibana-pull}128574[#128574] -* Fixes sorting by node status on nodes listing page {kibana-pull}128323[#128323] - -Observability:: -* Service environment should be selected when you edit the agent configuration {kibana-pull}129929[#129929] -* Adds migration to include synthetics and heartbeat indices on 8.2.0 {kibana-pull}129510[#129510] -* Rules summary on the Alerts view is not showing the count of rules {kibana-pull}129052[#129052] -* Fixes shadow for overview panels {kibana-pull}128878[#128878] -* Ensure rum_allow_origins setting only saves valid YAML strings {kibana-pull}128704[#128704] -* Standardize NOW as startedAt from executor options {kibana-pull}128020[#128020] -* Fixes synthetics recorder file upload {kibana-pull}127614[#127614] -* Service Maps popover detail metrics are aggregates over all transaction types {kibana-pull}125580[#125580] - -Platform:: -* Fixes an issue where duplicate data appears in the inspector datatable in *Lens* for heatmap visualizations, and stale data persists in the inspector datatable when you remove layers {kibana-pull}126786[#126786] -* Fixes an issue that caused {kib} to become unresponsive while generating a PDF report {kibana-pull}124787[#124787] -* Fixes an issue where an unfriendly notification title displays after you create a report {kibana-pull}123607[#123607] - -Security:: -* Fixes styles for "You do not have permission" screen {kibana-pull}129715[#129715] -* Change session expiration to override on app leave behavior {kibana-pull}129384[#129384] - -[[release-notes-8.1.3]] -== {kib} 8.1.3 - -Review the following information about the {kib} 8.1.3 release. - -[float] -[[security-update-v8.1.3]] -=== Security update - -The 8.1.3 release contains a fix to a potential security vulnerability. For more information, check link:https://discuss.elastic.co/t/kibana-7-17-3-and-8-1-3-security-update/302826[Security Announcements]. - -[float] -[[breaking-changes-8.1.3]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.1.3. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[fixes-v8.1.3]] -=== Bug fix -Discover:: -* Fixes toggle table column for classic table {kibana-pull}128603[#128603] - -[[release-notes-8.1.2]] -== {kib} 8.1.2 - -Review the following information about the {kib} 8.1.2 release. - -[float] -[[breaking-changes-8.1.2]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.1.2. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[enhancement-v8.1.2]] -=== Enhancement -Dashboard:: -Improve controls management UX {kibana-pull}127524[#127524] - -[float] -[[fixes-v8.1.2]] -=== Bug fixes -Discover:: -* Fixes toggle table column for classic table {kibana-pull}128603[#128603] -* Fixes selection popover close action without making a selection in Document Explorer {kibana-pull}128124[#128124] - -Elastic Security:: -For the Elastic Security 8.1.2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Management:: -Handle scenario when user has no indices {kibana-pull}128066[#128066] - -Monitoring:: -Rename "APM & Fleet Server" to "Integrations Server" {kibana-pull}128574[#128574] - -Platform:: -Fixes KQL typeahead missing description and improve display for long field names {kibana-pull}128480[#128480] - -[[release-notes-8.1.1]] -== {kib} 8.1.1 - -Review the following information about the {kib} 8.1.1 release. - -[float] -[[breaking-changes-8.1.1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.1.1, review the breaking changes, then mitigate the impact to your application. - -There are no breaking changes in {kib} 8.1.1. - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[enhancement-v8.1.1]] -=== Enhancement -Dashboard:: -Improves controls empty state {kibana-pull}125728[#125728] - -[float] -[[fixes-v8.1.1]] -=== Bug fixes -Data ingest:: -The dot expander processor in the Ingest Pipelines UI now allows setting a wildcard (`*`) for the field parameter {kibana-pull}123522[#123522] - -Elastic Security:: -For the Elastic Security 8.1.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.1.1 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Adds a new validation message {kibana-pull}127239[#127239] -* Fixes empty assets on package install {kibana-pull}127070[#127070] -* Hide enroll command when user creates a new agent policy in the Add agent flyout {kibana-pull}126431[#126431] -* Makes input IDs unique in agent policy yaml {kibana-pull}127343[#127343] -* Fixes links to Agent logs for APM, Endpoint, Synthetics, and OSQuery {kibana-pull}127480[#127480] - -[[release-notes-8.1.0]] -== {kib} 8.1.0 - -Review the following information about the {kib} 8.1.0 release. - -[float] -[[breaking-changes-8.1.0]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.1.0, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Removes legacy CSV export type -[%collapsible] -==== -*Details* + -The `/api/reporting/generate/csv` endpoint has been removed. For more information, refer to {kibana-pull}121435[#121435]. - -*Impact* + -If you are using 7.13.0 and earlier, {kibana-ref-all}/8.1/automating-report-generation.html[regenerate the POST URLs] that you use to automatically generate CSV reports. -==== - -[discrete] -.Removes legacy PDF shim -[%collapsible] -==== -*Details* + -The POST URLs that you generated in {kib} 6.2.0 no longer work. For more information, refer to {kibana-pull}121369[#121369]. - -*Impact* + -{kibana-ref-all}/8.1/automating-report-generation.html[Regenerate the POST URLs] that you use to automatatically generate PDF reports. -==== - -To review the breaking changes in previous versions, refer to the following: - -{kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] - -[float] -[[features-8.1.0]] -=== Features -{kib} 8.1.0 adds the following new and notable features. - -Canvas:: -* Adds Heatmap {kibana-pull}120239[#120239] -* Adds the *Filters* panel for element settings {kibana-pull}117270[#117270] and {kibana-pull}116592[#116592] - -Discover:: -* Adds document explorer callout {kibana-pull}123814[#123814] -* Adds ability to create data views from the sidebar {kibana-pull}123391[#123391] -* Adds redirect if there are no data views {kibana-pull}123366[#123366] -* Adds row height options {kibana-pull}122087[#122087] - -Elastic Security:: -For the Elastic Security 8.1.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Lens & Visualizations:: -* Adds the waffle visualization type to *Lens* {kibana-pull}119339[#119339] -* Adds the gauge visualization type to *Lens* {kibana-pull}118616[#118616] -* Adds multi terms support to *Top Values* in *Lens* {kibana-pull}118600[#118600] -* Adds a new heatmap implementation with elastic-charts to *Visualize Library* {kibana-pull}118338[#118338] -* Adds the Mosaic or mekko visualization type to *Lens* {kibana-pull}117668[#117668] -* Adds the ability to configure the Metric visualization type color palette in *Lens* {kibana-pull}116170[#116170] - -Machine Learning:: -* Enable Field statistics table on by default {kibana-pull}124046[#124046] -* Adds grouping to the side nav {kibana-pull}123805[#123805] -* Integration part 1: Create anomalies layer in maps {kibana-pull}122862[#122862] -* Replace navigation bar with a side nav {kibana-pull}121652[#121652] -* Overview page redesign {kibana-pull}120966[#120966] - -Management:: -* Support suggesting index templates v2 {kibana-pull}124655[#124655] -* *Console* now supports autocompletion for index templates and component templates introduced in {es} 7.8.0. -* Transforms: Support to set destination ingest pipeline {kibana-pull}123911[#123911] -* Transforms: Adds reset action to transforms management {kibana-pull}123735[#123735] -* Transforms: Support for terms agg in pivot configurations {kibana-pull}123634[#123634] - -Observability:: -* Adds Tail-based sampling settings {kibana-pull}124025[#124025] -* APM UI changes for serverless services / AWS lambda {kibana-pull}122775[#122775] - -For more information about the features introduced in 8.1.0, refer to <>. - -[[enhancements-and-bug-fixes-v8.1.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.1.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.1.0]] -=== Enhancements -Alerting:: -* Adds P50/95/99 for rule execution duration in the rules table {kibana-pull}123603[#123603] -* Adds dropdown for number of executions in Rule Details view {kibana-pull}122595[#122595] - -Canvas:: -* Adds titles to the heatmap axis {kibana-pull}123992[#123992] -* Adds the esql Monaco editor {kibana-pull}118531[#118531] -* Adds expression `metrisVis` workpad arguments {kibana-pull}114808[#114808] - -Dashboard:: -Adds the ability to always allow internal URLs in *Vega* {kibana-pull}124705[#124705] - -Data ingest:: -Adds the ability to create ingest pipelines from a CSV upload that enables mapping custom data source into ECS {kibana-pull}101216[#101216] - -Discover:: -* Improves the document explorer flyout {kibana-pull}120116[#120116] -* Adds the ability to preserve *Discover* main route state in breadcrumb links {kibana-pull}119838[#119838] -* Adds error state if chart loading fails {kibana-pull}119289[#119289] -* Enable Field statistics table on by default {kibana-pull}124046[#124046] - -Elastic Security:: -For the Elastic Security 8.1.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -* Adds shipper label {kibana-pull}122491[#122491] -* Adds support for non-superuser access to *Fleet* and *Integrations* {kibana-pull}122347[#122347] -* Adds support for bundling packages as zip archives with {kib} source {kibana-pull}122297[#122297] -* Makes the default integration install explicit {kibana-pull}121628[#121628] - -Lens & Visualizations:: -* Addx suport for timefilter/min/max in *Vega* URLs {kibana-pull}124077[#124077] -* The filtered field list now uses field caps API in *Lens* {kibana-pull}122915[#122915] -* Updates the *Lens* empty state copy {kibana-pull}122174[#122174] -* Adds a global filter for formulas in *Lens* {kibana-pull}121768[#121768] -* Adds rare terms in *Lens* {kibana-pull}121500[#121500] -* Adds previous time shift back in *Lens* {kibana-pull}121284[#121284] -* Adds the size ratio setting to pie and donut charts in *Lens* {kibana-pull}120101[#120101] -* Adds multi terms dnd support in *Lens* {kibana-pull}119841[#119841] -* Improves the color stop UI in *Lens* {kibana-pull}119165[#119165] -* Enables table pagination in *Lens* {kibana-pull}118557[#118557] -* Adds support for ephemeral sort to the data table embeddable {kibana-pull}117742[#117742] -* Debounce duplicate error messages in *Vega* {kibana-pull}116408[#116408] -* Replaces EUICodeEditor with Monaco in *Vega* {kibana-pull}116041[#116041] - -Machine Learning:: -* Adds missing document titles {kibana-pull}124125[#124125] -* Synchronize Anomaly charts cursor position for X-axis with *Lens* visualizations in *Dashboard* {kibana-pull}123951[#123951] -* Adds grouping to the side nav {kibana-pull}123805[#123805] -* Adds empty states for the Jobs list pages {kibana-pull}123462[#123462] -* Adds error messages to Index data visualizer and improve distribution charts for fields with low cardinality {kibana-pull}123306[#123306] -* Standardize Add embeddable flow from the Anomaly Explorer page {kibana-pull}123199[#123199] -* Integration part 1: Create anomalies layer in *Maps** {kibana-pull}122862[#122862] -* Adds options to exclude or include frozen data tier for Anomaly detection and Index data visualizer {kibana-pull}122306[#122306] -* Editing semi-structured text fields in grok pattern {kibana-pull}122274[#122274] -* Adds extra search deep links for nodes overview and file upload {kibana-pull}121740[#121740] -* Replace navigation bar with a side nav {kibana-pull}121652[#121652] -* File data visualizer reduce chunk size for slow processors {kibana-pull}121353[#121353] -* Adds ability to save session to Index data visualizer {kibana-pull}121053[#121053] -* Overview page redesign {kibana-pull}120966[#120966] -* Adds *Maps* UI action to Index data visualizer/*Discover* Field statistics {kibana-pull}120846[#120846] -* Adds auto generated drill down link to *Discover* for Anomaly explorer table {kibana-pull}120450[#120450] -* Adds multilayer time axis style to Data visualizer doc count chart {kibana-pull}117398[#117398] - -Management:: -* Transforms: Add call out warning & delete option if a task exists for a transform without a config {kibana-pull}123407[#123407] -* Adds warnings for actions for managed Anomaly detection jobs and Transforms {kibana-pull}122305[#122305] -* Refresh frequency refinements {kibana-pull}122125[#122125] -* Configure refresh frequency {kibana-pull}121874[#121874] -* Geo point field formatter {kibana-pull}121821[#121821] -* Adds links to docs {kibana-pull}121066[#121066] -* Highlight the tutorial example text with console syntax {kibana-pull}120474[#120474] -* Compress mappings response size for autocomplete {kibana-pull}120456[#120456] -* Handle binary data response {kibana-pull}119586[#119586] -* Improve error handling when local storage quota is full {kibana-pull}118495[#118495] -* Error handling {kibana-pull}109233[#109233] - -Maps:: -* Adds Shapefile import {kibana-pull}123764[#123764] -* Should be able to zoom in on selected range of timeslider {kibana-pull}122131[#122131] -* Delete button should be toggleable in Edit Features {kibana-pull}122017[#122017] -* Change "show as" from EuiSelect to EuiButtonGroup {kibana-pull}121960[#121960] -* Format counts {kibana-pull}119646[#119646] -* Convert maki icons to SDF sprites on-the-fly {kibana-pull}119245[#119245] -* Convert HeatmapLayer to vector tiles and add support for high resolution grids {kibana-pull}119070[#119070] -* Make the icon for max results limit more evident {kibana-pull}118044[#118044] -* Enable on-prem for *Vega* {kibana-pull}104422[#104422] - -Monitoring:: -Compatibility for agent data streams {kibana-pull}119112[#119112] - -Observability:: -* Adds Tail-based sampling settings {kibana-pull}124025[#124025] -* UI Monitor Management - Add namespace field {kibana-pull}123248[#123248] -* Default alert connectors email settings {kibana-pull}123244[#123244] -* Only show span.sync badge when relevant {kibana-pull}123038[#123038] -* Optimize waffle map {kibana-pull}122889[#122889] -* APM UI changes for serverless services / AWS lambda {kibana-pull}122775[#122775] -* Update the style of the service/backend info icons in the selected service/backend header {kibana-pull}122587[#122587] -* Adds basic infra metrics config {kibana-pull}120881[#120881] -* Adds comparision to service maps popover {kibana-pull}120839[#120839] -* Link originating service in traces list table {kibana-pull}120768[#120768] -* Prefer `service.name` for logs correlation {kibana-pull}120694[#120694] -* Query numerator & denominator simultaneously for log threshold alerts {kibana-pull}107566[#107566] - -Operations:: -Improves the file logging capabilities so that missing directories in the configured file path are now created before {kib} attempts to write to the file {kibana-pull}117666[#117666] - -Platform:: -* Add a new `elasticsearch.compression` configuration property to enable compression for communications between {kib} and {es} {kibana-pull}124009[#124009] -* Adds support of comments {kibana-pull}122457[#122457] -* Adds support for PNG and PDF reports on Darwin Arm64 architecture {kibana-pull}122057[#122057] -* Short URL client is now accessible on the frontend through plugin contract. *Dashboard* and *Discover* shared short URLs now contain a three word, human-readable slug {kibana-pull}121886[#121886] -* Adds the ability to add URL drilldowns to *Dashboard* panels {kibana-pull}121801[#121801] -* Adds a new structure to the report details flyout to help you find information faster {kibana-pull}120617[#120617] -* Adds HTML tag and impact level to axe-core CI violation reporter {kibana-pull}119903[#119903] -* Exposes {es} accuracy warnings to the user {kibana-pull}116632[#116632] - -Querying & Filtering:: -Improves the version field type {kibana-pull}123739[#123739] - -Security:: -* Audit logs now include records for individual saved objects when an entire space is deleted {kibana-pull}124145[#124145] -* User login audit events now include the session ID for better correlation, and single sign-on flows no longer result in an extra `user_logout` event {kibana-pull}124299[#124299] - -[float] -[[fixes-v8.1.0]] -=== Bug fixes -Alerting:: -* Fixes the pagination results for fetching existing alerts {kibana-pull}122474[#122474] -* Running disabled rules are now skipped {kibana-pull}119239[#119239] - -Canvas:: -* Fixes an issue where the image repeat element was not updating {kibana-pull}118701[#118701] -* Fixes an issue where *Canvas* validated values before saving variables {kibana-pull}118694[#118694] - -Dashboard:: -Adds the listing page callout when new dashboards are in progress {kibana-pull}117237[#117237] - -Discover:: -* Adds the ability to close the expanded document sidebar when you change data views {kibana-pull}119736[#119736] -* Fixes search on page load tests {kibana-pull}119087[#119087] - -Elastic Security:: -For the Elastic Security 8.1.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Enterprise Search:: -For the Elastic Enterprise Search 8.1.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. - -Fleet:: -* Readded missing packages to keep up to date list {kibana-pull}125787[#125787] -* Trimmed whitespace from package policy names {kibana-pull}125400[#125400] - -Lens & Visualizations:: -* Fixes some dashboard visualizations that could show "Could not located index pattern" errors when copied from one space to another {kibana-pull}126499[#126499] -* Rarity is not allowed in some cases in *Lens* {kibana-pull}125523[#125523] -* Fixes formatting logic for terms in *Lens* {kibana-pull}125408[#125408] -* Fixes focus on submitting filter popover in *Lens* {kibana-pull}125247[#125247] -* Fixes agg filter for sibling pipeline aggs {kibana-pull}125227[#125227] -* Panel intervals are now used for annotations in *TSVB* {kibana-pull}125222[#125222] -* Outdated inspector data is now hidden in *Vega* {kibana-pull}125051[#125051] -* *Vega* visualizations are no longer missing in sample data reports {kibana-pull}124886[#124886] -* Lucene queries on dashboards are now accepted on annotations and tables in *TSVB* {kibana-pull}124802[#124802] -* Top values now work for custom numeric formatters in *Lens* {kibana-pull}124566[#124566] -* Coloring tooltip in Heatmap is now working for `">= n"` values in *Visualize Library* {kibana-pull}124521[#124521] -* Fixes a metric contrast issue in *TSVB* {kibana-pull}124509[#124509] -* Do not refresh session on "now" drift on incoming data in *Lens* {kibana-pull}124389[#124389] -* Coloring tooltips in Pie are not properly positioned in *Visualize* {kibana-pull}124330[#124330] -* Label placeholder always defaults to the lens proposed text in *Lens* {kibana-pull}124222[#124222] -* Show warning for completely static formula in *Lens* {kibana-pull}124213[#124213] -* Adds step value to make Safari validation work properly in *Lens* {kibana-pull}124210[#124210] -* Guard against parse failures in *Visualize* {kibana-pull}124209[#124209] -* Fixes heatmap suggestions in *Lens* {kibana-pull}124099[#124099] -* Fixes the percentage format for percentiles series {kibana-pull}124098[#124098] -* Displays custom bounds error for right axis when lower bound is above 0 in *Lens* {kibana-pull}124037[#124037] -* Clicking a series agg timeseries chart split by terms should not create a filter in *TSVB* {kibana-pull}124031[#124031] -* Save default data view in *TSVB* {kibana-pull}123997[#123997] -* Switch default bar width to 0px in *TSVB* {kibana-pull}123926[#123926] -* Formatting in the left axis is not respected when I have two separate axis in *TSVB* {kibana-pull}123903[#123903] -* Fixes series containing colon in *TSVB* {kibana-pull}123897[#123897] -* Fixes records field name and migrate in *Lens* {kibana-pull}123894[#123894] -* Hides ticks on the y axis for layers with the same format and different template in *TSVB* {kibana-pull}123598[#123598] -* Various fixes for Lens embeddables in *Lens* {kibana-pull}123587[#123587] -* Make sure session is updated and passed to the embeddable in *Visualize* {kibana-pull}123538[#123538] -* Fixes time range issue on save in *Lens* {kibana-pull}123536[#123536] -* Report override data views to the dashboard in *TSVB* {kibana-pull}123530[#123530] -* Handle ignore daylight time correctly and fix shift problem in *TSVB* {kibana-pull}123398[#123398] -* AggConfigs: Make base id check more stable {kibana-pull}123367[#123367] -* TSVB fix flickering {kibana-pull}122921[#122921] -* Hide tooltips while dragging dimensions in *Lens* {kibana-pull}122198[#122198] -* Make sure saved search id is carried over to saved object {kibana-pull}121082[#121082] -* Paginate through index patterns {kibana-pull}120972[#120972] -* Show generic error for invalid time shift string in *Lens* {kibana-pull}120077[#120077] -* Improves column type detection in table for alignment in *Lens* {kibana-pull}120007[#120007] -* Fixes the broken "aggregate function" in *TSVB* table {kibana-pull}119967[#119967] -* Hide fit from suggestions in *Timelion* {kibana-pull}119568[#119568] -* Match visualization type to first series type when available {kibana-pull}119377[#119377] -* Timelion & vega apply dataview from first filter in *Vega* {kibana-pull}119209[#119209] -* Reset filter state whenever group-by changed in *TSVB* {kibana-pull}118953[#118953] -* Prevent KQL Popovers From Stacking in *Lens* {kibana-pull}118258[#118258] -* Improves outside label placement for pie/donut charts in *Lens* {kibana-pull}115966[#115966] - -Machine Learning:: -* Fixes permission check for 'View examples' link from Anomaly detection explorer page {kibana-pull}125090[#125090] -* Fixes auto-refresh interval {kibana-pull}124851[#124851] -* Fixes permission check for Discover/data view redirect from Anomaly detection explorer page {kibana-pull}124408[#124408] -* Fixes breadcrumbs inconsistencies and titles capitalisation {kibana-pull}123019[#123019] - -Management:: -* Update painless antlr grammar for fields API $-syntax {kibana-pull}125818[#125818] -* Adds permission check for 'Set as default data view' button on data view detail page {kibana-pull}124897[#124897] -* In *Index Management*, index details now display previously missing values for the number of deleted documents and the primary storage size {kibana-pull}124731[#124731] -* Transforms: Fix retention policy reset {kibana-pull}124698[#124698] -* Transforms: Fix sort on field names containing dots not applied in wizard preview grid {kibana-pull}124587[#124587] -* Transforms: Fix refresh when transform list is filtered {kibana-pull}124267[#124267] -* Fixes autocomplete inserting comma in triple quotes {kibana-pull}123572[#123572] -* Encode pathname {kibana-pull}122080[#122080] -* Autocomplete missing comma on correct location {kibana-pull}121611[#121611] -* Fixes wrong values in field format editor; fix wrong value formatting in field preview {kibana-pull}121300[#121300] -* Fixes autocomplete suggestions for lowercase methods and other related bug {kibana-pull}121033[#121033] -* Fixes autocomplete suggestions for repository of type `fs` (typo) {kibana-pull}120775[#120775] -* Fixes editor error while adding second request {kibana-pull}120593[#120593] -* Dev Tools Console: Expose the error_trace parameter for completion {kibana-pull}120290[#120290] -* Auto complete for script suggests deprecated query type {kibana-pull}120283[#120283] -* Fixes "Expected one of GET/POST/PUT/DELETE/HEAD" for lowercase methods {kibana-pull}120209[#120209] -* Make the Define script label non clickable {kibana-pull}119947[#119947] -* Fixes error markers in editor output {kibana-pull}119831[#119831] -* Change suggestions for Sampler and Diversified sampler aggregations {kibana-pull}119355[#119355] -* Adds Autocompletion for boxplot aggregation in Kibana Dev tools {kibana-pull}117024[#117024] -* Adds overrides for request parameters for Logstash PUT Pipeline API {kibana-pull}116450[#116450] -* @timestamp as default for timestamp field name in index pattern {kibana-pull}116126[#116126] - -Maps:: -* Fixes vector tile URL not properly encoded {kibana-pull}126208[#126208] -* Allows feature editing with vector tile scaling {kibana-pull}123409[#123409] -* Fixes Error rendering cluster layer of geoshape documents styled by category {kibana-pull}123308[#123308] -* Fetch geometry from fields API {kibana-pull}122431[#122431] -* Fixes vector tile double counting geo_shapes that cross tile boundaries {kibana-pull}121703[#121703] -* Refactor map telemetry to incrementally calculate usage stats {kibana-pull}121467[#121467] -* Fixes creating filter from array fields {kibana-pull}119548[#119548] - -Monitoring:: -* Stronger typing for monitoring configs {kibana-pull}125467[#125467] -* Fixes Alerts and Rules menu persisting to other apps {kibana-pull}124291[#124291] -* Fixes Logstash Pipeline hover timestamp isn't visible {kibana-pull}123091[#123091] -* Fixes date picker range options {kibana-pull}121295[#121295] - -Observability:: -* Set display names for columns and fix reason message {kibana-pull}124570[#124570] -* Rename Backend to Dependency {kibana-pull}124067[#124067] -* Enable parseTechnicalFields to accept partial alert documents {kibana-pull}123983[#123983] -* Include error documents in fallback query for services {kibana-pull}123554[#123554] -* Rewrite the data fetching for Inventory Threshold rule {kibana-pull}123095[#123095] -* Optimizations for Inventory Threshold Alerts {kibana-pull}122460[#122460] -* Increase composite size to 10K for Metric Threshold Rule and optimize processing {kibana-pull}121904[#121904] -* Fixes missing EUI theme in context {kibana-pull}121796[#121796] -* Rename alerting types in triggers_actions_ui {kibana-pull}121107[#121107] -* Fixes loading message for correlations table {kibana-pull}120921[#120921] -* Prefer host.name over host.hostname {kibana-pull}119952[#119952] - -Platform:: -* Improve `bfetch` error handling {kibana-pull}123455[#123455] -* Fixes a CSV export Reporting issue where expensive queries were used to collect the data when they were not needed {kibana-pull}123412[#123412] -* Fixes URL drilldown placeholder text and add placeholder capability to Monaco {kibana-pull}121420[#121420] -* Consider expired tasks invalid {kibana-pull}119664[#119664] -* `columns`. Fixes Bugs caused by using name instead of ID {kibana-pull}118470[#118470] - -Security:: -User login audit events now include the session ID for better correlation, and single sign-on flows no longer result in an extra `user_logout` event {kibana-pull}124299[#124299] - -[[release-notes-8.0.0]] -== {kib} 8.0.0 - -Review the {kib} 8.0.0 changes, then use the {kibana-ref-all}/7.17/upgrade-assistant.html[Upgrade Assistant] to complete the upgrade. - -[float] -[[breaking-changes-8.0.0]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade to 8.0.0, review the breaking change, then mitigate the impact to your application. - -[discrete] -.Removes the `console.ssl` setting -[%collapsible] -==== -*Details* + -The `console.ssl` setting has been removed. For more information, refer to {kibana-pull}123754[#123754]. - -*Impact* + -Before you upgrade to 8.0.0, remove `console.ssl` from kibana.yml. -==== - -To review the breaking changes in previous versions, refer to the following: - -<> | <> | <> | <> | -<> - -[float] -[[deprecations-8.0.0]] -=== Deprecation - -The following functionality is deprecated in 8.0.0, and will be removed in 9.0.0. -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend -you make the necessary updates after you upgrade to 8.0.0. - -[discrete] -[[deprecation-123229]] -.Removes support for `monitoring.cluster_alerts.allowedSpaces` -[%collapsible] -==== -*Details* + -The `monitoring.cluster_alerts.allowedSpaces` setting, which {kib} uses to create Stack Monitoring alerts, has been removed. For more information, refer to {kibana-pull}123229[#123229]. - -*Impact* + -Before you upgrade to 8.0.0, remove `monitoring.cluster_alerts.allowedSpaces` from kibana.yml. -==== - -To review the deprecations in previous versions, refer to the following: - -<> | <> - -[float] -[[known-issue-8.0.0]] -=== Known issue - -[discrete] -[[known-issue-123550]] -.Importing and copying saved objects causes weak links to break -[%collapsible] -==== -*Details* + -{kib} supports weak links in some saved objects. For example, a dashboard may include a Markdown panel that contains a relative URL to -another dashboard. Weak links are defined by free text, _not_ the saved object's relationships, and can break if **both** of the following -conditions are true: - -* You are importing saved objects into multiple spaces, _OR_ you are copying saved objects into another space -* Before you upgraded to 8.0.0, the saved objects did not already exist in the destinations - -In 8.0.0 and later, weak links break because <>. -This applies to both the UI and the API. -For more information, refer to {kibana-issue}123550[#123550]. - -*Impact* + -Saved objects in 7.x that are migrated during upgrade are **not** impacted. -Only _new_ saved objects that are imported or copied _multiple times_ (causing object IDs to change) are impacted. -If you are impacted, you can re-import or re-copy your saved objects after the fix is -implemented to preserve the weak links. -==== - -[float] -[[features-8.0.0]] -=== Features -For information about the features introduced in 8.0.0, refer to <>. - -Elastic Security:: -For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -To review the features in previous versions, refer to the following: - -<> | <> | <> | <> - -[[enhancements-and-bug-fixes-v8.0.0]] -=== Enhancements and bug fixes - -For detailed information about the 8.0.0 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.0.0]] -==== Enhancements -Dashboard:: -Clone ReferenceOrValueEmbeddables by value {kibana-pull}122199[#122199] - -Elastic Security:: -For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -[float] -[[fixes-v8.0.0]] -==== Bug fixes -APM:: -Restrict aggregated transaction metrics search to date range {kibana-pull}123445[#123445] - -Elastic Security:: -For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -Fleet:: -Allow empty strings for required text fields in package policies {kibana-pull}123610[#123610] - -Maps:: -Fixes Label border color is not removed from legend when disabled {kibana-pull}122705[#122705] - -Monitoring:: -Ensure logstash getNodes always contains a uuid {kibana-pull}124201[#124201] - -Security:: -Long-running requests no longer cause sporadic logouts in certain cases, even when user sessions are active {kibana-pull}122155[#122155] - -[[release-notes-8.0.0-rc2]] -== {kib} 8.0.0-rc2 - -For information about the {kib} 8.0.0-rc2 release, review the following information. - -[float] -[[breaking-changes-8.0.0-rc2]] -=== Breaking change - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking change, then mitigate the impact to your application. - -[discrete] -.Removes the ability to use `elasticsearch.username: elastic` in production -[%collapsible] -==== -*Details* + -In production, you are no longer able to use the `elastic` superuser to authenticate to {es}. For more information, refer to {kibana-pull}122722[#122722]. - -*Impact* + -When you configure `elasticsearch.username: elastic`, {kib} fails. -==== - -To review the breaking changes in previous versions, refer to the following: - -<> | <> | <> | -<> - -[float] -[[features-8.0.0-rc2]] -=== Features -{kib} 8.0.0-rc2 adds the following new and notable features. - -Dashboard:: -Dashboard Integration {kibana-pull}115991[#115991] -Elastic Security:: -For the Elastic Security 8.0.0-rc2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Monitoring:: -Enterprise Search Stack Monitoring {kibana-pull}114303[#114303] -Observability:: -* Adds Agent Keys in APM settings - Create agent keys {kibana-pull}120373[#120373] -* Adds Agent Keys in APM settings - Agent key table {kibana-pull}119543[#119543] -* Allows users to set Download Speed, Upload Speed, and Latency for their synthetic monitors in Uptime {kibana-pull}118594[#118594] -Platform:: -Changes saved objects management inspect view to a read-only JSON view of the whole saved object {kibana-pull}112034[#112034] - -[[enhancements-and-bug-fixes-v8.0.0-rc2]] -=== Enhancements and bug fixes - -For detailed information about the 8.0.0-rc2 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.0.0-rc2]] -==== Enhancements -Elastic Security:: -For the Elastic Security 8.0.0-rc2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Security:: -Adds session cleanup audit logging {kibana-pull}122419[#122419] -Observability:: -Make a monitor's steps details page work on mobile resolutions in Uptime {kibana-pull}122171[#122171] - -[float] -[[fixes-v8.0.0-rc2]] -==== Bug fixes -Alerting:: -Fixes PagerDuty timestamp validation {kibana-pull}122321[#122321] -Dashboard:: -* Creates Explicit Diffing System {kibana-pull}121241[#121241] -* Fixes blank panel save and display issue {kibana-pull}120815[#120815] -* Fixes full screen error when pressing back arrow on browser {kibana-pull}118113[#118113] -Elastic Security:: -For the Elastic Security 8.0.0-rc2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Maps:: -* Fixes Point to point and Tracks layers label properties not showing in the legend {kibana-pull}122993[#122993] -* Fixes Color ramp UI for percent of a top term in join layer is broken {kibana-pull}122718[#122718] -Observability:: -* Updates index pattern permission error in APM {kibana-pull}122680[#122680] -* Honor time unit for Inventory Threshold in Metrics {kibana-pull}122294[#122294] -* Adds locator to aid other plugins in linking properly to Uptime {kibana-pull}123004[#123004] -* Fixes a bug in which headers would be incorrectly centered on desktop in Uptime {kibana-pull}122643[#122643] - -[[release-notes-8.0.0-rc1]] -== {kib} 8.0.0-rc1 - -Review the {kib} 8.0.0-rc1 changes, then use the <> to complete the upgrade. - -[float] -[[breaking-changes-8.0.0-rc1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. -Before you upgrade, review the breaking changes, then mitigate the impact to your application. - -[discrete] -.Splits package policy `upgrade` endpoint for Fleet -[%collapsible] -==== -*Details* + -For package policy upgrades, the packagePolicy `upgrade` endpoint format supports a mutative upgrade operation (when `dryRun: false`) and a read-only dry run operation (when `dryRun: true`): - -[source,text] --- - POST /package_policies/upgrade - { - packagePolicyIds: [...], - dryRun: false - } --- - -For more information, refer to {kibana-pull}118854[#118854]. - -*Impact* + -The endpoint is now split into two separate endpoints: - -[source,text] --- - POST /package_policies/upgrade - { - packagePolicyIds: [...] - } - - POST /package_policies/upgrade/dry_run - { - packagePolicyIds: [...] - } --- -==== - -[discrete] -.Removes APM jobs from Machine Learning -[%collapsible] -==== -*Details* + -APM Node.js and RUM JavaScript anomaly detection job modules have been removed. For more information, refer to {kibana-pull}119945[#119945]. - -*Impact* + -When you upgrade to 8.0.0, you are unable to create and view the APM Node.js and RUM JavaScript jobs in Machine Learning. -==== - -[discrete] -.Fails migrations for unknown types -[%collapsible] -==== -*Details* + -Unknown saved object types now cause {kib} migrations to fail. For more information, refer to {kibana-issue}107678[#107678]. - -*Impact* + -To complete the migration, re enable plugins or delete documents from the index in the previous version. -==== - -[discrete] -.Removes deprecated config fields from Logs and Metrics APIs and saved objects -[%collapsible] -==== -*Details* + -On the Logs and Metrics UIs, references to the following API and saved object deprecated fields have been removed: - -* `timestamp` -* `tiebreaker` -* `container` -* `pod` -* `host` - -For more information, refer to {kibana-pull}116821[#116821] and {kibana-pull}115874[#115874]. - -*Impact* + -When you upgrade to 8.0.0, you are unable to use references to the deprecated fields. -==== - -To review the breaking changes in previous versions, refer to the following: - -<> | <> | -<> - -[float] -[[deprecations-8.0.0-rc1]] -=== Deprecations - -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend you make the necessary updates after you complete the upgrade. - -[discrete] -.Renames the `autocreate` data view APM setting -[%collapsible] -==== -*Details* + -The `xpack.apm.autocreateApmIndexPattern` APM setting has been removed. For more information, refer to {kibana-pull}120689[#120689]. - -*Impact* + -To automatically create data views in APM, use `xpack.apm.autoCreateApmDataView`. -==== - -[discrete] -.Updates Fleet API to improve consistency -[%collapsible] -==== -*Details* + -The Fleet API has been updated to improve consistency: - -* Hyphens are changed to underscores in some names. -* The `pkgkey` path parameter in the packages endpoint is split. -* The `response` and `list` properties are renamed to `items` or `item` in some -responses. - -For more information, refer to {kibana-pull}119494[#119494]. - -*Impact* + -When you upgrade to 8.0.0, use the following API changes: - -* Use `enrollment_api_keys` instead of `enrollment-api-keys`. - -* Use `agent_status` instead of `agent-status`. - -* Use `service_tokens` instead of `service-tokens`. - -* Use `/epm/packages/{packageName}/{version}` instead of `/epm/packages/{pkgkey}`. - -* Use `items[]` instead of `response[]` in: -+ -[source,text] --- -/api/fleet/enrollment_api_keys -/api/fleet/agents -/epm/packages/ -/epm/categories -/epm/packages/_bulk -/epm/packages/limited -/epm/packages/{packageName}/{version} <1> --- -<1> Use `items[]` when the verb is `POST` or `DELETE`. Use `item` when the verb -is `GET` or `PUT`. - -For more information, refer to {fleet-guide}/fleet-api-docs.html[Fleet APIs]. - -==== - -To review the deprecations in previous versions, refer to the <>. - - -[float] -[[features-8.0.0-rc1]] -=== Features -{kib} 8.0.0-rc1 adds the following new and notable features. - -Elastic Security:: -For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Management:: -Display managed badge for transforms {kibana-pull}117679[#117679] -Monitoring:: -Enterprise Search Stack Monitoring {kibana-pull}114303[#114303] -Observability:: -* Adds ability to create agent keys in APM settings {kibana-pull}120373[#120373] -* Adds Agent key table in APM settings {kibana-pull}119543[#119543] -* Allows users to set Download Speed, Upload Speed, and Latency for their synthetic monitors {kibana-pull}118594[#118594] - -[[enhancements-and-bug-fixes-v8.0.0-rc1]] -=== Enhancements and bug fixes - -For detailed information about the 8.0.0-rc1 release, review the enhancements and bug fixes. - -[float] -[[enhancement-v8.0.0-rc1]] -=== Enhancements -Canvas:: -Reverts By-Value Embeddables {kibana-pull}117613[#117613] -Discover:: -Adds multi-layer time axis for opt-out only {kibana-pull}115853[#115853] -Elastic Security:: -For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -Adds consistent `_meta` property to all Fleet ES assets {kibana-pull}119380[#119380] -Kibana Home & Add Data:: -Moves overview page and link to the accordion solution title {kibana-pull}114018[#114018] -Lens & Visualizations:: -* Handle removal of deprecated date histogram interval in *Vega* {kibana-pull}109090[#109090] -* Adds value labels to Heatmap in *Lens* {kibana-pull}106406[#106406] -Machine Learning:: -* Adds support for `force` stop deployment {kibana-pull}118563[#118563] -* Refactors data view loading to remove unnecessary searches {kibana-pull}116455[#116455] -Observability:: -* Service Maps: Adds sparklines to the detail popover {kibana-pull}120021[#120021] -* Offer users upgrade to multi-metric job {kibana-pull}119980[#119980] -* Display relevant anomalies from multi-metric job {kibana-pull}119709[#119709] -* Adds service icon for the originating service in traces table {kibana-pull}119421[#119421] -* Auto attachment for java agent beta in APM integration settings {kibana-pull}119131[#119131] -* Errors: Enhancements to the Errors list page (part II) {kibana-pull}118878[#118878] -* Store Alerts View table state in localStorage {kibana-pull}118207[#118207] -* Handle other values popup when correlated value is not in top 10 {kibana-pull}118069[#118069] -* Adds links to navigate from alerts table to rule {kibana-pull}118035[#118035] -* Reinstates ML multi-metric job {kibana-pull}117836[#117836] -* Re-enables metric-based UI {kibana-pull}117021[#117021] -* Make Alerts page use shared {kib} time range {kibana-pull}115192[#115192] -* Adds enabled toggle {kibana-pull}119994[#119994] -* Adds missing tooltip to the report metric badge in *Exploratory View* {kibana-pull}119940[#119940] -* Adds step duration in step list {kibana-pull}116266[#116266] -Platform:: -Moves developer architecture docs to user docs {kibana-pull}119125[#119125] -Reporting:: -* Decouples screenshotting plugin from the reporting {kibana-pull}120110[#120110] -* Updates the design of the *Reports* management UI, including the addition of a link to {kib} app where the report was generated {kibana-pull}111412[#111412] -Security:: -Adds ability to clone role mappings {kibana-pull}118434[#118434] -Adds user logout audit events {kibana-pull}121455[#121455] - -[float] -[[fixes-v8.0.0-rc1]] -=== Bug fixes -Canvas:: -* Fixes Error overflow {kibana-pull}122158[#122158] -* Fixes expression input {kibana-pull}121490[#121490] -* Hides edit menu when in view-only mode {kibana-pull}118779[#118779] -Dashboard:: -* Allow text wrapping for panel titles and dashboard descriptions for PDF generation {kibana-pull}121360[#121360] -* Page now resets to zero when rows per page is changed on *Add from Library* window {kibana-pull}118627[#118627] -* Fixes full screen error when pressing back arrow in browser {kibana-pull}118113[#118113] -Elastic Security:: -For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Fleet:: -* Adds `installed_kibana_space_id` to `epm-packages` saved objects {kibana-pull}120517[#120517] -* Handle Saved Object ID changes {kibana-pull}119527[#119527] -* Fixes upgradeable agents filter {kibana-pull}119338[#119338] -Lens & Visualizations:: -* Enables normal mode for percentage charts in *Lens* {kibana-pull}120197[#120197] -* Fixes existing fields query for epoch_millis dates in *Lens* {kibana-pull}119508[#119508] -* Include frozen indices in *Lens* {kibana-pull}118555[#118555] -* Fixes focus on legend action popovers in *Lens* {kibana-pull}115066[#115066] -Machine Learning:: -* Fixes data view search based on title {kibana-pull}120737[#120737] -* Data frame analytics wizard: Only allow data view creation if job will be started immediately {kibana-pull}120042[#120042] -* Fixes anomaly detection module manifest queries to ignore frozen and cold data tiers {kibana-pull}119635[#119635] -* Catches syntax error in job wizard editor {kibana-pull}119457[#119457] -* Fixes error handling for missing data view in data frame analytics wizard {kibana-pull}119455[#119455] -* Ensures auto refresh interval is used in Data Frame Analytics list {kibana-pull}117959[#117959] -* Ignores frozen indices in data recognizer {kibana-pull}117208[#117208] -Management:: -* Fixes data grid column actions button when histogram charts are visible {kibana-pull}120202[#120202] -* Disables delete data view for data frame analytics and transforms wizards {kibana-pull}119732[#119732] -* Check {kib} capabilities for all saving, editing, and deleting {kibana-pull}118480[#118480] -* Adds autocomplete for search_after and pit in search query {kibana-pull}117864[#117864] -* Autocomplete for t_test aggregation {kibana-pull}117782[#117782] -* Disables create data view for data frame analytics and transforms wizards {kibana-pull}117690[#117690] -Maps:: -* Fixes an issue where drawings do not show when there is a global filter {kibana-pull}121239[#121239] -* Use minimum symbol size if meta is not loaded {kibana-pull}119119[#119119] -* Do not fail migration when JSON.parse fails {kibana-pull}117342[#117342] -* Do not allow label overlap {kibana-pull}116190[#116190] -Monitoring:: -Correct linear regression formula {kibana-pull}120222[#120222] -Observability:: -* Renames alerting types in Infra {kibana-pull}121061[#121061] -* Renames occurrences of `alert_type` to `rule_type` in Infra {kibana-pull}120455[#120455] -* Fixes failing alerts table pagination functional tests {kibana-pull}119985[#119985] -* Switch to _source for updating documents instead of fields API {kibana-pull}118245[#118245] -* Fixes an issue where search terms with certain characters caused the APM UI to crash {kibana-pull}118063[#118063] -* Ignore unavailable indices for ML jobs {kibana-pull}117632[#117632] -* Disables the actions button when users have inadequate privileges {kibana-pull}117488[#117488] -* Replaces manual rate calculation with `rate` agg {kibana-pull}115651[#115651] -* Adds migration to fix incorrect action group spelling {kibana-pull}119626[#119626] -* Fixes bug with manage views button {kibana-pull}118547[#118547] -* Disables No Data checkboxes for doc count alerts {kibana-pull}117194[#117194] -* Prevent event propagation on step_duration {kibana-pull}122039[#122039] -* Disables the button to create alerts in Uptime when users do not have permissions to do so {kibana-pull}120379[#120379] -* Fixes a bug that prevented users from saving Uptime configurations when the `inspect` option was turned on {kibana-pull}119142[#119142] -* Adds a callout to informs users that they do not have permissions to create ML jobs for Uptime monitors {kibana-pull}117684[#117684] -Platform:: -Fixes font glitches in code editor {kibana-pull}121392[#121392] -Reporting:: -Fixes an issue where PDF and PNG reports break on Windows operating systems when the {kib} server hostname is `0.0.0.0` {kibana-pull}117022[#117022] - -[[release-notes-8.0.0-beta1]] -== {kib} 8.0.0-beta1 - -Review the {kib} 8.0.0-beta1 changes, then use the <> to complete the upgrade. - -[float] -[[breaking-changes-8.0.0-beta1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. Review the following breaking changes, then mitigate the impact to your application. - -To review the breaking changes in previous versions, refer to the following: - -<> | <> - -[float] -[[alerting-breaking-changes-8.0.0-beta1]] -==== Alerting - -[discrete] -.Removes `xpack.task_manager.index` -[%collapsible] -==== -*Details* + -The `xpack.task_manager.index` setting has been removed. For more information, refer to {kibana-pull}114558[#114558]. - -*Impact* + -Before you upgrade to 8.0.0, remove `xpack.task_manager.index` from kibana.yml. -==== - -[discrete] -.Removes ability to remove plugins -[%collapsible] -==== -*Details* + -The `xpack.actions.enabled` setting has been removed. For more information, refer to {kibana-pull}113461[#113461]. - -*Impact* + -Before you upgrade to 8.0.0, remove `xpack.actions.enabled` from kibana.yml. -==== - -[float] -[[lens-visualizations-breaking-changes-8.0.0-beta1]] -==== Lens & visualizations - -[discrete] -.Removes display options from legacy gauge -[%collapsible] -==== -*Details* + -The *Display warnings* option has been removed from the aggregation-based gauge visualization. For more information, refer to {kibana-pull}113516[#113516]. - -*Impact* + -When you create aggregation-based gauge visualizations, the *Display warnings* option is no longer available in *Options > Labels*. -==== - -[discrete] -.Removes settings from visEditors plugins -[%collapsible] -==== -*Details* + -The following deprecated visEditors plugin settings have been removed: - -* `metric_vis.enabled` -* `table_vis.enabled` -* `tagcloud.enabled` -* `metrics.enabled` -* `metrics.chartResolution` -* `chartResolution` -* `metrics.minimumBucketSize` -* `minimumBucketSize` -* `vega.enabled` -* `vega.enableExternalUrls` -* `vis_type_table.legacyVisEnabled` -* `timelion_vis.enabled` -* `timelion.enabled` -* `timelion.graphiteUrls` -* `timelion.ui.enabled` - -For more information, refer to {kibana-pull}112643[#112643]. - -*Impact* + -Before you upgrade, make the following changes in kibana.yml: - -* Replace `metric_vis.enabled` with `vis_type_metric.enabled` -* Replace `table_vis.enabled` with `vis_type_table.enabled` -* Replace `tagcloud.enabled` with `vis_type_tagcloud.enabled` -* Replace `metrics.enabled` with `vis_type_timeseries.enabled` -* Replace `metrics.chartResolution` and `chartResolution` with `vis_type_timeseries.chartResolution` -* Replace `metrics.minimumBucketSize` and `minimumBucketSize` with `vis_type_timeseries.minimumBucketSize` -* Replace `vega.enabled` with `vis_type_vega.enabled` -* Replace `vega.enableExternalUrls` with `vis_type_vega.enableExternalUrls` -* Remove `vis_type_table.legacyVisEnabled` -* Replace `timelion_vis.enabled` with `vis_type_timelion.enabled` -* Replace `timelion.enabled` with `vis_type_timelion.enabled` -* Replace `timelion.graphiteUrls` with `vis_type_timelion.graphiteUrls` -* Remove `timelion.ui.enabled` - -==== - -[discrete] -.Removes dimming opacity setting -[%collapsible] -==== -*Details* + -The *Dimming opacity* setting in *Advanced Settings* has been removed. For more information, refer to {kibana-pull}111704[#111704]. - -*Impact* + -When you upgrade to 8.0.0, you are no longer able to configure the dimming opactiy for visualizations. -==== - -[discrete] -.Removes Less stylesheet support -[%collapsible] -==== -*Details* + -In *TSVB*, custom Less stylesheets have been removed. For more information, refer to {kibana-pull}110985[#110985]. - -*Impact* + -Existing less stylesheets are automatically converted to CSS stylesheets. -==== - -[discrete] -.Disables the input string mode -[%collapsible] -==== -*Details* + -In *TSVB*, the *Index pattern selection mode* option has been removed. For more information, refer to {kibana-pull}110571[#110571]. - -*Impact* + -To use index patterns and {es} indices in *TSVB* visualizations: - -. Open the main menu, then click *Stack Management > Advanced Settings*. - -. Select *Allow string indices in TSVB*. - -. Click *Save changes*. -==== - -[float] -[[logs-breaking-changes-8.0.0-beta1]] -==== Logs - -[discrete] -.Removes deprecated alias config entries -[%collapsible] -==== -*Details* + -The deprecated `xpack.infra.sources.default.logAlias` and `xpack.infra.sources.default.logAlias` settings have been removed. For more information, refer to {kibana-pull}115974[#115974]. - -*Impact* + -Before you upgrade, remove the settings from kibana.yml, then configure the settings in <>. -==== - -[discrete] -.Removes configurable fields in settings -[%collapsible] -==== -*Details* + -The *Logs* and *Metrics* configurable fields settings have been removed. For more information, refer to {kibana-pull}61302[#61302]. - -*Impact* + -Configure the settings in https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[ECS]. -==== - -[float] -[[machine-learning-breaking-changes-8.0.0-beta1]] -==== Machine learning - -[discrete] -.Grants access to machine learning features when base privileges are used -[%collapsible] -==== -*Details* + -Machine learning features are included as base privileges. For more information, refer to {kibana-pull}115444[#115444]. - -*Impact* + -If you do not want to grant users privileges to machine learning features, update <>. -==== - -[float] -[[maps-breaking-changes-8.0.0-beta1]] -==== Maps - -[discrete] -.Removes proxyElasticMapsServiceInMaps -[%collapsible] -==== -*Details* + -The `map.proxyElasticMapsServiceInMaps` setting has been removed. For more information, refer to {kibana-pull}116184[#116184]. - -*Impact* + -Install the on-prem version of the <>, which is a Docker service that resides in the Elastic Docker registry, in an accessible location on your internal network. When you complete the installation, update kibana.yml to point to the service. -==== - -[float] -[[operations-breaking-changes-8.0.0-beta1]] -==== Operations - -[discrete] -.Removes environment variables -[%collapsible] -==== -*Details* + -The `CONFIG_PATH` and `DATA_PATH` environment variables have been removed. For more information, refer to {kibana-pull}111535[#111535]. - -*Impact* + -Replace the `CONFIG_PATH` environment variable with `KBN_PATH_CONF`, and replace `DATA_PATH` with the `path.data` setting. -==== - -[float] -[[platform-breaking-changes-8.0.0-beta1]] -==== Platform - -[discrete] -.Removes supports for csp.rules configuration -[%collapsible] -==== -*Details* + -Support for the `csp.rules` configuration property has been removed. For more information, refer to {kibana-pull}114379[#114379]. - -*Impact* + -Configuring the default `csp.script_src`, `csp.workers_src`, and `csp.style_src` values is not required. -==== - -[discrete] -.Changes and removes deprecated core settings and deprecated settings from core plugins -[%collapsible] -==== -*Details* + -The deprecation notice for `server.cors` has changed from `level:critical` to `level:warning`. - -The following settings have changed: - -* The `xpack.banners.placement` value has been renamed -* The `newsfeed.defaultLanguage` newsfeed item retrieval method and default language has changed - -Support for the following configuration settings has been removed: - -* `newsfeed.defaultLanguage` -* `cpu.cgroup.path.override` -* `cpuacct.cgroup.path.override` -* `server.xsrf.whitelist` -* `xpack.xpack_main.xpack_api_polling_frequency_millis` -* `KIBANA_PATH_CONF` - -For more information, refer to {kibana-pull}113653[#113653]. - -*Impact* + -* The `header` value provided to the `xpack.banners.placement` configuration has been renamed to 'top' -* The `newsfeed.defaultLanguage` newsfeed items are retrieved based on the browser locale and default to English -* Replace `cpu.cgroup.path.override` with `ops.cGroupOverrides.cpuPath` -* Replace `cpuacct.cgroup.path.override` with `ops.cGroupOverrides.cpuAcctPath` -* Replace `server.xsrf.whitelist` with `server.xsrf.allowlist` -* Replace `xpack.xpack_main.xpack_api_polling_frequency_millis` with `xpack.licensing.api_polling_frequency` -* Replace `KIBANA_PATH_CONF` path to the {kib} configuration file using the `KBN_PATH_CONF` environment variable -==== - -[discrete] -.Removes `enabled` settings from plugins -[%collapsible] -==== -*Details* + -Using `{plugin_name}.enabled` to disable plugins has been removed. Some plugins, such as `telemetry`, `newsfeed`, `reporting`, and the various `vis_type` plugins will continue to support this setting. All other {kib} plugins will not support this setting. Any new plugin will support this setting only when specified in the `configSchema`. For more information, refer to {kibana-pull}113495[#113495]. - -The `xpack.security.enabled` setting has been removed. For more information, refer to {kibana-pull}111681[#111681]. - -*Impact* + -Before you upgrade to 8.0.0: - -* Remove `{plugin_name}.enabled` from kibana.yml. If you use the setting to control user access to {kib} applications, use <> instead. -* Replace `xpack.security.enabled` with {ref}/security-settings.html#general-security-settings[`xpack.security.enabled`] in elasticsearch.yml. -==== - -[discrete] -.Removes `--plugin-dir` cli option -[%collapsible] -==== -*Details* + -The `plugins.scanDirs` setting and `--plugin-dir` cli option have been removed. For more information, refer to {kibana-pull}113367[#113367]. - -*Impact* + -Before you upgrade to 8.0.0, remove `plugins.scanDirs` from kibana.yml. -==== - -[discrete] -.Removes support for `optimize.*` settings -[%collapsible] -==== -*Details* + -The legacy `optimize.*` settings have been removed. If your configuration uses the following legacy `optimize.*` settings, {kib} fails to start: - -* `optimize.lazy` -* `optimize.lazyPort` -* `optimize.lazyHost` -* `optimize.lazyPrebuild` -* `optimize.lazyProxyTimeout` -* `optimize.enabled` -* `optimize.bundleFilter` -* `optimize.bundleDir` -* `optimize.viewCaching` -* `optimize.watch` -* `optimize.watchPort` -* `optimize.watchHost` -* `optimize.watchPrebuild` -* `optimize.watchProxyTimeout` -* `optimize.useBundleCache` -* `optimize.sourceMaps` -* `optimize.workers` -* `optimize.profile` -* `optimize.validateSyntaxOfNodeModules` - -For more information, refer to {kibana-pull}113296[#113296]. - -*Impact* + -To run the `@kbn/optimizer` separately in development, pass `--no-optimizer` to `yarn start`. For more details, refer to {kibana-pull}73154[#73154]. -==== - -[discrete] -.Removes `so/server/es` settings -[%collapsible] -==== -*Details* + -Some of the `so/server/es` settings have been removed. If your configuration uses the following settings, {kib} fails to start: - -* `savedObjects.indexCheckTimeout` -* `server.xsrf.token` -* `elasticsearch.preserveHost` -* `elasticsearch.startupTimeout` - -For more information, refer to {kibana-pull}113173[#113173]. - -*Impact* + -Before you upgrade to 8.0.0., remove these settings from kibana.yml. -==== - -[discrete] -.Adds requirement for inline scripting -[%collapsible] -==== -*Details* + -To start {kib}, you must enable inline scripting in {es}. For more information, refer to {kibana-pull}113068[#113068]. - -*Impact* + -Enable {ref}/modules-scripting-security.html[inline scripting]. -==== - -[discrete] -.Removes `kibana.index` settings -[%collapsible] -==== -*Details* + -The `kibana.index`, `xpack.reporting.index`, and `xpack.task_manager.index` settings have been removed. For more information, refer to {kibana-pull}112773[#112773]. - -*Impact* + -Use spaces, cross-cluster replication, or cross-cluster search. To migrate to <>, export your <> from a tenant into the default space. For more details, refer to link:https://github.com/elastic/kibana/issues/82020[#82020]. -==== - -[discrete] -.Removes legacy logging -[%collapsible] -==== -*Details* + -The logging configuration and log output format has changed. For more information, refer to {kibana-pull}112305[#112305]. - -*Impact* + -Use the new <>. -==== - -[float] -[[reporting-breaking-changes-8.0.0-beta1]] -==== Reporting - -[discrete] -.Removes reporting settings -[%collapsible] -==== -*Details* + -The following settings have been removed: - -* `xpack.reporting.capture.concurrency` - -* `xpack.reporting.capture.settleTime` - -* `xpack.reporting.capture.timeout` - -* `xpack.reporting.kibanaApp` - -For more information, refer to {kibana-pull}114216[#114216]. - -*Impact* + -Before you upgrade to 8.0.0, remove the settings from kibana.yml. -==== - -[float] -[[rest-api-breaking-changes-8.0.0-beta1]] -==== REST API - -[discrete] -.Removes `/api/settings` -[%collapsible] -==== -*Details* + -The `/api/settings` REST API has been removed. For more information, refer to {kibana-pull}114730[#114730]. - -*Impact* + -Use `/api/stats`. -==== - -[float] -[[security-breaking-changes-8.0.0-beta1]] -==== Security - -[discrete] -.Removes legacy audit logger -[%collapsible] -==== -*Details* + -The legacy audit logger has been removed. For more information, refer to {kibana-pull}116191[#116191]. - -*Impact* + -Audit logs will be written to the default location in the new ECS format. To change the output file, filter events, and more, use the <>. -==== - -[float] -[[deprecations-8.0.0-beta1]] -=== Deprecations - -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend you make the necessary updates after you complete the upgrade. - -To review the 8.0.0 depcrecations, refer to the <>. - -[float] -[[features-8.0.0-beta1]] -=== Features -The 8.0.0-beta1 release adds the following new and notable features. - -Dashboard:: -* Dashboard Integration {kibana-pull}115991[#115991] -Elastic Security:: -For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Machine Learning:: -* Addition of new Model Management tab {kibana-pull}115772[#115772] -Platform:: -* Changes edit view to json read-only view {kibana-pull}112034[#112034] - -[float] -[[enhancement-v8.0.0-beta1]] -=== Enhancements - -The 8.0.0-beta1 release includes the following enhancements. - -Canvas:: -* By-Value embeddables {kibana-pull}113827[#113827] -* Toolbar UI updates {kibana-pull}113329[#113329] -Elastic Security:: -For the Elastic Security 8.0.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Lens & Visualizations:: -* Handle removal of deprecated date histogram interval in *Vega* {kibana-pull}109090[#109090] -* Adds value labels to Heatmap in *Lens* {kibana-pull}106406[#106406] -Logs:: -* Make Alerts page use shared Kibana time range {kibana-pull}115192[#115192] -Machine Learning:: -* Adds support to {fleet} {integrations} for installing ML models {kibana-pull}107710[#107710] -* Adds Index data visualizer grid embeddable as extra view within Discover {kibana-pull}107184[#107184] -Maps:: -* Use Elastic Maps Service v8.0 {kibana-pull}116217[#116217] -* Use desaturated map tiles instead of bright map tiles by default {kibana-pull}116179[#116179] -* Use ES mvt {kibana-pull}114553[#114553] -Security:: -* Register "minimal" feature privileges regardless of the current license level {kibana-pull}115992[#115992] -Uptime:: -* Uptime index config using kibana.yml {kibana-pull}115775[#115775] - -[float] -[[fixes-v8.0.0-beta1]] -=== Bug fixes - -The 8.0.0-beta1 release includes the following bug fixes. - -Management:: -* Removes freeze action from Cold phase {kibana-pull}116160[#116160] -* Disallow creating runtime and scripted fields with * in the name {kibana-pull}116119[#116119] -Querying & Filtering:: -* Fixes Add filter button doesnt close popup after openning {kibana-pull}111917[#111917] - -[[release-notes-8.0.0-alpha2]] -== {kib} 8.0.0-alpha2 - -Review the {kib} 8.0.0-alpha2 changes, then use the <> to complete the upgrade. - -[float] -[[breaking-changes-8.0.0-alpha2]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. Review the following breaking changes, then mitigate the impact to your application. - -To review the breaking changes in the previous version, refer to <>. - -[discrete] -.Changes the `GET /api/status` default behavior -[%collapsible] -==== -*Details* + -`GET /api/status` reports a new and more verbose payload. For more information, refer to {kibana-pull}110830[#110830]. - -*Impact* + -To retrieve the {kib} status in the previous format, use `GET /api/status?v7format=true`. -==== - -[discrete] -.Removes support for legacy exports -[%collapsible] -==== -*Details* + -In {kib} 8.0.0 and later, the legacy format from {kib} 6.x is unsupported. For more information, refer to {kibana-pull}110738[#110738] - -*Impact* + -Using the user interface to import saved objects is restricted to `.ndjson` format imports. -==== - -[discrete] -.Removes `map.regionmap.*` -[%collapsible] -==== -*Details* + -The deprecated `map.regionmap.*` setting in kibana.yml has been removed. For more information, refer to {kibana-pull}109896[#109896]. - -*Impact* + -If you have maps that use `map.regionmap` layers: - -. Remove the `map.regionmap` layer. - -. To recreate the choropleth layer, use <> to index your static vector data into {es}. - -. Create a choropleth layer from the indexed vector data. -==== - -[discrete] -.Removes `kibana.defaultAppId` -[%collapsible] -==== -*Details* + -The deprecated `kibana.defaultAppId` setting in kibana.yml, which is also available as `kibana_legacy.defaultAppId`, has been removed. For more information, refer to {kibana-pull}109798[#109798]. - -*Impact* + -When you upgrade, remove `kibana.defaultAppId` from your kibana.yml file. To configure the default route for users when they enter a space, use the <> in *Advanced Settings*. -==== - -[discrete] -.Removes `courier:batchSearches` -[%collapsible] -==== -*Details* + -The deprecated `courier:batchSearches` setting in *Advanced Settings* has been removed. For more information, refer to {kibana-pull}109350[#109350]. - -*Impact* + -When you upgrade, the `courier:batchSearches` setting will no longer be available. -==== - -[discrete] -.Removes `xpack.task_manager.index` -[%collapsible] -==== -*Details* + -The deprecated `xpack.task_manager.index` setting in kibana.yml has been removed. For more information, refer to {kibana-pull}108111[#108111]. - -*Impact* + -When you upgrade, remove `xpack.task_manager.index` from your kibana.yml file. -==== - -[discrete] -.Removes dashboard-only mode -[%collapsible] -==== -*Details* + -The legacy dashboard-only mode has been removed. For more information, refer to {kibana-pull}108103[#108103]. - -*Impact* + -To grant users access to only dashboards, create a new role, then assign only the *Dashboard* feature privilege. For more information, refer to <>. -==== - -[discrete] -.Removes `xpack.maps.showMapVisualizationTypes` -[%collapsible] -==== -*Details* + -The deprecated `xpack.maps.showMapVisualizationTypes` setting in kibana.yml has been removed. For more information, refer to {kibana-pull}105979[#105979] - -*Impact* + -When you upgrade, remove `xpack.maps.showMapVisualizationTypes` from your kibana.yml file. -==== - -[float] -[[deprecations-8.0.0-alpha2]] -=== Deprecations - -Deprecated functionality does not have an immediate impact on your application, but we strongly recommend you make the necessary updates after you complete the upgrade. - -To review the 8.0.0 depcrecations, refer to the <>. - -[float] -[[features-8.0.0-alpha2]] -=== Features -The 8.0.0-alpha2 release adds the following new and notable feature. - -Security:: -* Adds the interactive setup mode {kibana-pull}106881[#106881] - -[float] -[[enhancement-v8.0.0-alpha2]] -=== Enhancements -The 8.0.0-alpha2 release includes the following enhancements. - -Elastic Security:: -For the Elastic Security 8.0.0-alpha2 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. -Security:: -* Interactive setup mode {kibana-pull}106881[#106881] - -[[release-notes-8.0.0-alpha1]] -== {kib} 8.0.0-alpha1 - -Review the {kib} 8.0.0-alpha1 changes, then use the <> to complete the upgrade. - -[float] -[[breaking-changes-8.0.0-alpha1]] -=== Breaking changes - -Breaking changes can prevent your application from optimal operation and performance. Review the breaking changes, then mitigate the impact to your application. - -[float] -[[enterprise-search-change]] -==== Enterprise Search changes - -[discrete] -.Required security plugin in 8.0 -[%collapsible] -==== -*Details* + -Enterprise Search now requires that you enable X-Pack Security. For more information, refer to {kibana-pull}106307[#106307] - -*Impact* + -Enable X-Pack Security. -==== - -[float] -[[index-pattern-change]] -==== Index pattern changes - -[discrete] -.Removed support for time-based interval index patterns -[%collapsible] -==== -*Details* + -Time-based interval index patterns were deprecated in 5.x. In 6.x, you could no longer create time-based interval index patterns, but they continued to function as expected. Support for these index patterns has been removed in 8.0. For more information, refer to {kibana-pull}35173[#35173] - -*Impact* + -You must migrate your time_based index patterns to a wildcard pattern. For example, logstash-*. -==== - -[float] -[[operations-changes]] -==== Operations changes - -[discrete] -.Removed platform from archive root directory -[%collapsible] -==== -*Details* + -After you extract an archive, the output directory no longer includes the target platform. For example, `kibana-8.0.0-linux-aarch64.tar.gz` produces a `kibana-8.0.0` folder. For more information, refer to {kibana-pull}93835[#93835]. - -*Impact* + -To use the new folder, update the configuration management tools and automation. -==== - -[discrete] -.Removed default support for TLS v1.0 and v1.1 -[%collapsible] -==== -*Details* + -The default support for TLS v1.0 and v1.1 has been removed. For more information, refer to {kibana-pull}90511[#90511]. - -*Impact* + -To enable support, set `--tls-min-1.0` in the `node.options` configuration file. To locate the configuration file, go to the kibana/config folder or any other configuration with the `KBN_PATH_CONF` environment variable. For example, if you are using a Debian-based system, the configuration file is located in /etc/kibana. -==== - -[discrete] -.Removed support for sysv init -[%collapsible] -==== -*Details* + -All supported operating systems use systemd service files. Any system that doesn’t have `service` aliased to use kibana.service should use `systemctl start kibana.service` instead of `service start kibana`. For more information, refer to {kibana-pull}74424[#74424]. - -*Impact* + -If your installation uses .deb or .rpm packages with SysV, migrate to systemd. -==== - -[discrete] -.Disabled response logging as a default -[%collapsible] -==== -*Details* + -In previous versions, all events are logged in `json` when `logging.json:true`. With the new logging configuration, you can choose the `json` and pattern output formats with layouts. For more information, refer to {kibana-pull}42353[#42353]. - -*Impact* + -To restore the previous behavior, configure the logging format for each custom appender with the `appender.layout property` in kibana.yml. There is no default for custom appenders, and each appender must be configured expilictly. - -[source,yaml] -------------------- -logging: - appenders: - custom_console: - type: console - layout: - type: pattern - custom_json: - type: console - layout: - type: json - loggers: - - name: plugins.myPlugin - appenders: [custom_console] - root: - appenders: [default, custom_json] - level: warn -------------------- -==== - -[float] -[[reporting-changes-8.0.0-alpha1]] -==== Reporting changes - -[discrete] -.Legacy job parameters are no longer supported -[%collapsible] -==== -*Details* + -*Reporting* is no longer compatible with POST URL snippets generated with {kib} 6.2.0 and earlier. For more information, refer to {kibana-pull}52539[#52539] - -*Impact* + -If you use POST URL snippets to automatically generate PDF reports, regenerate the POST URL strings. -==== - -[float] -[[rest-api-changes]] -==== Security changes - -[discrete] -.Removed `/api/security/v1/saml` route -[%collapsible] -==== -*Details* + -The `/api/security/v1/saml` route has been removed and is reflected in the kibana.yml `server.xsrf.whitelist` setting, {es}, and the Identity Provider SAML settings. For more information, refer to {kibana-pull}47929[#47929] - -*Impact* + -Use the `/api/security/saml/callback` route, or wait to upgrade to 8.0.0-alpha2 when the `/api/security/saml/callback` route breaking change is reverted. -==== - -[discrete] -.Reject legacy browsers by default -[%collapsible] -==== -*Details* + -To provide the maximum level of protection for most installations, the csp.strict config is now enabled by default. Legacy browsers not supported by Kibana, such as Internet Explorer 11, are unable to access {kib} unless explicitly enabled. All browsers officially supported by Kibana do not have this issue. For more information, refer to {kibana-pull}41700[#41700] - -*Impact* + -To enable support for legacy browsers, set `csp.strict: false` in kibana.yml. To effectively enforce the security protocol, we strongly discourage disabling `csp.strict` unless it is critical that you support Internet Explorer 11. -==== - -[float] -[[settings-changes-8.0.0-alpha1]] -==== Settings changes - -[discrete] -.Use new session timeout defaults -[%collapsible] -==== -*Details* + -The default values for the session timeout `xpack.security.session.{lifespan|idleTimeout}` settings have changed. For more information, refer to {kibana-pull}106061[#106061] - -*Impact* + -Use the following default values: - -* `xpack.security.session.idleTimeout: 3d` -* `xpack.security.session.lifespan: 30d` -==== - -[discrete] -.Removed support for setting `server.host` to '0' -[%collapsible] -==== -*Details* + -Support for configuring {kib} with `0` as the `server.host` has been removed. Please use `0.0.0.0` instead. For more information, refer to {kibana-pull}87114[#87114] - -*Impact* + -You are now unable to use `0` as the `server.host`. -==== - -[discrete] -.Removed `xpack.security.public` and `xpack.security.authProviders` -[%collapsible] -==== -*Details* + -The `xpack.security.public` and `xpack.security.authProviders` settings have been removed. For more information, refer to {kibana-pull}38657[#38657] - -*Impact* + -Use the `xpack.security.authc.saml.realm` and `xpack.security.authc.providers` settings. -==== - -[discrete] -.Removed useUTC deprecation -[%collapsible] -==== -*Details* + -The `logging.useUTC` setting has been removed. For more information, refer to {kibana-pull}22696[#22696] - -*Impact* + -The default timezone is UTC. To change the timezone, set `logging.timezone: false` in kibana.yml. Change the timezone when the system, such as a docker container, is configured for a nonlocal timezone. -==== - -[discrete] -.Removed environment variables `CONFIG_PATH` and `DATA_PATH` -[%collapsible] -==== -*Details* + -The environment variables `CONFIG_PATH` and `DATA_PATH` have been removed. For more information, refer to {kibana-pull}32049[#32049] - -*Impact* + -Use the environment variable `KBN_PATH_CONF` instead of `CONFIG_PATH`. Use the setting `path.data` instead of `DATA_PATH`. -==== - -[float] -[[deprecations-8.0.0-alpha1]] -=== Deprecations - -The following functionality is deprecated in 8.0.0, and will be removed in 9.0.0. Deprecated functionality does not have an immediate impact on your application, but we strongly recommend you make the necessary updates after you complete the upgrade. - -[discrete] -.Removed support for SysV init -[%collapsible] -==== -*Details* + -Systems that don't have `service` aliased to use kibana.service are unable to use `service start kibana`. For more information, refer to {kibana-pull}74424[#74424] - -*Impact* + -If your system doesn't have `service` aliased to use kibana.service, use `systemctl start kibana.service`. -==== - -[discrete] -.Removed `xpack:defaultAdminEmail` setting -[%collapsible] -==== -*Details* + -The `xpack:default_admin_email` setting for monitoring use has been removed. For more information, refer to {kibana-pull}33603[#33603] - -*Impact* + -Use the `xpack.monitoring.clusterAlertsEmail` in kibana.yml. -==== - -[float] -[[enhancements-and-bug-fixes-v8.0.0-alpha1]] -=== Bug fix - -The 8.0.0-alpha1 release includes the following bug fix. - -Operations:: -* Moves systemd service to /usr/lib/systemd/system {kibana-pull}83571[#83571] +[[fixes-v9.0.0]] +=== Bug fixes \ No newline at end of file diff --git a/docs/developer/architecture/development-visualize-index.asciidoc b/docs/developer/architecture/development-visualize-index.asciidoc index b941cdedf9df..6f5f9fc74242 100644 --- a/docs/developer/architecture/development-visualize-index.asciidoc +++ b/docs/developer/architecture/development-visualize-index.asciidoc @@ -19,7 +19,7 @@ We would recommend waiting until later in `7.x` to upgrade your plugins if possi If you would like to keep up with progress on the visualizations plugin in the meantime, here are a few resources: -* The <> documentation, where we try to capture any changes to the APIs as they occur across minors. +* The <> documentation, where we try to capture any changes to the APIs as they occur across minors. * link:https://github.com/elastic/kibana/issues/44121[Meta issue] which is tracking the move of the plugin to the new {kib} platform * Our link:https://www.elastic.co/blog/join-our-elastic-stack-workspace-on-slack[Elastic Stack workspace on Slack]. * The {kib-repo}blob/{branch}/src/plugins/visualizations[source code], which will continue to be diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index ea3186357611..8e8ee80ff81b 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -579,6 +579,10 @@ security and spaces filtering. |This plugin provides access to observed entity data, such as information about hosts, pods, containers, services, and more. +|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/entity_manager_app/README.md[entityManagerApp] +|This plugin provides a user interface to interact with the Entity Manager. + + |{kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog] |The event log plugin provides a persistent history of alerting and action activities. @@ -832,6 +836,10 @@ It uses Chromium and Puppeteer underneath to run the browser in headless mode. |The Inference Endpoints is a tool used to manage inference endpoints +|{kib-repo}blob/{branch}/x-pack/plugins/search_solution/search_navigation/README.mdx[searchNavigation] +|The Search Navigation plugin is used to handle navigation for search solution plugins across both stack and serverless. + + |{kib-repo}blob/{branch}/x-pack/plugins/search_notebooks/README.mdx[searchNotebooks] |This plugin contains endpoints and components for rendering search python notebooks in the persistent dev console. @@ -909,6 +917,10 @@ routes, etc. |This plugin provides an interface to manage streams +|{kib-repo}blob/{branch}/x-pack/plugins/streams_app/README.md[streamsApp] +|Home of the Streams app plugin, which allows users to manage Streams via the UI. + + |{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/synthetics/README.md[synthetics] |The purpose of this plugin is to provide users of Heartbeat more visibility of what's happening in their infrastructure. diff --git a/docs/discover/document-explorer.asciidoc b/docs/discover/document-explorer.asciidoc index 921e0504f459..47b4a5bc3fcf 100644 --- a/docs/discover/document-explorer.asciidoc +++ b/docs/discover/document-explorer.asciidoc @@ -29,7 +29,7 @@ image:images/discover-customize-table.png[Options to customize the table in Disc [[document-explorer-columns]] ==== Reorder and resize the columns -* To move a single column, open the column's contextual options, and select *Move left* or *Move right* in the available options. +* To move a single column, drag its header and drop it to the position you want. You can also open the column's contextual options, and select *Move left* or *Move right* in the available options. * To move multiple columns, click *Columns*. In the pop-up, drag the column names to their new order. diff --git a/docs/migration.asciidoc b/docs/migration.asciidoc index dd7a4d097d83..1e3b67b96ec1 100644 --- a/docs/migration.asciidoc +++ b/docs/migration.asciidoc @@ -6,9 +6,9 @@ This section discusses the changes that you need to be aware of when migrating your application from one version of Kibana to another. -* <> +* <> See also <> and <>. -- -include::migration/migrate_8_0.asciidoc[] \ No newline at end of file +include::migration/migrate_9_0.asciidoc[] \ No newline at end of file diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc deleted file mode 100644 index 7c545cd6ef19..000000000000 --- a/docs/migration/migrate_8_0.asciidoc +++ /dev/null @@ -1,402 +0,0 @@ -[[breaking-changes-8.0]] -== Breaking changes in 8.0 -++++ -8.0 -++++ - -This section discusses the changes that you need to be aware of when migrating -your application to Kibana 8.0. - -See also <> and <>. - -* <> -* <> - -[float] -[[breaking_80_index_pattern_changes]] -=== Index pattern changes - -[float] -==== Removed support for time-based internal index patterns -*Details:* Time-based interval index patterns were deprecated in 5.x. In 6.x, -you could no longer create time-based interval index patterns, but they continued -to function as expected. Support for these index patterns has been removed in 8.0. - -*Impact:* You must migrate your time_based index patterns to a wildcard pattern, -for example, `logstash-*`. - -[float] -[[breaking_80_setting_changes]] -=== Settings changes - -[float] -==== Multitenancy by changing `kibana.index` is no longer supported -*Details:* `kibana.index`, `xpack.reporting.index` and `xpack.task_manager.index` can no longer be specified. - -*Impact:* Users who relied on changing these settings to achieve multitenancy should use *Spaces*, cross-cluster replication, or cross-cluster search instead. To migrate to *Spaces*, users are encouraged to use saved object management to export their saved objects from a tenant into the default tenant in a space. Improvements are planned to improve on this workflow. See https://github.com/elastic/kibana/issues/82020 for more details. - -[float] -==== Disabling most plugins with the `{plugin_name}.enabled` setting is no longer supported -*Details:* The ability for most plugins to be disabled using the `{plugin_name}.enabled` config option has been removed. - -*Impact:* Some plugins, such as `telemetry`, `newsfeed`, `reporting`, and the various `vis_type` plugins will continue to support this setting, however the rest of the plugins that ship with Kibana will not. By default, any newly created plugins will not support this configuration unless it is explicitly added to the plugin's `configSchema`. - -If you are currently using one of these settings in your Kibana config, please remove it before upgrading to 8.0. If you were using these settings to control user access to certain Kibana applications, we recommend leveraging Feature Controls instead. - -[float] -==== Legacy browsers are now rejected by default -*Details:* `csp.strict` is now enabled by default, so Kibana will fail to load for older, legacy browsers that do not enforce basic Content Security Policy protections - notably Internet Explorer 11. - -*Impact:* To allow Kibana to function for these legacy browsers, set `csp.strict: false`. Since this is about enforcing a security protocol, we *strongly discourage* disabling `csp.strict` unless it is critical that you support Internet Explorer 11. - -[float] -==== Configuring content security policy rules is no longer supported -*Details:* Configuring `csp.rules` is removed in favor of per-directive specific configuration. Configuring the default `csp.script_src`, `csp.workers_src` and `csp.style_src` values is not required. - -*Impact:* Configure per-directive sources instead. See https://github.com/elastic/kibana/pull/102059 for more details. - -[float] -==== Default logging timezone is now the system's timezone -*Details:* In prior releases the timezone used in logs defaulted to UTC. We now use the host machine's timezone by default. - -*Impact:* To restore the previous behavior, in kibana.yml use the pattern layout, with a {kibana-ref}/logging-configuration.html#date-format[date modifier]: -[source,yaml] -------------------- -logging: - appenders: - custom: - type: console - layout: - type: pattern - pattern: "%date{ISO8601_TZ}{UTC}" -------------------- -See https://github.com/elastic/kibana/pull/90368 for more details. - -[float] -==== Responses are never logged by default -*Details:* Previously responses would be logged if either `logging.json` was true, `logging.dest` was specified, or a `TTY` was detected. With the new logging configuration, these are provided by a dedicated logger. - -*Impact:* To restore the previous behavior, in `kibana.yml` enable `debug` for the `http.server.response` logger: -[source,yaml] -------------------- -logging: - appenders: - custom: - type: console - layout: - type: pattern - loggers: - - name: http.server.response - appenders: [custom] - level: debug -------------------- -See https://github.com/elastic/kibana/pull/87939 for more details. - -[float] -==== Logging destination is specified by the appender -*Details:* Previously log destination would be `stdout` and could be changed to `file` using `logging.dest`. With the new logging configuration, you can specify the destination using {kibana-ref}/logging-configuration.html#logging-appenders[appenders]. - -*Impact:* To restore the previous behavior and log records to *stdout*, in `kibana.yml` use an appender with `type: console`. -[source,yaml] -------------------- -logging: - appenders: - custom: - type: console - layout: - type: pattern - root: - appenders: [default, custom] -------------------- - -To send logs to `file` with a given file path, you should define a custom appender with `type:file`: -[source,yaml] -------------------- -logging: - appenders: - file: - type: file - fileName: /var/log/kibana.log - layout: - type: pattern - root: - appenders: [default, file] -------------------- - -[float] -==== Set log verbosity with root -*Details:* Previously logging output would be specified by `logging.silent` (none), `logging.quiet` (error messages only) and `logging.verbose` (all). With the new logging configuration, set the minimum required {kibana-ref}/logging-configuration.html#log-level[log level]. - -*Impact:* To restore the previous behavior, in `kibana.yml` specify `logging.root.level`: -[source,yaml] -------------------- -# suppress all logs -logging: - root: - level: off -------------------- - -[source,yaml] -------------------- -# only log error messages -logging: - root: - level: error -------------------- - -[source,yaml] -------------------- -# log all events -logging: - root: - level: all -------------------- - -[float] -==== Declare log message format -*Details:* Previously all events would be logged in `json` format when `logging.json` was true. With the new logging configuration you can specify the output format with layouts. You can choose between `json` and pattern format depending on your needs. - -*Impact:* To restore the previous behavior, in `kibana.yml` configure the logging format for each custom appender with the `appender.layout` property. There is no default for custom appenders and each one must be configured expilictly. - -[source,yaml] -------------------- -logging: - appenders: - custom_console: - type: console - layout: - type: pattern - custom_json: - type: console - layout: - type: json - loggers: - - name: plugins.myPlugin - appenders: [custom_console] - root: - appenders: [default, custom_json] - level: warn -------------------- - -[float] -==== Configure log rotation with the rolling-file appender -*Details:* Previously log rotation would be enabled when `logging.rotate.enabled` was true. - -*Impact:* To restore the previous behavior, in `kibana.yml` use the {kibana-ref}/logging-configuration.html#rolling-file-appender[`rolling-file`] appender. - -[source,yaml] -------------------- -logging: - appenders: - rolling-file: - type: rolling-file - fileName: /var/logs/kibana.log - policy: - type: size-limit - size: 50mb - strategy: - type: numeric - pattern: '-%i' - max: 2 - layout: - type: pattern - loggers: - - name: plugins.myPlugin - appenders: [rolling-file] -------------------- - -[float] -==== `xpack.security.authProviders` is no longer valid -*Details:* The deprecated `xpack.security.authProviders` setting in the `kibana.yml` file has been removed. - -*Impact:* Use `xpack.security.authc.providers` instead. - -[float] -==== `xpack.security.authc.providers` has changed value format -*Details:* `xpack.security.authc.providers` setting in the `kibana.yml` has changed value format. - -*Impact:* Array of provider types as a value is no longer supported, use extended object format instead. - -[float] -==== `xpack.security.authc.saml` is no longer valid -*Details:* The deprecated `xpack.security.authc.saml` setting in the `kibana.yml` file has been removed. - -*Impact:* Configure SAML authentication providers using `xpack.security.authc.providers.saml.{provider unique name}.*` settings instead. - -[float] -==== `xpack.security.authc.oidc` is no longer valid -*Details:* The deprecated `xpack.security.authc.oidc` setting in the `kibana.yml` file has been removed. - -*Impact:* Configure OpenID Connect authentication providers using `xpack.security.authc.providers.oidc.{provider unique name}.*` settings instead. - -[float] -==== `xpack.security.public` is no longer valid -*Details:* Previously Kibana was choosing the appropriate Elasticsearch SAML realm automatically using the `Assertion Consumer Service` -URL that it derived from the actual server address and `xpack.security.public` setting. Starting in 8.0.0, the deprecated `xpack.security.public` setting in the `kibana.yml` file has been removed and the Elasticsearch SAML realm name that Kibana will use should be specified explicitly. - -*Impact:* Define `xpack.security.authc.providers.saml.{provider unique name}.realm` when using the SAML authentication providers instead. - -[float] -==== `/api/security/v1/saml` endpoint is no longer supported -*Details:* The deprecated `/api/security/v1/saml` endpoint is no longer supported. - -*Impact:* Rely on `/api/security/saml/callback` endpoint when using SAML instead. This change should be reflected in Elasticsearch and Identity Provider SAML settings. - -[float] -==== `/api/security/v1/oidc` endpoint is no longer supported -*Details:* The deprecated `/api/security/v1/oidc` endpoint is no longer supported. - -*Impact:* Rely on `/api/security/oidc/callback` endpoint when using OpenID Connect instead. This change should be reflected in Elasticsearch and OpenID Connect Provider settings. - -[float] -==== `/api/security/v1/oidc` endpoint is no longer supported for Third Party initiated login -*Details:* The deprecated `/api/security/v1/oidc` endpoint is no longer supported for Third Party initiated login. - -*Impact:* Rely on `/api/security/oidc/initiate_login` endpoint when using Third Party initiated OpenID Connect login instead. This change should be reflected in Elasticsearch and OpenID Connect Provider settings. - -[float] -==== `/api/security/v1/oidc/implicit` endpoint is no longer supported -*Details:* The deprecated `/api/security/v1/oidc/implicit` endpoint is no longer supported. - -*Impact:* Rely on `/api/security/oidc/implicit` endpoint when using OpenID Connect Implicit Flow instead. This change should be reflected in OpenID Connect Provider settings. - -[float] -=== `optimize` directory is now in the `data` folder -*Details:* Generated bundles have moved to the configured `path.data` folder. - -*Impact:* Any workflow that involved manually clearing generated bundles will have to be updated with the new path. - -[float] -=== Legacy `optimize.*` settings are no longer supported -*Details:* The legacy optimizer has been removed and any `optimize.*` settings have been deprecated since 7.10. These settings have been removed as they are no longer in use. - -*Impact:* Any of the legacy `optimize.*` settings will prevent Kibana from starting up. Going forward, to run the `@kbn/optimizer` separately in development, pass `--no-optimizer` to `yarn start`. See https://github.com/elastic/kibana/pull/73154 for more details. - -[float] -=== kibana.keystore has moved from the `data` folder to the `config` folder -*Details:* By default, kibana.keystore has moved from the configured `path.data` folder to `/config` for archive distributions -and `/etc/kibana` for package distributions. If a pre-existing keystore exists in the data directory that path will continue to be used. - -[float] -[[breaking_80_user_role_changes]] -=== User role changes - -[float] -=== `kibana_user` role has been removed and `kibana_admin` has been added. - -*Details:* The `kibana_user` role has been removed and `kibana_admin` has been added to better -reflect its intended use. This role continues to grant all access to every -{kib} feature. If you wish to restrict access to specific features, create -custom roles with {kibana-ref}/kibana-privileges.html[{kib} privileges]. - -*Impact:* Any users currently assigned the `kibana_user` role will need to -instead be assigned the `kibana_admin` role to maintain their current -access level. - -[float] -=== `kibana_dashboard_only_user` role has been removed. - -*Details:* The `kibana_dashboard_only_user` role has been removed. -If you wish to restrict access to just the Dashboard feature, create -custom roles with {kibana-ref}/kibana-privileges.html[{kib} privileges]. - -*Impact:* Any users currently assigned the `kibana_dashboard_only_user` role will need to be assigned a custom role which only grants access to the Dashboard feature. - -Granting additional cluster or index privileges may enable certain -**Stack Monitoring** features. - -[float] -[[breaking_80_reporting_changes]] -=== Reporting changes - -[float] -==== Legacy job parameters are no longer supported -*Details:* POST URL snippets that were copied in Kibana 6.2 or earlier are no longer supported. These logs have -been deprecated with warnings that have been logged throughout 7.x. Please use Kibana UI to re-generate the -POST URL snippets if you depend on these for automated PDF reports. - -[float] -=== Configurations starting with `xpack.telemetry` are no longer valid - -*Details:* -The `xpack.` prefix has been removed for all telemetry configurations. - -*Impact:* -For any configurations beginning with `xpack.telemetry`, remove the `xpack` prefix. Use {kibana-ref}/telemetry-settings-kbn.html#telemetry-general-settings[`telemetry.enabled`] instead. - -[float] -=== SysV init support has been removed - -*Details:* -All supported operating systems support using systemd service files. Any system that doesn't already have service aliased to use kibana.service should use `systemctl start kibana.service` instead of the `service start kibana`. - -*Impact:* -Any installations using `.deb` or `.rpm` packages using SysV will need to migrate to systemd. - -[float] -=== TLS v1.0 and v1.1 are disabled by default - -*Details:* -Support can be re-enabled by setting `--tls-min-1.0` in the `node.options` config file that can be found inside `kibana/config` folder or any other configured with the environment variable `KBN_PATH_CONF` (for example in Debian based system would be `/etc/kibana`). - -*Impact:* -Browser and proxy clients communicating over TLS v1.0 and v1.1. - -[float] -=== Platform removed from root folder name for `.tar.gz` and `.zip` archives - -*Details:* -The output directory after extracting an archive no longer includes the target platform. For example, `kibana-8.0.0-linux-aarch64.tar.gz` will produce a folder named `kibana-8.0.0`. - -*Impact:* -Configuration management tools and automation will need to be updated to use the new directory. - -[float] -=== `elasticsearch.preserveHost` is no longer valid -*Details:* The deprecated `elasticsearch.preserveHost` setting in the `kibana.yml` file has been removed. - -*Impact:* Configure {kibana-ref}/settings.html#elasticsearch-requestHeadersWhitelist[`elasticsearch.requestHeadersWhitelist`] to whitelist client-side headers. - -[float] -=== `elasticsearch.startupTimeout` is no longer valid -*Details:* The deprecated `elasticsearch.startupTimeout` setting in the `kibana.yml` file has been removed. - -*Impact:* Kibana will keep on trying to connect to Elasticsearch until it manages to connect. - -[float] -=== `savedObjects.indexCheckTimeout` is no longer valid -*Details:* The deprecated `savedObjects.indexCheckTimeout` setting in the `kibana.yml` file has been removed. - -[float] -=== `server.xsrf.token` is no longer valid -*Details:* The deprecated `server.xsrf.token` setting in the `kibana.yml` file has been removed. - -[float] -=== `newsfeed.defaultLanguage` is no longer valid -*Details:* Specifying a default language to retrieve newsfeed items is no longer supported. - -*Impact:* Newsfeed items will be retrieved based on the browser locale and fallback to 'en' if an item does not have a translation for the locale. Configure {kibana-ref}/i18n-settings-kb.html#general-i18n-settings-kb[`i18n.locale`] to override the default behavior. - -[float] -=== `xpack.banners.placement` has changed value -*Details:* `xpack.banners.placement: 'header'` setting in `kibana.yml` has changed value. - -*Impact:* Use {kibana-ref}/banners-settings-kb.html#banners-settings-kb[`xpack.banners.placement: 'top'`] instead. - -[float] -=== `cpu.cgroup.path.override` is no longer valid -*Details:* The deprecated `cpu.cgroup.path.override` setting is no longer supported. - -*Impact:* Configure {kibana-ref}/settings.html#ops-cGroupOverrides-cpuPath[`ops.cGroupOverrides.cpuPath`] instead. - -[float] -=== `cpuacct.cgroup.path.override` is no longer valid -*Details:* The deprecated `cpuacct.cgroup.path.override` setting is no longer supported. - -*Impact:* Configure {kibana-ref}/settings.html#ops-cGroupOverrides-cpuAcctPath[`ops.cGroupOverrides.cpuAcctPath`] instead. - -[float] -=== `server.xsrf.whitelist` is no longer valid -*Details:* The deprecated `server.xsrf.whitelist` setting is no longer supported. - -*Impact:* Use {kibana-ref}/settings.html#settings-xsrf-allowlist[`server.xsrf.allowlist`] instead. diff --git a/docs/migration/migrate_9_0.asciidoc b/docs/migration/migrate_9_0.asciidoc new file mode 100644 index 000000000000..a3d2f1587eb3 --- /dev/null +++ b/docs/migration/migrate_9_0.asciidoc @@ -0,0 +1,18 @@ +[[breaking-changes-9.0]] +== Breaking changes in 9.0 +++++ +9.0 +++++ + +This section discusses the changes that you need to be aware of when migrating +your application to Kibana 9.0. + +See also <> and <>. + +* <> + + +[float] +[[breaking_90_setting_changes]] +=== Settings changes + diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 84cf809c6666..c9a81c5d398c 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -418,10 +418,6 @@ This page has been deleted. Refer to <>. This page has been deleted. Refer to <>. -[role="exclude",id="enhancements-and-bug-fixes-v8.10.0"] -== Enhancements and bug fixes for 8.10.0 - -This content has moved. Refer to <> for 8.10.0. [role="exclude",id="gen-ai-action-type"] == Generative AI connector and action diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 49aa22de9fd3..b43f3b268e43 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -302,11 +302,5 @@ Enables a check that warns you when there's a potential formula included in the `xpack.reporting.csv.escapeFormulaValues`:: Escape formula values in cells with a `'`. See OWASP: https://www.owasp.org/index.php/CSV_Injection. Defaults to `true`. -`xpack.reporting.csv.enablePanelActionDownload`:: -deprecated:[8.14.0,This setting will be removed in an upcoming version of {kib}.] When `true`, this -setting enables a deprecated feature which allows users to download a CSV export from a saved search -panel on a dashboard. When `false`, users can generate regular CSV reports from a saved search panel on a -dashboard and later download them in *Stack Management > Reporting*. Defaults to `false`. - `xpack.reporting.csv.useByteOrderMarkEncoding`:: Adds a byte order mark (`\ufeff`) at the beginning of the CSV file. Defaults to `false`. diff --git a/docs/setup/upgrade/upgrade-standard.asciidoc b/docs/setup/upgrade/upgrade-standard.asciidoc index f78a26e8acc8..fb8c4ff41969 100644 --- a/docs/setup/upgrade/upgrade-standard.asciidoc +++ b/docs/setup/upgrade/upgrade-standard.asciidoc @@ -30,7 +30,7 @@ Different versions of {kib} running against the same {es} index, such as during + Make sure you remove or update any configurations -that are indicated in the <> documentation +that are indicated in the <> documentation otherwise {kib} will fail to start. -- . Upgrade any plugins by removing the existing plugin and reinstalling the @@ -51,7 +51,7 @@ and becomes a new instance in the monitoring data. -- . Copy the files from the `config` directory from your old installation to your new installation. Make sure you remove or update any configurations that are - indicated in the <> documentation + indicated in the <> documentation otherwise {kib} will fail to start. . Copy the files from the `data` directory from your old installation to your new installation. diff --git a/docs/upgrade-notes.asciidoc b/docs/upgrade-notes.asciidoc index 4d4208b2253f..329b6ab5cb66 100644 --- a/docs/upgrade-notes.asciidoc +++ b/docs/upgrade-notes.asciidoc @@ -39,15 +39,15 @@ Check https://docs.elastic.dev/docs/kibana-doc-links (internal) for more details //// -Before you upgrade, review the breaking changes and deprecations introduced in {kib} 8.x, then mitigate the impact. +Before you upgrade, review the breaking changes and deprecations introduced since the version you are migrating from, then mitigate the impact. -For Elastic Security release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +If you are migrating from a version prior to version 9.0, you must first upgrade to the last 8.x version available. + +For Elastic Security solution release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. [float] === Breaking changes -[float] -==== Kibana APIs [discrete] [[breaking-199656]] @@ -97,1327 +97,12 @@ We would love to discuss your use case. ==== -[discrete] -[[breaking-162506]] -.Get case metrics APIs became internal. (8.10) -[%collapsible] -==== -*Details* + -The get case metrics APIs are now internal. For more information, refer to ({kibana-pull}162506[#162506]). -==== - -[discrete] -[[breaking-155470]] -.Removed legacy project monitor API. (8.8) -[%collapsible] -==== -*Details* + -The project monitor API for Synthetics in Elastic Observability has been removed. For more information, refer to {kibana-pull}155470[#155470]. - -*Impact* + -In 8.8.0 and later, an error appears when you use the project monitor API. -==== - -[discrete] -[[breaking-147616]] -.Removed the `current_upgrades` endpoint. (8.7) -[%collapsible] -==== -*Details* + -The `/api/fleet/current_upgrades` endpoint has been removed. For more information, refer to {kibana-pull}147616[#147616]. - -*Impact* + -When you upgrade to 8.7.0, use the `api/fleet/agents/action_status` endpoint. -==== - -[discrete] -[[breaking-147199]] -.Removed the `preconfiguration` API route. (8.7) -[%collapsible] -==== -*Details* + -The `/api/fleet/setup/preconfiguration` API, which was released as generally available by error, has been removed. For more information, refer to {kibana-pull}147199[#147199]. - -*Impact* + -Do not use `/api/fleet/setup/preconfiguration`. To manage preconfigured agent policies, use kibana.yml. For more information, check link:https://www.elastic.co/guide/en/kibana/current/fleet-settings-kb.html#_preconfiguration_settings_for_advanced_use_cases[Preconfigured settings]. -==== - -[discrete] -[[breaking-141757]] -.Updated bulk action API to return actionId instead of agent success. (8.5) -[%collapsible] -==== -*Details* + -To make bulk action responses consistent, returns `actionId` instead of agent ids with `success: True` or `success: False` results. For more information, refer to {kibana-pull}141757[#141757]. - -*Impact* + -When you use `FleetBulkResponse`, you now receive only `actionId` responses. -==== - -[discrete] -[[breaking-116821]] -.Removed deprecated config fields from Logs and Metrics APIs and saved objects. (8.0) -[%collapsible] -==== -*Details* + -On the Logs and Metrics UIs, references to the following API and saved object deprecated fields have been removed: - -* `timestamp` -* `tiebreaker` -* `container` -* `pod` -* `host` - -For more information, refer to {kibana-pull}116821[#116821] and {kibana-pull}115874[#115874]. - -*Impact* + -When you upgrade to 8.0.0, you are unable to use references to the deprecated fields. -==== - -[discrete] -[[breaking-114730]] -.Removed `/api/settings`. (8.0) -[%collapsible] -==== -*Details* + -The `/api/settings` REST API has been removed. For more information, refer to {kibana-pull}114730[#114730]. - -*Impact* + -Use `/api/stats`. -==== - -[discrete] -[[breaking-110830]] -.Changed the `GET /api/status` default behavior. (8.0) -[%collapsible] -==== -*Details* + -`GET /api/status` reports a new and more verbose payload. For more information, refer to {kibana-pull}110830[#110830]. - -*Impact* + -To retrieve the {kib} status in the previous format, use `GET /api/status?v7format=true`. -==== - [float] -==== Kibana platform - -// Alerting -[discrete] -[[breaking-170635]] -.[Alerting] A new sub-feature privilege to control user access to the cases settings. (8.12) -[%collapsible] -==== -*Details* + -Roles with at least a sub-feature privilege configured will not have access to the cases setting like they had previously. All roles without a sub-feature privilege configured will not be affected. For more information, refer to ({kibana-pull}170635[#170635]). -==== - -[discrete] -[[breaking-162492]] -.[Alerting] New case limits. (8.10) -[%collapsible] -==== -*Details* + -Limits are now imposed on the number of objects cases can process or the amount of data those objects can store. -//// -For example: -* Updating a case comment is now included in the 10000 user actions restriction. ({kibana-pull}163150[#163150]) -* Updating a case now fails if the operation makes it reach more than 10000 user actions. ({kibana-pull}161848[#161848]) -* The total number of characters per comment is limited to 30000. ({kibana-pull}161357[#161357]) -* The getConnectors API now limits the number of supported connectors returned to 1000. ({kibana-pull}161282[#161282]) -* There are new limits and restrictions when retrieving cases. ({kibana-pull}162411[#162411]), ({kibana-pull}162245[#162245]), ({kibana-pull}161111[#161111]), ({kibana-pull}160705[#160705]) -* A case can now only have 100 external references and persistable state(excluding files) attachments combined. ({kibana-pull}162071[#162071]). -* New limits on titles, descriptions, tags and category. ({kibana-pull}160844[#160844]). -* The maximum number of cases that can be updated simultaneously is now 100. The minimum is 1. ({kibana-pull}161076[#161076]). -* The Delete cases API now limits the number of cases to be deleted to 100.({kibana-pull}160846[#160846]). -//// -For the full list, refer to {kib-issue}146945[#146945]. -==== - -[discrete] -[[breaking-147985]] -.[Alerting] Changed privileges for alerts and cases. (8.8) -[%collapsible] -==== -*Details* + -The privileges for attaching alerts to cases has changed. For more information, refer to {kibana-pull}147985[#147985]. - -*Impact* + -To attach alerts to cases, you must have `Read` access to an {observability} or Security feature that has alerts and `All` access to the **Cases** feature. For detailed information, check link:https://www.elastic.co/guide/en/kibana/current/kibana-privileges.html[{kib} privileges] and link:https://www.elastic.co/guide/en/kibana/current/setup-cases.html[Configure access to cases]. -==== - -[discrete] -.[Alerting] Removed support for `monitoring.cluster_alerts.allowedSpaces`. (8.0) -[%collapsible] -==== -*Details* + -The `monitoring.cluster_alerts.allowedSpaces` setting, which {kib} uses to create Stack Monitoring alerts, has been removed. For more information, refer to {kibana-pull}123229[#123229]. - -*Impact* + -Before you upgrade to 8.0.0, remove `monitoring.cluster_alerts.allowedSpaces` from kibana.yml. -==== - -[discrete] -[[breaking-114558]] -.[Alerting] Removed `xpack.task_manager.index` setting. (8.0) -[%collapsible] -==== -*Details* + -The `xpack.task_manager.index` setting has been removed. For more information, refer to {kibana-pull}114558[#114558]. - -*Impact* + -Before you upgrade to 8.0.0, remove `xpack.task_manager.index` from kibana.yml. -==== - -[discrete] -[[breaking-113461]] -.[Alerting] Removed ability to remove Elastic-managed plugins. (8.0) -[%collapsible] -==== -*Details* + -The `xpack.actions.enabled` setting has been removed. For more information, refer to {kibana-pull}113461[#113461]. - -*Impact* + -Before you upgrade to 8.0.0, remove `xpack.actions.enabled` from kibana.yml. -==== - - -// Data views - -[discrete] -[[breaking-139431]] -.[Data views] Removed filter validation for ad-hoc data views (8.5) -[%collapsible] -==== -*Details* + -Filters associated with unknown data views, such as deleted data views, are no longer automatically disabled. For more information, refer to {kibana-pull}139431[#139431]. - -*Impact* + -Filters associated with unknown data views now display a warning message instead of being automatically disabled. -==== - -// Dev tools - -[discrete] -[[breaking-159041]] -.[Dev tools] The `addProcessorDefinition` function was removed from Console. (8.10) -[%collapsible] -==== -*Details* + -The function `addProcessorDefinition` is removed from the Console plugin start contract (server side). For more information, refer to ({kibana-pull}159041[#159041]). -==== - -[discrete] -[[breaking-123754]] -.[Dev tools] Removed the `console.ssl` setting. (8.0) -[%collapsible] -==== -*Details* + -The `console.ssl` setting has been removed. For more information, refer to {kibana-pull}123754[#123754]. - -*Impact* + -Before you upgrade to 8.0.0, remove `console.ssl` from kibana.yml. -==== - -// ECS - -[discrete] -.[Elastic Common Schema] Moved `doc_root.vulnerability.package` to doc_root.package (ECS). (8.11) -[%collapsible] -==== -*Details* + -This change updates all instances of `vulnerability.package` to the ECS standard package fieldset. For more information, refer to ({kibana-pull}164651[#164651]). -==== - -// ESQL -[discrete] -[[breaking-182074]] -.[ES|QL] Renamed an advanced setting to enable {esql}. (8.14) -[%collapsible] -==== -*Details* + -The advanced setting which hides {esql} from the UI has been renamed from `discover:enableESQL` to `enableESQL`. It is enabled by default and must be switched off to disable {esql} features from your {kib} applications. For more information, refer to ({kibana-pull}182074[#182074]). -==== - -[discrete] -[[breaking-174674]] -.[ES|QL] Removed `is_nan`, `is_finite`, and `is_infinite` functions from {esql}. (8.13) -[%collapsible] -==== -*Details* + -These functions have been removed from {esql} queries as they are not supported. Errors would be thrown when trying to use them. For more information, refer to ({kibana-pull}174674[#174674]). -==== - -// Fleet -[discrete] -[[breaking-184036]] -.[Fleet] Added rate limiting to install by upload endpoint. (8.15) -[%collapsible] -==== -*Details* + -Rate limiting was added to the upload `api/fleet/epm/packages` endpoint. For more information, refer to {kibana-pull}184036[#184036]. - -*Impact* + -If you do two or more requests in less than 10 seconds, the subsequent requests fail with `429 Too Many Requests`. -Wait 10 seconds before uploading again. -This change could potentially break automations for users that rely on frequent package uploads. -==== - -[discrete] -[[breaking-176879]] -.[Fleet]Removed conditional topics for Kafka outputs. (8.13) -[%collapsible] -==== -*Details* + -The Kafka output no longer supports conditional topics. For more information, refer to ({kibana-pull}176879[#176879]). -==== - -[discrete] -[[breaking-176443]] -.[Fleet]Most Fleet installed integrations are now read-only and labelled with a *Managed* tag in the Kibana UI. (8.13) -[%collapsible] -==== -*Details* + - -Integration content installed by {fleet} is no longer editable. This content is tagged with *Managed* in the {kib} UI, and is Elastic managed. This content cannot be edited or deleted. However, managed visualizations, dashboards, and saved searches can be cloned. The clones can be customized. - -When cloning a dashboard the cloned panels become entirely independent copies that are unlinked from the original configurations and dependencies. - -For managed content relating to specific visualization editors such as Lens, TSVB, and Maps, the clones retain the original reference configurations. The same applies to editing any saved searches in a managed visualization. - -For more information, refer to ({kibana-pull}172393[#172393]). -==== - -[discrete] -[[breaking-167085]] -.[Fleet] Improved config output validation for default output. (8.11) -[%collapsible] -==== -*Details* + -Improve config output validation to not allow to defining multiple default outputs in {kib} configuration. For more information, refer to ({kibana-pull}167085[#167085]). -==== - -[discrete] -[[breaking-138677]] -.[Fleet] Removed the `package_policies` field from the agent policy saved object. (8.5) -[%collapsible] -==== -*Details* + -The bidirectional foreign key between agent policy and package policy has been removed. For more information, refer to {kibana-pull}138677[#138677]. - -*Impact* + -The agent policy saved object no longer includes the `package_policies` field. -==== - -[discrete] -[[breaking-135669]] -.[Fleet] xpack.agents.* are now uneditable in UI when defined in kibana.yml. (8.4) -[%collapsible] -==== -*Details* + -When you configure `xpack.fleet.agents.fleet_server.hosts` and `xpack.fleet.agents.elasticsearch.hosts` in kibana.yml, you are unable to update the fields on the Fleet UI. - -For more information, refer to {kibana-pull}135669[#135669]. - -*Impact* + -To configure `xpack.fleet.agents.fleet_server.hosts` and `xpack.fleet.agents.elasticsearch.hosts` on the Fleet UI, avoid configuring the settings in kibana.yml. -==== - -[discrete] -[[breaking-118854]] -.[Fleet] Split package policy `upgrade` endpoint for Fleet. (8.0) -[%collapsible] -==== -*Details* + -For package policy upgrades, the packagePolicy `upgrade` endpoint format supports a mutative upgrade operation (when `dryRun: false`) and a read-only dry run operation (when `dryRun: true`): - -[source,text] --- - POST /package_policies/upgrade - { - packagePolicyIds: [...], - dryRun: false - } --- - -For more information, refer to {kibana-pull}118854[#118854]. - -*Impact* + -The endpoint is now split into two separate endpoints: - -[source,text] --- - POST /package_policies/upgrade - { - packagePolicyIds: [...] - } - - POST /package_policies/upgrade/dry_run - { - packagePolicyIds: [...] - } --- -==== - -// General settings - -[discrete] -[[breaking-180986]] -.[General settings] Updated request processing during shutdown. (8.16) -[%collapsible] -==== -*Details* + -During shutdown, {kib} now waits for all the ongoing requests to complete according to the `server.shutdownTimeout` setting. During that period, the incoming socket is closed and any new incoming requests are rejected. Before this update, new incoming requests received a response with the status code 503 and body `{"message": "Kibana is shutting down and not accepting new incoming requests"}`. For more information, refer to {kibana-pull}180986[#180986]. -==== - -[discrete] -[[breaking-111535]] -.[General settings] Removed `CONFIG_PATH` and `DATA_PATH` environment variables. (8.0) -[%collapsible] -==== -*Details* + -The `CONFIG_PATH` and `DATA_PATH` environment variables have been removed. For more information, refer to {kibana-pull}111535[#111535]. - -*Impact* + -Replace the `CONFIG_PATH` environment variable with `KBN_PATH_CONF`, and replace `DATA_PATH` with the `path.data` setting. -==== - -[discrete] -[[breaking-114379]] -.[General settings] Removed support for csp.rules configuration. (8.0) -[%collapsible] -==== -*Details* + -Support for the `csp.rules` configuration property has been removed. For more information, refer to {kibana-pull}114379[#114379]. - -*Impact* + -Configuring the default `csp.script_src`, `csp.workers_src`, and `csp.style_src` values is not required. -==== - -[discrete] -[[breaking-113653]] -.[General settings] Changed and removed deprecated core settings and deprecated settings from core plugins. (8.0) -[%collapsible] -==== -*Details* + -The deprecation notice for `server.cors` has changed from `level:critical` to `level:warning`. - -The following settings have changed: - -* The `xpack.banners.placement` value of `header` has been renamed to `top` - -Support for the following configuration settings has been removed: - -* `newsfeed.defaultLanguage` -* `cpu.cgroup.path.override` -* `cpuacct.cgroup.path.override` -* `server.xsrf.whitelist` -* `xpack.xpack_main.xpack_api_polling_frequency_millis` -* `KIBANA_PATH_CONF` - -For more information, refer to {kibana-pull}113653[#113653]. +=== Deprecation notices -*Impact* + -* The `header` value provided to the `xpack.banners.placement` configuration has been renamed to 'top' -* The `newsfeed.defaultLanguage` newsfeed items are retrieved based on the browser locale and default to English -* Replace `cpu.cgroup.path.override` with `ops.cGroupOverrides.cpuPath` -* Replace `cpuacct.cgroup.path.override` with `ops.cGroupOverrides.cpuAcctPath` -* Replace `server.xsrf.whitelist` with `server.xsrf.allowlist` -* Replace `xpack.xpack_main.xpack_api_polling_frequency_millis` with `xpack.licensing.api_polling_frequency` -* Replace `KIBANA_PATH_CONF` path to the {kib} configuration file using the `KBN_PATH_CONF` environment variable -==== - -[discrete] -[[breaking-113495]] -.[General settings] Removed `enabled` settings from plugins. (8.0) -[%collapsible] -==== -*Details* + -Using `{plugin_name}.enabled` to disable plugins has been removed. Some plugins, such as `telemetry`, `newsfeed`, `reporting`, and the various `vis_type` plugins will continue to support this setting. All other {kib} plugins will not support this setting. Any new plugin will support this setting only when specified in the `configSchema`. For more information, refer to {kibana-pull}113495[#113495]. - -The `xpack.security.enabled` setting has been removed. For more information, refer to {kibana-pull}111681[#111681]. - -*Impact* + -Before you upgrade to 8.0.0: - -* Remove `{plugin_name}.enabled` from kibana.yml. If you use the setting to control user access to {kib} applications, use <> instead. -* Replace `xpack.security.enabled` with {ref}/security-settings.html#general-security-settings[`xpack.security.enabled`] in elasticsearch.yml. -==== - -[discrete] -[[breaking-113367]] -.[General settings] Removed `--plugin-dir` cli option. (8.0) -[%collapsible] -==== -*Details* + -The `plugins.scanDirs` setting and `--plugin-dir` cli option have been removed. For more information, refer to {kibana-pull}113367[#113367]. - -*Impact* + -Before you upgrade to 8.0.0, remove `plugins.scanDirs` from kibana.yml. -==== - -[discrete] -[[breaking-113296]] -.[General settings] Removed support for `optimize.*` settings. (8.0) -[%collapsible] -==== -*Details* + -The legacy `optimize.*` settings have been removed. If your configuration uses the following legacy `optimize.*` settings, {kib} fails to start: - -* `optimize.lazy` -* `optimize.lazyPort` -* `optimize.lazyHost` -* `optimize.lazyPrebuild` -* `optimize.lazyProxyTimeout` -* `optimize.enabled` -* `optimize.bundleFilter` -* `optimize.bundleDir` -* `optimize.viewCaching` -* `optimize.watch` -* `optimize.watchPort` -* `optimize.watchHost` -* `optimize.watchPrebuild` -* `optimize.watchProxyTimeout` -* `optimize.useBundleCache` -* `optimize.sourceMaps` -* `optimize.workers` -* `optimize.profile` -* `optimize.validateSyntaxOfNodeModules` - -For more information, refer to {kibana-pull}113296[#113296]. - -*Impact* + -To run the `@kbn/optimizer` separately in development, pass `--no-optimizer` to `yarn start`. For more details, refer to {kibana-pull}73154[#73154]. -==== - -[discrete] -[[breaking-113173]] -.[General settings] Removed `so/server/es` settings. (8.0) -[%collapsible] -==== -*Details* + -Some of the `savedObjects`, `server`, and `elasticsearch` settings have been removed. If your configuration uses the following settings, {kib} fails to start: - -* `savedObjects.indexCheckTimeout` -* `server.xsrf.token` -* `elasticsearch.preserveHost` -* `elasticsearch.startupTimeout` - -For more information, refer to {kibana-pull}113173[#113173]. - -*Impact* + -Before you upgrade to 8.0.0., remove these settings from kibana.yml. -==== - -[discrete] -[[breaking-113068]] -.[General settings] Added requirement for inline scripting. (8.0) -[%collapsible] -==== -*Details* + -To start {kib}, you must enable inline scripting in {es}. For more information, refer to {kibana-pull}113068[#113068]. - -*Impact* + -Enable {ref}/modules-scripting-security.html[inline scripting]. -==== - -[discrete] -[[breaking-112773]] -.[General settings] Removed `kibana.index` settings. (8.0) -[%collapsible] -==== -*Details* + -The `kibana.index`, `xpack.reporting.index`, and `xpack.task_manager.index` settings have been removed. For more information, refer to {kibana-pull}112773[#112773]. - -*Impact* + -Use spaces, cross-cluster replication, or cross-cluster search. To migrate to <>, export your <> from a tenant into the default space. For more details, refer to link:https://github.com/elastic/kibana/issues/82020[#82020]. -==== - -[discrete] -[[breaking-112305]] -.[General settings] Removed legacy logging. (8.0) -[%collapsible] -==== -*Details* + -The logging configuration and log output format has changed. For more information, refer to {kibana-pull}112305[#112305]. - -*Impact* + -Use the new <>. -==== - -[discrete] -[[breaking-109798]] -.[General settings] Removed `kibana.defaultAppId` setting. (8.0) -[%collapsible] -==== -*Details* + -The deprecated `kibana.defaultAppId` setting in kibana.yml, which is also available as `kibana_legacy.defaultAppId`, has been removed. For more information, refer to {kibana-pull}109798[#109798]. - -*Impact* + -When you upgrade, remove `kibana.defaultAppId` from your kibana.yml file. To configure the default route for users when they enter a space, use the <> in *Advanced Settings*. -==== - -[discrete] -[[breaking-109350]] -.[General settings] Removed `courier:batchSearches` setting. (8.0) -[%collapsible] -==== -*Details* + -The deprecated `courier:batchSearches` setting in *Advanced Settings* has been removed. For more information, refer to {kibana-pull}109350[#109350]. - -*Impact* + -When you upgrade, the `courier:batchSearches` setting will no longer be available. -==== - - -[discrete] -[[breaking-106061]] -.[General settings] New session timeout defaults. (8.0) -[%collapsible] -==== -*Details* + -The default values for the session timeout `xpack.security.session.{lifespan|idleTimeout}` settings have changed. For more information, refer to {kibana-pull}106061[#106061] - -*Impact* + -The new default values are as follows: - -* `xpack.security.session.idleTimeout: 3d` -* `xpack.security.session.lifespan: 30d` -==== - -[discrete] -[[breaking-87114]] -.[General settings] Removed support for setting `server.host` to '0'. (8.0) -[%collapsible] -==== -*Details* + -Support for configuring {kib} with `0` as the `server.host` has been removed. Please use `0.0.0.0` instead. For more information, refer to {kibana-pull}87114[#87114] - -*Impact* + -You are now unable to use `0` as the `server.host`. -==== - -[discrete] -[[breaking-38657]] -.[General settings] Removed `xpack.security.public` and `xpack.security.authProviders` settings. (8.0) -[%collapsible] -==== -*Details* + -The `xpack.security.public` and `xpack.security.authProviders` settings have been removed. For more information, refer to {kibana-pull}38657[#38657] - -*Impact* + -Use the `xpack.security.authc.saml.realm` and `xpack.security.authc.providers` settings. -==== - -[discrete] -[[breaking-22696]] -.[General settings] Removed `logging.useUTC` setting. (8.0) -[%collapsible] -==== -*Details* + -The `logging.useUTC` setting has been removed. For more information, refer to {kibana-pull}22696[#22696] - -*Impact* + -The default timezone is UTC. To change the timezone, set `logging.timezone: false` in kibana.yml. Change the timezone when the system, such as a docker container, is configured for a nonlocal timezone. -==== - -// Index management - -[discrete] -[[breaking-35173]] -.[Index management] Removed support for time-based interval index patterns. (8.0) -[%collapsible] -==== -*Details* + -Time-based interval index patterns were deprecated in 5.x. In 6.x, you could no longer create time-based interval index patterns, but they continued to function as expected. Support for these index patterns has been removed in 8.0. For more information, refer to {kibana-pull}35173[#35173] - -*Impact* + -You must migrate your time_based index patterns to a wildcard pattern. For example, logstash-*. -==== - -// Logs - -[discrete] -[[breaking-115974]] -.[Logs] Removed deprecated alias config entries. (8.0) -[%collapsible] -==== -*Details* + -The deprecated `xpack.infra.sources.default.logAlias` and `xpack.infra.sources.default.logAlias` settings have been removed. For more information, refer to {kibana-pull}115974[#115974]. - -*Impact* + -Before you upgrade, remove the settings from kibana.yml, then configure the settings in <>. -==== - -[discrete] -[[breaking-61302]] -.[Logs] Removed configurable fields in settings. (8.0) -[%collapsible] -==== -*Details* + -The *Logs* and *Metrics* configurable fields settings have been removed. For more information, refer to {kibana-pull}61302[#61302]. - -*Impact* + -Configure the settings in https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[ECS]. -==== - -// Machine Learning - -[discrete] -[[breaking-119945]] -.[Machine learning] Removed APM jobs from Machine Learning. (8.0) -[%collapsible] -==== -*Details* + -APM Node.js and RUM JavaScript anomaly detection job modules have been removed. For more information, refer to {kibana-pull}119945[#119945]. - -*Impact* + -When you upgrade to 8.0.0, you are unable to create and view the APM Node.js and RUM JavaScript jobs in Machine Learning. -==== - -[discrete] -[[breaking-115444]] -.[Machine learning] Granted access to machine learning features when base privileges are used. (8.0) -[%collapsible] -==== -*Details* + -Machine learning features are included as base privileges. For more information, refer to {kibana-pull}115444[#115444]. - -*Impact* + -If you do not want to grant users privileges to machine learning features, update <>. -==== - -// Osquery - -[discrete] -[[breaking-134855]] -.[Osquery] "All" base privilege option now also applies to Osquery. (8.3) -[%collapsible] -==== -*Details* + -The Osquery {kib} privilege has been updated, so that when the *Privileges for all features level* is set to *All*, this now applies *All* to Osquery privileges as well. Previously, users had to choose the *Customize* option to grant any access to Osquery. For more information, refer to {kibana-pull}130523[#130523]. - -*Impact* + -This impacts user roles that have *Privileges for all features* set to *All*. After this update, users with this role will have access to the Osquery page in {kib}. However, to use the Osquery feature fully, these requirements remain the same: users also need Read access to the logs-osquery_manager.result* index and the Osquery Manager integration must be deployed to Elastic Agents. -==== - -// Saved objects - -[discrete] -[[breaking-118300]] -.[Saved objects] Fail migrations for saved objects with unknown types. (8.0) -[%collapsible] -==== -*Details* + -Unknown saved object types now cause {kib} migrations to fail. For more information, refer to {kibana-issue}107678[#107678]. - -*Impact* + -To complete the migration, re-enable plugins or delete documents from the index in the previous version. -==== - -[discrete] -[[breaking-110738]] -.[Saved objects] Removed support for legacy exports. (8.0) -[%collapsible] -==== -*Details* + -In {kib} 8.0.0 and later, the legacy format from {kib} 6.x is unsupported. For more information, refer to {kibana-pull}110738[#110738] - -*Impact* + -Using the user interface to import saved objects is restricted to `.ndjson` format imports. -==== - -// Security - -[discrete] -[[breaking-116191]] -.[Security] Removed legacy audit logger. (8.0) -[%collapsible] -==== -*Details* + -The legacy audit logger has been removed. For more information, refer to {kibana-pull}116191[#116191]. - -*Impact* + -Audit logs will be written to the default location in the new ECS format. To change the output file, filter events, and more, use the <>. -==== - -[discrete] -[[breaking-41700]] -.[Security] Legacy browsers rejected by default. (8.0) -[%collapsible] -==== -*Details* + -To provide the maximum level of protection for most installations, the csp.strict config is now enabled by default. Legacy browsers not supported by Kibana, such as Internet Explorer 11, are unable to access {kib} unless explicitly enabled. All browsers officially supported by Kibana do not have this issue. For more information, refer to {kibana-pull}41700[#41700] - -*Impact* + -To enable support for legacy browsers, set `csp.strict: false` in kibana.yml. To effectively enforce the security protocol, we strongly discourage disabling `csp.strict` unless it is critical that you support Internet Explorer 11. -==== - -// Setup - -[discrete] -[[breaking-93835]] -.[Setup] Removed platform from archive root directory. (8.0) -[%collapsible] -==== -*Details* + -After you extract an archive, the output directory no longer includes the target platform. For example, `kibana-8.0.0-linux-aarch64.tar.gz` produces a `kibana-8.0.0` folder. For more information, refer to {kibana-pull}93835[#93835]. - -*Impact* + -To use the new folder, update the configuration management tools and automation. -==== - -[discrete] -[[breaking-90511]] -.[Setup] Removed default support for TLS v1.0 and v1.1. (8.0) -[%collapsible] -==== -*Details* + -The default support for TLS v1.0 and v1.1 has been removed. For more information, refer to {kibana-pull}90511[#90511]. - -*Impact* + -To enable support, set `--tls-min-1.0` in the `node.options` configuration file. To locate the configuration file, go to the kibana/config folder or any other configuration with the `KBN_PATH_CONF` environment variable. For example, if you are using a Debian-based system, the configuration file is located in /etc/kibana. -==== - -[discrete] -[[breaking-74424]] -.[Setup] Removed support for sysv init. (8.0) -[%collapsible] -==== -*Details* + -All supported operating systems use systemd service files. Any system that doesn’t have `service` aliased to use kibana.service should use `systemctl start kibana.service` instead of `service start kibana`. For more information, refer to {kibana-pull}74424[#74424]. - -*Impact* + -If your installation uses .deb or .rpm packages with SysV, migrate to systemd. -==== - -[discrete] -[[breaking-42353]] -.[Setup] Disabled response logging as a default. (8.0) -[%collapsible] -==== -*Details* + -In previous versions, all events are logged in `json` when `logging.json:true`. With the new logging configuration, you can choose the `json` and pattern output formats with layouts. For more information, refer to {kibana-pull}42353[#42353]. - -*Impact* + -To restore the previous behavior, configure the logging format for each custom appender with the `appender.layout property` in kibana.yml. There is no default for custom appenders, and each appender must be configured explicitly. -//// -[source,yaml] ----- -logging: - appenders: - custom_console: - type: console - layout: - type: pattern - custom_json: - type: console - layout: - type: json - loggers: - - name: plugins.myPlugin - appenders: [custom_console] - root: - appenders: [default, custom_json] - level: warn ----- -//// -==== - -// Sharing and reporting - -[discrete] -[[breaking-162288]] -.[Sharing & Reporting] The Download CSV endpoint has changed. (8.10) -[%collapsible] -==== -*Details* + -The API endpoint for downloading a CSV file from a saved search in the Dashboard application has changed to reflect the fact that this is an internal API. The previous API path of -`/api/reporting/v1/generate/immediate/csv_searchsource` has been changed to `/internal/reporting/generate/immediate/csv_searchsource`. For more information, refer to {kibana-pull}162288[#162288]. -==== - -[discrete] -[[breaking-158338]] -.[Sharing & Reporting] CSV reports now use PIT instead of Scroll. (8.6) -[%collapsible] -==== -*Details* + -CSV reports now use PIT instead of Scroll. Previously generated CSV reports that used an index alias with alias-only privileges, but without privileges on the alias referenced-indices will no longer generate. For more information, refer to {kibana-pull}158338[#158338]. - -*Impact* + -To generate CSV reports, grant `read` privileges to the underlying indices. -==== - -[discrete] -[[breaking-121435]] -.[Sharing & Reporting] Removed legacy CSV export type. (8.1) -[%collapsible] -==== -*Details* + -The `/api/reporting/generate/csv` endpoint has been removed. For more information, refer to {kibana-pull}121435[#121435]. - -*Impact* + -If you are using 7.13.0 and earlier, {kibana-ref-all}/8.1/automating-report-generation.html[regenerate the POST URLs] that you use to automatically generate CSV reports. -==== - -[discrete] -[[breaking-121369]] -.[Sharing & Reporting]Removed legacy PDF shim. (8.1) -[%collapsible] -==== -*Details* + -The POST URLs that you generated in {kib} 6.2.0 no longer work. For more information, refer to {kibana-pull}121369[#121369]. - -*Impact* + -{kibana-ref-all}/8.1/automating-report-generation.html[Regenerate the POST URLs] that you use to automatatically generate PDF reports. -==== - -[discrete] -[[breaking-114216]] -.[Sharing & Reporting] Removed reporting settings. (8.0) -[%collapsible] -==== -*Details* + -The following settings have been removed: - -* `xpack.reporting.capture.concurrency` - -* `xpack.reporting.capture.settleTime` - -* `xpack.reporting.capture.timeout` - -* `xpack.reporting.kibanaApp` - -For more information, refer to {kibana-pull}114216[#114216]. - -*Impact* + -Before you upgrade to 8.0.0, remove the settings from kibana.yml. -==== - -[discrete] -[[breaking-52539]] -.[Sharing & Reporting] Legacy job parameters are no longer supported. (8.0) -[%collapsible] -==== -*Details* + -*Reporting* is no longer compatible with POST URL snippets generated with {kib} 6.2.0 and earlier. For more information, refer to {kibana-pull}52539[#52539] - -*Impact* + -If you use POST URL snippets to automatically generate PDF reports, regenerate the POST URL strings. -==== - -// User management - -[discrete] -[[breaking-122722]] -.[User management] Removed the ability to use `elasticsearch.username: elastic` in production. (8.0) -[%collapsible] -==== -*Details* + -In production, you are no longer able to use the `elastic` superuser to authenticate to {es}. For more information, refer to {kibana-pull}122722[#122722]. - -*Impact* + -When you configure `elasticsearch.username: elastic`, {kib} fails. -==== - -// Visualizations and dashboards - -[discrete] -[[breaking-149482]] -.[Visualizations] Removed the fields list sampling setting from Lens. (8.7) -[%collapsible] -==== -*Details* + -`lens:useFieldExistenceSampling` has been removed from *Advanced Settings*. The setting allowed you to enable document sampling to determine the fields that are displayed in *Lens*. For more information, refer to {kibana-pull}149482[#149482]. - -*Impact* + -In 8.1.0 and later, {kib} uses the field caps API, by default, to determine the fields that are displayed in *Lens*. -==== - -[discrete] -[[breaking-146990]] -.[Visualizations] Removed legacy pie chart visualization setting. (8.7) -[%collapsible] -==== -*Details* + -`visualization:visualize:legacyPieChartsLibrary` has been removed from *Advanced Settings*. The setting allowed you to create aggregation-based pie chart visualizations using the legacy charts library. For more information, refer to {kibana-pull}146990[#146990]. - -*Impact* + -In 7.14.0 and later, the new aggregation-based pie chart visualization is available by default. For more information, check <>. -==== - -[discrete] -[[breaking-143081]] -.[Visualizations] Changed the `histogram:maxBars` default setting. (8.6) -[%collapsible] -==== -*Details* + -To configure higher resolution data histogram aggregations without changing the *Advanced Settings*, the default histogram:maxBars setting is now 1000 instead of 100. For more information, refer to {kibana-pull}143081[#143081]. - -*Impact* + -For each {kibana-ref}/xpack-spaces.html[space], complete the following to change *histogram:maxBars* to the previous default setting: - -. Open the main menu, then click *Stack Management > Advanced Settings*. -. Scroll or search for *histogram:maxBars*. -. Enter `100`, then click *Save changes*. -==== - -[discrete] -[[breaking-134336]] -.[Visualizations] Removed the legacy Timelion charts library. (8.4) -[%collapsible] -==== -*Details* + -The legacy implementation of the *Timelion* visualization charts library has been removed. All *Timelion* visualizations now use the elastic-charts library, which was introduced in 7.15.0. - -For more information, refer to {kibana-pull}134336[#134336]. - -*Impact* + -In 8.4.0 and later, you are unable to configure the *Timelion* legacy charts library advanced setting. For information about visualization Advanced Settings, check link:https://www.elastic.co/guide/en/kibana/8.4/advanced-options.html#kibana-visualization-settings[Visualization]. -==== - -[discrete] -[[breaking-129581]] -.[Visualizations] Removed Quandl and Graphite integrations. (8.3) -[%collapsible] -==== -*Details* + -The experimental `.quandl` and `.graphite` functions and advanced settings are removed from *Timelion*. For more information, check {kibana-pull}129581[#129581]. - -*Impact* + -When you use the `vis_type_timelion.graphiteUrls` kibana.yml setting, {kib} successfully starts, but logs a `[WARN ][config.deprecation] You no longer need to configure "vis_type_timelion.graphiteUrls".` warning. - -To leave your feedback about the removal of `.quandl` and `.graphite`, go to the link:https://discuss.elastic.co/c/elastic-stack/kibana/7[discuss forum]. -==== - -[discrete] -[[breaking-113516]] -.[Visualizations] Removed display options from legacy gauge visualizations. (8.0) -[%collapsible] -==== -*Details* + -The *Display warnings* option has been removed from the aggregation-based gauge visualization. For more information, refer to {kibana-pull}113516[#113516]. - -*Impact* + -When you create aggregation-based gauge visualizations, the *Display warnings* option is no longer available in *Options > Labels*. -==== - -[discrete] -[[breaking-112643]] -.[Visualizations] Removed settings from visEditors plugins. (8.0) -[%collapsible] -==== -*Details* + -The following deprecated visEditors plugin settings have been removed: - -* `metric_vis.enabled` -* `table_vis.enabled` -* `tagcloud.enabled` -* `metrics.enabled` -* `metrics.chartResolution` -* `chartResolution` -* `metrics.minimumBucketSize` -* `minimumBucketSize` -* `vega.enabled` -* `vega.enableExternalUrls` -* `vis_type_table.legacyVisEnabled` -* `timelion_vis.enabled` -* `timelion.enabled` -* `timelion.graphiteUrls` -* `timelion.ui.enabled` - -For more information, refer to {kibana-pull}112643[#112643]. - -*Impact* + -Before you upgrade, make the following changes in kibana.yml: - -* Replace `metric_vis.enabled` with `vis_type_metric.enabled` -* Replace `table_vis.enabled` with `vis_type_table.enabled` -* Replace `tagcloud.enabled` with `vis_type_tagcloud.enabled` -* Replace `metrics.enabled` with `vis_type_timeseries.enabled` -* Replace `metrics.chartResolution` and `chartResolution` with `vis_type_timeseries.chartResolution` -* Replace `metrics.minimumBucketSize` and `minimumBucketSize` with `vis_type_timeseries.minimumBucketSize` -* Replace `vega.enabled` with `vis_type_vega.enabled` -* Replace `vega.enableExternalUrls` with `vis_type_vega.enableExternalUrls` -* Remove `vis_type_table.legacyVisEnabled` -* Replace `timelion_vis.enabled` with `vis_type_timelion.enabled` -* Replace `timelion.enabled` with `vis_type_timelion.enabled` -* Replace `timelion.graphiteUrls` with `vis_type_timelion.graphiteUrls` -* Remove `timelion.ui.enabled` - -==== - -[discrete] -[[breaking-111704]] -.[Visualizations] Removed dimming opacity setting. (8.0) -[%collapsible] -==== -*Details* + -The *Dimming opacity* setting in *Advanced Settings* has been removed. For more information, refer to {kibana-pull}111704[#111704]. - -*Impact* + -When you upgrade to 8.0.0, you are no longer able to configure the dimming opactiy for visualizations. -==== - -[discrete] -[[breaking-110985]] -.[Visualizations] Removes Less stylesheet support in TSVB. (8.0) -[%collapsible] -==== -*Details* + -In *TSVB*, custom Less stylesheets have been removed. For more information, refer to {kibana-pull}110985[#110985]. - -*Impact* + -Existing less stylesheets are automatically converted to CSS stylesheets. -==== - -[discrete] -[[breaking-110571]] -.[Visualizations] Disabled the input string mode in TSVB. (8.0) -[%collapsible] -==== -*Details* + -In *TSVB*, the *Index pattern selection mode* option has been removed. For more information, refer to {kibana-pull}110571[#110571]. - -*Impact* + -To use index patterns and {es} indices in *TSVB* visualizations: - -. Open the main menu, then click *Stack Management > Advanced Settings*. - -. Select *Allow string indices in TSVB*. - -. Click *Save changes*. -==== - -[discrete] -[[breaking-116184]] -.[Visualizations] Removed proxyElasticMapsServiceInMaps Maps setting. (8.0) -[%collapsible] -==== -*Details* + -The `map.proxyElasticMapsServiceInMaps` setting has been removed. For more information, refer to {kibana-pull}116184[#116184]. - -*Impact* + -Install the on-prem version of the <>, which is a Docker service that resides in the Elastic Docker registry, in an accessible location on your internal network. When you complete the installation, update kibana.yml to point to the service. -==== - -[discrete] -[[breaking-109896]] -.[Visualizations] Removed `map.regionmap.*`. (8.0) -[%collapsible] -==== -*Details* + -The deprecated `map.regionmap.*` setting in kibana.yml has been removed. For more information, refer to {kibana-pull}109896[#109896]. - -*Impact* + -If you have maps that use `map.regionmap` layers: - -. Remove the `map.regionmap` layer. - -. To recreate the choropleth layer, use <> to index your static vector data into {es}. - -. Create a choropleth layer from the indexed vector data. -==== - - -[discrete] -[[breaking-108103]] -.[Visualizations] Removed dashboard-only mode. (8.0) -[%collapsible] -==== -*Details* + -The legacy dashboard-only mode has been removed. For more information, refer to {kibana-pull}108103[#108103]. - -*Impact* + -To grant users access to only dashboards, create a new role, then assign only the *Dashboard* feature privilege. For more information, refer to <>. -==== - -[discrete] -[[breaking-105979]] -.[Visualizations] Removed `xpack.maps.showMapVisualizationTypes` setting. (8.0) -[%collapsible] -==== -*Details* + -The deprecated `xpack.maps.showMapVisualizationTypes` setting in kibana.yml has been removed. For more information, refer to {kibana-pull}105979[#105979] - -*Impact* + -When you upgrade, remove `xpack.maps.showMapVisualizationTypes` from your kibana.yml file. -==== - -[float] -==== Elastic Observability solution - -[discrete] -[[kibana-132790]] -.[APM] Removed `apm_user`. (8.3) -[%collapsible] -==== -*Details* + -Removes the `apm_user` role. For more information, check {kibana-pull}132790[#132790]. - -*Impact* + -The `apm_user` role is replaced with the `viewer` and `editor` built-in roles. -==== - -[discrete] -[[breaking-172224]] -.[SLOs]New SLO architecture. (8.12) -[%collapsible] -==== -*Details* + -We introduced a breaking change in the SLO features that will break any SLOs created before 8.12. These SLOs have to be manually reset through an API until we provide a UI for it. The data aggregated over time (rollup) is still available in the SLI v2 index, but won't be used for summary calculation when reset. - -The previous summary transforms summarizing every SLOs won't be used anymore and can be stopped and deleted: - -* slo-summary-occurrences-7d-rolling -* slo-summary-occurrences-30d-rolling -* slo-summary-occurrences-90d-rolling -* slo-summary-occurrences-monthly-aligned -* slo-summary-occurrences-weekly-aligned -* slo-summary-timeslices-7d-rolling -* slo-summary-timeslices-30d-rolling -* slo-summary-timeslices-90d-rolling -* slo-summary-timeslices-monthly-aligned -* slo-summary-timeslices-weekly-aligned - -Be aware that when installing a new SLO (or after resetting an SLO), we install two transforms (one for the rollup data and one that summarize the rollup data). Do not delete the new `slo-summary-{slo_id}-{slo_revision}` transforms. For more information, refer to ({kibana-pull}172224[#172224]). -==== - -[discrete] -[[breaking-162665]] -.[SLO] Introduced new summary search capabilities that will cause SLOs created before 8.10 to stop working. (8.10) -[%collapsible] -==== -*Details* + - -* SLO find API body parameters have changed. -* The index mapping used by the rollup data has changed, and we have added a summary index that becomes the new source of truth for search. -* The rollup transforms have been updated, but existing SLO with their transforms won't be updated. - -If some SLOs have been installed in a prior version at 8.10, they won't work after migrating to 8.10. There are two approaches to handle this breaking change. The recommended route is to delete all SLOs before migrating to 8.10. The alternative is to migrate to 8.10 and manually remove the SLOs. - -*Removing SLOs before migrating to 8.10* - -Use the SLO UI or the SLO delete API to delete all existing SLOs. This takes care of the saved object, transform and rollup data. When all SLOs have been deleted, then delete the residual rollup indices: `.slo-observability.sli-v1*`. Note that this is v1. - -*Removing SLOs after migrating to 8.10* - -After migrating to 8.10, the previously created SLOs won’t appear in the UI because the API is using a new index. The previously created SLOs still exist, and associated transforms are still rolling up data into the previous index `.slo-observability.sli-v1*`. The SLO delete API can't be used now, so remove the resources resources manually: - -. Find all existing transforms -All SLO related transforms start with the `slo-` prefix, this request returns them all: -+ -[source, bash] ----- -GET _transform/slo-* ----- -+ -Make a note of all the transforms IDs for later. - -. Stop all transforms -+ -[source, bash] ----- -POST _transform/slo-*/_stop?force=true ----- - -. Remove all transforms -+ -From the list of transforms returned during the first step, now delete them one by one: -+ -[source, bash] ----- -DELETE _transform/{transform_id}?force=true ----- - -. Find the SLO saved objects -+ -This request lists all the SLO saved objects. The SLO IDs and the saved object IDs are not the same. -+ -[source, bash] ----- -GET kbn:/api/saved_objects/_find?type=slo ----- -+ -Make a note of all the saved object IDs from the response. - -. Remove the SLO saved objects -+ -For each saved object ID, run the following: -+ -[source, bash] ----- -DELETE kbn:/api/saved_objects/slo/{Saved_Object_Id} ----- - -. Delete the rollup indices v1 -+ -Note that this is v1. -+ -[source, bash] ----- -DELETE .slo-observability.sli-v1* ----- -==== - -[discrete] -[[breaking-159118]] -.[Uptime] Uptime app now hidden when no data is available. (8.9) -[%collapsible] -==== -*Details* + -The Uptime app now gets hidden from the interface when it doesn't have any data for more than a week. If you have a standalone Heartbeat pushing data to Elasticsearch, the Uptime app is considered active. You can disable this automatic behavior from the advanced settings in Kibana using the **Always show legacy Uptime app** option. -For synthetic monitoring, we now recommend to use the new Synthetics app. For more information, refer to {kibana-pull}159118[#159118] -==== - -[discrete] -[[breaking-159012]] -.[Uptime] Removed synthetics pattern from Uptime settings. (8.9) -[%collapsible] -==== -*Details* + -Data from browser monitors and monitors of all types created within the Synthetics App or via the Elastic Synthetics Fleet Integration will no longer appear in Uptime. For more information, refer to {kibana-pull}159012[#159012] -==== - - - -[float] -==== Elastic Search solution - -[discrete] -[[breaking-106307]] -.Required security plugin. (8.0) -[%collapsible] -==== -*Details* + -Enterprise Search now requires that you enable X-Pack Security. For more information, refer to {kibana-pull}106307[#106307] - -*Impact* + -Enable X-Pack Security. -==== - - -[float] -==== Elastic Security solution - -NOTE: For the complete Elastic Security solution release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - -[discrete] -[[breaking-161806]] -.[Elastic Defend] Converted filterQuery to KQL.(8.11) -[%collapsible] -==== -*Details* + -Converts `filterQuery` to a KQL query string. For more information, refer to ({kibana-pull}161806[#161806]). -==== - - - -[float] -=== Deprecation notices - -The following functionality is deprecated and will be removed at a future date. Deprecated functionality -does not have an immediate impact on your application, but we strongly recommend you make the necessary -updates to avoid use of deprecated features. +The following functionality is deprecated and will be removed at a future date. Deprecated functionality +does not have an immediate impact on your application, but we strongly recommend you make the necessary +updates to avoid use of deprecated features. Use the **Kibana Upgrade Assistant** to prepare for your upgrade to the next version of the Elastic Stack. The assistant identifies deprecated settings in your configuration and guides you through the process of @@ -1425,399 +110,6 @@ resolving issues if any deprecated features are enabled. To access the assistant, go to **Stack Management** > **Upgrade Assistant**. -[float] -==== Kibana APIs - -[discrete] -[[kibana-152236]] -.Deprecated Agent reassign API PUT endpoint. (8.8) -[%collapsible] -==== -*Details* + -The PUT endpoint for the agent reassign API is deprecated. For more information, refer to {kibana-pull}152236[#152236]. - -*Impact* + -Use the POST endpoint for the agent reassign API. -==== - -[discrete] -[[kibana-151564]] -.Deprecated `total` in `/agent_status` Fleet API. (8.8) -[%collapsible] -==== -*Details* + -The `total` field in `/agent_status` Fleet API responses is deprecated. For more information, refer to {kibana-pull}151564[#151564]. - -*Impact* + -The `/agent_status` Fleet API now returns the following statuses: - -* `all` — All active and inactive -* `active` — All active -==== - -[discrete] -[[kibana-150267]] -.Deprecated Saved objects APIs. (8.7) -[%collapsible] -==== -*Details* + -The following saved objects APIs have been deprecated. - -[source,text] --- -/api/saved_objects/{type}/{id} -/api/saved_objects/resolve/{type}/{id} -/api/saved_objects/{type}/{id?} -/api/saved_objects/{type}/{id} -/api/saved_objects/_find -/api/saved_objects/{type}/{id} -/api/saved_objects/_bulk_get -/api/saved_objects/_bulk_create -/api/saved_objects/_bulk_resolve -/api/saved_objects/_bulk_update -/api/saved_objects/_bulk_delete --- - -For more information, refer to ({kibana-pull}150267[#150267]). - -*Impact* + -Use dedicated public APIs instead, for example use <> to manage Data Views. -==== - -[discrete] -[[deprecation-119494]] -.Updates Fleet API to improve consistency. (8.0) -[%collapsible] -==== -*Details* + -The Fleet API has been updated to improve consistency: - -* Hyphens are changed to underscores in some names. -* The `pkgkey` path parameter in the packages endpoint is split. -* The `response` and `list` properties are renamed to `items` or `item` in some -responses. - -For more information, refer to {kibana-pull}119494[#119494]. - -*Impact* + -When you upgrade to 8.0.0, use the following API changes: - -* Use `enrollment_api_keys` instead of `enrollment-api-keys`. - -* Use `agent_status` instead of `agent-status`. - -* Use `service_tokens` instead of `service-tokens`. - -* Use `/epm/packages/{packageName}/{version}` instead of `/epm/packages/{pkgkey}`. - -* Use `items[]` instead of `response[]` in: -+ -[source,text] --- -/api/fleet/enrollment_api_keys -/api/fleet/agents -/epm/packages/ -/epm/categories -/epm/packages/_bulk -/epm/packages/limited -/epm/packages/{packageName}/{version} <1> --- -<1> Use `items[]` when the verb is `POST` or `DELETE`. Use `item` when the verb -is `GET` or `PUT`. - -For more information, refer to {fleet-guide}/fleet-api-docs.html[Fleet APIs]. - -==== - -[float] -==== Kibana platform - -// Alerting - -[discrete] -[[kibana-161136]] -.[Alerting] Action variables in the UI and in tests that were no longer used have been replaced. (8.10) -[%collapsible] -==== -*Details* + -The following rule action variables have been deprecated. Use the recommended variables (in parentheses) instead: - -* alertActionGroup (alert.actionGroup) -* alertActionGroupName (alert.actionGroupName) -* alertActionSubgroup (alert.actionSubgroup) -* alertId (rule.id) -* alertInstanceId (alert.id) -* alertName (rule.name) -* params (rule.params) -* spaceId (rule.spaceId) -* tags (rule.tags) - -For more information, refer to ({kibana-pull}161136[#161136]). -==== - -// Discover - -[discrete] -[[deprecation-search-sessions]] -.[Discover] <> are deprecated in 8.15.0 and will be removed in a future version. (8.15) -[%collapsible] -==== -*Details* + -Search sessions are now deprecated and will be removed in a future version. By default, queries that take longer than 10 minutes (the default for the advanced setting `search:timeout`) will be canceled. To allow queries to run longer, consider increasing `search:timeout` or setting it to `0` which will allow queries to continue running as long as a user is waiting on-screen for results. -==== - - -// General settings - -[discrete] -[[kibana-154275]] -.[General settings] Deprecated ephemeral Task Manager settings (8.8) -[%collapsible] -==== -*Details* + -The following Task Manager settings are deprecated: - -* `xpack.task_manager.ephemeral_tasks.enabled` -* `xpack.task_manager.ephemeral_tasks.request_capacity` -* `xpack.alerting.maxEphemeralActionsPerAlert` - -For more information, refer to {kibana-pull}154275[#154275]. - -*Impact* + -To improve task execution resiliency, remove the deprecated settings from the `kibana.yml` file. For detailed information, check link:https://www.elastic.co/guide/en/kibana/current/task-manager-settings-kb.html[Task Manager settings in {kib}]. -==== - -[discrete] -[[kibana-122075]] -.[General settings] Deprecated `xpack.data_enhanced.*` setting. (8.3) -[%collapsible] -==== -*Details* + -In kibana.yml, the `xpack.data_enhanced.*` setting is deprecated. For more information, check {kibana-pull}122075[#122075]. - -*Impact* + -Use the `data.*` configuration parameters instead. -==== - -[discrete] -[[deprecation-33603]] -.[General settings] Removed `xpack:defaultAdminEmail` setting. (8.0) -[%collapsible] -==== -*Details* + -The `xpack:default_admin_email` setting for monitoring use has been removed. For more information, refer to {kibana-pull}33603[#33603] - -*Impact* + -Use the `xpack.monitoring.clusterAlertsEmail` in kibana.yml. -==== - -// Security - -[discrete] -[[kibana-136422]] -.[Security] Deprecated ApiKey authentication for interactive users. (8.4) -[%collapsible] -==== -*Details* + -The ability to authenticate interactive users with ApiKey via a web browser has been deprecated, and will be removed in a future version. - -For more information, refer to {kibana-pull}136422[#136422]. - -*Impact* + -To authenticate interactive users via a web browser, use <>. Use API keys only for programmatic access to {kib} and {es}. -==== - -[discrete] -[[kibana-131636]] -.[Security] Deprecated anonymous authentication credentials. (8.3) -[%collapsible] -==== -*Details* + -The apiKey, including key and ID/key pair, and `elasticsearch_anonymous_user` credential types for anonymous authentication providers are deprecated. For more information, check {kibana-pull}131636[#131636]. - -*Impact* + -If you have anonymous authentication provider configured with apiKey or `elasticsearch_anonymous_user` credential types, a deprecation warning appears, even when the provider is not enabled. -==== - -[discrete] -[[kibana-131166]] -.[Security] Deprecated v1 and v2 security_linux and security_windows jobs. (8.3) -[%collapsible] -==== -*Details* + -The v1 and v2 job configurations for security_linux and security_windows are deprecated. For more information, check {kibana-pull}131166[#131166]. - -*Impact* + -The following security_linux and security_windows job configurations are updated to v3: - -* security_linux: - -** v3_linux_anomalous_network_activity -** v3_linux_anomalous_network_port_activity_ecs -** v3_linux_anomalous_process_all_hosts_ecs -** v3_linux_anomalous_user_name_ecs -** v3_linux_network_configuration_discovery -** v3_linux_network_connection_discovery -** v3_linux_rare_metadata_process -** v3_linux_rare_metadata_user -** v3_linux_rare_sudo_user -** v3_linux_rare_user_compiler -** v3_linux_system_information_discovery -** v3_linux_system_process_discovery -** v3_linux_system_user_discovery -** v3_rare_process_by_host_linux_ecs - -* security_windows: - -** v3_rare_process_by_host_windows_ecs -** v3_windows_anomalous_network_activity_ecs -** v3_windows_anomalous_path_activity_ecs -** v3_windows_anomalous_process_all_hosts_ecs -** v3_windows_anomalous_process_creation -** v3_windows_anomalous_script -** v3_windows_anomalous_service -** v3_windows_anomalous_user_name_ecs -** v3_windows_rare_metadata_process -** v3_windows_rare_metadata_user -** v3_windows_rare_user_runas_event -** v3_windows_rare_user_type10_remote_login -==== - - -// Sharing & Reporting - -[discrete] -[[kibana-178159]] -.[Sharing & Reporting] Downloading a CSV file from a saved search panel in a dashboard has become deprecated in favor of generating a CSV report. (8.14) -[%collapsible] -==== -*Details* + -The mechanism of exporting CSV data from a saved search panel in a dashboard has been changed to generate a CSV report, rather than allowing the CSV data to be downloaded -without creating a report. To preserve the original behavior, it is necessary to update `kibana.yml` with the setting of `xpack.reporting.csv.enablePanelActionDownload: -true`. The scope of this breaking change is limited to downloading CSV files from saved search panels only; downloading CSV files from other types of dashboard panels is -unchanged. For more information, refer to {kibana-pull}178159[#178159]. -==== - - - -// Visualizations - -[discrete] -[[kibana-156455]] -.[Visualizations] The ability to create legacy input controls was hidden. (8.9) -[%collapsible] -==== -*Details* + -The option to create legacy input controls when creating a new visualization is hidden. For more information, refer to {kibana-pull}156455[#156455] -==== - -[discrete] -[[kibana-155503]] -.[Visualizations] Removed legacy field stats. (8.9) -[%collapsible] -==== -*Details* + -Legacy felid stats that were previously shown within a popover have been removed. For more information, refer to {kibana-pull}155503[#155503] -==== - -[discrete] -.[Visualizations] Deprecated input control panels in dashboards. (8.3) -[%collapsible] -==== -*Details* + -The input control panels, which allow you to add interactive filters to dashboards, are deprecated. For more information, check {kibana-pull}132562[#132562]. - -*Impact* + -To add interactive filters to your dashboards, use the link:https://www.elastic.co/guide/en/kibana/8.3/add-controls.html[new controls]. -==== - -[discrete] -[[kibana-130336]] -.[Visualizations] Deprecated the `Auto` default legend size in Lens. (8.3) -[%collapsible] -==== -*Details* + -In the *Lens* visualization editor, the *Auto* default for *Legend width* has been deprecated. For more information, check {kibana-pull}130336[#130336]. - -*Impact* + -When you create *Lens* visualization, the default for the *Legend width* is now *Medium*. -==== - -[float] -==== Elastic Observability solution - -[discrete] -[[deprecation-192003]] -.Deprecated the Observability AI Assistant specific advanced setting `observability:aiAssistantLogsIndexPattern`. (8.16) -[%collapsible] -==== -*Details* + -The Observability AI Assistant specific advanced setting for Logs index patterns `observability:aiAssistantLogsIndexPattern` is deprecated and no longer used. The AI Assistant will now use the existing **Log sources** setting `observability:logSources` instead. For more information, refer to ({kibana-pull}192003[#192003]). -==== - -[discrete] -[[deprecation-194519]] -.The Logs Stream was hidden by default in favor of the Logs Explorer app. (8.16) -[%collapsible] -==== -*Details* + -You can find the Logs Explorer app in the navigation menu under Logs > Explorer, or as a separate tab in Discover. For more information, refer to ({kibana-pull}194519[#194519]). - -*Impact* + -You can still show the Logs Stream app again by navigating to Stack Management > Advanced Settings and by enabling the `observability:enableLogsStream` setting. -==== - - -[discrete] -[[deprecation-120689]] -.[APM] Renamed the `autocreate` data view APM setting. (8.0) -[%collapsible] -==== -*Details* + -The `xpack.apm.autocreateApmIndexPattern` APM setting has been removed. For more information, refer to {kibana-pull}120689[#120689]. - -*Impact* + -To automatically create data views in APM, use `xpack.apm.autoCreateApmDataView`. -==== - -[discrete] -[[kibana-uptime-deprecation]] -.[Uptime] Uptime is deprecated in 8.15.0. (8.15) -[%collapsible] -==== -*Details* + -The Uptime app is already hidden from Kibana when there is no recent Heartbeat data. Migrate to Synthetics as an alternative. For more details, refer to the {observability-guide}/uptime-intro.html[Uptime documentation]. -==== - -[discrete] -[[kibana-154010]] -.[Uptime] Deprecated Synthetics and Uptime monitor schedules (8.8) -[%collapsible] -==== -*Details* + -Synthetics and Uptime monitor schedules and zip URL fields are deprecated. For more information, refer to {kibana-pull}154010[#154010] and {kibana-pull}154952[#154952]. - -*Impact* + -When you create monitors in Uptime Monitor Management and the Synthetics app, unsupported schedules are automatically transferred to the nearest supported schedule. To use zip URLs, use project monitors. -==== - -[discrete] -[[kibana-149506]] -.[Uptime] Deprecated Elastic Synthetics integration. (8.8) -[%collapsible] -==== -*Details* + -The Elastic Synthetics integration is deprecated. For more information, refer to {kibana-pull}149506[#149506]. - -*Impact* + -To monitor endpoints, pages, and user journeys, go to **{observability}** -> **Synthetics (beta)**. -==== - -[float] -==== Elastic Security solution - -NOTE: For the complete Elastic Security solution release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. - diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 21803b90034a..e0ae6c0e6a81 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -5,6 +5,8 @@ [partintro] -- +*Note:* Canvas is only available for upgraded installations with existing workpads. + *Canvas* is a data visualization and presentation tool that allows you to pull live data from {es}, then combine the data with colors, images, text, and your imagination to create dynamic, multi-page, pixel-perfect displays. If you are a little bit creative, a little bit technical, and a whole lot curious, then *Canvas* is for you. diff --git a/docs/user/reporting/automating-report-generation.asciidoc b/docs/user/reporting/automating-report-generation.asciidoc index 9587674b59e6..b4334b7c7ea8 100644 --- a/docs/user/reporting/automating-report-generation.asciidoc +++ b/docs/user/reporting/automating-report-generation.asciidoc @@ -16,11 +16,9 @@ To create the POST URL for PDF reports: . Open the dashboard, visualization, or **Canvas** workpad you want to view as a report. -. From the toolbar, click *Share > PDF Reports*, then choose an option: +* If you are using *Dashboard* or *Visualize Library*, from the toolbar, click *Share > Export*, select the PDF option then click *Copy POST URL*. -* If you are using *Dashboard* or *Visulize Library*, click *Copy POST URL*. - -* If you are using *Canvas*, click *Advanced options > Copy POST URL*. +* If you are using *Canvas*, from the toolbar, click *Share > PDF Reports*, then click *Advanced options > Copy POST URL*. To create the POST URL for CSV reports: @@ -28,7 +26,7 @@ To create the POST URL for CSV reports: . Open the saved search you want to share. -. In the toolbar, click *Share > CSV Reports > Copy POST URL*. +. In the toolbar, click *Share > Export > Copy POST URL*. [float] [[use-watcher]] diff --git a/docs/user/whats-new.asciidoc b/docs/user/whats-new.asciidoc index 25568518ad2e..32e7bbbc359b 100644 --- a/docs/user/whats-new.asciidoc +++ b/docs/user/whats-new.asciidoc @@ -1,144 +1,10 @@ [[whats-new]] -== What's new in 8.16 +== What's new in 9.0 -Here are the highlights of what's new and improved in 8.16. +Here are the highlights of what's new and improved in 9.0. For detailed information about this release, check the <>. -Previous versions: {kibana-ref-all}/8.15/whats-new.html[8.15] | {kibana-ref-all}/8.14/whats-new.html[8.14] | {kibana-ref-all}/8.13/whats-new.html[8.13] | {kibana-ref-all}/8.12/whats-new.html[8.12] | {kibana-ref-all}/8.11/whats-new.html[8.11] | {kibana-ref-all}/8.10/whats-new.html[8.10] | {kibana-ref-all}/8.9/whats-new.html[8.9] | {kibana-ref-all}/8.8/whats-new.html[8.8] | {kibana-ref-all}/8.7/whats-new.html[8.7] | {kibana-ref-all}/8.6/whats-new.html[8.6] | {kibana-ref-all}/8.5/whats-new.html[8.5] | {kibana-ref-all}/8.4/whats-new.html[8.4] | {kibana-ref-all}/8.3/whats-new.html[8.3] | {kibana-ref-all}/8.2/whats-new.html[8.2] | {kibana-ref-all}/8.1/whats-new.html[8.1] | {kibana-ref-all}/8.0/whats-new.html[8.0] - -[discrete] -=== Solution-oriented navigation -On Elastic Cloud Hosted deployments running on version 8.16, you can now navigate Kibana using a lighter, solution-oriented left navigation menu, called **Solution view**. - -There are four selectable solution views: Search, Observability, Security, and Classic. Search, Observability, and Security are the new navigation menus. Each of those brings simplicity by focusing the left navigation menu on a relevant subset of features, scoped to its associated use cases, and offers a dedicated home page. Classic has the same navigation menu as 8.15 and before. - -Each space has its own solution view setting which determines the navigation experience for all users of that space. - -When creating a new deployment, you will now be asked to choose between one of the 3 new solution views for your default space. If you prefer to stick with the classic, multi-layered navigation, you can do so once the deployment is created by navigating to your space settings. - -Deployments upgrading from a previous version to 8.16 keep the classic navigation. Admins can enable one of the new solution views from the space settings. - -image::images/solution-view-obs.png[Example of observability solution view] -_The Observability solution view and its Home page._ - -[discrete] -=== Discover and ES|QL - -[discrete] -==== Contextual Data presentation - -In this release, Discover introduces enhanced contextual data presentation. Previously, you needed to manually select relevant fields and set up your workspace before diving into data exploration. Now, Discover automatically tailors the user experience based on the data being explored, powered by a scalable contextual architecture. For example, when analyzing logs, you'll see a *log.level* field rendered directly in the table, a custom Logs overview in the document viewer, and log.level indicators on individual rows. - -image::images/discover-log-level.png[Log level badge displaying in the Discover grid] - -[discrete] -==== Recommended ES|QL queries - -Writing ES|QL queries just got easier. Many users face challenges when authoring queries, and even more so when unfamiliar with the syntax or data structure. This can lead to inefficiencies in data analysis and visualization. We want to reduce the time it takes to create queries and to lower the learning curve for both new and existing users by suggesting recommended queries within the ES|QL Help menu and from the auto-complete. - -image::images/esql-suggestions.png[A list of suggestions to get started with an ES|QL query, width=30%] -_Recommended ES|QL queries from the ES|QL help menu_ - -image::images/esql-autocomplete-suggestions.png[A list of suggestions in the autocomplete menu of an ES|QL query, width=50%] -_Recommended ES|QL queries from auto-complete suggestions_ - - - -[discrete] -=== Dashboards - -[discrete] -==== Manage dashboards more easily and efficiently -As part of a series of improvements to help you find and manage your dashboards https://www.elastic.co/guide/en/kibana/8.15/whats-new.html#_view_dashboard_creator_and_last_editor[started in version 8.15], the new default way to sort your dashboards is by recently viewed, and we are adding an option to star your favorite dashboards, as well as some statistics to monitor the usage of your dashboards. - -You can find your favorite dashboards in the new **Starred** tab. - -image::images/dashboard-star.png[Viewing starred dashboards] - -By opening a dashboard's details using the “info” icon from the dashboard list view, you can now get a sense of the popularity of that dashboard with a histogram showing how many times the dashboard was viewed in the last 90 days. - -image::images/dashboard-usage.png[Dashboard usage chart] - -[discrete] -==== Log Pattern Analysis dashboard panels -Log Pattern Analysis panels are now available for you to add to your dashboards, making AIOps even more embedded in your workflows and where you need it. When filtering patterns, the dashboard’s data adjusts accordingly. You can also choose the filtering to transition you into Discover for further exploration. - -image:https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt8288e01386b5830c/67222fb0d2da223e27bc1e67/log_analysis_panel.gif[Log pattern analysis panel in dashboards] - - -[discrete] -==== Color text values in tables -Previously, you could only decide to color numeric values in tables. We're adding the ability to also color your string values. You can decide whether you want to color the whole cell, or only the text. - -image::images/table-coloring.png[Coloring table cells with string values] - - -[discrete] -==== Formatting options for your metrics -We've received a lot of feedback asking for more flexibility to customize the appearance of your metrics. In this version, we are adding the ability to customize the title and value alignment, as well as the font size. Selecting the *Fit* option will adjust the font size and make the metric value occupy the entire panel. - -image::images/metric-customization.png[Customization options for a metric panel] - - - -//[discrete] -//=== Alerting, cases, and connectors - - -[discrete] -=== Managing {kib} and data - -[discrete] -==== Edit space access from the space settings -As an admin, you can now assign roles to and edit role permissions on a given space directly from the settings of that space. - -Prior to 8.16, you could only do this from the role settings, which was counterintuitive. - -image::space-settings.png[Editing space settings with new options] - -[discrete] -==== New IP Location processor -Enhancing location information based on IP addresses just got easier with the new IP Location processor. In addition to the existing free GeoLite offerings from MaxMind, we have integrated with MaxMind’s premium GeoIP databases for users who have licensed MaxMind’s products. If you're an Enterprise Elastic customer, you now have an additional third-party product, IP Info, available for use as well. These additional data sources provide improved options for enriching data with location information associated with IP addresses to improve telemetry and insights. To utilize these features beyond the free MaxMind GeoIP database, you will need to have licensed premium MaxMind products and/or the IP Info database. - -image::images/ip-location-processor.png[The IP Location processor] - -[discrete] -==== File uploader PDF support -The file uploader provides a quick way to upload data and start using Elastic. In 8.16, we are improving it to allow you to upload data from PDF files. - -image:https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blte8f0b295330b7e68/67222fb0ca492a5044b51bd8/file_uploader_pdf.gif[File uploader with PDF support] - -[discrete] -=== Developer Tools Console redesign -We're excited to introduce a number of improvements to the overall user experience on one of our most popular features: **Console**. If you're new to Console, you will be welcomed by an onboarding tour that will help you get started quickly with your first requests. And if you're already a regular Console user, you will notice a variety of new features, including the ability to copy outputs to the clipboard, import and export request files, enjoy improved responsiveness, and other quality of life improvements. - -image::images/monaco-console.png[Console's redesign featuring the Monaco editor] - -[discrete] -=== Machine Learning - -[discrete] -==== The Inference API is now Generally Available - -Starting in 8.16, the {ref}/inference-apis.html[Inference API] is now GA, offering production-level stability, robustness and performance. Elastic’s Inference API integrates the state-of-the-art in AI inference, including ELSER, your Elastic hosted models and {ref}/put-inference-api.html#put-inference-api-desc[an increasing array of external models and tasks] in a unified, lean syntax. Used with {ref}/semantic-text.html[semantic_text] or the vector fields supported by the Elastic vector database, you can perform AI search, reranking, and completion with simplicity. In 8.16, we're also adding streamed completions for improved flows and real time interactions and GenAI experiences. - -[discrete] -==== ELSER and trained models adaptive resources and chunking strategies - -From 8.16, ELSER and the other AI search and NLP models you use in Elastic automatically adapt resource consumption according to the inference load, providing the performance you need during peak times and reducing the cost during slow periods, all the way down to zero cost during idle times. - -We're also improving the UX through which you deploy your models. You can provision search-optimized and ingest-optimized model deployments with a one-click selection. An optimized configuration is created without the need to specify parameters such as threads and allocations. Combined with the flexibility of ML auto-scaling on Elastic Cloud and the incredible elasticity of Elastic Cloud Serverless, you are in full control of both performance and cost. - -image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt429790e1de1b4f93/67222fb048ec8c73255ef4eb/trained_models.gif[Trained models and ELSER] - -In addition, from 8.16 you can choose between a word or sequence-based chunking strategy to use with your trained models, and you can also customize the maximum size and overlap parameters. A suitable chunking strategy can result in gains depending on the model you use, the length and nature of the texts and the length and complexity of the search queries. - - -[discrete] -==== Support for Daylight Saving Time changes in Anomaly Detection - -In 8.16, we are introducing support for DST changes in Anomaly Detection. Set up a DST calendar by selecting the right timezone and apply it to your anomaly detection jobs individually or in groups. This feature eliminates any false positives that you may have experienced previously due to Daylight Saving Time changes, and works without the need for your intervention for many years ahead. - -image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt5fb82f18cde26710/67222fb086339971144a31e5/daylight_savings.gif[DST support in Anomaly Detection] +//Uncomment and edit from 9.1: +//Previous versions: {kibana-ref-all}/9.0/whats-new.html[9.0] | {kibana-ref-all}/8.18/whats-new.html[8.18] diff --git a/examples/feature_flags_example/public/application.tsx b/examples/feature_flags_example/public/application.tsx index eab558d9301b..ecc8fc22faf1 100644 --- a/examples/feature_flags_example/public/application.tsx +++ b/examples/feature_flags_example/public/application.tsx @@ -10,22 +10,19 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { AppMountParameters, CoreStart } from '@kbn/core/public'; -import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { FeatureFlagsExampleApp } from './components/app'; export const renderApp = (coreStart: CoreStart, { element }: AppMountParameters) => { const { notifications, http, featureFlags } = coreStart; ReactDOM.render( - - - - - , + + + , element ); diff --git a/examples/feature_flags_example/public/components/app.tsx b/examples/feature_flags_example/public/components/app.tsx index 97f850b5dd47..87579b0c93e3 100644 --- a/examples/feature_flags_example/public/components/app.tsx +++ b/examples/feature_flags_example/public/components/app.tsx @@ -59,8 +59,7 @@ export const FeatureFlagsExampleApp = ({ featureFlags }: FeatureFlagsExampleAppD

Rendered together

`useObservable` causes a full re-render of the component, updating the{' '} - statically - evaluated flags as well. + statically evaluated flags as well.

diff --git a/examples/feature_flags_example/tsconfig.json b/examples/feature_flags_example/tsconfig.json index 77eb5d09ca85..7f7a1e300b10 100644 --- a/examples/feature_flags_example/tsconfig.json +++ b/examples/feature_flags_example/tsconfig.json @@ -14,12 +14,11 @@ "exclude": ["target/**/*"], "kbn_references": [ "@kbn/core", - "@kbn/shared-ux-page-kibana-template", - "@kbn/react-kibana-context-root", "@kbn/core-feature-flags-server", "@kbn/core-plugins-server", "@kbn/config-schema", "@kbn/developer-examples-plugin", "@kbn/core-feature-flags-browser", + "@kbn/react-kibana-context-render", ] } diff --git a/examples/grid_example/public/app.tsx b/examples/grid_example/public/app.tsx index 0e73a76d790f..f144daa29f1a 100644 --- a/examples/grid_example/public/app.tsx +++ b/examples/grid_example/public/app.tsx @@ -7,9 +7,10 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { cloneDeep } from 'lodash'; -import React, { useRef, useState } from 'react'; +import deepEqual from 'fast-deep-equal'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; +import { combineLatest, debounceTime } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; import { @@ -25,29 +26,77 @@ import { } from '@elastic/eui'; import { AppMountParameters } from '@kbn/core-application-browser'; import { CoreStart } from '@kbn/core-lifecycle-browser'; -import { GridLayout, GridLayoutData, isLayoutEqual, type GridLayoutApi } from '@kbn/grid-layout'; +import { GridLayout, GridLayoutData } from '@kbn/grid-layout'; import { i18n } from '@kbn/i18n'; import { getPanelId } from './get_panel_id'; import { - clearSerializedGridLayout, - getSerializedGridLayout, + clearSerializedDashboardState, + getSerializedDashboardState, setSerializedGridLayout, } from './serialized_grid_layout'; +import { MockSerializedDashboardState } from './types'; +import { useMockDashboardApi } from './use_mock_dashboard_api'; +import { dashboardInputToGridLayout, gridLayoutToDashboardPanelMap } from './utils'; const DASHBOARD_MARGIN_SIZE = 8; const DASHBOARD_GRID_HEIGHT = 20; const DASHBOARD_GRID_COLUMN_COUNT = 48; -const DEFAULT_PANEL_HEIGHT = 15; -const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2; export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => { + const savedState = useRef(getSerializedDashboardState()); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const [currentLayout, setCurrentLayout] = useState( + dashboardInputToGridLayout(savedState.current) + ); + + const mockDashboardApi = useMockDashboardApi({ savedState: savedState.current }); - const [layoutKey, setLayoutKey] = useState(uuidv4()); - const [gridLayoutApi, setGridLayoutApi] = useState(); - const savedLayout = useRef(getSerializedGridLayout()); - const currentLayout = useRef(savedLayout.current); + useEffect(() => { + combineLatest([mockDashboardApi.panels$, mockDashboardApi.rows$]) + .pipe(debounceTime(0)) // debounce to avoid subscribe being called twice when both panels$ and rows$ publish + .subscribe(([panels, rows]) => { + const hasChanges = !( + deepEqual(panels, savedState.current.panels) && deepEqual(rows, savedState.current.rows) + ); + setHasUnsavedChanges(hasChanges); + setCurrentLayout(dashboardInputToGridLayout({ panels, rows })); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderBasicPanel = useCallback( + (id: string) => { + return ( + <> +
{id}
+ { + mockDashboardApi.removePanel(id); + }} + > + {i18n.translate('examples.gridExample.deletePanelButton', { + defaultMessage: 'Delete panel', + })} + + { + const newPanelId = await getPanelId({ + coreStart, + suggestion: id, + }); + if (newPanelId) mockDashboardApi.replacePanel(id, newPanelId); + }} + > + {i18n.translate('examples.gridExample.replacePanelButton', { + defaultMessage: 'Replace panel', + })} + + + ); + }, + [coreStart, mockDashboardApi] + ); return ( @@ -69,7 +118,7 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => { color="accent" size="s" onClick={() => { - clearSerializedGridLayout(); + clearSerializedDashboardState(); window.location.reload(); }} > @@ -85,13 +134,9 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => { onClick={async () => { const panelId = await getPanelId({ coreStart, - suggestion: `panel${(gridLayoutApi?.getPanelCount() ?? 0) + 1}`, + suggestion: uuidv4(), }); - if (panelId) - gridLayoutApi?.addPanel(panelId, { - width: DEFAULT_PANEL_WIDTH, - height: DEFAULT_PANEL_HEIGHT, - }); + if (panelId) mockDashboardApi.addNewPanel({ id: panelId }); }} > {i18n.translate('examples.gridExample.addPanelButton', { @@ -113,9 +158,9 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => { { - currentLayout.current = cloneDeep(savedLayout.current); - setHasUnsavedChanges(false); - setLayoutKey(uuidv4()); // force remount of grid + const { panels, rows } = savedState.current; + mockDashboardApi.panels$.next(panels); + mockDashboardApi.rows$.next(rows); }} > {i18n.translate('examples.gridExample.resetLayoutButton', { @@ -126,12 +171,13 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => { { - if (gridLayoutApi) { - const layoutToSave = gridLayoutApi.serializeState(); - setSerializedGridLayout(layoutToSave); - savedLayout.current = layoutToSave; - setHasUnsavedChanges(false); - } + const newSavedState = { + panels: mockDashboardApi.panels$.getValue(), + rows: mockDashboardApi.rows$.getValue(), + }; + savedState.current = newSavedState; + setHasUnsavedChanges(false); + setSerializedGridLayout(newSavedState); }} > {i18n.translate('examples.gridExample.saveLayoutButton', { @@ -144,50 +190,17 @@ export const GridExample = ({ coreStart }: { coreStart: CoreStart }) => { { - currentLayout.current = cloneDeep(newLayout); - setHasUnsavedChanges(!isLayoutEqual(savedLayout.current, newLayout)); + layout={currentLayout} + gridSettings={{ + gutterSize: DASHBOARD_MARGIN_SIZE, + rowHeight: DASHBOARD_GRID_HEIGHT, + columnCount: DASHBOARD_GRID_COLUMN_COUNT, }} - ref={setGridLayoutApi} - renderPanelContents={(id) => { - return ( - <> -
{id}
- { - gridLayoutApi?.removePanel(id); - }} - > - {i18n.translate('examples.gridExample.deletePanelButton', { - defaultMessage: 'Delete panel', - })} - - { - const newPanelId = await getPanelId({ - coreStart, - suggestion: `panel${(gridLayoutApi?.getPanelCount() ?? 0) + 1}`, - }); - if (newPanelId) gridLayoutApi?.replacePanel(id, newPanelId); - }} - > - {i18n.translate('examples.gridExample.replacePanelButton', { - defaultMessage: 'Replace panel', - })} - - - ); - }} - getCreationOptions={() => { - return { - gridSettings: { - gutterSize: DASHBOARD_MARGIN_SIZE, - rowHeight: DASHBOARD_GRID_HEIGHT, - columnCount: DASHBOARD_GRID_COLUMN_COUNT, - }, - initialLayout: cloneDeep(currentLayout.current), - }; + renderPanelContents={renderBasicPanel} + onLayoutChange={(newLayout) => { + const { panels, rows } = gridLayoutToDashboardPanelMap(newLayout); + mockDashboardApi.panels$.next(panels); + mockDashboardApi.rows$.next(rows); }} /> diff --git a/examples/grid_example/public/serialized_grid_layout.ts b/examples/grid_example/public/serialized_grid_layout.ts index 2bb20052398f..3e40380d91ac 100644 --- a/examples/grid_example/public/serialized_grid_layout.ts +++ b/examples/grid_example/public/serialized_grid_layout.ts @@ -7,46 +7,39 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { type GridLayoutData } from '@kbn/grid-layout'; +import { MockSerializedDashboardState } from './types'; const STATE_SESSION_STORAGE_KEY = 'kibana.examples.gridExample.state'; -export function clearSerializedGridLayout() { +export function clearSerializedDashboardState() { sessionStorage.removeItem(STATE_SESSION_STORAGE_KEY); } -export function getSerializedGridLayout(): GridLayoutData { +export function getSerializedDashboardState(): MockSerializedDashboardState { const serializedStateJSON = sessionStorage.getItem(STATE_SESSION_STORAGE_KEY); - return serializedStateJSON ? JSON.parse(serializedStateJSON) : initialGridLayout; + return serializedStateJSON ? JSON.parse(serializedStateJSON) : initialState; } -export function setSerializedGridLayout(layout: GridLayoutData) { - sessionStorage.setItem(STATE_SESSION_STORAGE_KEY, JSON.stringify(layout)); +export function setSerializedGridLayout(state: MockSerializedDashboardState) { + sessionStorage.setItem(STATE_SESSION_STORAGE_KEY, JSON.stringify(state)); } -const initialGridLayout: GridLayoutData = [ - { - title: 'Large section', - isCollapsed: false, - panels: { - panel1: { column: 0, row: 0, width: 12, height: 6, id: 'panel1' }, - panel2: { column: 0, row: 6, width: 8, height: 4, id: 'panel2' }, - panel3: { column: 8, row: 6, width: 12, height: 4, id: 'panel3' }, - panel4: { column: 0, row: 10, width: 48, height: 4, id: 'panel4' }, - panel5: { column: 12, row: 0, width: 36, height: 6, id: 'panel5' }, - panel6: { column: 24, row: 6, width: 24, height: 4, id: 'panel6' }, - panel7: { column: 20, row: 6, width: 4, height: 2, id: 'panel7' }, - panel8: { column: 20, row: 8, width: 4, height: 2, id: 'panel8' }, - }, +const initialState: MockSerializedDashboardState = { + panels: { + panel1: { id: 'panel1', gridData: { i: 'panel1', x: 0, y: 0, w: 12, h: 6, row: 0 } }, + panel2: { id: 'panel2', gridData: { i: 'panel2', x: 0, y: 6, w: 8, h: 4, row: 0 } }, + panel3: { id: 'panel3', gridData: { i: 'panel3', x: 8, y: 6, w: 12, h: 4, row: 0 } }, + panel4: { id: 'panel4', gridData: { i: 'panel4', x: 0, y: 10, w: 48, h: 4, row: 0 } }, + panel5: { id: 'panel5', gridData: { i: 'panel5', x: 12, y: 0, w: 36, h: 6, row: 0 } }, + panel6: { id: 'panel6', gridData: { i: 'panel6', x: 24, y: 6, w: 24, h: 4, row: 0 } }, + panel7: { id: 'panel7', gridData: { i: 'panel7', x: 20, y: 6, w: 4, h: 2, row: 0 } }, + panel8: { id: 'panel8', gridData: { i: 'panel8', x: 20, y: 8, w: 4, h: 2, row: 0 } }, + panel9: { id: 'panel9', gridData: { i: 'panel9', x: 0, y: 0, w: 12, h: 16, row: 1 } }, + panel10: { id: 'panel10', gridData: { i: 'panel10', x: 24, y: 0, w: 12, h: 6, row: 2 } }, }, - { - title: 'Small section', - isCollapsed: false, - panels: { panel9: { column: 0, row: 0, width: 12, height: 16, id: 'panel9' } }, - }, - { - title: 'Another small section', - isCollapsed: false, - panels: { panel10: { column: 24, row: 0, width: 12, height: 6, id: 'panel10' } }, - }, -]; + rows: [ + { title: 'Large section', collapsed: false }, + { title: 'Small section', collapsed: false }, + { title: 'Another small section', collapsed: false }, + ], +}; diff --git a/src/plugins/charts/public/services/legacy_colors/mock.ts b/examples/grid_example/public/types.ts similarity index 51% rename from src/plugins/charts/public/services/legacy_colors/mock.ts rename to examples/grid_example/public/types.ts index 06d3a3934bd3..39885e25e715 100644 --- a/src/plugins/charts/public/services/legacy_colors/mock.ts +++ b/examples/grid_example/public/types.ts @@ -7,17 +7,21 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { coreMock } from '@kbn/core/public/mocks'; -import { LegacyColorsService } from './colors'; +export interface DashboardGridData { + w: number; + h: number; + x: number; + y: number; + i: string; +} -const colors = new LegacyColorsService(); -colors.init(coreMock.createSetup().uiSettings); +export interface MockedDashboardPanelMap { + [key: string]: { id: string; gridData: DashboardGridData & { row: number } }; +} -export const colorsServiceMock: LegacyColorsService = { - createColorLookupFunction: jest.fn(colors.createColorLookupFunction.bind(colors)), - mappedColors: { - mapKeys: jest.fn(), - get: jest.fn(), - getColorFromConfig: jest.fn(), - }, -} as any; +export type MockedDashboardRowMap = Array<{ title: string; collapsed: boolean }>; + +export interface MockSerializedDashboardState { + panels: MockedDashboardPanelMap; + rows: MockedDashboardRowMap; +} diff --git a/examples/grid_example/public/use_mock_dashboard_api.tsx b/examples/grid_example/public/use_mock_dashboard_api.tsx new file mode 100644 index 000000000000..8388bd83f264 --- /dev/null +++ b/examples/grid_example/public/use_mock_dashboard_api.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { cloneDeep } from 'lodash'; +import { useMemo } from 'react'; +import { BehaviorSubject } from 'rxjs'; + +import { + MockSerializedDashboardState, + MockedDashboardPanelMap, + MockedDashboardRowMap, +} from './types'; + +const DASHBOARD_GRID_COLUMN_COUNT = 48; +const DEFAULT_PANEL_HEIGHT = 15; +const DEFAULT_PANEL_WIDTH = DASHBOARD_GRID_COLUMN_COUNT / 2; + +export const useMockDashboardApi = ({ + savedState, +}: { + savedState: MockSerializedDashboardState; +}) => { + const mockDashboardApi = useMemo(() => { + return { + viewMode: new BehaviorSubject('edit'), + panels$: new BehaviorSubject(savedState.panels), + rows$: new BehaviorSubject(savedState.rows), + removePanel: (id: string) => { + const panels = { ...mockDashboardApi.panels$.getValue() }; + delete panels[id]; // the grid layout component will handle compacting, if necessary + mockDashboardApi.panels$.next(panels); + }, + replacePanel: (oldId: string, newId: string) => { + const currentPanels = mockDashboardApi.panels$.getValue(); + const otherPanels = { ...currentPanels }; + const oldPanel = currentPanels[oldId]; + delete otherPanels[oldId]; + otherPanels[newId] = { id: newId, gridData: { ...oldPanel.gridData, i: newId } }; + mockDashboardApi.panels$.next(otherPanels); + }, + addNewPanel: ({ id: newId }: { id: string }) => { + // we are only implementing "place at top" here, for demo purposes + const currentPanels = mockDashboardApi.panels$.getValue(); + const otherPanels = { ...currentPanels }; + for (const [id, panel] of Object.entries(currentPanels)) { + const currentPanel = cloneDeep(panel); + currentPanel.gridData.y = currentPanel.gridData.y + DEFAULT_PANEL_HEIGHT; + otherPanels[id] = currentPanel; + } + mockDashboardApi.panels$.next({ + ...otherPanels, + [newId]: { + id: newId, + gridData: { + i: newId, + row: 0, + x: 0, + y: 0, + w: DEFAULT_PANEL_WIDTH, + h: DEFAULT_PANEL_HEIGHT, + }, + }, + }); + }, + canRemovePanels: () => true, + }; + // only run onMount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return mockDashboardApi; +}; diff --git a/examples/grid_example/public/utils.ts b/examples/grid_example/public/utils.ts new file mode 100644 index 000000000000..5d2dfd0fa300 --- /dev/null +++ b/examples/grid_example/public/utils.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { GridLayoutData } from '@kbn/grid-layout'; +import { MockedDashboardPanelMap, MockedDashboardRowMap } from './types'; + +export const gridLayoutToDashboardPanelMap = ( + layout: GridLayoutData +): { panels: MockedDashboardPanelMap; rows: MockedDashboardRowMap } => { + const panels: MockedDashboardPanelMap = {}; + const rows: MockedDashboardRowMap = []; + layout.forEach((row, rowIndex) => { + rows.push({ title: row.title, collapsed: row.isCollapsed }); + Object.values(row.panels).forEach((panelGridData) => { + panels[panelGridData.id] = { + id: panelGridData.id, + gridData: { + i: panelGridData.id, + y: panelGridData.row, + x: panelGridData.column, + w: panelGridData.width, + h: panelGridData.height, + row: rowIndex, + }, + }; + }); + }); + return { panels, rows }; +}; + +export const dashboardInputToGridLayout = ({ + panels, + rows, +}: { + panels: MockedDashboardPanelMap; + rows: MockedDashboardRowMap; +}): GridLayoutData => { + const layout: GridLayoutData = []; + + rows.forEach((row) => { + layout.push({ title: row.title, isCollapsed: row.collapsed, panels: {} }); + }); + + Object.keys(panels).forEach((panelId) => { + const gridData = panels[panelId].gridData; + layout[gridData.row].panels[panelId] = { + id: panelId, + row: gridData.y, + column: gridData.x, + width: gridData.w, + height: gridData.h, + }; + }); + + return layout; +}; diff --git a/examples/resizable_layout_examples/public/application.tsx b/examples/resizable_layout_examples/public/application.tsx index b70e3642548b..7637cf2a79c6 100644 --- a/examples/resizable_layout_examples/public/application.tsx +++ b/examples/resizable_layout_examples/public/application.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { CoreThemeProvider } from '@kbn/core-theme-browser-internal'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import type { AppMountParameters } from '@kbn/core/public'; import { I18nProvider } from '@kbn/i18n-react'; import React, { ReactNode, useState } from 'react'; @@ -101,7 +101,7 @@ const ResizableSection = ({ export const renderApp = ({ element, theme$ }: AppMountParameters) => { ReactDOM.render( - +
{ } />
-
+
, element ); diff --git a/examples/resizable_layout_examples/tsconfig.json b/examples/resizable_layout_examples/tsconfig.json index e998e2c117f4..ba9001547e7d 100644 --- a/examples/resizable_layout_examples/tsconfig.json +++ b/examples/resizable_layout_examples/tsconfig.json @@ -6,10 +6,10 @@ "include": ["common/**/*", "public/**/*", "server/**/*", "../../typings/**/*"], "kbn_references": [ "@kbn/resizable-layout", - "@kbn/core-theme-browser-internal", "@kbn/core", "@kbn/i18n-react", "@kbn/developer-examples-plugin", + "@kbn/react-kibana-context-theme", ], "exclude": ["target/**/*"] } diff --git a/examples/search_examples/public/search_sessions/app.tsx b/examples/search_examples/public/search_sessions/app.tsx index d9fe9d454848..5fcb0e326c8b 100644 --- a/examples/search_examples/public/search_sessions/app.tsx +++ b/examples/search_examples/public/search_sessions/app.tsx @@ -32,7 +32,6 @@ import { lastValueFrom, of } from 'rxjs'; import { CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { mountReactNode } from '@kbn/core-mount-utils-browser-internal'; import type { TimeRange } from '@kbn/es-query'; import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; @@ -48,6 +47,7 @@ import { import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { createStateContainer, useContainerState } from '@kbn/kibana-utils-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { PLUGIN_ID } from '../../common'; import { getInitialStateFromUrl, SEARCH_SESSIONS_EXAMPLES_APP_LOCATOR } from './app_locator'; @@ -735,7 +735,7 @@ function doSearch( ); notifications.toasts.addSuccess({ title: 'Query result', - text: mountReactNode(message), + text: toMountPoint(message, startServices), }); } }), diff --git a/examples/search_examples/tsconfig.json b/examples/search_examples/tsconfig.json index 842a75b63491..eb4fb7b3de5c 100644 --- a/examples/search_examples/tsconfig.json +++ b/examples/search_examples/tsconfig.json @@ -28,7 +28,6 @@ "@kbn/utility-types", "@kbn/es-query", "@kbn/i18n", - "@kbn/core-mount-utils-browser-internal", "@kbn/config-schema", "@kbn/shared-ux-router", "@kbn/search-types", diff --git a/examples/unified_field_list_examples/public/application.tsx b/examples/unified_field_list_examples/public/application.tsx index ca9e71f6ada9..6cbdc8242b6b 100644 --- a/examples/unified_field_list_examples/public/application.tsx +++ b/examples/unified_field_list_examples/public/application.tsx @@ -10,7 +10,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n-react'; -import { CoreThemeProvider } from '@kbn/core-theme-browser-internal'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import type { AppMountParameters, CoreStart } from '@kbn/core/public'; import { AppPluginStartDependencies } from './types'; import { UnifiedFieldListExampleApp } from './example_app'; @@ -22,14 +22,14 @@ export const renderApp = ( ) => { ReactDOM.render( - + - + , element ); diff --git a/examples/unified_field_list_examples/tsconfig.json b/examples/unified_field_list_examples/tsconfig.json index ae6e203d6770..cd91501d05be 100644 --- a/examples/unified_field_list_examples/tsconfig.json +++ b/examples/unified_field_list_examples/tsconfig.json @@ -28,7 +28,7 @@ "@kbn/field-formats-plugin", "@kbn/data-view-field-editor-plugin", "@kbn/unified-field-list", - "@kbn/core-theme-browser-internal", "@kbn/ui-actions-plugin", + "@kbn/react-kibana-context-theme", ] } diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index d30ac3c4552e..b96aaa3a3ce0 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -6355,18 +6355,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -6910,6 +6919,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -7113,18 +7128,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -7361,18 +7385,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -7916,6 +7949,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -8145,18 +8184,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -8700,6 +8748,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -9190,18 +9244,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -9745,6 +9808,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -9947,18 +10016,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -10195,18 +10273,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -10750,6 +10837,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -10980,18 +11073,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -11535,6 +11637,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -11890,6 +11998,42 @@ }, "type": "object" }, + "limits": { + "additionalProperties": false, + "properties": { + "go_max_procs": { + "type": "number" + } + }, + "type": "object" + }, + "logging": { + "additionalProperties": false, + "properties": { + "files": { + "additionalProperties": false, + "properties": { + "interval": { + "type": "string" + }, + "keepfiles": { + "type": "number" + }, + "rotateeverybytes": { + "type": "number" + } + }, + "type": "object" + }, + "level": { + "type": "string" + }, + "to_files": { + "type": "boolean" + } + }, + "type": "object" + }, "monitoring": { "additionalProperties": false, "properties": { @@ -12636,6 +12780,22 @@ ] } }, + { + "in": "query", + "name": "pkgName", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "pkgVersion", + "required": false, + "schema": { + "type": "string" + } + }, { "in": "query", "name": "previewData", @@ -29968,6 +30128,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -30455,6 +30621,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "additionalProperties": false, @@ -30692,6 +30864,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "anyOf": [ @@ -31228,6 +31406,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -31930,6 +32114,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -33102,6 +33292,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -33514,6 +33710,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "additionalProperties": false, @@ -34206,6 +34408,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -34700,6 +34908,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "additionalProperties": false, @@ -34936,6 +35150,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "anyOf": [ @@ -35471,6 +35691,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -36515,6 +36741,7 @@ "type": "boolean" }, "use_space_awareness_migration_started_at": { + "nullable": true, "type": "string" }, "use_space_awareness_migration_status": { @@ -36716,6 +36943,7 @@ "type": "boolean" }, "use_space_awareness_migration_started_at": { + "nullable": true, "type": "string" }, "use_space_awareness_migration_status": { diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index 4b56e3581c66..9115670ecb31 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -6355,18 +6355,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -6910,6 +6919,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -7113,18 +7128,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -7361,18 +7385,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -7916,6 +7949,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -8145,18 +8184,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -8700,6 +8748,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -9190,18 +9244,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -9745,6 +9808,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -9947,18 +10016,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -10195,18 +10273,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -10750,6 +10837,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -10980,18 +11073,27 @@ "nullable": true }, "agent_download_timeout": { - "default": "2h", "nullable": true }, "agent_limits_go_max_procs": { "nullable": true }, + "agent_logging_files_interval": { + "nullable": true + }, + "agent_logging_files_keepfiles": { + "nullable": true + }, + "agent_logging_files_rotateeverybytes": { + "nullable": true + }, "agent_logging_level": { - "default": "info", "nullable": true }, "agent_logging_metrics_period": { - "default": "30s", + "nullable": true + }, + "agent_logging_to_files": { "nullable": true } }, @@ -11535,6 +11637,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -11890,6 +11998,42 @@ }, "type": "object" }, + "limits": { + "additionalProperties": false, + "properties": { + "go_max_procs": { + "type": "number" + } + }, + "type": "object" + }, + "logging": { + "additionalProperties": false, + "properties": { + "files": { + "additionalProperties": false, + "properties": { + "interval": { + "type": "string" + }, + "keepfiles": { + "type": "number" + }, + "rotateeverybytes": { + "type": "number" + } + }, + "type": "object" + }, + "level": { + "type": "string" + }, + "to_files": { + "type": "boolean" + } + }, + "type": "object" + }, "monitoring": { "additionalProperties": false, "properties": { @@ -12636,6 +12780,22 @@ ] } }, + { + "in": "query", + "name": "pkgName", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "pkgVersion", + "required": false, + "schema": { + "type": "string" + } + }, { "in": "query", "name": "previewData", @@ -29968,6 +30128,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -30455,6 +30621,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "additionalProperties": false, @@ -30692,6 +30864,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "anyOf": [ @@ -31228,6 +31406,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -31930,6 +32114,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -33102,6 +33292,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -33514,6 +33710,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "additionalProperties": false, @@ -34206,6 +34408,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -34700,6 +34908,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "additionalProperties": false, @@ -34936,6 +35150,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "vars": { "additionalProperties": { "anyOf": [ @@ -35471,6 +35691,12 @@ }, "type": "array" }, + "supports_agentless": { + "default": false, + "description": "Indicates whether the package policy belongs to an agentless agent policy.", + "nullable": true, + "type": "boolean" + }, "updated_at": { "type": "string" }, @@ -36515,6 +36741,7 @@ "type": "boolean" }, "use_space_awareness_migration_started_at": { + "nullable": true, "type": "string" }, "use_space_awareness_migration_status": { @@ -36716,6 +36943,7 @@ "type": "boolean" }, "use_space_awareness_migration_started_at": { + "nullable": true, "type": "string" }, "use_space_awareness_migration_status": { diff --git a/oas_docs/kibana.info.serverless.yaml b/oas_docs/kibana.info.serverless.yaml index b2f451373e7a..b56ec070027a 100644 --- a/oas_docs/kibana.info.serverless.yaml +++ b/oas_docs/kibana.info.serverless.yaml @@ -2,10 +2,6 @@ openapi: 3.0.3 info: title: Kibana Serverless APIs description: | - **Technical preview** - This functionality is in technical preview and may be changed or removed in a future release. - Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. - The Kibana REST APIs for Elastic serverless enable you to manage resources such as connectors, data views, and saved objects. The API calls are stateless. Each request that you make happens in isolation from other calls diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 4b35e4d9c78f..68a5ccaff2e1 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -3,10 +3,6 @@ info: contact: name: Kibana Team description: | - **Technical preview** - This functionality is in technical preview and may be changed or removed in a future release. - Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. - The Kibana REST APIs for Elastic serverless enable you to manage resources such as connectors, data views, and saved objects. The API calls are stateless. Each request that you make happens in isolation from other calls @@ -9522,15 +9518,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -9908,6 +9909,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -10047,15 +10053,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -10221,15 +10232,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -10607,6 +10623,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -10765,15 +10786,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -11151,6 +11177,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -11289,15 +11320,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -11675,6 +11711,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -11813,15 +11854,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -11987,15 +12033,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -12373,6 +12424,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -12531,15 +12587,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -12917,6 +12978,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -13153,6 +13219,30 @@ paths: required: - enabled type: object + limits: + additionalProperties: false + type: object + properties: + go_max_procs: + type: number + logging: + additionalProperties: false + type: object + properties: + files: + additionalProperties: false + type: object + properties: + interval: + type: string + keepfiles: + type: number + rotateeverybytes: + type: number + level: + type: string + to_files: + type: boolean monitoring: additionalProperties: false type: object @@ -13831,6 +13921,16 @@ paths: type: string type: array - type: string + - in: query + name: pkgName + required: false + schema: + type: string + - in: query + name: pkgVersion + required: false + schema: + type: string - in: query name: previewData required: false @@ -25477,6 +25577,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -25805,6 +25910,11 @@ paths: description: Agent policy IDs where that package policy will be added type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: additionalProperties: false @@ -25955,6 +26065,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: anyOf: @@ -26306,6 +26421,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -26766,6 +26886,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -27265,6 +27390,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -27595,6 +27725,11 @@ paths: description: Agent policy IDs where that package policy will be added type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: additionalProperties: false @@ -27744,6 +27879,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: anyOf: @@ -28094,6 +28234,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -28871,6 +29016,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -29150,6 +29300,11 @@ paths: description: Agent policy IDs where that package policy will be added type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: additionalProperties: false @@ -29779,6 +29934,7 @@ paths: secret_storage_requirements_met: type: boolean use_space_awareness_migration_started_at: + nullable: true type: string use_space_awareness_migration_status: enum: @@ -29912,6 +30068,7 @@ paths: secret_storage_requirements_met: type: boolean use_space_awareness_migration_started_at: + nullable: true type: string use_space_awareness_migration_status: enum: @@ -31557,13 +31714,6 @@ paths: required: true responses: '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - type: object - properties: - data: - type: object description: Indicates the note was successfully deleted. summary: Delete a note tags: @@ -31625,9 +31775,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - oneOf: - - $ref: '#/components/schemas/Security_Timeline_API_GetNotesResult' - - type: object + $ref: '#/components/schemas/Security_Timeline_API_GetNotesResult' description: Indicates the requested notes were returned. summary: Get notes tags: @@ -31671,17 +31819,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistNote: - $ref: '#/components/schemas/Security_Timeline_API_ResponseNote' - required: - - persistNote - required: - - data + $ref: '#/components/schemas/Security_Timeline_API_ResponseNote' description: Indicates the note was successfully created. summary: Add or update a note tags: @@ -32034,17 +32172,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistPinnedEventOnTimeline: - $ref: '#/components/schemas/Security_Timeline_API_PersistPinnedEventResponse' - required: - - persistPinnedEventOnTimeline - required: - - data + $ref: '#/components/schemas/Security_Timeline_API_PersistPinnedEventResponse' description: Indicates the event was successfully pinned to the Timeline. summary: Pin an event tags: @@ -33655,20 +33783,6 @@ paths: required: true responses: '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - type: object - properties: - data: - type: object - properties: - deleteTimeline: - type: boolean - required: - - deleteTimeline - required: - - data description: Indicates the Timeline was successfully deleted. summary: Delete Timelines or Timeline templates tags: @@ -33693,20 +33807,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - oneOf: - - type: object - properties: - data: - type: object - properties: - getOneTimeline: - $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' - required: - - getOneTimeline - required: - - data - - additionalProperties: false - type: object + $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' description: Indicates that the (template) Timeline was found and returned. summary: Get Timeline or Timeline template details tags: @@ -34017,17 +34118,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistFavorite: - $ref: '#/components/schemas/Security_Timeline_API_FavoriteTimelineResponse' - required: - - persistFavorite - required: - - data + $ref: '#/components/schemas/Security_Timeline_API_FavoriteTimelineResponse' description: Indicates the favorite status was successfully updated. '403': content: @@ -34184,15 +34275,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - oneOf: - - type: object - properties: - data: - $ref: '#/components/schemas/Security_Timeline_API_ResolvedTimeline' - required: - - data - - additionalProperties: false - type: object + $ref: '#/components/schemas/Security_Timeline_API_ResolvedTimeline' description: The (template) Timeline has been found '400': description: The request is missing parameters @@ -47230,16 +47313,10 @@ components: Security_Timeline_API_FavoriteTimelineResponse: type: object properties: - code: - nullable: true - type: number favorite: items: $ref: '#/components/schemas/Security_Timeline_API_FavoriteTimelineResult' type: array - message: - nullable: true - type: string savedObjectId: type: string templateTimelineId: @@ -47408,28 +47485,15 @@ components: - version Security_Timeline_API_PersistPinnedEventResponse: oneOf: - - allOf: - - $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' - - $ref: '#/components/schemas/Security_Timeline_API_PinnedEventBaseResponseBody' - - nullable: true - type: object - Security_Timeline_API_PersistTimelineResponse: - type: object - properties: - data: - type: object + - $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + - type: object properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' - required: - - timeline + unpinned: + type: boolean required: - - persistTimeline - required: - - data + - unpinned + Security_Timeline_API_PersistTimelineResponse: + $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' Security_Timeline_API_PinnedEvent: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BarePinnedEvent' @@ -47442,15 +47506,6 @@ components: required: - pinnedEventId - version - Security_Timeline_API_PinnedEventBaseResponseBody: - type: object - properties: - code: - type: number - message: - type: string - required: - - code Security_Timeline_API_QueryMatchResult: type: object properties: @@ -47491,15 +47546,9 @@ components: Security_Timeline_API_ResponseNote: type: object properties: - code: - type: number - message: - type: string note: $ref: '#/components/schemas/Security_Timeline_API_Note' required: - - code - - message - note Security_Timeline_API_RowRendererId: enum: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 94e987510c64..208bced5d70f 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -12378,15 +12378,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -12764,6 +12769,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -12902,15 +12912,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -13076,15 +13091,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -13462,6 +13482,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -13619,15 +13644,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -14005,6 +14035,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -14142,15 +14177,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -14528,6 +14568,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -14665,15 +14710,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -14839,15 +14889,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -15225,6 +15280,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -15382,15 +15442,20 @@ paths: agent_download_target_directory: nullable: true agent_download_timeout: - default: 2h nullable: true agent_limits_go_max_procs: nullable: true + agent_logging_files_interval: + nullable: true + agent_logging_files_keepfiles: + nullable: true + agent_logging_files_rotateeverybytes: + nullable: true agent_logging_level: - default: info nullable: true agent_logging_metrics_period: - default: 30s + nullable: true + agent_logging_to_files: nullable: true agent_features: items: @@ -15768,6 +15833,11 @@ paths: required: - id type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -16002,6 +16072,30 @@ paths: required: - enabled type: object + limits: + additionalProperties: false + type: object + properties: + go_max_procs: + type: number + logging: + additionalProperties: false + type: object + properties: + files: + additionalProperties: false + type: object + properties: + interval: + type: string + keepfiles: + type: number + rotateeverybytes: + type: number + level: + type: string + to_files: + type: boolean monitoring: additionalProperties: false type: object @@ -16675,6 +16769,16 @@ paths: type: string type: array - type: string + - in: query + name: pkgName + required: false + schema: + type: string + - in: query + name: pkgVersion + required: false + schema: + type: string - in: query name: previewData required: false @@ -28256,6 +28360,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -28583,6 +28692,11 @@ paths: description: Agent policy IDs where that package policy will be added type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: additionalProperties: false @@ -28733,6 +28847,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: anyOf: @@ -29084,6 +29203,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -29543,6 +29667,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -30040,6 +30169,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -30369,6 +30503,11 @@ paths: description: Agent policy IDs where that package policy will be added type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: additionalProperties: false @@ -30518,6 +30657,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: anyOf: @@ -30868,6 +31012,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -31642,6 +31791,11 @@ paths: items: type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean updated_at: type: string updated_by: @@ -31921,6 +32075,11 @@ paths: description: Agent policy IDs where that package policy will be added type: string type: array + supports_agentless: + default: false + description: Indicates whether the package policy belongs to an agentless agent policy. + nullable: true + type: boolean vars: additionalProperties: additionalProperties: false @@ -32543,6 +32702,7 @@ paths: secret_storage_requirements_met: type: boolean use_space_awareness_migration_started_at: + nullable: true type: string use_space_awareness_migration_status: enum: @@ -32675,6 +32835,7 @@ paths: secret_storage_requirements_met: type: boolean use_space_awareness_migration_started_at: + nullable: true type: string use_space_awareness_migration_status: enum: @@ -34297,13 +34458,6 @@ paths: required: true responses: '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - type: object - properties: - data: - type: object description: Indicates the note was successfully deleted. summary: Delete a note tags: @@ -34364,9 +34518,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - oneOf: - - $ref: '#/components/schemas/Security_Timeline_API_GetNotesResult' - - type: object + $ref: '#/components/schemas/Security_Timeline_API_GetNotesResult' description: Indicates the requested notes were returned. summary: Get notes tags: @@ -34409,17 +34561,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistNote: - $ref: '#/components/schemas/Security_Timeline_API_ResponseNote' - required: - - persistNote - required: - - data + $ref: '#/components/schemas/Security_Timeline_API_ResponseNote' description: Indicates the note was successfully created. summary: Add or update a note tags: @@ -34757,17 +34899,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistPinnedEventOnTimeline: - $ref: '#/components/schemas/Security_Timeline_API_PersistPinnedEventResponse' - required: - - persistPinnedEventOnTimeline - required: - - data + $ref: '#/components/schemas/Security_Timeline_API_PersistPinnedEventResponse' description: Indicates the event was successfully pinned to the Timeline. summary: Pin an event tags: @@ -37306,20 +37438,6 @@ paths: required: true responses: '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - type: object - properties: - data: - type: object - properties: - deleteTimeline: - type: boolean - required: - - deleteTimeline - required: - - data description: Indicates the Timeline was successfully deleted. summary: Delete Timelines or Timeline templates tags: @@ -37343,20 +37461,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - oneOf: - - type: object - properties: - data: - type: object - properties: - getOneTimeline: - $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' - required: - - getOneTimeline - required: - - data - - additionalProperties: false - type: object + $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' description: Indicates that the (template) Timeline was found and returned. summary: Get Timeline or Timeline template details tags: @@ -37660,17 +37765,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - type: object - properties: - data: - type: object - properties: - persistFavorite: - $ref: '#/components/schemas/Security_Timeline_API_FavoriteTimelineResponse' - required: - - persistFavorite - required: - - data + $ref: '#/components/schemas/Security_Timeline_API_FavoriteTimelineResponse' description: Indicates the favorite status was successfully updated. '403': content: @@ -37824,15 +37919,7 @@ paths: content: application/json; Elastic-Api-Version=2023-10-31: schema: - oneOf: - - type: object - properties: - data: - $ref: '#/components/schemas/Security_Timeline_API_ResolvedTimeline' - required: - - data - - additionalProperties: false - type: object + $ref: '#/components/schemas/Security_Timeline_API_ResolvedTimeline' description: The (template) Timeline has been found '400': description: The request is missing parameters @@ -54949,16 +55036,10 @@ components: Security_Timeline_API_FavoriteTimelineResponse: type: object properties: - code: - nullable: true - type: number favorite: items: $ref: '#/components/schemas/Security_Timeline_API_FavoriteTimelineResult' type: array - message: - nullable: true - type: string savedObjectId: type: string templateTimelineId: @@ -55127,28 +55208,15 @@ components: - version Security_Timeline_API_PersistPinnedEventResponse: oneOf: - - allOf: - - $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' - - $ref: '#/components/schemas/Security_Timeline_API_PinnedEventBaseResponseBody' - - nullable: true - type: object - Security_Timeline_API_PersistTimelineResponse: - type: object - properties: - data: - type: object + - $ref: '#/components/schemas/Security_Timeline_API_PinnedEvent' + - type: object properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' - required: - - timeline + unpinned: + type: boolean required: - - persistTimeline - required: - - data + - unpinned + Security_Timeline_API_PersistTimelineResponse: + $ref: '#/components/schemas/Security_Timeline_API_TimelineResponse' Security_Timeline_API_PinnedEvent: allOf: - $ref: '#/components/schemas/Security_Timeline_API_BarePinnedEvent' @@ -55161,15 +55229,6 @@ components: required: - pinnedEventId - version - Security_Timeline_API_PinnedEventBaseResponseBody: - type: object - properties: - code: - type: number - message: - type: string - required: - - code Security_Timeline_API_QueryMatchResult: type: object properties: @@ -55210,15 +55269,9 @@ components: Security_Timeline_API_ResponseNote: type: object properties: - code: - type: number - message: - type: string note: $ref: '#/components/schemas/Security_Timeline_API_Note' required: - - code - - message - note Security_Timeline_API_RowRendererId: enum: diff --git a/package.json b/package.json index 54a41747033a..8522a1c27de4 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,6 @@ "build": "node scripts/build --all-platforms", "build:apidocs": "node scripts/build_api_docs", "checkLicenses": "node scripts/check_licenses --dev", - "cover:functional:merge": "nyc report --temp-dir target/kibana-coverage/functional --report-dir target/coverage/report/functional --reporter=json-summary", - "cover:report": "nyc report --temp-dir target/kibana-coverage/functional --report-dir target/coverage/report --reporter=lcov && open ./target/coverage/report/lcov-report/index.html", "debug": "node --nolazy --inspect scripts/kibana --dev", "debug-break": "node --nolazy --inspect-brk scripts/kibana --dev", "dev-docs": "scripts/dev_docs.sh", @@ -314,6 +312,7 @@ "@kbn/core-http-router-server-internal": "link:packages/core/http/core-http-router-server-internal", "@kbn/core-http-server": "link:packages/core/http/core-http-server", "@kbn/core-http-server-internal": "link:packages/core/http/core-http-server-internal", + "@kbn/core-http-server-utils": "link:packages/core/http/core-http-server-utils", "@kbn/core-i18n-browser": "link:packages/core/i18n/core-i18n-browser", "@kbn/core-i18n-browser-internal": "link:packages/core/i18n/core-i18n-browser-internal", "@kbn/core-i18n-server": "link:packages/core/i18n/core-i18n-server", @@ -480,6 +479,7 @@ "@kbn/entities-data-access-plugin": "link:x-pack/plugins/observability_solution/entities_data_access", "@kbn/entities-schema": "link:x-pack/packages/kbn-entities-schema", "@kbn/entity-manager-fixture-plugin": "link:x-pack/test/api_integration/apis/entity_manager/fixture_plugin", + "@kbn/entityManager-app-plugin": "link:x-pack/plugins/observability_solution/entity_manager_app", "@kbn/entityManager-plugin": "link:x-pack/plugins/entity_manager", "@kbn/error-boundary-example-plugin": "link:examples/error_boundary", "@kbn/es-errors": "link:packages/kbn-es-errors", @@ -642,7 +642,6 @@ "@kbn/management-settings-types": "link:packages/kbn-management/settings/types", "@kbn/management-settings-utilities": "link:packages/kbn-management/settings/utilities", "@kbn/management-test-plugin": "link:test/plugin_functional/plugins/management_test_plugin", - "@kbn/manifest": "link:packages/kbn-manifest", "@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl", "@kbn/maps-custom-raster-source-plugin": "link:x-pack/examples/third_party_maps_source_example", "@kbn/maps-ems-plugin": "link:src/plugins/maps_ems", @@ -804,6 +803,7 @@ "@kbn/search-index-documents": "link:packages/kbn-search-index-documents", "@kbn/search-indices": "link:x-pack/plugins/search_indices", "@kbn/search-inference-endpoints": "link:x-pack/plugins/search_inference_endpoints", + "@kbn/search-navigation": "link:x-pack/plugins/search_solution/search_navigation", "@kbn/search-notebooks": "link:x-pack/plugins/search_notebooks", "@kbn/search-playground": "link:x-pack/plugins/search_playground", "@kbn/search-response-warnings": "link:packages/kbn-search-response-warnings", @@ -937,6 +937,7 @@ "@kbn/status-plugin-a-plugin": "link:test/server_integration/plugins/status_plugin_a", "@kbn/status-plugin-b-plugin": "link:test/server_integration/plugins/status_plugin_b", "@kbn/std": "link:packages/kbn-std", + "@kbn/streams-app-plugin": "link:x-pack/plugins/streams_app", "@kbn/streams-plugin": "link:x-pack/plugins/streams", "@kbn/synthetics-plugin": "link:x-pack/plugins/observability_solution/synthetics", "@kbn/synthetics-private-location": "link:x-pack/packages/kbn-synthetics-private-location", @@ -1082,7 +1083,7 @@ "ajv": "^8.12.0", "ansi-regex": "^6.1.0", "antlr4": "^4.13.1-patch-1", - "archiver": "^5.3.1", + "archiver": "^7.0.1", "async": "^3.2.3", "aws4": "^1.12.0", "axios": "^1.7.4", @@ -1221,7 +1222,7 @@ "query-string": "^6.13.2", "rbush": "^3.0.1", "re-resizable": "^6.9.9", - "re2js": "0.4.2", + "re2js": "0.4.3", "react": "^17.0.2", "react-diff-view": "^3.2.1", "react-dom": "^17.0.2", @@ -1346,8 +1347,6 @@ "@emotion/babel-preset-css-prop": "^11.11.0", "@emotion/jest": "^11.11.0", "@frsource/cypress-plugin-visual-regression-diff": "^3.3.10", - "@istanbuljs/nyc-config-typescript": "^1.0.2", - "@istanbuljs/schema": "^0.1.2", "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/transform": "^29.6.1", @@ -1431,6 +1430,7 @@ "@kbn/core-ui-settings-server-mocks": "link:packages/core/ui-settings/core-ui-settings-server-mocks", "@kbn/core-usage-data-server-mocks": "link:packages/core/usage-data/core-usage-data-server-mocks", "@kbn/cypress-config": "link:packages/kbn-cypress-config", + "@kbn/dependency-usage": "link:packages/kbn-dependency-usage", "@kbn/dev-cli-errors": "link:packages/kbn-dev-cli-errors", "@kbn/dev-cli-runner": "link:packages/kbn-dev-cli-runner", "@kbn/dev-proc-runner": "link:packages/kbn-dev-proc-runner", @@ -1464,6 +1464,7 @@ "@kbn/managed-vscode-config": "link:packages/kbn-managed-vscode-config", "@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli", "@kbn/management-storybook-config": "link:packages/kbn-management/storybook/config", + "@kbn/manifest": "link:packages/kbn-manifest", "@kbn/mock-idp-plugin": "link:packages/kbn-mock-idp-plugin", "@kbn/mock-idp-utils": "link:packages/kbn-mock-idp-utils", "@kbn/observability-onboarding-e2e": "link:x-pack/plugins/observability_solution/observability_onboarding/e2e", @@ -1532,7 +1533,7 @@ "@storybook/testing-react": "^1.3.0", "@storybook/theming": "^6.5.16", "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.5.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.0.1", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.5.2", @@ -1689,7 +1690,7 @@ "buildkite-test-collector": "^1.7.0", "callsites": "^3.1.0", "chance": "1.0.18", - "chromedriver": "^130.0.4", + "chromedriver": "^131.0.0", "clarify": "^2.2.0", "clean-webpack-plugin": "^3.0.0", "cli-progress": "^3.12.0", @@ -1709,6 +1710,7 @@ "cypress-recurse": "^1.35.2", "date-fns": "^2.29.3", "dependency-check": "^4.1.0", + "dependency-cruiser": "^16.4.2", "ejs": "^3.1.10", "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.2", diff --git a/packages/analytics/utils/analytics_collection_utils/kibana.jsonc b/packages/analytics/utils/analytics_collection_utils/kibana.jsonc index f909070bbe36..2c7097a9fc5b 100644 --- a/packages/analytics/utils/analytics_collection_utils/kibana.jsonc +++ b/packages/analytics/utils/analytics_collection_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/analytics-collection-utils", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/cloud/kibana.jsonc b/packages/cloud/kibana.jsonc index e39a0dbe4061..da8df46a19b6 100644 --- a/packages/cloud/kibana.jsonc +++ b/packages/cloud/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/cloud", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/content_editor/kibana.jsonc b/packages/content-management/content_editor/kibana.jsonc index 27bd7a53b1ee..403c21b634df 100644 --- a/packages/content-management/content_editor/kibana.jsonc +++ b/packages/content-management/content_editor/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/content-management-content-editor", - "owner": "@elastic/appex-sharedux", -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/content_insights/content_insights_public/kibana.jsonc b/packages/content-management/content_insights/content_insights_public/kibana.jsonc index fc4e12374faf..99b52889ed2a 100644 --- a/packages/content-management/content_insights/content_insights_public/kibana.jsonc +++ b/packages/content-management/content_insights/content_insights_public/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/content-management-content-insights-public", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/content_insights/content_insights_server/kibana.jsonc b/packages/content-management/content_insights/content_insights_server/kibana.jsonc index 386c1a6bf130..32bcff168489 100644 --- a/packages/content-management/content_insights/content_insights_server/kibana.jsonc +++ b/packages/content-management/content_insights/content_insights_server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/content-management-content-insights-server", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/favorites/favorites_common/kibana.jsonc b/packages/content-management/favorites/favorites_common/kibana.jsonc index 69e13e656639..46a8bdf6862b 100644 --- a/packages/content-management/favorites/favorites_common/kibana.jsonc +++ b/packages/content-management/favorites/favorites_common/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/content-management-favorites-common", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/content-management/favorites/favorites_public/kibana.jsonc b/packages/content-management/favorites/favorites_public/kibana.jsonc index a6564f6a1740..bae7450fabef 100644 --- a/packages/content-management/favorites/favorites_public/kibana.jsonc +++ b/packages/content-management/favorites/favorites_public/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/content-management-favorites-public", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/favorites/favorites_server/kibana.jsonc b/packages/content-management/favorites/favorites_server/kibana.jsonc index 8d3fe725d1e8..9ab55caadaf8 100644 --- a/packages/content-management/favorites/favorites_server/kibana.jsonc +++ b/packages/content-management/favorites/favorites_server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/content-management-favorites-server", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/tabbed_table_list_view/kibana.jsonc b/packages/content-management/tabbed_table_list_view/kibana.jsonc index 2a4926625e74..ec30f2315a46 100644 --- a/packages/content-management/tabbed_table_list_view/kibana.jsonc +++ b/packages/content-management/tabbed_table_list_view/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/content-management-tabbed-table-list-view", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/content-management/table_list_view/kibana.jsonc b/packages/content-management/table_list_view/kibana.jsonc index 5309f399ae13..b1d38ab0e6ca 100644 --- a/packages/content-management/table_list_view/kibana.jsonc +++ b/packages/content-management/table_list_view/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/content-management-table-list-view", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/table_list_view_common/kibana.jsonc b/packages/content-management/table_list_view_common/kibana.jsonc index d5a4bf3fbdec..a0257d0c3966 100644 --- a/packages/content-management/table_list_view_common/kibana.jsonc +++ b/packages/content-management/table_list_view_common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/content-management-table-list-view-common", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/table_list_view_table/kibana.jsonc b/packages/content-management/table_list_view_table/kibana.jsonc index d842e74a83a8..defa356d8e61 100644 --- a/packages/content-management/table_list_view_table/kibana.jsonc +++ b/packages/content-management/table_list_view_table/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/content-management-table-list-view-table", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/content-management/user_profiles/kibana.jsonc b/packages/content-management/user_profiles/kibana.jsonc index 5b9c73619d77..1d5083a5a5f7 100644 --- a/packages/content-management/user_profiles/kibana.jsonc +++ b/packages/content-management/user_profiles/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/content-management-user-profiles", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/analytics/core-analytics-browser-internal/kibana.jsonc b/packages/core/analytics/core-analytics-browser-internal/kibana.jsonc index b762d25410ad..4032e0aaf53f 100644 --- a/packages/core/analytics/core-analytics-browser-internal/kibana.jsonc +++ b/packages/core/analytics/core-analytics-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-analytics-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/analytics/core-analytics-browser-mocks/kibana.jsonc b/packages/core/analytics/core-analytics-browser-mocks/kibana.jsonc index b3012a5456fd..2090fc6fe0da 100644 --- a/packages/core/analytics/core-analytics-browser-mocks/kibana.jsonc +++ b/packages/core/analytics/core-analytics-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-analytics-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/analytics/core-analytics-browser/kibana.jsonc b/packages/core/analytics/core-analytics-browser/kibana.jsonc index 3b98d20b277f..5b237dd4eed7 100644 --- a/packages/core/analytics/core-analytics-browser/kibana.jsonc +++ b/packages/core/analytics/core-analytics-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-analytics-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/analytics/core-analytics-server-internal/kibana.jsonc b/packages/core/analytics/core-analytics-server-internal/kibana.jsonc index e9eaa029903b..7483af13c418 100644 --- a/packages/core/analytics/core-analytics-server-internal/kibana.jsonc +++ b/packages/core/analytics/core-analytics-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-analytics-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/analytics/core-analytics-server-mocks/kibana.jsonc b/packages/core/analytics/core-analytics-server-mocks/kibana.jsonc index 325f0216755e..639127c69f44 100644 --- a/packages/core/analytics/core-analytics-server-mocks/kibana.jsonc +++ b/packages/core/analytics/core-analytics-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-analytics-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/analytics/core-analytics-server/kibana.jsonc b/packages/core/analytics/core-analytics-server/kibana.jsonc index 8f56bff10b6c..2a824d4accf1 100644 --- a/packages/core/analytics/core-analytics-server/kibana.jsonc +++ b/packages/core/analytics/core-analytics-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-analytics-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/application/core-application-browser-internal/kibana.jsonc b/packages/core/application/core-application-browser-internal/kibana.jsonc index 56bd06188558..c2401090a40c 100644 --- a/packages/core/application/core-application-browser-internal/kibana.jsonc +++ b/packages/core/application/core-application-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-application-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/application/core-application-browser-mocks/kibana.jsonc b/packages/core/application/core-application-browser-mocks/kibana.jsonc index f1f9ae3f1b6c..15acb592c480 100644 --- a/packages/core/application/core-application-browser-mocks/kibana.jsonc +++ b/packages/core/application/core-application-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-application-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/application/core-application-browser/kibana.jsonc b/packages/core/application/core-application-browser/kibana.jsonc index 11ff8b45731f..c38a82f36376 100644 --- a/packages/core/application/core-application-browser/kibana.jsonc +++ b/packages/core/application/core-application-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-application-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/application/core-application-common/kibana.jsonc b/packages/core/application/core-application-common/kibana.jsonc index 762e4f62119e..e9b95fd9d4de 100644 --- a/packages/core/application/core-application-common/kibana.jsonc +++ b/packages/core/application/core-application-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-application-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/application/core-application-common/src/default_app_categories.ts b/packages/core/application/core-application-common/src/default_app_categories.ts index da76c18e62ce..f9bb68199bc8 100644 --- a/packages/core/application/core-application-common/src/default_app_categories.ts +++ b/packages/core/application/core-application-common/src/default_app_categories.ts @@ -23,7 +23,7 @@ export const DEFAULT_APP_CATEGORIES: Record = Object.freeze enterpriseSearch: { id: 'enterpriseSearch', label: i18n.translate('core.ui.searchNavList.label', { - defaultMessage: 'Search', + defaultMessage: 'Elasticsearch', }), order: 2000, euiIconType: 'logoEnterpriseSearch', diff --git a/packages/core/apps/core-apps-browser-internal/kibana.jsonc b/packages/core/apps/core-apps-browser-internal/kibana.jsonc index e271c86c9e97..e337aad22d1b 100644 --- a/packages/core/apps/core-apps-browser-internal/kibana.jsonc +++ b/packages/core/apps/core-apps-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-apps-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/apps/core-apps-browser-mocks/kibana.jsonc b/packages/core/apps/core-apps-browser-mocks/kibana.jsonc index 9b6a0e45ce07..48307ba77a64 100644 --- a/packages/core/apps/core-apps-browser-mocks/kibana.jsonc +++ b/packages/core/apps/core-apps-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-apps-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/apps/core-apps-server-internal/kibana.jsonc b/packages/core/apps/core-apps-server-internal/kibana.jsonc index fd5224a19182..3e1006b1a61d 100644 --- a/packages/core/apps/core-apps-server-internal/kibana.jsonc +++ b/packages/core/apps/core-apps-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-apps-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/base/core-base-browser-internal/kibana.jsonc b/packages/core/base/core-base-browser-internal/kibana.jsonc index bb1d4c322ca2..a5d8aadb2850 100644 --- a/packages/core/base/core-base-browser-internal/kibana.jsonc +++ b/packages/core/base/core-base-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-base-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/base/core-base-browser-mocks/kibana.jsonc b/packages/core/base/core-base-browser-mocks/kibana.jsonc index 5d07dc98fb92..080f81e74f8b 100644 --- a/packages/core/base/core-base-browser-mocks/kibana.jsonc +++ b/packages/core/base/core-base-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-base-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/base/core-base-common-internal/kibana.jsonc b/packages/core/base/core-base-common-internal/kibana.jsonc index 8f2083119d1b..3c1d71177bd1 100644 --- a/packages/core/base/core-base-common-internal/kibana.jsonc +++ b/packages/core/base/core-base-common-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-base-common-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/base/core-base-common/kibana.jsonc b/packages/core/base/core-base-common/kibana.jsonc index 5a9691ad80c4..8ae6e6e0e15e 100644 --- a/packages/core/base/core-base-common/kibana.jsonc +++ b/packages/core/base/core-base-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-base-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/base/core-base-server-internal/kibana.jsonc b/packages/core/base/core-base-server-internal/kibana.jsonc index 2bd0a2f91042..233d8d464552 100644 --- a/packages/core/base/core-base-server-internal/kibana.jsonc +++ b/packages/core/base/core-base-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-base-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/base/core-base-server-mocks/kibana.jsonc b/packages/core/base/core-base-server-mocks/kibana.jsonc index f43820e33050..37ed4c146acc 100644 --- a/packages/core/base/core-base-server-mocks/kibana.jsonc +++ b/packages/core/base/core-base-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-base-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/capabilities/core-capabilities-browser-internal/kibana.jsonc b/packages/core/capabilities/core-capabilities-browser-internal/kibana.jsonc index 3bf55481bf59..736b1c7c8c7f 100644 --- a/packages/core/capabilities/core-capabilities-browser-internal/kibana.jsonc +++ b/packages/core/capabilities/core-capabilities-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-capabilities-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/capabilities/core-capabilities-browser-mocks/kibana.jsonc b/packages/core/capabilities/core-capabilities-browser-mocks/kibana.jsonc index f418f2fe7a56..e5441f394d00 100644 --- a/packages/core/capabilities/core-capabilities-browser-mocks/kibana.jsonc +++ b/packages/core/capabilities/core-capabilities-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-capabilities-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/capabilities/core-capabilities-common/kibana.jsonc b/packages/core/capabilities/core-capabilities-common/kibana.jsonc index 87bb4d2977a3..9751a8d59e25 100644 --- a/packages/core/capabilities/core-capabilities-common/kibana.jsonc +++ b/packages/core/capabilities/core-capabilities-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-capabilities-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/capabilities/core-capabilities-server-internal/kibana.jsonc b/packages/core/capabilities/core-capabilities-server-internal/kibana.jsonc index e4ffc9a80329..3e6131c7e223 100644 --- a/packages/core/capabilities/core-capabilities-server-internal/kibana.jsonc +++ b/packages/core/capabilities/core-capabilities-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-capabilities-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/capabilities/core-capabilities-server-mocks/kibana.jsonc b/packages/core/capabilities/core-capabilities-server-mocks/kibana.jsonc index e881e0f0de94..55a0ff6c9b10 100644 --- a/packages/core/capabilities/core-capabilities-server-mocks/kibana.jsonc +++ b/packages/core/capabilities/core-capabilities-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-capabilities-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/capabilities/core-capabilities-server/kibana.jsonc b/packages/core/capabilities/core-capabilities-server/kibana.jsonc index 5fb36e34a068..7d1bdfe18273 100644 --- a/packages/core/capabilities/core-capabilities-server/kibana.jsonc +++ b/packages/core/capabilities/core-capabilities-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-capabilities-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/chrome/core-chrome-browser-internal/kibana.jsonc b/packages/core/chrome/core-chrome-browser-internal/kibana.jsonc index cfadc0fadeb8..2c04c62ee36d 100644 --- a/packages/core/chrome/core-chrome-browser-internal/kibana.jsonc +++ b/packages/core/chrome/core-chrome-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-chrome-browser-internal", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/chrome/core-chrome-browser-mocks/kibana.jsonc b/packages/core/chrome/core-chrome-browser-mocks/kibana.jsonc index 7acd89dd4acf..700a6909b652 100644 --- a/packages/core/chrome/core-chrome-browser-mocks/kibana.jsonc +++ b/packages/core/chrome/core-chrome-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-chrome-browser-mocks", - "devOnly": true, - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/chrome/core-chrome-browser/kibana.jsonc b/packages/core/chrome/core-chrome-browser/kibana.jsonc index 257c3aea5874..3fa61785fa7a 100644 --- a/packages/core/chrome/core-chrome-browser/kibana.jsonc +++ b/packages/core/chrome/core-chrome-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-chrome-browser", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/config/core-config-server-internal/kibana.jsonc b/packages/core/config/core-config-server-internal/kibana.jsonc index db0adc862909..129486d0d37f 100644 --- a/packages/core/config/core-config-server-internal/kibana.jsonc +++ b/packages/core/config/core-config-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-config-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/custom-branding/core-custom-branding-browser-internal/kibana.jsonc b/packages/core/custom-branding/core-custom-branding-browser-internal/kibana.jsonc index a7934eead593..d955e4c98404 100644 --- a/packages/core/custom-branding/core-custom-branding-browser-internal/kibana.jsonc +++ b/packages/core/custom-branding/core-custom-branding-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-custom-branding-browser-internal", - "owner": "@elastic/appex-sharedux", -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/custom-branding/core-custom-branding-browser-mocks/kibana.jsonc b/packages/core/custom-branding/core-custom-branding-browser-mocks/kibana.jsonc index a38fb4e9ba04..8051fadb0485 100644 --- a/packages/core/custom-branding/core-custom-branding-browser-mocks/kibana.jsonc +++ b/packages/core/custom-branding/core-custom-branding-browser-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-custom-branding-browser-mocks", - "owner": "@elastic/appex-sharedux", -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/custom-branding/core-custom-branding-browser/kibana.jsonc b/packages/core/custom-branding/core-custom-branding-browser/kibana.jsonc index 506f5d23be4b..65922ad38be1 100644 --- a/packages/core/custom-branding/core-custom-branding-browser/kibana.jsonc +++ b/packages/core/custom-branding/core-custom-branding-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-custom-branding-browser", - "owner": "@elastic/appex-sharedux", -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/custom-branding/core-custom-branding-common/kibana.jsonc b/packages/core/custom-branding/core-custom-branding-common/kibana.jsonc index 7043dc5c8121..b3e9ab4cfea5 100644 --- a/packages/core/custom-branding/core-custom-branding-common/kibana.jsonc +++ b/packages/core/custom-branding/core-custom-branding-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-custom-branding-common", - "owner": "@elastic/appex-sharedux", -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/custom-branding/core-custom-branding-server-internal/kibana.jsonc b/packages/core/custom-branding/core-custom-branding-server-internal/kibana.jsonc index 60370f95342f..079b1cd46541 100644 --- a/packages/core/custom-branding/core-custom-branding-server-internal/kibana.jsonc +++ b/packages/core/custom-branding/core-custom-branding-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-custom-branding-server-internal", - "owner": "@elastic/appex-sharedux", -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/custom-branding/core-custom-branding-server-mocks/kibana.jsonc b/packages/core/custom-branding/core-custom-branding-server-mocks/kibana.jsonc index 5ed605b18dd3..8ae2ffaccc78 100644 --- a/packages/core/custom-branding/core-custom-branding-server-mocks/kibana.jsonc +++ b/packages/core/custom-branding/core-custom-branding-server-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-custom-branding-server-mocks", - "owner": "@elastic/appex-sharedux", -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/custom-branding/core-custom-branding-server/kibana.jsonc b/packages/core/custom-branding/core-custom-branding-server/kibana.jsonc index e6a7c037d461..90ca02c45b72 100644 --- a/packages/core/custom-branding/core-custom-branding-server/kibana.jsonc +++ b/packages/core/custom-branding/core-custom-branding-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-custom-branding-server", - "owner": "@elastic/appex-sharedux", -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/deprecations/core-deprecations-browser-internal/kibana.jsonc b/packages/core/deprecations/core-deprecations-browser-internal/kibana.jsonc index c4fd76e10bd8..e7af55ecb6e1 100644 --- a/packages/core/deprecations/core-deprecations-browser-internal/kibana.jsonc +++ b/packages/core/deprecations/core-deprecations-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-deprecations-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/deprecations/core-deprecations-browser-mocks/kibana.jsonc b/packages/core/deprecations/core-deprecations-browser-mocks/kibana.jsonc index f52fc0943824..8893b621ab07 100644 --- a/packages/core/deprecations/core-deprecations-browser-mocks/kibana.jsonc +++ b/packages/core/deprecations/core-deprecations-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-deprecations-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/deprecations/core-deprecations-browser/kibana.jsonc b/packages/core/deprecations/core-deprecations-browser/kibana.jsonc index d24e14cb049e..81b3711802e6 100644 --- a/packages/core/deprecations/core-deprecations-browser/kibana.jsonc +++ b/packages/core/deprecations/core-deprecations-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-deprecations-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/deprecations/core-deprecations-common/kibana.jsonc b/packages/core/deprecations/core-deprecations-common/kibana.jsonc index 81fc008124c7..0e857f88107e 100644 --- a/packages/core/deprecations/core-deprecations-common/kibana.jsonc +++ b/packages/core/deprecations/core-deprecations-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-deprecations-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/deprecations/core-deprecations-server-internal/kibana.jsonc b/packages/core/deprecations/core-deprecations-server-internal/kibana.jsonc index 335b964f4b25..4607178f9df0 100644 --- a/packages/core/deprecations/core-deprecations-server-internal/kibana.jsonc +++ b/packages/core/deprecations/core-deprecations-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-deprecations-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/deprecations/core-deprecations-server-mocks/kibana.jsonc b/packages/core/deprecations/core-deprecations-server-mocks/kibana.jsonc index 7d32dce1a9f7..7d74d5aed9b6 100644 --- a/packages/core/deprecations/core-deprecations-server-mocks/kibana.jsonc +++ b/packages/core/deprecations/core-deprecations-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-deprecations-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/deprecations/core-deprecations-server/kibana.jsonc b/packages/core/deprecations/core-deprecations-server/kibana.jsonc index 8985ca948a65..a3e5030516d4 100644 --- a/packages/core/deprecations/core-deprecations-server/kibana.jsonc +++ b/packages/core/deprecations/core-deprecations-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-deprecations-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/doc-links/core-doc-links-browser-internal/kibana.jsonc b/packages/core/doc-links/core-doc-links-browser-internal/kibana.jsonc index 0a541080ce1a..9f2a67b5b8a9 100644 --- a/packages/core/doc-links/core-doc-links-browser-internal/kibana.jsonc +++ b/packages/core/doc-links/core-doc-links-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-doc-links-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/doc-links/core-doc-links-browser-mocks/kibana.jsonc b/packages/core/doc-links/core-doc-links-browser-mocks/kibana.jsonc index 3d37d0652c5c..f13fdab327fa 100644 --- a/packages/core/doc-links/core-doc-links-browser-mocks/kibana.jsonc +++ b/packages/core/doc-links/core-doc-links-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-doc-links-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/doc-links/core-doc-links-browser/kibana.jsonc b/packages/core/doc-links/core-doc-links-browser/kibana.jsonc index 038630337877..eb138cb6aa59 100644 --- a/packages/core/doc-links/core-doc-links-browser/kibana.jsonc +++ b/packages/core/doc-links/core-doc-links-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-doc-links-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/doc-links/core-doc-links-server-internal/kibana.jsonc b/packages/core/doc-links/core-doc-links-server-internal/kibana.jsonc index 9c42d96aa6f0..fbefdc0d17d9 100644 --- a/packages/core/doc-links/core-doc-links-server-internal/kibana.jsonc +++ b/packages/core/doc-links/core-doc-links-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-doc-links-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/doc-links/core-doc-links-server-mocks/kibana.jsonc b/packages/core/doc-links/core-doc-links-server-mocks/kibana.jsonc index d7eabe4c4c50..2b01099137f5 100644 --- a/packages/core/doc-links/core-doc-links-server-mocks/kibana.jsonc +++ b/packages/core/doc-links/core-doc-links-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-doc-links-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/doc-links/core-doc-links-server/kibana.jsonc b/packages/core/doc-links/core-doc-links-server/kibana.jsonc index 2f514e6031ea..4f26c69d3563 100644 --- a/packages/core/doc-links/core-doc-links-server/kibana.jsonc +++ b/packages/core/doc-links/core-doc-links-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-doc-links-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/kibana.jsonc b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/kibana.jsonc index 3591e6f14213..02e47fb6aab0 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/kibana.jsonc +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-elasticsearch-client-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/kibana.jsonc b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/kibana.jsonc index da7297ebef40..0ccfa6a33067 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/kibana.jsonc +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-elasticsearch-client-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/kibana.jsonc b/packages/core/elasticsearch/core-elasticsearch-server-internal/kibana.jsonc index 9ac8f0e982fd..14ae0d680bb6 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/kibana.jsonc +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-elasticsearch-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/elasticsearch/core-elasticsearch-server-mocks/kibana.jsonc b/packages/core/elasticsearch/core-elasticsearch-server-mocks/kibana.jsonc index 77f9199a531e..a8a344b9f0af 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-mocks/kibana.jsonc +++ b/packages/core/elasticsearch/core-elasticsearch-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-elasticsearch-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/elasticsearch/core-elasticsearch-server/kibana.jsonc b/packages/core/elasticsearch/core-elasticsearch-server/kibana.jsonc index e855f5d75a61..c07305294616 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/kibana.jsonc +++ b/packages/core/elasticsearch/core-elasticsearch-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-elasticsearch-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/environment/core-environment-server-internal/kibana.jsonc b/packages/core/environment/core-environment-server-internal/kibana.jsonc index 7e0d68e96d35..252cb8876cac 100644 --- a/packages/core/environment/core-environment-server-internal/kibana.jsonc +++ b/packages/core/environment/core-environment-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-environment-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/environment/core-environment-server-mocks/kibana.jsonc b/packages/core/environment/core-environment-server-mocks/kibana.jsonc index 6dbbc97b8ba6..8b5cf42eef45 100644 --- a/packages/core/environment/core-environment-server-mocks/kibana.jsonc +++ b/packages/core/environment/core-environment-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-environment-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/execution-context/core-execution-context-browser-internal/kibana.jsonc b/packages/core/execution-context/core-execution-context-browser-internal/kibana.jsonc index 58bc6361a167..4b3c33afc040 100644 --- a/packages/core/execution-context/core-execution-context-browser-internal/kibana.jsonc +++ b/packages/core/execution-context/core-execution-context-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-execution-context-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/execution-context/core-execution-context-browser-mocks/kibana.jsonc b/packages/core/execution-context/core-execution-context-browser-mocks/kibana.jsonc index 1aeb9063aaed..2714ba0737e6 100644 --- a/packages/core/execution-context/core-execution-context-browser-mocks/kibana.jsonc +++ b/packages/core/execution-context/core-execution-context-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-execution-context-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/execution-context/core-execution-context-browser/kibana.jsonc b/packages/core/execution-context/core-execution-context-browser/kibana.jsonc index 570753b177b7..46050ccd3a54 100644 --- a/packages/core/execution-context/core-execution-context-browser/kibana.jsonc +++ b/packages/core/execution-context/core-execution-context-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-execution-context-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/execution-context/core-execution-context-common/kibana.jsonc b/packages/core/execution-context/core-execution-context-common/kibana.jsonc index 4775632a026b..30c9d754f076 100644 --- a/packages/core/execution-context/core-execution-context-common/kibana.jsonc +++ b/packages/core/execution-context/core-execution-context-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-execution-context-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/execution-context/core-execution-context-server-internal/kibana.jsonc b/packages/core/execution-context/core-execution-context-server-internal/kibana.jsonc index b7377b5cc8fc..9299c3688591 100644 --- a/packages/core/execution-context/core-execution-context-server-internal/kibana.jsonc +++ b/packages/core/execution-context/core-execution-context-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-execution-context-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/execution-context/core-execution-context-server-mocks/kibana.jsonc b/packages/core/execution-context/core-execution-context-server-mocks/kibana.jsonc index db21d4a76c29..8652f3508c56 100644 --- a/packages/core/execution-context/core-execution-context-server-mocks/kibana.jsonc +++ b/packages/core/execution-context/core-execution-context-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-execution-context-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/execution-context/core-execution-context-server/kibana.jsonc b/packages/core/execution-context/core-execution-context-server/kibana.jsonc index 97a123688065..b280813b0666 100644 --- a/packages/core/execution-context/core-execution-context-server/kibana.jsonc +++ b/packages/core/execution-context/core-execution-context-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-execution-context-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-internal/kibana.jsonc b/packages/core/fatal-errors/core-fatal-errors-browser-internal/kibana.jsonc index a2abf8bdac3f..946ee0b3fe97 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-internal/kibana.jsonc +++ b/packages/core/fatal-errors/core-fatal-errors-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-fatal-errors-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-mocks/kibana.jsonc b/packages/core/fatal-errors/core-fatal-errors-browser-mocks/kibana.jsonc index fa9111fb12da..f55fc6068266 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-mocks/kibana.jsonc +++ b/packages/core/fatal-errors/core-fatal-errors-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-fatal-errors-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/fatal-errors/core-fatal-errors-browser/kibana.jsonc b/packages/core/fatal-errors/core-fatal-errors-browser/kibana.jsonc index ad3f9422482c..593cbe875534 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser/kibana.jsonc +++ b/packages/core/fatal-errors/core-fatal-errors-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-fatal-errors-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/feature-flags/core-feature-flags-browser-internal/kibana.jsonc b/packages/core/feature-flags/core-feature-flags-browser-internal/kibana.jsonc index 150509b99f51..9f0b2a909d0d 100644 --- a/packages/core/feature-flags/core-feature-flags-browser-internal/kibana.jsonc +++ b/packages/core/feature-flags/core-feature-flags-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-feature-flags-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.test.ts b/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.test.ts index 3f14a2dd9226..cc11599d48d5 100644 --- a/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.test.ts +++ b/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.test.ts @@ -8,10 +8,10 @@ */ import { firstValueFrom } from 'rxjs'; -import { apm } from '@elastic/apm-rum'; +import { Transaction, apm } from '@elastic/apm-rum'; import { type Client, OpenFeature, type Provider } from '@openfeature/web-sdk'; import { coreContextMock } from '@kbn/core-base-browser-mocks'; -import type { FeatureFlagsStart } from '@kbn/core-feature-flags-browser'; +import type { FeatureFlagsSetup, FeatureFlagsStart } from '@kbn/core-feature-flags-browser'; import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser-mocks'; import type { InternalInjectedMetadataSetup } from '@kbn/core-injected-metadata-browser-internal'; import { FeatureFlagsService } from '..'; @@ -63,7 +63,7 @@ describe('FeatureFlagsService Browser', () => { test('awaits initialization in the start context', async () => { const { setProvider } = featureFlagsService.setup({ injectedMetadata }); let externalResolve: Function = () => void 0; - const spy = jest.spyOn(OpenFeature, 'setProviderAndWait').mockImplementation(async () => { + const spy = jest.spyOn(OpenFeature, 'setProviderAndWait').mockImplementationOnce(async () => { await new Promise((resolve) => { externalResolve = resolve; }); @@ -80,7 +80,7 @@ describe('FeatureFlagsService Browser', () => { test('do not hold for too long during initialization', async () => { const { setProvider } = featureFlagsService.setup({ injectedMetadata }); - const spy = jest.spyOn(OpenFeature, 'setProviderAndWait').mockImplementation(async () => { + const spy = jest.spyOn(OpenFeature, 'setProviderAndWait').mockImplementationOnce(async () => { await new Promise(() => {}); // never resolves }); const apmCaptureErrorSpy = jest.spyOn(apm, 'captureError'); @@ -95,6 +95,60 @@ describe('FeatureFlagsService Browser', () => { expect.stringContaining('The feature flags provider took too long to initialize.') ); }); + + describe('APM instrumentation', () => { + const fakeProvider = { metadata: { name: 'fake provider' } } as Provider; + + let setProvider: FeatureFlagsSetup['setProvider']; + let apmSpy: jest.SpyInstance; + let setProviderSpy: jest.SpyInstance>; + + beforeEach(() => { + const setup = featureFlagsService.setup({ injectedMetadata }); + setProvider = setup.setProvider; + setProviderSpy = jest.spyOn(OpenFeature, 'setProviderAndWait'); + apmSpy = jest.spyOn(apm, 'startTransaction'); + }); + + test('starts an APM transaction to track the time it takes to set a provider', () => { + expect.assertions(1); + setProvider(fakeProvider); + expect(apmSpy).toHaveBeenCalledWith('set-provider', 'feature-flags'); + }); + + test('APM transaction tracks success', async () => { + expect.assertions(4); + + setProviderSpy.mockResolvedValueOnce(); + setProvider(fakeProvider); + + const transaction = apmSpy.mock.results[0].value; + const endTransactionSpy = jest.spyOn(transaction, 'end'); + expect(transaction.outcome).toBeUndefined(); + expect(endTransactionSpy).toHaveBeenCalledTimes(0); + await setProviderSpy.mock.results[0].value.catch(() => {}); + expect(transaction.outcome).toBe('success'); + expect(endTransactionSpy).toHaveBeenCalledTimes(1); + }); + + test('APM transaction tracks failures', async () => { + expect.assertions(5); + + const apmCaptureErrorSpy = jest.spyOn(apm, 'captureError'); + const error = new Error('Something went terribly wrong'); + setProviderSpy.mockRejectedValueOnce(error); + setProvider(fakeProvider); + + const transaction = apmSpy.mock.results[0].value; + const endTransactionSpy = jest.spyOn(transaction, 'end'); + expect(transaction.outcome).toBeUndefined(); + expect(endTransactionSpy).toHaveBeenCalledTimes(0); + await setProviderSpy.mock.results[0].value.catch(() => {}); + expect(apmCaptureErrorSpy).toHaveBeenCalledWith(error); + expect(transaction.outcome).toBe('failure'); + expect(endTransactionSpy).toHaveBeenCalledTimes(1); + }); + }); }); describe('context handling', () => { diff --git a/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.ts b/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.ts index afc32d93aee3..388a4056e4a8 100644 --- a/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.ts +++ b/packages/core/feature-flags/core-feature-flags-browser-internal/src/feature_flags_service.ts @@ -71,11 +71,21 @@ export class FeatureFlagsService { const transaction = apm.startTransaction('set-provider', 'feature-flags'); this.isProviderReadyPromise = OpenFeature.setProviderAndWait(provider); this.isProviderReadyPromise - .then(() => transaction?.end()) + .then(() => { + if (transaction) { + // @ts-expect-error RUM types are not correct + transaction.outcome = 'success'; + transaction.end(); + } + }) .catch((err) => { this.logger.error(err); apm.captureError(err); - transaction?.end(); + if (transaction) { + // @ts-expect-error RUM types are not correct + transaction.outcome = 'failure'; + transaction.end(); + } }); }, appendContext: (contextToAppend) => this.appendContext(contextToAppend), diff --git a/packages/core/feature-flags/core-feature-flags-browser-mocks/kibana.jsonc b/packages/core/feature-flags/core-feature-flags-browser-mocks/kibana.jsonc index 0917a098841c..0ffae6725561 100644 --- a/packages/core/feature-flags/core-feature-flags-browser-mocks/kibana.jsonc +++ b/packages/core/feature-flags/core-feature-flags-browser-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-feature-flags-browser-mocks", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/feature-flags/core-feature-flags-browser/kibana.jsonc b/packages/core/feature-flags/core-feature-flags-browser/kibana.jsonc index 56187119509b..266c72a78bb4 100644 --- a/packages/core/feature-flags/core-feature-flags-browser/kibana.jsonc +++ b/packages/core/feature-flags/core-feature-flags-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-feature-flags-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/feature-flags/core-feature-flags-server-internal/kibana.jsonc b/packages/core/feature-flags/core-feature-flags-server-internal/kibana.jsonc index 60a01597c045..5f4a48b31a9d 100644 --- a/packages/core/feature-flags/core-feature-flags-server-internal/kibana.jsonc +++ b/packages/core/feature-flags/core-feature-flags-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-feature-flags-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/feature-flags/core-feature-flags-server-mocks/kibana.jsonc b/packages/core/feature-flags/core-feature-flags-server-mocks/kibana.jsonc index 69b03f0badbd..3e7748721e7d 100644 --- a/packages/core/feature-flags/core-feature-flags-server-mocks/kibana.jsonc +++ b/packages/core/feature-flags/core-feature-flags-server-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-feature-flags-server-mocks", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/feature-flags/core-feature-flags-server/kibana.jsonc b/packages/core/feature-flags/core-feature-flags-server/kibana.jsonc index dc896ed83b97..b3488340c6c6 100644 --- a/packages/core/feature-flags/core-feature-flags-server/kibana.jsonc +++ b/packages/core/feature-flags/core-feature-flags-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-feature-flags-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/http/core-http-browser-internal/kibana.jsonc b/packages/core/http/core-http-browser-internal/kibana.jsonc index 628fabf38eb5..d0f32e6b8a0a 100644 --- a/packages/core/http/core-http-browser-internal/kibana.jsonc +++ b/packages/core/http/core-http-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-http-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/http/core-http-browser-mocks/kibana.jsonc b/packages/core/http/core-http-browser-mocks/kibana.jsonc index a97b6052fa0f..533733bde9b9 100644 --- a/packages/core/http/core-http-browser-mocks/kibana.jsonc +++ b/packages/core/http/core-http-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-http-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/http/core-http-browser/kibana.jsonc b/packages/core/http/core-http-browser/kibana.jsonc index ee7f45d35429..3d91035bb062 100644 --- a/packages/core/http/core-http-browser/kibana.jsonc +++ b/packages/core/http/core-http-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-http-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/http/core-http-common/kibana.jsonc b/packages/core/http/core-http-common/kibana.jsonc index 372eaf2d892a..f027a0c0be89 100644 --- a/packages/core/http/core-http-common/kibana.jsonc +++ b/packages/core/http/core-http-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-http-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/http/core-http-context-server-internal/kibana.jsonc b/packages/core/http/core-http-context-server-internal/kibana.jsonc index 20d82599501f..1552b646a347 100644 --- a/packages/core/http/core-http-context-server-internal/kibana.jsonc +++ b/packages/core/http/core-http-context-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-http-context-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/http/core-http-context-server-mocks/kibana.jsonc b/packages/core/http/core-http-context-server-mocks/kibana.jsonc index 20778b8e1053..2460079ed537 100644 --- a/packages/core/http/core-http-context-server-mocks/kibana.jsonc +++ b/packages/core/http/core-http-context-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-http-context-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/http/core-http-request-handler-context-server-internal/kibana.jsonc b/packages/core/http/core-http-request-handler-context-server-internal/kibana.jsonc index 8fc7abfabeee..df616684d6ac 100644 --- a/packages/core/http/core-http-request-handler-context-server-internal/kibana.jsonc +++ b/packages/core/http/core-http-request-handler-context-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-http-request-handler-context-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/http/core-http-request-handler-context-server/kibana.jsonc b/packages/core/http/core-http-request-handler-context-server/kibana.jsonc index 45786e53decb..63754fc77b93 100644 --- a/packages/core/http/core-http-request-handler-context-server/kibana.jsonc +++ b/packages/core/http/core-http-request-handler-context-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-http-request-handler-context-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/http/core-http-resources-server-internal/kibana.jsonc b/packages/core/http/core-http-resources-server-internal/kibana.jsonc index d9217be8446d..9c9a601ffd4e 100644 --- a/packages/core/http/core-http-resources-server-internal/kibana.jsonc +++ b/packages/core/http/core-http-resources-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-http-resources-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/http/core-http-resources-server-mocks/kibana.jsonc b/packages/core/http/core-http-resources-server-mocks/kibana.jsonc index e6ec34b3b590..3c227ea942e0 100644 --- a/packages/core/http/core-http-resources-server-mocks/kibana.jsonc +++ b/packages/core/http/core-http-resources-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-http-resources-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/http/core-http-resources-server/kibana.jsonc b/packages/core/http/core-http-resources-server/kibana.jsonc index 2b80c017c4d9..37c9c516fc44 100644 --- a/packages/core/http/core-http-resources-server/kibana.jsonc +++ b/packages/core/http/core-http-resources-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-http-resources-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/http/core-http-router-server-internal/kibana.jsonc b/packages/core/http/core-http-router-server-internal/kibana.jsonc index 31b2f2dec88a..5f724841f2ac 100644 --- a/packages/core/http/core-http-router-server-internal/kibana.jsonc +++ b/packages/core/http/core-http-router-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-http-router-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/http/core-http-router-server-internal/src/router.test.ts b/packages/core/http/core-http-router-server-internal/src/router.test.ts index 2c702fb3ef70..f0eaa96879d4 100644 --- a/packages/core/http/core-http-router-server-internal/src/router.test.ts +++ b/packages/core/http/core-http-router-server-internal/src/router.test.ts @@ -305,6 +305,23 @@ describe('Router', () => { ); }); + it('throws if route has security declared wrong', () => { + const router = new Router('', logger, enhanceWithContext, routerOptions); + expect(() => + router.get( + // we use 'any' because validate requires valid Type or function usage + { + path: '/', + validate: false, + options: { security: { authz: { requiredPrivileges: [] } } } as any, + }, + (context, req, res) => res.ok({}) + ) + ).toThrowErrorMatchingInlineSnapshot( + `"\`options.security\` is not allowed in route config. Use \`security\` instead."` + ); + }); + it('throws if options.body.output is not a valid value', () => { const router = new Router('', logger, enhanceWithContext, routerOptions); expect(() => diff --git a/packages/core/http/core-http-router-server-internal/src/router.ts b/packages/core/http/core-http-router-server-internal/src/router.ts index c2ea09605c4e..c3e44f8bc9d7 100644 --- a/packages/core/http/core-http-router-server-internal/src/router.ts +++ b/packages/core/http/core-http-router-server-internal/src/router.ts @@ -115,6 +115,11 @@ function validOptions( ); } + // @ts-expect-error to eliminate problems with `security` in the options for route factories abstractions + if (options.security) { + throw new Error('`options.security` is not allowed in route config. Use `security` instead.'); + } + const body = shouldNotHavePayload ? undefined : { diff --git a/packages/core/http/core-http-router-server-mocks/kibana.jsonc b/packages/core/http/core-http-router-server-mocks/kibana.jsonc index 505cad6714c8..70d2412b9b0e 100644 --- a/packages/core/http/core-http-router-server-mocks/kibana.jsonc +++ b/packages/core/http/core-http-router-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-http-router-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/http/core-http-router-server-mocks/src/router.mock.ts b/packages/core/http/core-http-router-server-mocks/src/router.mock.ts index 858c0753eeb2..a9995ec887a8 100644 --- a/packages/core/http/core-http-router-server-mocks/src/router.mock.ts +++ b/packages/core/http/core-http-router-server-mocks/src/router.mock.ts @@ -12,16 +12,16 @@ import { Socket } from 'net'; import { stringify } from 'query-string'; import { hapiMocks } from '@kbn/hapi-mocks'; import { schema } from '@kbn/config-schema'; -import type { - IRouter, - KibanaRequest, - RouteMethod, - RouteValidationSpec, - KibanaRouteOptions, - KibanaRequestState, - KibanaResponseFactory, +import { + type IRouter, + type KibanaRequest, + type RouteMethod, + type RouteValidationSpec, + type KibanaRouteOptions, + type KibanaRequestState, + type KibanaResponseFactory, } from '@kbn/core-http-server'; -import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; import { createVersionedRouterMock, type MockedVersionedRouter } from './versioned_router.mock'; export type RouterMock = jest.Mocked> & { versioned: MockedVersionedRouter }; @@ -84,7 +84,7 @@ function createKibanaRequestMock

({ const queryString = stringify(query, { sort: false }); const url = new URL(`${path}${queryString ? `?${queryString}` : ''}`, 'http://localhost'); - return CoreKibanaRequest.from( + return kibanaRequestFactory( hapiMocks.createRequest({ app: kibanaRequestState, auth, @@ -128,7 +128,7 @@ function createFakeKibanaRequestMock({ path: '/', }; - return CoreKibanaRequest.from(fakeRequest); + return kibanaRequestFactory(fakeRequest); } const createResponseFactoryMock = (): jest.Mocked => ({ diff --git a/packages/core/http/core-http-router-server-mocks/tsconfig.json b/packages/core/http/core-http-router-server-mocks/tsconfig.json index 4504850612be..96ef422a65f6 100644 --- a/packages/core/http/core-http-router-server-mocks/tsconfig.json +++ b/packages/core/http/core-http-router-server-mocks/tsconfig.json @@ -14,7 +14,7 @@ "@kbn/hapi-mocks", "@kbn/config-schema", "@kbn/core-http-server", - "@kbn/core-http-router-server-internal" + "@kbn/core-http-server-utils", ], "exclude": [ "target/**/*", diff --git a/packages/core/http/core-http-server-internal/kibana.jsonc b/packages/core/http/core-http-server-internal/kibana.jsonc index b3c59759d39a..1b4876b72c1b 100644 --- a/packages/core/http/core-http-server-internal/kibana.jsonc +++ b/packages/core/http/core-http-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-http-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/http/core-http-server-mocks/kibana.jsonc b/packages/core/http/core-http-server-mocks/kibana.jsonc index 9af9873c9bd5..2e3a7104a0d8 100644 --- a/packages/core/http/core-http-server-mocks/kibana.jsonc +++ b/packages/core/http/core-http-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-http-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/http/core-http-server-utils/README.md b/packages/core/http/core-http-server-utils/README.md new file mode 100644 index 000000000000..3954a4ff2847 --- /dev/null +++ b/packages/core/http/core-http-server-utils/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-server-utils + +This package contains public utilities for Core's server-side http service. diff --git a/src/plugins/charts/public/services/legacy_colors/index.ts b/packages/core/http/core-http-server-utils/index.ts similarity index 86% rename from src/plugins/charts/public/services/legacy_colors/index.ts rename to packages/core/http/core-http-server-utils/index.ts index da06062e1f8c..4a1d62331973 100644 --- a/src/plugins/charts/public/services/legacy_colors/index.ts +++ b/packages/core/http/core-http-server-utils/index.ts @@ -7,4 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { LegacyColorsService } from './colors'; +export { kibanaRequestFactory, isCoreKibanaRequest } from './src/request'; diff --git a/src/plugins/unified_histogram/public/layout/helpers.ts b/packages/core/http/core-http-server-utils/jest.config.js similarity index 67% rename from src/plugins/unified_histogram/public/layout/helpers.ts rename to packages/core/http/core-http-server-utils/jest.config.js index 3ab7942c31c5..eff6f9ba0e4c 100644 --- a/src/plugins/unified_histogram/public/layout/helpers.ts +++ b/packages/core/http/core-http-server-utils/jest.config.js @@ -7,9 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { AggregateQuery } from '@kbn/es-query'; -import { hasTransformationalCommand } from '@kbn/esql-utils'; - -export const shouldDisplayHistogram = (query: AggregateQuery) => { - return !hasTransformationalCommand(query.esql); +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/http/core-http-server-utils'], }; diff --git a/packages/core/http/core-http-server-utils/kibana.jsonc b/packages/core/http/core-http-server-utils/kibana.jsonc new file mode 100644 index 000000000000..0f4ec42aaaca --- /dev/null +++ b/packages/core/http/core-http-server-utils/kibana.jsonc @@ -0,0 +1,9 @@ +{ + "type": "shared-server", + "id": "@kbn/core-http-server-utils", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} diff --git a/packages/core/http/core-http-server-utils/package.json b/packages/core/http/core-http-server-utils/package.json new file mode 100644 index 000000000000..380dac95dd05 --- /dev/null +++ b/packages/core/http/core-http-server-utils/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/core-http-server-utils", + "private": true, + "version": "1.0.0", + "author": "Kibana Core", + "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0" +} diff --git a/packages/core/http/core-http-server-utils/src/request.ts b/packages/core/http/core-http-server-utils/src/request.ts new file mode 100644 index 000000000000..7ac797727a70 --- /dev/null +++ b/packages/core/http/core-http-server-utils/src/request.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; +import type { + KibanaRequest, + RawRequest, + RouteValidator, + RouteValidatorFullConfigRequest, +} from '@kbn/core-http-server'; + +/** + * Allows building a KibanaRequest from a RawRequest, leveraging internal CoreKibanaRequest. + * @param req The raw request to build from + * @param routeSchemas The route schemas + * @param withoutSecretHeaders Whether we want to exclude secret headers + * @returns A KibanaRequest object + */ +export function kibanaRequestFactory( + req: RawRequest, + routeSchemas?: RouteValidator | RouteValidatorFullConfigRequest, + withoutSecretHeaders: boolean = true +): KibanaRequest { + return CoreKibanaRequest.from(req, routeSchemas, withoutSecretHeaders); +} + +export function isCoreKibanaRequest(req: KibanaRequest) { + return req instanceof CoreKibanaRequest; +} diff --git a/packages/core/http/core-http-server-utils/tsconfig.json b/packages/core/http/core-http-server-utils/tsconfig.json new file mode 100644 index 000000000000..0a0e4516c8c5 --- /dev/null +++ b/packages/core/http/core-http-server-utils/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "kbn_references": [ + "@kbn/core-http-router-server-internal", + "@kbn/core-http-server", + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/packages/core/http/core-http-server/kibana.jsonc b/packages/core/http/core-http-server/kibana.jsonc index 3e1bc4b7b248..d8b11db34881 100644 --- a/packages/core/http/core-http-server/kibana.jsonc +++ b/packages/core/http/core-http-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-http-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/http/core-http-server/src/router/request.ts b/packages/core/http/core-http-server/src/router/request.ts index 066372faca1e..6fff29eb42b5 100644 --- a/packages/core/http/core-http-server/src/router/request.ts +++ b/packages/core/http/core-http-server/src/router/request.ts @@ -48,9 +48,11 @@ export interface KibanaRequestState extends RequestApplicationState { * Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. * @public */ -export type KibanaRequestRouteOptions = Method extends 'get' | 'options' +export type KibanaRequestRouteOptions = (Method extends + | 'get' + | 'options' ? Required, 'body'>> - : Required>; + : Required>) & { security?: RouteSecurity }; /** * Request specific route information exposed to a handler. diff --git a/packages/core/http/core-http-server/src/router/route.ts b/packages/core/http/core-http-server/src/router/route.ts index da24adcb6802..2efd40527411 100644 --- a/packages/core/http/core-http-server/src/router/route.ts +++ b/packages/core/http/core-http-server/src/router/route.ts @@ -398,13 +398,6 @@ export interface RouteConfigOptions { */ discontinued?: string; - /** - * Defines the security requirements for a route, including authorization and authentication. - * - * @remarks This will be surfaced in OAS documentation. - */ - security?: RouteSecurity; - /** * Whether this endpoint is being used to serve generated or static HTTP resources * like JS, CSS or HTML. _Do not set to `true` for HTTP APIs._ diff --git a/packages/core/http/core-http-server/src/versioning/types.ts b/packages/core/http/core-http-server/src/versioning/types.ts index 63e1e3775480..fa08810ce1fb 100644 --- a/packages/core/http/core-http-server/src/versioning/types.ts +++ b/packages/core/http/core-http-server/src/versioning/types.ts @@ -19,6 +19,7 @@ import type { RequestHandlerContextBase, RouteValidationFunction, LazyValidator, + RouteSecurity, } from '../..'; import type { RouteDeprecationInfo } from '../router/route'; type RqCtx = RequestHandlerContextBase; @@ -35,12 +36,12 @@ export type VersionedRouteConfig = Omit< > & { options?: Omit< RouteConfigOptions, - 'access' | 'description' | 'summary' | 'deprecated' | 'discontinued' | 'security' + 'access' | 'description' | 'summary' | 'deprecated' | 'discontinued' >; /** See {@link RouteConfigOptions['access']} */ access: Exclude['access'], undefined>; /** See {@link RouteConfigOptions['security']} */ - security?: Exclude['security'], undefined>; + security?: RouteSecurity; /** * When enabled, the router will also check for the presence of an `apiVersion` * query parameter to determine the route version to resolve to: @@ -337,7 +338,7 @@ export interface AddVersionOpts { */ validate: false | VersionedRouteValidation | (() => VersionedRouteValidation); // Provide a way to lazily load validation schemas - security?: Exclude['security'], undefined>; + security?: RouteSecurity; options?: { deprecated?: RouteDeprecationInfo; diff --git a/packages/core/i18n/core-i18n-browser-internal/kibana.jsonc b/packages/core/i18n/core-i18n-browser-internal/kibana.jsonc index 472c9906f891..4712e8a4fcfc 100644 --- a/packages/core/i18n/core-i18n-browser-internal/kibana.jsonc +++ b/packages/core/i18n/core-i18n-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-i18n-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/i18n/core-i18n-browser-mocks/kibana.jsonc b/packages/core/i18n/core-i18n-browser-mocks/kibana.jsonc index e1e06ed6fd62..808f40e28d00 100644 --- a/packages/core/i18n/core-i18n-browser-mocks/kibana.jsonc +++ b/packages/core/i18n/core-i18n-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-i18n-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/i18n/core-i18n-browser/kibana.jsonc b/packages/core/i18n/core-i18n-browser/kibana.jsonc index 5d716dd51160..a550e894b8c6 100644 --- a/packages/core/i18n/core-i18n-browser/kibana.jsonc +++ b/packages/core/i18n/core-i18n-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-i18n-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/i18n/core-i18n-server-internal/kibana.jsonc b/packages/core/i18n/core-i18n-server-internal/kibana.jsonc index 267895c3f691..5432b3ac2926 100644 --- a/packages/core/i18n/core-i18n-server-internal/kibana.jsonc +++ b/packages/core/i18n/core-i18n-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-i18n-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/i18n/core-i18n-server-mocks/kibana.jsonc b/packages/core/i18n/core-i18n-server-mocks/kibana.jsonc index d5c3c4ae6362..a87032d41eb0 100644 --- a/packages/core/i18n/core-i18n-server-mocks/kibana.jsonc +++ b/packages/core/i18n/core-i18n-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-i18n-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/i18n/core-i18n-server/kibana.jsonc b/packages/core/i18n/core-i18n-server/kibana.jsonc index 4502f9057696..a856c99e27ea 100644 --- a/packages/core/i18n/core-i18n-server/kibana.jsonc +++ b/packages/core/i18n/core-i18n-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-i18n-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-internal/kibana.jsonc b/packages/core/injected-metadata/core-injected-metadata-browser-internal/kibana.jsonc index e899f3410c37..2085640f35d8 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-internal/kibana.jsonc +++ b/packages/core/injected-metadata/core-injected-metadata-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-injected-metadata-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/kibana.jsonc b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/kibana.jsonc index 4dfed6e72d7f..84e4c734b3ad 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/kibana.jsonc +++ b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-injected-metadata-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/injected-metadata/core-injected-metadata-common-internal/kibana.jsonc b/packages/core/injected-metadata/core-injected-metadata-common-internal/kibana.jsonc index c3dcd61159ae..68a5c161e3db 100644 --- a/packages/core/injected-metadata/core-injected-metadata-common-internal/kibana.jsonc +++ b/packages/core/injected-metadata/core-injected-metadata-common-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-injected-metadata-common-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/integrations/core-integrations-browser-internal/kibana.jsonc b/packages/core/integrations/core-integrations-browser-internal/kibana.jsonc index 6b51ef7e6ef8..543fcc446ea3 100644 --- a/packages/core/integrations/core-integrations-browser-internal/kibana.jsonc +++ b/packages/core/integrations/core-integrations-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-integrations-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/integrations/core-integrations-browser-mocks/kibana.jsonc b/packages/core/integrations/core-integrations-browser-mocks/kibana.jsonc index ee13f1e79a59..fc3b7e5cfcf6 100644 --- a/packages/core/integrations/core-integrations-browser-mocks/kibana.jsonc +++ b/packages/core/integrations/core-integrations-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-integrations-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/lifecycle/core-lifecycle-browser-internal/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-browser-internal/kibana.jsonc index 4e7a537a6d13..b441b9bbcfc8 100644 --- a/packages/core/lifecycle/core-lifecycle-browser-internal/kibana.jsonc +++ b/packages/core/lifecycle/core-lifecycle-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-lifecycle-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/lifecycle/core-lifecycle-browser-mocks/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-browser-mocks/kibana.jsonc index 2834401d929c..b0b0df3c12d7 100644 --- a/packages/core/lifecycle/core-lifecycle-browser-mocks/kibana.jsonc +++ b/packages/core/lifecycle/core-lifecycle-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-lifecycle-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/lifecycle/core-lifecycle-browser/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-browser/kibana.jsonc index c757e67b8ed3..65d756876f5a 100644 --- a/packages/core/lifecycle/core-lifecycle-browser/kibana.jsonc +++ b/packages/core/lifecycle/core-lifecycle-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-lifecycle-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-server-internal/kibana.jsonc index 9cb8ad860034..e7ac6d1c8a6e 100644 --- a/packages/core/lifecycle/core-lifecycle-server-internal/kibana.jsonc +++ b/packages/core/lifecycle/core-lifecycle-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-lifecycle-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-server-mocks/kibana.jsonc index 93f8b8b8eb95..04e464a4e48a 100644 --- a/packages/core/lifecycle/core-lifecycle-server-mocks/kibana.jsonc +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-lifecycle-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/lifecycle/core-lifecycle-server/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-server/kibana.jsonc index 6ee8465d66cf..b7ad8f6df90f 100644 --- a/packages/core/lifecycle/core-lifecycle-server/kibana.jsonc +++ b/packages/core/lifecycle/core-lifecycle-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-lifecycle-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/logging/core-logging-browser-internal/kibana.jsonc b/packages/core/logging/core-logging-browser-internal/kibana.jsonc index da41db7028f8..d20ee0ef2e86 100644 --- a/packages/core/logging/core-logging-browser-internal/kibana.jsonc +++ b/packages/core/logging/core-logging-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-logging-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/logging/core-logging-browser-mocks/kibana.jsonc b/packages/core/logging/core-logging-browser-mocks/kibana.jsonc index 97044b221916..555833971e68 100644 --- a/packages/core/logging/core-logging-browser-mocks/kibana.jsonc +++ b/packages/core/logging/core-logging-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-logging-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/logging/core-logging-common-internal/kibana.jsonc b/packages/core/logging/core-logging-common-internal/kibana.jsonc index 4e4f23a47c56..8600b47c4eef 100644 --- a/packages/core/logging/core-logging-common-internal/kibana.jsonc +++ b/packages/core/logging/core-logging-common-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-logging-common-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/logging/core-logging-server-internal/kibana.jsonc b/packages/core/logging/core-logging-server-internal/kibana.jsonc index 180b112fa004..5519d9ae4d87 100644 --- a/packages/core/logging/core-logging-server-internal/kibana.jsonc +++ b/packages/core/logging/core-logging-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-logging-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/logging/core-logging-server-mocks/kibana.jsonc b/packages/core/logging/core-logging-server-mocks/kibana.jsonc index b0457e357fef..c43f68ca10ee 100644 --- a/packages/core/logging/core-logging-server-mocks/kibana.jsonc +++ b/packages/core/logging/core-logging-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-logging-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/logging/core-logging-server/kibana.jsonc b/packages/core/logging/core-logging-server/kibana.jsonc index d522793fc7cb..cde9189144e4 100644 --- a/packages/core/logging/core-logging-server/kibana.jsonc +++ b/packages/core/logging/core-logging-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-logging-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/kibana.jsonc b/packages/core/metrics/core-metrics-collectors-server-internal/kibana.jsonc index a6e5c586df1c..fdf04d802674 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/kibana.jsonc +++ b/packages/core/metrics/core-metrics-collectors-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-metrics-collectors-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/metrics/core-metrics-collectors-server-mocks/kibana.jsonc b/packages/core/metrics/core-metrics-collectors-server-mocks/kibana.jsonc index 709f4fa7137f..432c85d94428 100644 --- a/packages/core/metrics/core-metrics-collectors-server-mocks/kibana.jsonc +++ b/packages/core/metrics/core-metrics-collectors-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-metrics-collectors-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/metrics/core-metrics-server-internal/kibana.jsonc b/packages/core/metrics/core-metrics-server-internal/kibana.jsonc index 229e11fd697a..1cc6156b0361 100644 --- a/packages/core/metrics/core-metrics-server-internal/kibana.jsonc +++ b/packages/core/metrics/core-metrics-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-metrics-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/metrics/core-metrics-server-mocks/kibana.jsonc b/packages/core/metrics/core-metrics-server-mocks/kibana.jsonc index e04bb7a6a414..a9ece36f0691 100644 --- a/packages/core/metrics/core-metrics-server-mocks/kibana.jsonc +++ b/packages/core/metrics/core-metrics-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-metrics-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/metrics/core-metrics-server/kibana.jsonc b/packages/core/metrics/core-metrics-server/kibana.jsonc index 34654016db00..403ec2391ac9 100644 --- a/packages/core/metrics/core-metrics-server/kibana.jsonc +++ b/packages/core/metrics/core-metrics-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-metrics-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/mount-utils/core-mount-utils-browser-internal/kibana.jsonc b/packages/core/mount-utils/core-mount-utils-browser-internal/kibana.jsonc index 886d30950749..75f8921e9b31 100644 --- a/packages/core/mount-utils/core-mount-utils-browser-internal/kibana.jsonc +++ b/packages/core/mount-utils/core-mount-utils-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-mount-utils-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/mount-utils/core-mount-utils-browser/kibana.jsonc b/packages/core/mount-utils/core-mount-utils-browser/kibana.jsonc index 733fbd5b374d..bd3e8527dc38 100644 --- a/packages/core/mount-utils/core-mount-utils-browser/kibana.jsonc +++ b/packages/core/mount-utils/core-mount-utils-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-mount-utils-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/node/core-node-server-internal/kibana.jsonc b/packages/core/node/core-node-server-internal/kibana.jsonc index 23fe9ae5eeb7..d2bcd84e7e9a 100644 --- a/packages/core/node/core-node-server-internal/kibana.jsonc +++ b/packages/core/node/core-node-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-node-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/node/core-node-server-mocks/kibana.jsonc b/packages/core/node/core-node-server-mocks/kibana.jsonc index 095a3bd154cc..ed88d544a84f 100644 --- a/packages/core/node/core-node-server-mocks/kibana.jsonc +++ b/packages/core/node/core-node-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-node-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/node/core-node-server/kibana.jsonc b/packages/core/node/core-node-server/kibana.jsonc index a0ae2183674f..4b6273197545 100644 --- a/packages/core/node/core-node-server/kibana.jsonc +++ b/packages/core/node/core-node-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-node-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/notifications/core-notifications-browser-internal/kibana.jsonc b/packages/core/notifications/core-notifications-browser-internal/kibana.jsonc index 0fc101756893..1db00b09999f 100644 --- a/packages/core/notifications/core-notifications-browser-internal/kibana.jsonc +++ b/packages/core/notifications/core-notifications-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-notifications-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/notifications/core-notifications-browser-mocks/kibana.jsonc b/packages/core/notifications/core-notifications-browser-mocks/kibana.jsonc index a1a4166e5071..41c6cf977b56 100644 --- a/packages/core/notifications/core-notifications-browser-mocks/kibana.jsonc +++ b/packages/core/notifications/core-notifications-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-notifications-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/notifications/core-notifications-browser/kibana.jsonc b/packages/core/notifications/core-notifications-browser/kibana.jsonc index 57f94b45d3b4..96d95760641a 100644 --- a/packages/core/notifications/core-notifications-browser/kibana.jsonc +++ b/packages/core/notifications/core-notifications-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-notifications-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/overlays/core-overlays-browser-internal/kibana.jsonc b/packages/core/overlays/core-overlays-browser-internal/kibana.jsonc index fd9cb866623d..fead7f3f868e 100644 --- a/packages/core/overlays/core-overlays-browser-internal/kibana.jsonc +++ b/packages/core/overlays/core-overlays-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-overlays-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/overlays/core-overlays-browser-mocks/kibana.jsonc b/packages/core/overlays/core-overlays-browser-mocks/kibana.jsonc index dfaa8ee75a3e..3794c6014eec 100644 --- a/packages/core/overlays/core-overlays-browser-mocks/kibana.jsonc +++ b/packages/core/overlays/core-overlays-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-overlays-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/overlays/core-overlays-browser/kibana.jsonc b/packages/core/overlays/core-overlays-browser/kibana.jsonc index eb984476abc6..ad152f783e2f 100644 --- a/packages/core/overlays/core-overlays-browser/kibana.jsonc +++ b/packages/core/overlays/core-overlays-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-overlays-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-base-server-internal/kibana.jsonc b/packages/core/plugins/core-plugins-base-server-internal/kibana.jsonc index a4613a19a079..179be4b6ed6b 100644 --- a/packages/core/plugins/core-plugins-base-server-internal/kibana.jsonc +++ b/packages/core/plugins/core-plugins-base-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-plugins-base-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-browser-internal/kibana.jsonc b/packages/core/plugins/core-plugins-browser-internal/kibana.jsonc index 69463787683b..df72417ca34e 100644 --- a/packages/core/plugins/core-plugins-browser-internal/kibana.jsonc +++ b/packages/core/plugins/core-plugins-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-plugins-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-browser-mocks/kibana.jsonc b/packages/core/plugins/core-plugins-browser-mocks/kibana.jsonc index ced5e9a156fd..58a19d31de97 100644 --- a/packages/core/plugins/core-plugins-browser-mocks/kibana.jsonc +++ b/packages/core/plugins/core-plugins-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-plugins-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-browser/kibana.jsonc b/packages/core/plugins/core-plugins-browser/kibana.jsonc index 75ae0e37db65..23c08b494cb7 100644 --- a/packages/core/plugins/core-plugins-browser/kibana.jsonc +++ b/packages/core/plugins/core-plugins-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-plugins-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-contracts-browser/kibana.jsonc b/packages/core/plugins/core-plugins-contracts-browser/kibana.jsonc index 4363d5a4048b..5a7508741a18 100644 --- a/packages/core/plugins/core-plugins-contracts-browser/kibana.jsonc +++ b/packages/core/plugins/core-plugins-contracts-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-plugins-contracts-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-contracts-server/kibana.jsonc b/packages/core/plugins/core-plugins-contracts-server/kibana.jsonc index dc29e2f6343b..1889795a04ee 100644 --- a/packages/core/plugins/core-plugins-contracts-server/kibana.jsonc +++ b/packages/core/plugins/core-plugins-contracts-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-plugins-contracts-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-server-internal/kibana.jsonc b/packages/core/plugins/core-plugins-server-internal/kibana.jsonc index b9e0f861ef68..a8a7613c4823 100644 --- a/packages/core/plugins/core-plugins-server-internal/kibana.jsonc +++ b/packages/core/plugins/core-plugins-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-plugins-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-server-mocks/kibana.jsonc b/packages/core/plugins/core-plugins-server-mocks/kibana.jsonc index 85cbd66366bd..f6dd7689557a 100644 --- a/packages/core/plugins/core-plugins-server-mocks/kibana.jsonc +++ b/packages/core/plugins/core-plugins-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-plugins-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/plugins/core-plugins-server/kibana.jsonc b/packages/core/plugins/core-plugins-server/kibana.jsonc index 3ecaaf32ee1c..d40afd6b8742 100644 --- a/packages/core/plugins/core-plugins-server/kibana.jsonc +++ b/packages/core/plugins/core-plugins-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-plugins-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/preboot/core-preboot-server-internal/kibana.jsonc b/packages/core/preboot/core-preboot-server-internal/kibana.jsonc index 397670fdcb6f..38b814f426ac 100644 --- a/packages/core/preboot/core-preboot-server-internal/kibana.jsonc +++ b/packages/core/preboot/core-preboot-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-preboot-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/preboot/core-preboot-server-mocks/kibana.jsonc b/packages/core/preboot/core-preboot-server-mocks/kibana.jsonc index 725c8917b5c8..bc880eecb00e 100644 --- a/packages/core/preboot/core-preboot-server-mocks/kibana.jsonc +++ b/packages/core/preboot/core-preboot-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-preboot-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/preboot/core-preboot-server/kibana.jsonc b/packages/core/preboot/core-preboot-server/kibana.jsonc index efadbe758dda..c7c1fd92d919 100644 --- a/packages/core/preboot/core-preboot-server/kibana.jsonc +++ b/packages/core/preboot/core-preboot-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-preboot-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/rendering/core-rendering-browser-internal/kibana.jsonc b/packages/core/rendering/core-rendering-browser-internal/kibana.jsonc index c1fd6e01a8e4..28e9165581d1 100644 --- a/packages/core/rendering/core-rendering-browser-internal/kibana.jsonc +++ b/packages/core/rendering/core-rendering-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-rendering-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/rendering/core-rendering-browser-mocks/kibana.jsonc b/packages/core/rendering/core-rendering-browser-mocks/kibana.jsonc index 5d2d1b81e368..55c27f146268 100644 --- a/packages/core/rendering/core-rendering-browser-mocks/kibana.jsonc +++ b/packages/core/rendering/core-rendering-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-rendering-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/rendering/core-rendering-browser/kibana.jsonc b/packages/core/rendering/core-rendering-browser/kibana.jsonc index 4b43c1186513..e869c847c715 100644 --- a/packages/core/rendering/core-rendering-browser/kibana.jsonc +++ b/packages/core/rendering/core-rendering-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-rendering-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/rendering/core-rendering-server-internal/kibana.jsonc b/packages/core/rendering/core-rendering-server-internal/kibana.jsonc index aed5dd9ca56c..10ae7e41a224 100644 --- a/packages/core/rendering/core-rendering-server-internal/kibana.jsonc +++ b/packages/core/rendering/core-rendering-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-rendering-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/rendering/core-rendering-server-mocks/kibana.jsonc b/packages/core/rendering/core-rendering-server-mocks/kibana.jsonc index 4f21e2965c05..d681c829848c 100644 --- a/packages/core/rendering/core-rendering-server-mocks/kibana.jsonc +++ b/packages/core/rendering/core-rendering-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-rendering-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/root/core-root-browser-internal/kibana.jsonc b/packages/core/root/core-root-browser-internal/kibana.jsonc index 688f03f72f9a..07b31493495b 100644 --- a/packages/core/root/core-root-browser-internal/kibana.jsonc +++ b/packages/core/root/core-root-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-root-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/root/core-root-server-internal/kibana.jsonc b/packages/core/root/core-root-server-internal/kibana.jsonc index ce17c8da954d..a9c8ea106165 100644 --- a/packages/core/root/core-root-server-internal/kibana.jsonc +++ b/packages/core/root/core-root-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-root-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-api-browser/kibana.jsonc index 03af10ac7574..183bb16ffc2f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-api-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-saved-objects-api-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-api-server-internal/kibana.jsonc index aa37176be967..4c228264bf91 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-api-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-api-server-mocks/kibana.jsonc index 6861249ad218..bf5e0073399a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-api-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-api-server/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-api-server/kibana.jsonc index f6c63453e8cd..e8f9e7d47211 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-api-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-api-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-base-server-internal/kibana.jsonc index 54d171315913..4d6555e965db 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-base-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-base-server-mocks/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-base-server-mocks/kibana.jsonc index fe587abdc39a..50e3300108d7 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-mocks/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-base-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-base-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-browser-internal/kibana.jsonc index 60e888d1b3b3..1ae4d81606e8 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-saved-objects-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-browser-mocks/kibana.jsonc index 94359faaca95..e91e555e6158 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-saved-objects-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-browser/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-browser/kibana.jsonc index 57cb6bd12f1c..f86a468762e4 100644 --- a/packages/core/saved-objects/core-saved-objects-browser/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-saved-objects-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-common/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-common/kibana.jsonc index 205503f731e7..927d99dd0eb1 100644 --- a/packages/core/saved-objects/core-saved-objects-common/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-saved-objects-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/kibana.jsonc index fe57ff428f97..d8a43d0dbaa4 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-import-export-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/kibana.jsonc index 603d097e6256..02db65f6df46 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-import-export-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-migration-server-internal/kibana.jsonc index cb6444d2841a..cf68424c1044 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-migration-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/kibana.jsonc index f892512e6564..92a097b2c781 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-migration-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-server-internal/kibana.jsonc index 5c6c74e11ac1..71871b7f5ee7 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-server-mocks/kibana.jsonc index 48db247796e9..5d2618ddc352 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-server/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-server/kibana.jsonc index 5c463bd67b91..884e084e5385 100644 --- a/packages/core/saved-objects/core-saved-objects-server/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/kibana.jsonc b/packages/core/saved-objects/core-saved-objects-utils-server/kibana.jsonc index 5a638dc512dd..dccc15bc4741 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/kibana.jsonc +++ b/packages/core/saved-objects/core-saved-objects-utils-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-saved-objects-utils-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/security/core-security-browser-internal/kibana.jsonc b/packages/core/security/core-security-browser-internal/kibana.jsonc index 74eb1bc18dea..3846992da2cf 100644 --- a/packages/core/security/core-security-browser-internal/kibana.jsonc +++ b/packages/core/security/core-security-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-security-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/security/core-security-browser-mocks/kibana.jsonc b/packages/core/security/core-security-browser-mocks/kibana.jsonc index 33682e42a043..881f31883b3b 100644 --- a/packages/core/security/core-security-browser-mocks/kibana.jsonc +++ b/packages/core/security/core-security-browser-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-security-browser-mocks", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/security/core-security-browser/kibana.jsonc b/packages/core/security/core-security-browser/kibana.jsonc index d77227239427..2e4310f0b444 100644 --- a/packages/core/security/core-security-browser/kibana.jsonc +++ b/packages/core/security/core-security-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-security-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/security/core-security-common/kibana.jsonc b/packages/core/security/core-security-common/kibana.jsonc index a72829d07924..602c9562bb04 100644 --- a/packages/core/security/core-security-common/kibana.jsonc +++ b/packages/core/security/core-security-common/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/core-security-common", - "owner": ["@elastic/kibana-core", "@elastic/kibana-security"] -} + "owner": [ + "@elastic/kibana-core", + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/security/core-security-server-internal/kibana.jsonc b/packages/core/security/core-security-server-internal/kibana.jsonc index c361f06d5798..f07315187408 100644 --- a/packages/core/security/core-security-server-internal/kibana.jsonc +++ b/packages/core/security/core-security-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-security-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/security/core-security-server-mocks/kibana.jsonc b/packages/core/security/core-security-server-mocks/kibana.jsonc index 2a523bfd2a71..0d10a0ec9831 100644 --- a/packages/core/security/core-security-server-mocks/kibana.jsonc +++ b/packages/core/security/core-security-server-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-security-server-mocks", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/security/core-security-server/kibana.jsonc b/packages/core/security/core-security-server/kibana.jsonc index bf89f90c64e7..8c4586cb82c0 100644 --- a/packages/core/security/core-security-server/kibana.jsonc +++ b/packages/core/security/core-security-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-security-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/status/core-status-common/kibana.jsonc b/packages/core/status/core-status-common/kibana.jsonc index bb40934299c7..da93f2ef012d 100644 --- a/packages/core/status/core-status-common/kibana.jsonc +++ b/packages/core/status/core-status-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-status-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/status/core-status-server-internal/kibana.jsonc b/packages/core/status/core-status-server-internal/kibana.jsonc index ee806bea35cc..1bdaf78df6e8 100644 --- a/packages/core/status/core-status-server-internal/kibana.jsonc +++ b/packages/core/status/core-status-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-status-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/status/core-status-server-mocks/kibana.jsonc b/packages/core/status/core-status-server-mocks/kibana.jsonc index 1b4ed5a6e7cd..7793e4fee4ef 100644 --- a/packages/core/status/core-status-server-mocks/kibana.jsonc +++ b/packages/core/status/core-status-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-status-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/status/core-status-server/kibana.jsonc b/packages/core/status/core-status-server/kibana.jsonc index dd7ab2ca731c..febcca81eba3 100644 --- a/packages/core/status/core-status-server/kibana.jsonc +++ b/packages/core/status/core-status-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-status-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/test-helpers/core-test-helpers-deprecations-getters/kibana.jsonc b/packages/core/test-helpers/core-test-helpers-deprecations-getters/kibana.jsonc index 58b022edc214..1a4725336043 100644 --- a/packages/core/test-helpers/core-test-helpers-deprecations-getters/kibana.jsonc +++ b/packages/core/test-helpers/core-test-helpers-deprecations-getters/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-test-helpers-deprecations-getters", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/test-helpers/core-test-helpers-http-setup-browser/kibana.jsonc b/packages/core/test-helpers/core-test-helpers-http-setup-browser/kibana.jsonc index ccb44bf669f6..1c0b916281b8 100644 --- a/packages/core/test-helpers/core-test-helpers-http-setup-browser/kibana.jsonc +++ b/packages/core/test-helpers/core-test-helpers-http-setup-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-test-helpers-http-setup-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/test-helpers/core-test-helpers-kbn-server/kibana.jsonc b/packages/core/test-helpers/core-test-helpers-kbn-server/kibana.jsonc index 38e166e1d42b..7a9a6212710b 100644 --- a/packages/core/test-helpers/core-test-helpers-kbn-server/kibana.jsonc +++ b/packages/core/test-helpers/core-test-helpers-kbn-server/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-test-helpers-kbn-server", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/core/test-helpers/core-test-helpers-model-versions/kibana.jsonc b/packages/core/test-helpers/core-test-helpers-model-versions/kibana.jsonc index d6ea333ad06f..dbd2f079e1e2 100644 --- a/packages/core/test-helpers/core-test-helpers-model-versions/kibana.jsonc +++ b/packages/core/test-helpers/core-test-helpers-model-versions/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/core-test-helpers-model-versions", - "owner": "@elastic/kibana-core", + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/core/test-helpers/core-test-helpers-so-type-serializer/kibana.jsonc b/packages/core/test-helpers/core-test-helpers-so-type-serializer/kibana.jsonc index 7c8c4da8c303..a85594e9317b 100644 --- a/packages/core/test-helpers/core-test-helpers-so-type-serializer/kibana.jsonc +++ b/packages/core/test-helpers/core-test-helpers-so-type-serializer/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-test-helpers-so-type-serializer", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/test-helpers/core-test-helpers-test-utils/kibana.jsonc b/packages/core/test-helpers/core-test-helpers-test-utils/kibana.jsonc index 3e4b11f13d95..acac0e87986e 100644 --- a/packages/core/test-helpers/core-test-helpers-test-utils/kibana.jsonc +++ b/packages/core/test-helpers/core-test-helpers-test-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-test-helpers-test-utils", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/theme/core-theme-browser-internal/kibana.jsonc b/packages/core/theme/core-theme-browser-internal/kibana.jsonc index b960ca19a162..b93b1baee9dd 100644 --- a/packages/core/theme/core-theme-browser-internal/kibana.jsonc +++ b/packages/core/theme/core-theme-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-theme-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/theme/core-theme-browser-mocks/kibana.jsonc b/packages/core/theme/core-theme-browser-mocks/kibana.jsonc index 4abe096bf952..d64c120c1013 100644 --- a/packages/core/theme/core-theme-browser-mocks/kibana.jsonc +++ b/packages/core/theme/core-theme-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-theme-browser-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/theme/core-theme-browser/kibana.jsonc b/packages/core/theme/core-theme-browser/kibana.jsonc index a83f9d100e89..19b14c9f3141 100644 --- a/packages/core/theme/core-theme-browser/kibana.jsonc +++ b/packages/core/theme/core-theme-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-theme-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/kibana.jsonc b/packages/core/ui-settings/core-ui-settings-browser-internal/kibana.jsonc index 3df21a50e4ee..0c59c532e67d 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-internal/kibana.jsonc +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-ui-settings-browser-internal", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/ui-settings/core-ui-settings-browser-mocks/kibana.jsonc b/packages/core/ui-settings/core-ui-settings-browser-mocks/kibana.jsonc index 5bad0a8987f3..9db388859a88 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-mocks/kibana.jsonc +++ b/packages/core/ui-settings/core-ui-settings-browser-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/core-ui-settings-browser-mocks", - "devOnly": true, - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/ui-settings/core-ui-settings-browser/kibana.jsonc b/packages/core/ui-settings/core-ui-settings-browser/kibana.jsonc index b659a8483b12..67cb268897ed 100644 --- a/packages/core/ui-settings/core-ui-settings-browser/kibana.jsonc +++ b/packages/core/ui-settings/core-ui-settings-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-ui-settings-browser", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/ui-settings/core-ui-settings-common/kibana.jsonc b/packages/core/ui-settings/core-ui-settings-common/kibana.jsonc index 6ecff8c3c5be..8f44aa8f7fec 100644 --- a/packages/core/ui-settings/core-ui-settings-common/kibana.jsonc +++ b/packages/core/ui-settings/core-ui-settings-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-ui-settings-common", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/kibana.jsonc b/packages/core/ui-settings/core-ui-settings-server-internal/kibana.jsonc index 7d159eb886a9..3a3b59017746 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/kibana.jsonc +++ b/packages/core/ui-settings/core-ui-settings-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-ui-settings-server-internal", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/ui-settings/core-ui-settings-server-mocks/kibana.jsonc b/packages/core/ui-settings/core-ui-settings-server-mocks/kibana.jsonc index 2db68ae26f2c..4ba4ab62c519 100644 --- a/packages/core/ui-settings/core-ui-settings-server-mocks/kibana.jsonc +++ b/packages/core/ui-settings/core-ui-settings-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-ui-settings-server-mocks", - "devOnly": true, - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/ui-settings/core-ui-settings-server/kibana.jsonc b/packages/core/ui-settings/core-ui-settings-server/kibana.jsonc index a060e4ed93cc..282250e8f8d1 100644 --- a/packages/core/ui-settings/core-ui-settings-server/kibana.jsonc +++ b/packages/core/ui-settings/core-ui-settings-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-ui-settings-server", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/usage-data/core-usage-data-base-server-internal/kibana.jsonc b/packages/core/usage-data/core-usage-data-base-server-internal/kibana.jsonc index f1dba28f7a82..97f83a41418b 100644 --- a/packages/core/usage-data/core-usage-data-base-server-internal/kibana.jsonc +++ b/packages/core/usage-data/core-usage-data-base-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-usage-data-base-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/usage-data/core-usage-data-server-internal/kibana.jsonc b/packages/core/usage-data/core-usage-data-server-internal/kibana.jsonc index 005599f12350..0a870ac820d7 100644 --- a/packages/core/usage-data/core-usage-data-server-internal/kibana.jsonc +++ b/packages/core/usage-data/core-usage-data-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-usage-data-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/usage-data/core-usage-data-server-mocks/kibana.jsonc b/packages/core/usage-data/core-usage-data-server-mocks/kibana.jsonc index 02e9d1e3a5c3..9a19614e0dce 100644 --- a/packages/core/usage-data/core-usage-data-server-mocks/kibana.jsonc +++ b/packages/core/usage-data/core-usage-data-server-mocks/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/core-usage-data-server-mocks", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/core/usage-data/core-usage-data-server/kibana.jsonc b/packages/core/usage-data/core-usage-data-server/kibana.jsonc index b90d5b3027b4..d1400215baee 100644 --- a/packages/core/usage-data/core-usage-data-server/kibana.jsonc +++ b/packages/core/usage-data/core-usage-data-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-usage-data-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/user-profile/core-user-profile-browser-internal/kibana.jsonc b/packages/core/user-profile/core-user-profile-browser-internal/kibana.jsonc index b3ffc7c2d1f7..d8df09391b65 100644 --- a/packages/core/user-profile/core-user-profile-browser-internal/kibana.jsonc +++ b/packages/core/user-profile/core-user-profile-browser-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-user-profile-browser-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/user-profile/core-user-profile-browser-mocks/kibana.jsonc b/packages/core/user-profile/core-user-profile-browser-mocks/kibana.jsonc index 557849cb6a39..4b8c6005c983 100644 --- a/packages/core/user-profile/core-user-profile-browser-mocks/kibana.jsonc +++ b/packages/core/user-profile/core-user-profile-browser-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-user-profile-browser-mocks", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/user-profile/core-user-profile-browser/kibana.jsonc b/packages/core/user-profile/core-user-profile-browser/kibana.jsonc index 01bdb62b0fd0..d93a75c531f7 100644 --- a/packages/core/user-profile/core-user-profile-browser/kibana.jsonc +++ b/packages/core/user-profile/core-user-profile-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/core-user-profile-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/user-profile/core-user-profile-common/kibana.jsonc b/packages/core/user-profile/core-user-profile-common/kibana.jsonc index 1cc049fd4171..9a7d9aa06c22 100644 --- a/packages/core/user-profile/core-user-profile-common/kibana.jsonc +++ b/packages/core/user-profile/core-user-profile-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/core-user-profile-common", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/user-profile/core-user-profile-server-internal/kibana.jsonc b/packages/core/user-profile/core-user-profile-server-internal/kibana.jsonc index 6d10f1f7b4b4..f17fc7023b25 100644 --- a/packages/core/user-profile/core-user-profile-server-internal/kibana.jsonc +++ b/packages/core/user-profile/core-user-profile-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-user-profile-server-internal", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/user-profile/core-user-profile-server-mocks/kibana.jsonc b/packages/core/user-profile/core-user-profile-server-mocks/kibana.jsonc index 9a5fd404a673..a9e76eb36335 100644 --- a/packages/core/user-profile/core-user-profile-server-mocks/kibana.jsonc +++ b/packages/core/user-profile/core-user-profile-server-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-user-profile-server-mocks", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/user-profile/core-user-profile-server/kibana.jsonc b/packages/core/user-profile/core-user-profile-server/kibana.jsonc index 4a6f847fc5ee..7c58fa07b774 100644 --- a/packages/core/user-profile/core-user-profile-server/kibana.jsonc +++ b/packages/core/user-profile/core-user-profile-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-user-profile-server", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/user-settings/core-user-settings-server-internal/kibana.jsonc b/packages/core/user-settings/core-user-settings-server-internal/kibana.jsonc index c7716aa9b61f..6ff34326cd0e 100644 --- a/packages/core/user-settings/core-user-settings-server-internal/kibana.jsonc +++ b/packages/core/user-settings/core-user-settings-server-internal/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-user-settings-server-internal", - "owner": "@elastic/kibana-security", -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/core/user-settings/core-user-settings-server-mocks/kibana.jsonc b/packages/core/user-settings/core-user-settings-server-mocks/kibana.jsonc index 9860eb095122..1660d7380393 100644 --- a/packages/core/user-settings/core-user-settings-server-mocks/kibana.jsonc +++ b/packages/core/user-settings/core-user-settings-server-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-user-settings-server-mocks", - "owner": "@elastic/kibana-security", -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/core/user-settings/core-user-settings-server/kibana.jsonc b/packages/core/user-settings/core-user-settings-server/kibana.jsonc index eac6fde03c28..30b506dddd89 100644 --- a/packages/core/user-settings/core-user-settings-server/kibana.jsonc +++ b/packages/core/user-settings/core-user-settings-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/core-user-settings-server", - "owner": "@elastic/kibana-security", -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/analytics/kibana.jsonc b/packages/deeplinks/analytics/kibana.jsonc index a171c67d3ed4..8c6a026f02cf 100644 --- a/packages/deeplinks/analytics/kibana.jsonc +++ b/packages/deeplinks/analytics/kibana.jsonc @@ -5,5 +5,7 @@ "@elastic/kibana-data-discovery", "@elastic/kibana-presentation", "@elastic/kibana-visualizations" - ] -} + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/devtools/kibana.jsonc b/packages/deeplinks/devtools/kibana.jsonc index df03b80bca7d..2ad324c7e8fe 100644 --- a/packages/deeplinks/devtools/kibana.jsonc +++ b/packages/deeplinks/devtools/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/deeplinks-devtools", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/fleet/kibana.jsonc b/packages/deeplinks/fleet/kibana.jsonc index 2190110ac778..1615a5476be5 100644 --- a/packages/deeplinks/fleet/kibana.jsonc +++ b/packages/deeplinks/fleet/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/deeplinks-fleet", - "owner": "@elastic/fleet" -} + "owner": [ + "@elastic/fleet" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/management/kibana.jsonc b/packages/deeplinks/management/kibana.jsonc index d88dfb3df234..7082fed4e18b 100644 --- a/packages/deeplinks/management/kibana.jsonc +++ b/packages/deeplinks/management/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/deeplinks-management", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/ml/kibana.jsonc b/packages/deeplinks/ml/kibana.jsonc index 912c9026a6ad..87c97f1191d4 100644 --- a/packages/deeplinks/ml/kibana.jsonc +++ b/packages/deeplinks/ml/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/deeplinks-ml", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/observability/constants.ts b/packages/deeplinks/observability/constants.ts index 25642ba69613..3dcc009481d0 100644 --- a/packages/deeplinks/observability/constants.ts +++ b/packages/deeplinks/observability/constants.ts @@ -35,3 +35,5 @@ export const OBLT_UX_APP_ID = 'ux'; export const OBLT_PROFILING_APP_ID = 'profiling'; export const INVENTORY_APP_ID = 'inventory'; + +export const STREAMS_APP_ID = 'streams'; diff --git a/packages/deeplinks/observability/deep_links.ts b/packages/deeplinks/observability/deep_links.ts index 1253b4e889fc..256350feb2e2 100644 --- a/packages/deeplinks/observability/deep_links.ts +++ b/packages/deeplinks/observability/deep_links.ts @@ -21,6 +21,7 @@ import { OBLT_UX_APP_ID, OBLT_PROFILING_APP_ID, INVENTORY_APP_ID, + STREAMS_APP_ID, } from './constants'; type LogsApp = typeof LOGS_APP_ID; @@ -36,6 +37,7 @@ type AiAssistantApp = typeof AI_ASSISTANT_APP_ID; type ObltUxApp = typeof OBLT_UX_APP_ID; type ObltProfilingApp = typeof OBLT_PROFILING_APP_ID; type InventoryApp = typeof INVENTORY_APP_ID; +type StreamsApp = typeof STREAMS_APP_ID; export type AppId = | LogsApp @@ -50,7 +52,8 @@ export type AppId = | AiAssistantApp | ObltUxApp | ObltProfilingApp - | InventoryApp; + | InventoryApp + | StreamsApp; export type LogsLinkId = 'log-categories' | 'settings' | 'anomalies' | 'stream'; @@ -83,13 +86,16 @@ export type SyntheticsLinkId = 'certificates' | 'overview'; export type ProfilingLinkId = 'stacktraces' | 'flamegraphs' | 'functions'; +export type StreamsLinkId = 'overview'; + export type LinkId = | LogsLinkId | ObservabilityOverviewLinkId | MetricsLinkId | ApmLinkId | SyntheticsLinkId - | ProfilingLinkId; + | ProfilingLinkId + | StreamsLinkId; export type DeepLinkId = | AppId @@ -99,4 +105,5 @@ export type DeepLinkId = | `${ApmApp}:${ApmLinkId}` | `${SyntheticsApp}:${SyntheticsLinkId}` | `${ObltProfilingApp}:${ProfilingLinkId}` - | `${InventoryApp}:${InventoryLinkId}`; + | `${InventoryApp}:${InventoryLinkId}` + | `${StreamsApp}:${StreamsLinkId}`; diff --git a/packages/deeplinks/observability/kibana.jsonc b/packages/deeplinks/observability/kibana.jsonc index da2c0505737a..c7d0885edf59 100644 --- a/packages/deeplinks/observability/kibana.jsonc +++ b/packages/deeplinks/observability/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/deeplinks-observability", - "owner": "@elastic/obs-ux-management-team" -} + "owner": [ + "@elastic/obs-ux-management-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/search/kibana.jsonc b/packages/deeplinks/search/kibana.jsonc index 668514b98912..28337dfceaeb 100644 --- a/packages/deeplinks/search/kibana.jsonc +++ b/packages/deeplinks/search/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/deeplinks-search", - "owner": "@elastic/search-kibana" -} + "owner": [ + "@elastic/search-kibana" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/security/deep_links.ts b/packages/deeplinks/security/deep_links.ts index 644691bd5b8b..c1d9b3b3cb6a 100644 --- a/packages/deeplinks/security/deep_links.ts +++ b/packages/deeplinks/security/deep_links.ts @@ -69,6 +69,7 @@ export enum SecurityPageName { rulesAdd = 'rules-add', rulesCreate = 'rules-create', rulesLanding = 'rules-landing', + siemMigrationsRules = 'siem_migrations-rules', /* * Warning: Computed values are not permitted in an enum with string valued members * All threat intelligence page names must match `TIPageId` in x-pack/plugins/threat_intelligence/public/common/navigation/types.ts diff --git a/packages/deeplinks/security/kibana.jsonc b/packages/deeplinks/security/kibana.jsonc index 42aefc7fdc3a..334d67a2feda 100644 --- a/packages/deeplinks/security/kibana.jsonc +++ b/packages/deeplinks/security/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/deeplinks-security", - "owner": "@elastic/security-solution" -} + "owner": [ + "@elastic/security-solution" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/deeplinks/shared/kibana.jsonc b/packages/deeplinks/shared/kibana.jsonc index c223a88e907f..9fa040345bcc 100644 --- a/packages/deeplinks/shared/kibana.jsonc +++ b/packages/deeplinks/shared/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/deeplinks-shared", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/default-nav/analytics/kibana.jsonc b/packages/default-nav/analytics/kibana.jsonc index 5974f0872d6f..16614bc609cf 100644 --- a/packages/default-nav/analytics/kibana.jsonc +++ b/packages/default-nav/analytics/kibana.jsonc @@ -5,5 +5,7 @@ "@elastic/kibana-data-discovery", "@elastic/kibana-presentation", "@elastic/kibana-visualizations" - ] -} + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/default-nav/devtools/kibana.jsonc b/packages/default-nav/devtools/kibana.jsonc index 7ed8bd537b28..8f88eaa36ad4 100644 --- a/packages/default-nav/devtools/kibana.jsonc +++ b/packages/default-nav/devtools/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/default-nav-devtools", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/default-nav/management/kibana.jsonc b/packages/default-nav/management/kibana.jsonc index f5d49d902982..0582900e9894 100644 --- a/packages/default-nav/management/kibana.jsonc +++ b/packages/default-nav/management/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/default-nav-management", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/default-nav/ml/kibana.jsonc b/packages/default-nav/ml/kibana.jsonc index a9d0a2fe4b1d..157ca0fe9611 100644 --- a/packages/default-nav/ml/kibana.jsonc +++ b/packages/default-nav/ml/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/default-nav-ml", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/home/sample_data_card/kibana.jsonc b/packages/home/sample_data_card/kibana.jsonc index 330df0f2b7cb..8fc2dc9784d1 100644 --- a/packages/home/sample_data_card/kibana.jsonc +++ b/packages/home/sample_data_card/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/home-sample-data-card", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/home/sample_data_tab/kibana.jsonc b/packages/home/sample_data_tab/kibana.jsonc index a44c2f7446c7..68d427291e60 100644 --- a/packages/home/sample_data_tab/kibana.jsonc +++ b/packages/home/sample_data_tab/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/home-sample-data-tab", - "owner": "@elastic/appex-sharedux" + "owner": [ + "@elastic/appex-sharedux" + ], + // 'home' plugin depends on it + "group": "platform", + "visibility": "shared" } diff --git a/packages/home/sample_data_types/kibana.jsonc b/packages/home/sample_data_types/kibana.jsonc index 0297a131db3b..688cd31ffd26 100644 --- a/packages/home/sample_data_types/kibana.jsonc +++ b/packages/home/sample_data_types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/home-sample-data-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-actions-types/kibana.jsonc b/packages/kbn-actions-types/kibana.jsonc index 873c4c08c09f..f73c81bad789 100644 --- a/packages/kbn-actions-types/kibana.jsonc +++ b/packages/kbn-actions-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/actions-types", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-alerting-types/kibana.jsonc b/packages/kbn-alerting-types/kibana.jsonc index 9828c7cc38f3..08dd252c70cc 100644 --- a/packages/kbn-alerting-types/kibana.jsonc +++ b/packages/kbn-alerting-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/alerting-types", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-alerts-as-data-utils/kibana.jsonc b/packages/kbn-alerts-as-data-utils/kibana.jsonc index 07e8490dde7b..711e5edadbea 100644 --- a/packages/kbn-alerts-as-data-utils/kibana.jsonc +++ b/packages/kbn-alerts-as-data-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/alerts-as-data-utils", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-alerts-grouping/kibana.jsonc b/packages/kbn-alerts-grouping/kibana.jsonc index b98a1f0779eb..1b9c259992eb 100644 --- a/packages/kbn-alerts-grouping/kibana.jsonc +++ b/packages/kbn-alerts-grouping/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/alerts-grouping", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "observability", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-alerts-ui-shared/kibana.jsonc b/packages/kbn-alerts-ui-shared/kibana.jsonc index a4cfc39e987f..a0912c56f55a 100644 --- a/packages/kbn-alerts-ui-shared/kibana.jsonc +++ b/packages/kbn-alerts-ui-shared/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/alerts-ui-shared", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-alerts-ui-shared/src/alert_filter_controls/hooks/use_filters_sync_to_local_storage.test.ts b/packages/kbn-alerts-ui-shared/src/alert_filter_controls/hooks/use_filters_sync_to_local_storage.test.ts index cb7a9b7f3c5c..dafc4e16c899 100644 --- a/packages/kbn-alerts-ui-shared/src/alert_filter_controls/hooks/use_filters_sync_to_local_storage.test.ts +++ b/packages/kbn-alerts-ui-shared/src/alert_filter_controls/hooks/use_filters_sync_to_local_storage.test.ts @@ -8,7 +8,7 @@ */ import type { ControlGroupRuntimeState } from '@kbn/controls-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, act, renderHook } from '@testing-library/react'; import { useControlGroupSyncToLocalStorage } from './use_control_group_sync_to_local_storage'; import { Storage } from '@kbn/kibana-utils-plugin/public'; @@ -34,77 +34,97 @@ describe('Filters Sync to Local Storage', () => { writable: true, }); }); + afterEach(() => { mockLocalStorage = {}; }); - it('should not be undefined if localStorage has initial value', () => { + + it('should not be undefined if localStorage has initial value', async () => { global.localStorage.setItem(TEST_STORAGE_KEY, JSON.stringify(DEFAULT_STORED_VALUE)); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useControlGroupSyncToLocalStorage({ Storage, storageKey: TEST_STORAGE_KEY, shouldSync: true, }) ); - waitForNextUpdate(); - expect(result.current.controlGroupState).toMatchObject(DEFAULT_STORED_VALUE); + await waitFor(() => + expect(result.current.controlGroupState).toMatchObject(DEFAULT_STORED_VALUE) + ); }); - it('should be undefined if localstorage as NO initial value', () => { - const { result, waitForNextUpdate } = renderHook(() => + + it('should be undefined if localstorage as NO initial value', async () => { + const { result } = renderHook(() => useControlGroupSyncToLocalStorage({ Storage, storageKey: TEST_STORAGE_KEY, shouldSync: true, }) ); - waitForNextUpdate(); - expect(result.current.controlGroupState).toBeUndefined(); - expect(result.current.setControlGroupState).toBeTruthy(); + await waitFor(() => + expect(result.current).toEqual( + expect.objectContaining({ + controlGroupState: undefined, + setControlGroupState: expect.any(Function), + }) + ) + ); }); - it('should be update values to local storage when sync is ON', () => { - const { result, waitFor } = renderHook(() => + it('should be update values to local storage when sync is ON', async () => { + const { result } = renderHook(() => useControlGroupSyncToLocalStorage({ Storage, storageKey: TEST_STORAGE_KEY, shouldSync: true, }) ); - waitFor(() => { + await waitFor(() => { expect(result.current.controlGroupState).toBeUndefined(); expect(result.current.setControlGroupState).toBeTruthy(); }); - result.current.setControlGroupState(DEFAULT_STORED_VALUE); - waitFor(() => { + act(() => { + result.current.setControlGroupState(DEFAULT_STORED_VALUE); + }); + await waitFor(() => { expect(result.current.controlGroupState).toMatchObject(DEFAULT_STORED_VALUE); expect(global.localStorage.getItem(TEST_STORAGE_KEY)).toBe( JSON.stringify(DEFAULT_STORED_VALUE) ); }); }); - it('should not update values to local storage when sync is OFF', () => { - const { waitFor, result, rerender } = renderHook(() => - useControlGroupSyncToLocalStorage({ - Storage, - storageKey: TEST_STORAGE_KEY, - shouldSync: true, - }) - ); + it('should not update values to local storage when sync is OFF', async () => { + const initialProps = { + Storage, + storageKey: TEST_STORAGE_KEY, + shouldSync: true, + }; + + const { result, rerender } = renderHook(useControlGroupSyncToLocalStorage, { + initialProps, + }); // Sync is ON - waitFor(() => { + await waitFor(() => { expect(result.current.controlGroupState).toBeUndefined(); expect(result.current.setControlGroupState).toBeTruthy(); }); - result.current.setControlGroupState(DEFAULT_STORED_VALUE); - waitFor(() => { + act(() => { + result.current.setControlGroupState(DEFAULT_STORED_VALUE); + }); + + await waitFor(() => { expect(result.current.controlGroupState).toMatchObject(DEFAULT_STORED_VALUE); }); // Sync is OFF - rerender({ storageKey: TEST_STORAGE_KEY, shouldSync: false }); - result.current.setControlGroupState(ANOTHER_SAMPLE_VALUE); - waitFor(() => { + rerender({ ...initialProps, shouldSync: false }); + + act(() => { + result.current.setControlGroupState(ANOTHER_SAMPLE_VALUE); + }); + + await waitFor(() => { expect(result.current.controlGroupState).toMatchObject(ANOTHER_SAMPLE_VALUE); // old value expect(global.localStorage.getItem(TEST_STORAGE_KEY)).toBe( diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.test.tsx index 2f4e8598a4cf..b4945f298a69 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; +import { renderHook, waitFor } from '@testing-library/react'; import { DataView } from '@kbn/data-views-plugin/common'; import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; @@ -69,7 +69,7 @@ describe('useAlertsDataView', () => { dataView: undefined, }; - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useAlertsDataView({ ...mockServices, @@ -84,7 +84,7 @@ describe('useAlertsDataView', () => { }); it('fetches indexes and fields for non-siem feature ids, returning a DataViewBase object', async () => { - const { result, waitForValueToChange } = renderHook( + const { result } = renderHook( () => useAlertsDataView({ ...mockServices, @@ -95,7 +95,7 @@ describe('useAlertsDataView', () => { } ); - await waitForValueToChange(() => result.current.isLoading, { timeout: 5000 }); + await waitFor(() => result.current.isLoading, { timeout: 5000 }); expect(mockFetchAlertsFields).toHaveBeenCalledTimes(1); expect(mockFetchAlertsIndexNames).toHaveBeenCalledTimes(1); @@ -103,7 +103,7 @@ describe('useAlertsDataView', () => { }); it('only fetches index names for the siem feature id, returning a DataView', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useAlertsDataView({ ...mockServices, featureIds: [AlertConsumers.SIEM] }), { wrapper, @@ -117,7 +117,7 @@ describe('useAlertsDataView', () => { }); it('does not fetch anything if siem and other feature ids are mixed together', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useAlertsDataView({ ...mockServices, @@ -141,7 +141,7 @@ describe('useAlertsDataView', () => { it('returns an undefined data view if any of the queries fails', async () => { mockFetchAlertsIndexNames.mockRejectedValue('error'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useAlertsDataView({ ...mockServices, featureIds: observabilityFeatureIds }), { wrapper, @@ -159,12 +159,9 @@ describe('useAlertsDataView', () => { it('shows an error toast if any of the queries fails', async () => { mockFetchAlertsIndexNames.mockRejectedValue('error'); - const { waitFor } = renderHook( - () => useAlertsDataView({ ...mockServices, featureIds: observabilityFeatureIds }), - { - wrapper, - } - ); + renderHook(() => useAlertsDataView({ ...mockServices, featureIds: observabilityFeatureIds }), { + wrapper, + }); await waitFor(() => expect(mockServices.toasts.addDanger).toHaveBeenCalled()); }); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx index b51f878592da..46768f355dbc 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import type { HttpStart } from '@kbn/core-http-browser'; import { useCreateRule } from './use_create_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.test.tsx index 3607e75bc868..39818fab28cb 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.test.tsx @@ -10,7 +10,7 @@ import React, { FC } from 'react'; import { AlertConsumers } from '@kbn/rule-data-utils'; import * as ReactQuery from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { testQueryClientConfig } from '../test_utils/test_query_client_config'; import { useFetchAlertsFieldsQuery } from './use_fetch_alerts_fields_query'; import { httpServiceMock } from '@kbn/core-http-browser-mocks'; @@ -88,35 +88,37 @@ describe('useFetchAlertsFieldsQuery', () => { }); it('should call the api only once', async () => { - const { result, rerender, waitForValueToChange } = renderHook( + const { result, rerender } = renderHook( () => useFetchAlertsFieldsQuery({ http: mockHttpClient, featureIds: ['apm'] }), { wrapper, } ); - await waitForValueToChange(() => result.current.data); - - expect(mockHttpGet).toHaveBeenCalledTimes(1); - expect(result.current.data).toEqual({ - browserFields: { fakeCategory: {} }, - fields: [ - { - name: 'fakeCategory', - }, - ], + await waitFor(() => { + expect(mockHttpGet).toHaveBeenCalledTimes(1); + expect(result.current.data).toEqual({ + browserFields: { fakeCategory: {} }, + fields: [ + { + name: 'fakeCategory', + }, + ], + }); }); rerender(); - expect(mockHttpGet).toHaveBeenCalledTimes(1); - expect(result.current.data).toEqual({ - browserFields: { fakeCategory: {} }, - fields: [ - { - name: 'fakeCategory', - }, - ], + await waitFor(() => { + expect(mockHttpGet).toHaveBeenCalledTimes(1); + expect(result.current.data).toEqual({ + browserFields: { fakeCategory: {} }, + fields: [ + { + name: 'fakeCategory', + }, + ], + }); }); }); @@ -132,8 +134,10 @@ describe('useFetchAlertsFieldsQuery', () => { } ); - expect(mockHttpGet).toHaveBeenCalledTimes(0); - expect(result.current.data).toEqual(emptyData); + await waitFor(() => { + expect(mockHttpGet).toHaveBeenCalledTimes(0); + expect(result.current.data).toEqual(emptyData); + }); }); it('should not fetch if all featureId are not valid', async () => { @@ -148,8 +152,10 @@ describe('useFetchAlertsFieldsQuery', () => { } ); - expect(mockHttpGet).toHaveBeenCalledTimes(0); - expect(result.current.data).toEqual(emptyData); + await waitFor(() => { + expect(mockHttpGet).toHaveBeenCalledTimes(0); + expect(result.current.data).toEqual(emptyData); + }); }); it('should filter out the non valid feature id', async () => { @@ -164,9 +170,11 @@ describe('useFetchAlertsFieldsQuery', () => { } ); - expect(mockHttpGet).toHaveBeenCalledTimes(1); - expect(mockHttpGet).toHaveBeenCalledWith('/internal/rac/alerts/browser_fields', { - query: { featureIds: ['apm', 'logs'] }, + await waitFor(() => { + expect(mockHttpGet).toHaveBeenCalledTimes(1); + expect(mockHttpGet).toHaveBeenCalledWith('/internal/rac/alerts/browser_fields', { + query: { featureIds: ['apm', 'logs'] }, + }); }); }); }); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.test.tsx index ab702a2ea09e..c3480a6ce267 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.test.tsx @@ -9,7 +9,7 @@ import React, { FunctionComponent } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { testQueryClientConfig } from '../test_utils/test_query_client_config'; import { useFetchAlertsIndexNamesQuery } from './use_fetch_alerts_index_names_query'; import { fetchAlertsIndexNames } from '../apis/fetch_alerts_index_names'; @@ -56,14 +56,14 @@ describe('useFetchAlertsIndexNamesQuery', () => { }); it('correctly caches the index names', async () => { - const { result, rerender, waitForValueToChange } = renderHook( + const { result, rerender } = renderHook( () => useFetchAlertsIndexNamesQuery({ http: mockHttpClient, featureIds: ['apm'] }), { wrapper, } ); - await waitForValueToChange(() => result.current.data); + await waitFor(() => result.current.data); expect(mockFetchAlertsIndexNames).toHaveBeenCalledTimes(1); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_flapping_settings.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_flapping_settings.test.tsx index 10e1869b9e64..6aad133fee5e 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_flapping_settings.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_flapping_settings.test.tsx @@ -9,7 +9,7 @@ import React, { FunctionComponent } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { testQueryClientConfig } from '../test_utils/test_query_client_config'; import { useFetchFlappingSettings } from './use_fetch_flapping_settings'; import { httpServiceMock } from '@kbn/core-http-browser-mocks'; @@ -43,12 +43,9 @@ describe('useFetchFlappingSettings', () => { }); test('should call fetchFlappingSettings with the correct parameters', async () => { - const { result, waitFor } = renderHook( - () => useFetchFlappingSettings({ http, enabled: true }), - { - wrapper, - } - ); + const { result } = renderHook(() => useFetchFlappingSettings({ http, enabled: true }), { + wrapper, + }); await waitFor(() => { return expect(result.current.isInitialLoading).toEqual(false); @@ -66,12 +63,9 @@ describe('useFetchFlappingSettings', () => { }); test('should not call fetchFlappingSettings if enabled is false', async () => { - const { result, waitFor } = renderHook( - () => useFetchFlappingSettings({ http, enabled: false }), - { - wrapper, - } - ); + const { result } = renderHook(() => useFetchFlappingSettings({ http, enabled: false }), { + wrapper, + }); await waitFor(() => { return expect(result.current.isInitialLoading).toEqual(false); @@ -82,7 +76,7 @@ describe('useFetchFlappingSettings', () => { test('should call onSuccess when the fetching was successful', async () => { const onSuccessMock = jest.fn(); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useFetchFlappingSettings({ http, enabled: true, onSuccess: onSuccessMock }), { wrapper, diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_get_alerts_group_aggregations_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_get_alerts_group_aggregations_query.test.tsx index 14aca036ed87..50dcf3d7e290 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_get_alerts_group_aggregations_query.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_get_alerts_group_aggregations_query.test.tsx @@ -9,13 +9,12 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; import type { HttpStart } from '@kbn/core-http-browser'; import { ToastsStart } from '@kbn/core-notifications-browser'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { useGetAlertsGroupAggregationsQuery } from './use_get_alerts_group_aggregations_query'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { BASE_RAC_ALERTS_API_PATH } from '../constants'; const queryClient = new QueryClient({ diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_health_check.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_health_check.test.tsx index da4d5bc3a878..ecf4ddc685e2 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_health_check.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_health_check.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import type { HttpStart } from '@kbn/core-http-browser'; import { useHealthCheck } from './use_health_check'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connector_types.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connector_types.test.tsx index 63e494ab8708..cc21ecef9745 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connector_types.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connector_types.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { httpServiceMock } from '@kbn/core/public/mocks'; import { useLoadConnectorTypes } from './use_load_connector_types'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connectors.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connectors.test.tsx index 6ab5c58cb151..7d81a67ebce9 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connectors.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_connectors.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { httpServiceMock } from '@kbn/core/public/mocks'; import { useLoadConnectors } from './use_load_connectors'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_type_aad_template_fields.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_type_aad_template_fields.test.tsx index 7d3c0815ffae..0af35ccddba4 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_type_aad_template_fields.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_type_aad_template_fields.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { httpServiceMock } from '@kbn/core/public/mocks'; import { useLoadRuleTypeAadTemplateField } from './use_load_rule_type_aad_template_fields'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx index 4f74179fa8d5..69c3234d812f 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import type { HttpStart } from '@kbn/core-http-browser'; import { useResolveRule } from './use_resolve_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx index 664a525796d4..4d1d8c93407e 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx @@ -12,7 +12,7 @@ import { of } from 'rxjs'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { IKibanaSearchResponse } from '@kbn/search-types'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import type { UseSearchAlertsQueryParams } from './use_search_alerts_query'; import { AlertsQueryContext } from '../contexts/alerts_query_context'; import { useSearchAlertsQuery } from './use_search_alerts_query'; @@ -126,126 +126,128 @@ describe('useSearchAlertsQuery', () => { }); it('returns the response correctly', async () => { - const { result, waitForValueToChange } = renderHook(() => useSearchAlertsQuery(params), { + const { result } = renderHook(() => useSearchAlertsQuery(params), { wrapper, }); - await waitForValueToChange(() => result.current.data); - expect(result.current.data).toEqual( - expect.objectContaining({ - ...expectedResponse, - alerts: [ - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', - '@timestamp': ['2022-03-22T16:48:07.518Z'], - 'host.name': ['Host-4dbzugdlqd'], - 'kibana.alert.reason': [ - 'registry event with process iexlorer.exe, by 5qcxz8o4j7 on Host-4dbzugdlqd created low alert test.', - ], - 'kibana.alert.risk_score': [21], - 'kibana.alert.rule.name': ['test'], - 'kibana.alert.severity': ['low'], - 'process.name': ['iexlorer.exe'], - 'user.name': ['5qcxz8o4j7'], - }, - { - _index: '.internal.alerts-security.alerts-default-000001', - _id: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', - '@timestamp': ['2022-03-22T16:17:50.769Z'], - 'host.name': ['Host-4dbzugdlqd'], - 'kibana.alert.reason': [ - 'network event with process iexlorer.exe, by hdgsmwj08h on Host-4dbzugdlqd created low alert test.', - ], - 'kibana.alert.risk_score': [21], - 'kibana.alert.rule.name': ['test'], - 'kibana.alert.severity': ['low'], - 'process.name': ['iexlorer.exe'], - 'user.name': ['hdgsmwj08h'], - }, - ], - total: 2, - ecsAlertsData: [ - { - kibana: { - alert: { - severity: ['low'], - risk_score: [21], - rule: { name: ['test'] }, - reason: [ - 'registry event with process iexlorer.exe, by 5qcxz8o4j7 on Host-4dbzugdlqd created low alert test.', - ], - }, - }, - process: { name: ['iexlorer.exe'] }, - '@timestamp': ['2022-03-22T16:48:07.518Z'], - user: { name: ['5qcxz8o4j7'] }, - host: { name: ['Host-4dbzugdlqd'] }, - _id: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', - _index: '.internal.alerts-security.alerts-default-000001', - }, - { - kibana: { - alert: { - severity: ['low'], - risk_score: [21], - rule: { name: ['test'] }, - reason: [ - 'network event with process iexlorer.exe, by hdgsmwj08h on Host-4dbzugdlqd created low alert test.', - ], - }, - }, - process: { name: ['iexlorer.exe'] }, - '@timestamp': ['2022-03-22T16:17:50.769Z'], - user: { name: ['hdgsmwj08h'] }, - host: { name: ['Host-4dbzugdlqd'] }, - _id: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', - _index: '.internal.alerts-security.alerts-default-000001', - }, - ], - oldAlertsData: [ - [ - { field: 'kibana.alert.severity', value: ['low'] }, - { field: 'process.name', value: ['iexlorer.exe'] }, - { field: '@timestamp', value: ['2022-03-22T16:48:07.518Z'] }, - { field: 'kibana.alert.risk_score', value: [21] }, - { field: 'kibana.alert.rule.name', value: ['test'] }, - { field: 'user.name', value: ['5qcxz8o4j7'] }, + await waitFor(() => { + expect(result.current.data).toBeDefined(); + expect(result.current.data).toEqual( + expect.objectContaining({ + ...expectedResponse, + alerts: [ { - field: 'kibana.alert.reason', - value: [ + _index: '.internal.alerts-security.alerts-default-000001', + _id: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', + '@timestamp': ['2022-03-22T16:48:07.518Z'], + 'host.name': ['Host-4dbzugdlqd'], + 'kibana.alert.reason': [ 'registry event with process iexlorer.exe, by 5qcxz8o4j7 on Host-4dbzugdlqd created low alert test.', ], + 'kibana.alert.risk_score': [21], + 'kibana.alert.rule.name': ['test'], + 'kibana.alert.severity': ['low'], + 'process.name': ['iexlorer.exe'], + 'user.name': ['5qcxz8o4j7'], }, - { field: 'host.name', value: ['Host-4dbzugdlqd'] }, { - field: '_id', - value: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', + _index: '.internal.alerts-security.alerts-default-000001', + _id: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', + '@timestamp': ['2022-03-22T16:17:50.769Z'], + 'host.name': ['Host-4dbzugdlqd'], + 'kibana.alert.reason': [ + 'network event with process iexlorer.exe, by hdgsmwj08h on Host-4dbzugdlqd created low alert test.', + ], + 'kibana.alert.risk_score': [21], + 'kibana.alert.rule.name': ['test'], + 'kibana.alert.severity': ['low'], + 'process.name': ['iexlorer.exe'], + 'user.name': ['hdgsmwj08h'], }, - { field: '_index', value: '.internal.alerts-security.alerts-default-000001' }, ], - [ - { field: 'kibana.alert.severity', value: ['low'] }, - { field: 'process.name', value: ['iexlorer.exe'] }, - { field: '@timestamp', value: ['2022-03-22T16:17:50.769Z'] }, - { field: 'kibana.alert.risk_score', value: [21] }, - { field: 'kibana.alert.rule.name', value: ['test'] }, - { field: 'user.name', value: ['hdgsmwj08h'] }, + total: 2, + ecsAlertsData: [ { - field: 'kibana.alert.reason', - value: [ - 'network event with process iexlorer.exe, by hdgsmwj08h on Host-4dbzugdlqd created low alert test.', - ], + kibana: { + alert: { + severity: ['low'], + risk_score: [21], + rule: { name: ['test'] }, + reason: [ + 'registry event with process iexlorer.exe, by 5qcxz8o4j7 on Host-4dbzugdlqd created low alert test.', + ], + }, + }, + process: { name: ['iexlorer.exe'] }, + '@timestamp': ['2022-03-22T16:48:07.518Z'], + user: { name: ['5qcxz8o4j7'] }, + host: { name: ['Host-4dbzugdlqd'] }, + _id: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', + _index: '.internal.alerts-security.alerts-default-000001', }, - { field: 'host.name', value: ['Host-4dbzugdlqd'] }, { - field: '_id', - value: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', + kibana: { + alert: { + severity: ['low'], + risk_score: [21], + rule: { name: ['test'] }, + reason: [ + 'network event with process iexlorer.exe, by hdgsmwj08h on Host-4dbzugdlqd created low alert test.', + ], + }, + }, + process: { name: ['iexlorer.exe'] }, + '@timestamp': ['2022-03-22T16:17:50.769Z'], + user: { name: ['hdgsmwj08h'] }, + host: { name: ['Host-4dbzugdlqd'] }, + _id: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', + _index: '.internal.alerts-security.alerts-default-000001', }, - { field: '_index', value: '.internal.alerts-security.alerts-default-000001' }, ], - ], - }) - ); + oldAlertsData: [ + [ + { field: 'kibana.alert.severity', value: ['low'] }, + { field: 'process.name', value: ['iexlorer.exe'] }, + { field: '@timestamp', value: ['2022-03-22T16:48:07.518Z'] }, + { field: 'kibana.alert.risk_score', value: [21] }, + { field: 'kibana.alert.rule.name', value: ['test'] }, + { field: 'user.name', value: ['5qcxz8o4j7'] }, + { + field: 'kibana.alert.reason', + value: [ + 'registry event with process iexlorer.exe, by 5qcxz8o4j7 on Host-4dbzugdlqd created low alert test.', + ], + }, + { field: 'host.name', value: ['Host-4dbzugdlqd'] }, + { + field: '_id', + value: '38dd308706a127696cc63b8f142e8e4d66f8f79bc7d491dd79a42ea4ead62dd1', + }, + { field: '_index', value: '.internal.alerts-security.alerts-default-000001' }, + ], + [ + { field: 'kibana.alert.severity', value: ['low'] }, + { field: 'process.name', value: ['iexlorer.exe'] }, + { field: '@timestamp', value: ['2022-03-22T16:17:50.769Z'] }, + { field: 'kibana.alert.risk_score', value: [21] }, + { field: 'kibana.alert.rule.name', value: ['test'] }, + { field: 'user.name', value: ['hdgsmwj08h'] }, + { + field: 'kibana.alert.reason', + value: [ + 'network event with process iexlorer.exe, by hdgsmwj08h on Host-4dbzugdlqd created low alert test.', + ], + }, + { field: 'host.name', value: ['Host-4dbzugdlqd'] }, + { + field: '_id', + value: '8361363c0db6f30ca2dfb4aeb4835e7d6ec57bc195b96d9ee5a4ead1bb9f8b86', + }, + { field: '_index', value: '.internal.alerts-security.alerts-default-000001' }, + ], + ], + }) + ); + }); }); it('returns empty placeholder data', () => { diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx index ec3579f20db5..654166f4bbac 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import type { HttpStart } from '@kbn/core-http-browser'; import { useUpdateRule } from './use_update_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.test.tsx index 834409a87f52..09be28ec1503 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.test.tsx @@ -9,7 +9,7 @@ import React, { FunctionComponent } from 'react'; import * as ReactQuery from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { testQueryClientConfig } from '../test_utils/test_query_client_config'; import { queryKeyPrefix, useVirtualDataViewQuery } from './use_virtual_data_view_query'; import { DataView } from '@kbn/data-views-plugin/common'; @@ -38,10 +38,11 @@ describe('useVirtualDataViewQuery', () => { it('does not create a data view if indexNames is empty or nullish', () => { const { rerender } = renderHook( - ({ indexNames }: React.PropsWithChildren<{ indexNames: string[] }>) => + ({ indexNames }: { indexNames?: string[] }) => useVirtualDataViewQuery({ dataViewsService: mockDataViewsService, indexNames }), { wrapper, + initialProps: {}, } ); @@ -89,7 +90,7 @@ describe('useVirtualDataViewQuery', () => { }); it('removes the data view from the instance cache on unmount', async () => { - const { result, waitForValueToChange, unmount } = renderHook( + const { result, unmount } = renderHook( () => useVirtualDataViewQuery({ dataViewsService: mockDataViewsService, @@ -100,10 +101,10 @@ describe('useVirtualDataViewQuery', () => { } ); - await waitForValueToChange(() => result.current.data); + await waitFor(() => expect(result.current.data).toBeDefined()); unmount(); - expect(mockDataViewsService.clearInstanceCache).toHaveBeenCalled(); + await waitFor(() => expect(mockDataViewsService.clearInstanceCache).toHaveBeenCalled()); }); }); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.test.tsx index f0a14ac82e4a..3dd6a6a3cee1 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/hooks/use_load_dependencies.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import type { HttpStart } from '@kbn/core-http-browser'; import type { ToastsStart } from '@kbn/core-notifications-browser'; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_system_actions_item.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_system_actions_item.test.tsx index a64dcca57387..c94062e320a8 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_system_actions_item.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_system_actions_item.test.tsx @@ -21,6 +21,7 @@ import { import { ActionTypeModel } from '../../common'; import { RuleActionsMessageProps } from './rule_actions_message'; import { RuleActionsSystemActionsItem } from './rule_actions_system_actions_item'; +import { I18nProvider } from '@kbn/i18n-react'; jest.mock('../hooks', () => ({ useRuleFormState: jest.fn(), @@ -81,7 +82,7 @@ const { validateParamsForWarnings } = jest.requireMock( '../validation/validate_params_for_warnings' ); -const mockConnectors = [getConnector('1', { id: 'action-1' })]; +const mockConnectors = [getConnector('1', { id: 'action-1', isSystemAction: true })]; const mockActionTypes = [getActionType('1')]; @@ -260,4 +261,59 @@ describe('ruleActionsSystemActionsItem', () => { expect(screen.getByText('warning message!')).toBeInTheDocument(); }); + + describe('licensing', () => { + it('should render the licensing message if the user does not have the sufficient license', async () => { + const mockConnectorsWithLicensing = [ + getConnector('1', { id: 'action-1', isSystemAction: true }), + ]; + const mockActionTypesWithLicensing = [ + getActionType('1', { + enabledInLicense: false, + minimumLicenseRequired: 'platinum' as const, + }), + ]; + + const actionTypeRegistry = new TypeRegistry(); + actionTypeRegistry.register( + getActionTypeModel('1', { + id: 'actionType-1', + validateParams: mockValidate, + }) + ); + useRuleFormState.mockReturnValue({ + plugins: { + actionTypeRegistry, + http: { + basePath: { + publicBaseUrl: 'publicUrl', + }, + }, + }, + actionsParamsErrors: {}, + selectedRuleType: { + ...ruleType, + enabledInLicense: false, + minimumLicenseRequired: 'platinum' as const, + }, + aadTemplateFields: [], + connectors: mockConnectorsWithLicensing, + connectorTypes: mockActionTypesWithLicensing, + }); + + render( + + + + ); + + expect( + await screen.findByText('This feature requires a Platinum license.') + ).toBeInTheDocument(); + }); + }); }); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_system_actions_item.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_system_actions_item.tsx index 4598d42d91aa..7432aa4c4343 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_system_actions_item.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_system_actions_item.tsx @@ -28,7 +28,7 @@ import { RuleActionParam, RuleSystemAction } from '@kbn/alerting-types'; import { SavedObjectAttribute } from '@kbn/core/types'; import { css } from '@emotion/react'; import { useRuleFormDispatch, useRuleFormState } from '../hooks'; -import { RuleFormParamsErrors } from '../../common'; +import { ActionConnector, RuleFormParamsErrors } from '../../common'; import { ACTION_ERROR_TOOLTIP, ACTION_WARNING_TITLE, @@ -38,6 +38,11 @@ import { import { RuleActionsMessage } from './rule_actions_message'; import { validateParamsForWarnings } from '../validation'; import { getAvailableActionVariables } from '../../action_variables'; +import { + IsDisabledResult, + IsEnabledResult, + checkActionFormActionTypeEnabled, +} from '../utils/check_action_type_enabled'; interface RuleActionsSystemActionsItemProps { action: RuleSystemAction; @@ -45,6 +50,64 @@ interface RuleActionsSystemActionsItemProps { producerId: string; } +interface SystemActionAccordionContentProps extends RuleActionsSystemActionsItemProps { + connector: ActionConnector; + checkEnabledResult?: IsEnabledResult | IsDisabledResult | null; + warning?: string | null; + onParamsChange: (key: string, value: RuleActionParam) => void; +} + +const SystemActionAccordionContent: React.FC = React.memo( + ({ connector, checkEnabledResult, action, index, producerId, warning, onParamsChange }) => { + const { aadTemplateFields } = useRuleFormState(); + const { euiTheme } = useEuiTheme(); + const plain = useEuiBackgroundColor('plain'); + + if (!connector || !checkEnabledResult) { + return null; + } + + if (!checkEnabledResult.isEnabled) { + return ( + + {checkEnabledResult.messageCard} + + ); + } + + return ( + + + + + + ); + } +); + export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItemProps) => { const { action, index, producerId } = props; @@ -54,7 +117,6 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem selectedRuleType, connectorTypes, connectors, - aadTemplateFields, } = useRuleFormState(); const [isOpen, setIsOpen] = useState(true); @@ -64,7 +126,6 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem const [warning, setWarning] = useState(null); const subdued = useEuiBackgroundColor('subdued'); - const plain = useEuiBackgroundColor('plain'); const { euiTheme } = useEuiTheme(); const dispatch = useRuleFormDispatch(); @@ -156,6 +217,13 @@ export const RuleActionsSystemActionsItem = (props: RuleActionsSystemActionsItem ] ); + const checkEnabledResult = useMemo(() => { + if (!actionType) { + return null; + } + return checkActionFormActionTypeEnabled(actionType, []); + }, [actionType]); + return ( } > - - - - - + ); }; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.test.tsx index d8e6380462f9..f560c3ace22a 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_form_state/rule_form_state_reducer.test.tsx @@ -8,7 +8,7 @@ */ import React, { useReducer } from 'react'; -import { act, renderHook } from '@testing-library/react-hooks/dom'; +import { renderHook, act } from '@testing-library/react'; import { ruleFormStateReducer } from './rule_form_state_reducer'; import { RuleFormState } from '../types'; import { getAction } from '../../common/test_utils/actions_test_utils'; diff --git a/packages/kbn-analytics/kibana.jsonc b/packages/kbn-analytics/kibana.jsonc index b10ca7bb960f..446bce1fcb54 100644 --- a/packages/kbn-analytics/kibana.jsonc +++ b/packages/kbn-analytics/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/analytics", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-apm-config-loader/kibana.jsonc b/packages/kbn-apm-config-loader/kibana.jsonc index 32c994307eb6..6994fd195e10 100644 --- a/packages/kbn-apm-config-loader/kibana.jsonc +++ b/packages/kbn-apm-config-loader/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-server", "id": "@kbn/apm-config-loader", - "owner": ["@elastic/kibana-core", "@vigneshshanmugam"] -} + "owner": [ + "@elastic/kibana-core", + "@vigneshshanmugam" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-apm-data-view/kibana.jsonc b/packages/kbn-apm-data-view/kibana.jsonc index 3cbeed3811d7..6608a32e123a 100644 --- a/packages/kbn-apm-data-view/kibana.jsonc +++ b/packages/kbn-apm-data-view/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/apm-data-view", - "owner": "@elastic/obs-ux-infra_services-team" -} + "owner": [ + "@elastic/obs-ux-infra_services-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-apm-synthtrace-client/kibana.jsonc b/packages/kbn-apm-synthtrace-client/kibana.jsonc index a50ec2be221d..e4c868911ef4 100644 --- a/packages/kbn-apm-synthtrace-client/kibana.jsonc +++ b/packages/kbn-apm-synthtrace-client/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "shared-common", "id": "@kbn/apm-synthtrace-client", - "devOnly": true, - "owner": ["@elastic/obs-ux-infra_services-team", "@elastic/obs-ux-logs-team"] -} + "owner": [ + "@elastic/obs-ux-infra_services-team", + "@elastic/obs-ux-logs-team" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts index 8665ef0120e2..4b316ebb822e 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts @@ -15,15 +15,16 @@ import { k8sClusterJobEntity } from './kubernetes/cluster_entity'; import { k8sCronJobEntity } from './kubernetes/cron_job_entity'; import { k8sDaemonSetEntity } from './kubernetes/daemon_set_entity'; import { k8sDeploymentEntity } from './kubernetes/deployment_entity'; -import { k8sJobSetEntity } from './kubernetes/job_set_entity'; +import { k8sJobEntity } from './kubernetes/job_entity'; import { k8sNodeEntity } from './kubernetes/node_entity'; import { k8sPodEntity } from './kubernetes/pod_entity'; import { k8sReplicaSetEntity } from './kubernetes/replica_set'; import { k8sStatefulSetEntity } from './kubernetes/stateful_set'; +import { k8sServiceEntity } from './kubernetes/service'; import { k8sContainerEntity } from './kubernetes/container_entity'; export type EntityDataStreamType = 'metrics' | 'logs' | 'traces'; -export type Schema = 'ecs' | 'semconv'; +export type Schema = 'ecs' | 'otel'; export type EntityFields = Fields & Partial<{ @@ -52,11 +53,12 @@ export const entities = { k8sCronJobEntity, k8sDaemonSetEntity, k8sDeploymentEntity, - k8sJobSetEntity, + k8sJobEntity, k8sNodeEntity, k8sPodEntity, k8sReplicaSetEntity, k8sStatefulSetEntity, + k8sServiceEntity, k8sContainerEntity, }, }; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/cluster_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/cluster_entity.ts index 9fa4c81d86ff..487ddc89a8c6 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/cluster_entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/cluster_entity.ts @@ -23,6 +23,7 @@ export function k8sClusterJobEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { + 'entity.definition_id': 'cluster', 'entity.type': 'cluster', 'orchestrator.cluster.name': name, 'entity.id': entityId, @@ -31,6 +32,7 @@ export function k8sClusterJobEntity({ } return new K8sEntity(schema, { + 'entity.definition_id': 'cluster', 'entity.type': 'cluster', 'k8s.cluster.uid': name, 'entity.id': entityId, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/container_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/container_entity.ts index b05d412b0dd5..116563e731f0 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/container_entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/container_entity.ts @@ -23,6 +23,7 @@ export function k8sContainerEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { + 'entity.definition_id': 'container', 'entity.type': 'container', 'kubernetes.container.id': id, 'entity.id': entityId, @@ -31,6 +32,7 @@ export function k8sContainerEntity({ } return new K8sEntity(schema, { + 'entity.definition_id': 'container', 'entity.type': 'container', 'container.id': id, 'entity.id': entityId, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/cron_job_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/cron_job_entity.ts index 8590378e699f..86e17b5d4cfc 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/cron_job_entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/cron_job_entity.ts @@ -27,9 +27,9 @@ export function k8sCronJobEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { - 'entity.type': 'cron_job', + 'entity.definition_id': 'cron_job', + 'entity.type': 'cronjob', 'kubernetes.cronjob.name': name, - 'kubernetes.cronjob.uid': uid, 'kubernetes.namespace': clusterName, 'entity.id': entityId, ...others, @@ -37,7 +37,8 @@ export function k8sCronJobEntity({ } return new K8sEntity(schema, { - 'entity.type': 'cron_job', + 'entity.definition_id': 'cron_job', + 'entity.type': 'cronjob', 'k8s.cronjob.name': name, 'k8s.cronjob.uid': uid, 'k8s.cluster.name': clusterName, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/daemon_set_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/daemon_set_entity.ts index 7e20b1c6d506..59fe25dedf5a 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/daemon_set_entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/daemon_set_entity.ts @@ -27,7 +27,8 @@ export function k8sDaemonSetEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { - 'entity.type': 'daemon_set', + 'entity.definition_id': 'daemon_set', + 'entity.type': 'daemonset', 'kubernetes.daemonset.name': name, 'kubernetes.daemonset.uid': uid, 'kubernetes.namespace': clusterName, @@ -37,7 +38,8 @@ export function k8sDaemonSetEntity({ } return new K8sEntity(schema, { - 'entity.type': 'daemon_set', + 'entity.definition_id': 'daemon_set', + 'entity.type': 'daemonset', 'k8s.daemonset.name': name, 'k8s.daemonset.uid': uid, 'k8s.cluster.name': clusterName, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/deployment_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/deployment_entity.ts index 7eabdd082732..bc266d554f6d 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/deployment_entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/deployment_entity.ts @@ -27,9 +27,9 @@ export function k8sDeploymentEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { + 'entity.definition_id': 'deployment', 'entity.type': 'deployment', 'kubernetes.deployment.name': name, - 'kubernetes.deployment.uid': uid, 'kubernetes.namespace': clusterName, 'entity.id': entityId, ...others, @@ -37,6 +37,7 @@ export function k8sDeploymentEntity({ } return new K8sEntity(schema, { + 'entity.definition_id': 'deployment', 'entity.type': 'deployment', 'k8s.deployment.name': name, 'k8s.deployment.uid': uid, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/index.ts index 6da1decaab9a..ba6cb1f892f4 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/index.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/index.ts @@ -12,27 +12,28 @@ import { Serializable } from '../../serializable'; const identityFieldsMap: Record> = { ecs: { - pod: ['kubernetes.pod.name'], + pod: ['kubernetes.pod.uid'], cluster: ['orchestrator.cluster.name'], - cron_job: ['kubernetes.cronjob.name'], - daemon_set: ['kubernetes.daemonset.name'], + cronjob: ['kubernetes.cronjob.name'], + daemonset: ['kubernetes.daemonset.name'], deployment: ['kubernetes.deployment.name'], job: ['kubernetes.job.name'], node: ['kubernetes.node.name'], - replica_set: ['kubernetes.replicaset.name'], - stateful_set: ['kubernetes.statefulset.name'], + replicaset: ['kubernetes.replicaset.name'], + statefulset: ['kubernetes.statefulset.name'], + service: ['kubernetes.service.name'], container: ['kubernetes.container.id'], }, - semconv: { - pod: ['k8s.pod.name'], + otel: { + pod: ['k8s.pod.uid'], cluster: ['k8s.cluster.uid'], - cron_job: ['k8s.cronjob.name'], - daemon_set: ['k8s.daemonset.name'], - deployment: ['k8s.deployment.name'], - job: ['k8s.job.name'], + cronjob: ['k8s.cronjob.uid'], + daemonset: ['k8s.daemonset.uid'], + deployment: ['k8s.deployment.uid'], + job: ['k8s.job.uid'], node: ['k8s.node.uid'], - replica_set: ['k8s.replicaset.name'], - stateful_set: ['k8s.statefulset.name'], + replicaset: ['k8s.replicaset.uid'], + statefulset: ['k8s.statefulset.uid'], container: ['container.id'], }, }; @@ -41,10 +42,17 @@ export class K8sEntity extends Serializable { constructor(schema: Schema, fields: EntityFields) { const entityType = fields['entity.type']; if (entityType === undefined) { - throw new Error(`Entity type not defined: ${entityType}`); + throw new Error(`Entity type not defined`); } - const entityTypeWithSchema = `kubernetes_${entityType}_${schema}`; + const entityDefinitionId = fields['entity.definition_id']; + if (entityDefinitionId === undefined) { + throw new Error(`Entity definition id not defined`); + } + + const entityDefinitionWithSchema = `kubernetes_${entityDefinitionId}_${ + schema === 'ecs' ? schema : 'semconv' + }`; const identityFields = identityFieldsMap[schema][entityType]; if (identityFields === undefined || identityFields.length === 0) { throw new Error( @@ -54,8 +62,8 @@ export class K8sEntity extends Serializable { super({ ...fields, - 'entity.type': entityTypeWithSchema, - 'entity.definition_id': `builtin_${entityTypeWithSchema}`, + 'entity.type': `k8s.${entityType}.${schema}`, + 'entity.definition_id': `builtin_${entityDefinitionWithSchema}`, 'entity.identity_fields': identityFields, 'entity.display_name': getDisplayName({ identityFields, fields }), 'entity.definition_version': '1.0.0', diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/job_set_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/job_entity.ts similarity index 91% rename from packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/job_set_entity.ts rename to packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/job_entity.ts index e0383563c726..007f74d8a5bb 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/job_set_entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/job_entity.ts @@ -10,7 +10,7 @@ import { Schema } from '..'; import { K8sEntity } from '.'; -export function k8sJobSetEntity({ +export function k8sJobEntity({ schema, name, uid, @@ -27,9 +27,9 @@ export function k8sJobSetEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { + 'entity.definition_id': 'job', 'entity.type': 'job', 'kubernetes.job.name': name, - 'kubernetes.job.uid': uid, 'kubernetes.namespace': clusterName, 'entity.id': entityId, ...others, @@ -37,6 +37,7 @@ export function k8sJobSetEntity({ } return new K8sEntity(schema, { + 'entity.definition_id': 'job', 'entity.type': 'job', 'k8s.job.name': name, 'k8s.job.uid': uid, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/node_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/node_entity.ts index 283df5250d41..4ab3441d7b82 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/node_entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/node_entity.ts @@ -27,9 +27,9 @@ export function k8sNodeEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { + 'entity.definition_id': 'node', 'entity.type': 'node', 'kubernetes.node.name': name, - 'kubernetes.node.uid': uid, 'kubernetes.namespace': clusterName, 'entity.id': entityId, ...others, @@ -37,6 +37,7 @@ export function k8sNodeEntity({ } return new K8sEntity(schema, { + 'entity.definition_id': 'node', 'entity.type': 'node', 'k8s.node.uid': uid, 'k8s.cluster.name': clusterName, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/pod_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/pod_entity.ts index 1b71c4e39a4f..47c24b01144f 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/pod_entity.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/pod_entity.ts @@ -27,6 +27,7 @@ export function k8sPodEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { + 'entity.definition_id': 'pod', 'entity.type': 'pod', 'kubernetes.pod.name': name, 'kubernetes.pod.uid': uid, @@ -37,6 +38,7 @@ export function k8sPodEntity({ } return new K8sEntity(schema, { + 'entity.definition_id': 'pod', 'entity.type': 'pod', 'k8s.pod.name': name, 'k8s.pod.uid': uid, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/replica_set.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/replica_set.ts index fcf20c0530c3..b98397d2bee9 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/replica_set.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/replica_set.ts @@ -27,9 +27,9 @@ export function k8sReplicaSetEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { - 'entity.type': 'replica_set', + 'entity.definition_id': 'replica_set', + 'entity.type': 'replicaset', 'kubernetes.replicaset.name': name, - 'kubernetes.replicaset.uid': uid, 'kubernetes.namespace': clusterName, 'entity.id': entityId, ...others, @@ -37,7 +37,8 @@ export function k8sReplicaSetEntity({ } return new K8sEntity(schema, { - 'entity.type': 'replica_set', + 'entity.definition_id': 'replica_set', + 'entity.type': 'replicaset', 'k8s.replicaset.name': name, 'k8s.replicaset.uid': uid, 'k8s.cluster.name': clusterName, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/service.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/service.ts new file mode 100644 index 000000000000..4793048aeee0 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/service.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Schema } from '..'; +import { K8sEntity } from '.'; + +export function k8sServiceEntity({ + schema, + name, + uid, + clusterName, + entityId, + ...others +}: { + schema: Schema; + name: string; + uid?: string; + clusterName?: string; + entityId: string; + [key: string]: any; +}) { + if (schema !== 'ecs') { + throw new Error('Schema not supported for service entity: ' + schema); + } + return new K8sEntity(schema, { + 'entity.definition_id': 'service', + 'entity.type': 'service', + 'kubernetes.service.name': name, + 'kubernetes.namespace': clusterName, + 'entity.id': entityId, + ...others, + }); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/stateful_set.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/stateful_set.ts index 58c603704ebc..1857471c1563 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/stateful_set.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/kubernetes/stateful_set.ts @@ -27,9 +27,9 @@ export function k8sStatefulSetEntity({ }) { if (schema === 'ecs') { return new K8sEntity(schema, { - 'entity.type': 'stateful_set', + 'entity.definition_id': 'stateful_set', + 'entity.type': 'statefulset', 'kubernetes.statefulset.name': name, - 'kubernetes.statefulset.uid': uid, 'kubernetes.namespace': clusterName, 'entity.id': entityId, ...others, @@ -37,7 +37,8 @@ export function k8sStatefulSetEntity({ } return new K8sEntity(schema, { - 'entity.type': 'stateful_set', + 'entity.definition_id': 'stateful_set', + 'entity.type': 'statefulset', 'k8s.statefulset.name': name, 'k8s.statefulset.uid': uid, 'k8s.cluster.name': clusterName, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts index 8b3ed0cda107..8fa7a5997f4f 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts @@ -24,6 +24,7 @@ const defaultLogsOptions: LogsOptions = { export type LogDocument = Fields & Partial<{ + _index?: string; 'input.type': string; 'log.file.path'?: string; 'service.name'?: string; @@ -74,6 +75,14 @@ export type LogDocument = Fields & svc: string; hostname: string; [LONG_FIELD_NAME]: string; + 'http.status_code'?: number; + 'http.request.method'?: string; + 'url.path'?: string; + 'process.name'?: string; + 'kubernetes.namespace'?: string; + 'kubernetes.pod.name'?: string; + 'kubernetes.container.name'?: string; + 'orchestrator.resource.name'?: string; }>; class Log extends Serializable { @@ -155,6 +164,16 @@ function create(logsOptions: LogsOptions = defaultLogsOptions): Log { ).dataset('synth'); } +function createForIndex(index: string): Log { + return new Log( + { + 'input.type': 'logs', + _index: index, + }, + defaultLogsOptions + ); +} + function createMinimal({ dataset = 'synth', namespace = 'default', @@ -176,6 +195,7 @@ function createMinimal({ export const log = { create, + createForIndex, createMinimal, }; diff --git a/packages/kbn-apm-synthtrace/kibana.jsonc b/packages/kbn-apm-synthtrace/kibana.jsonc index 9c85fa210bdd..42f6c3ed1cf7 100644 --- a/packages/kbn-apm-synthtrace/kibana.jsonc +++ b/packages/kbn-apm-synthtrace/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "shared-server", "id": "@kbn/apm-synthtrace", - "devOnly": true, - "owner": ["@elastic/obs-ux-infra_services-team", "@elastic/obs-ux-logs-team"] -} + "owner": [ + "@elastic/obs-ux-infra_services-team", + "@elastic/obs-ux-logs-team" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts b/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts index d6f899b2084a..f4646de82d19 100644 --- a/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts +++ b/packages/kbn-apm-synthtrace/src/cli/run_synthtrace.ts @@ -48,6 +48,11 @@ function options(y: Argv) { description: 'Generate and index data continuously', boolean: true, }) + .option('liveBucketSize', { + description: 'Bucket size in ms for live streaming', + default: 1000, + number: true, + }) .option('clean', { describe: 'Clean APM indices before indexing new data', default: false, diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/parse_run_cli_flags.ts b/packages/kbn-apm-synthtrace/src/cli/utils/parse_run_cli_flags.ts index d069f89b168a..1a7b82dcd364 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/parse_run_cli_flags.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/parse_run_cli_flags.ts @@ -74,7 +74,8 @@ export function parseRunCliFlags(flags: RunCliFlags) { 'concurrency', 'versionOverride', 'clean', - 'assume-package-version' + 'assume-package-version', + 'liveBucketSize' ), logLevel: parsedLogLevel, file: parsedFile, diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/start_live_data_upload.ts b/packages/kbn-apm-synthtrace/src/cli/utils/start_live_data_upload.ts index 38404be15161..9478ae8f26af 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/start_live_data_upload.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/start_live_data_upload.ts @@ -52,7 +52,7 @@ export async function startLiveDataUpload({ }); } - const bucketSizeInMs = 1000 * 60; + const bucketSizeInMs = runOptions.liveBucketSize; let requestedUntil = start; let currentStreams: PassThrough[] = []; diff --git a/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts index 4c5d17111fca..624b44eab088 100644 --- a/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts @@ -75,15 +75,13 @@ function getRoutingTransform() { return new Transform({ objectMode: true, transform(document: ESDocumentWithOperation, encoding, callback) { - const entityType: string | undefined = document['entity.type']; - if (entityType === undefined) { - throw new Error(`entity.type was not defined: ${JSON.stringify(document)}`); + const definitionId: string | undefined = document['entity.definition_id']; + if (definitionId === undefined) { + throw new Error(`entity.definition_id was not defined: ${JSON.stringify(document)}`); } - const entityIndexName = `${entityType}s`; document._action = { index: { - _index: - `.entities.v1.latest.builtin_${entityIndexName}_from_ecs_data`.toLocaleLowerCase(), + _index: `.entities.v1.latest.${definitionId}`.toLocaleLowerCase(), _id: document['entity.id'], }, }; diff --git a/packages/kbn-apm-synthtrace/src/lib/shared/data_stream_get_routing_transform.ts b/packages/kbn-apm-synthtrace/src/lib/shared/data_stream_get_routing_transform.ts index 40d1b05878c0..daa631a5ff11 100644 --- a/packages/kbn-apm-synthtrace/src/lib/shared/data_stream_get_routing_transform.ts +++ b/packages/kbn-apm-synthtrace/src/lib/shared/data_stream_get_routing_transform.ts @@ -16,7 +16,7 @@ export function getRoutingTransform(dataStreamType: string) { transform(document: ESDocumentWithOperation, encoding, callback) { if ('data_stream.dataset' in document && 'data_stream.namespace' in document) { document._index = `${dataStreamType}-${document['data_stream.dataset']}-${document['data_stream.namespace']}`; - } else { + } else if (!('_index' in document)) { throw new Error('Cannot determine index for event'); } diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/logs_mock_data.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/logs_mock_data.ts index 5f3cbd5f054d..9a8e90f295c6 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/helpers/logs_mock_data.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/helpers/logs_mock_data.ts @@ -22,12 +22,351 @@ const { // Arrays for data const LOG_LEVELS: string[] = ['FATAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE']; -const JAVA_LOG_MESSAGES = [ +export const LINUX_PROCESSES = ['cron', 'sshd', 'systemd', 'nginx', 'apache2']; + +// generate 20 short ids to cycle through +const shortIds = Array.from({ length: 20 }, (_, i) => generateShortId()); + +export function getStableShortId() { + return shortIds[Math.floor(Math.random() * shortIds.length)]; +} + +export const getLinuxMessages = () => + ({ + cron: [ + `(${moment().toISOString()}) INFO: (CRON) User ran command: '/usr/bin/backup.sh'.`, + `(${moment().toISOString()}) WARN: (CRON) Missing crontab entry for user .`, + `(${moment().toISOString()}) ERROR: (CRON) Failed to execute '/usr/bin/backup.sh'.`, + `(${moment().toISOString()}) INFO: (CRON) New cron job added for user .`, + `(${moment().toISOString()}) DEBUG: (CRON) Skipping execution of disabled job 'jobID-${getStableShortId()}'.`, + `(${moment().toISOString()}) INFO: (CRON) Daily backup completed successfully in ${Math.floor( + Math.random() * 300 + )} seconds.`, + `(${moment().toISOString()}) ERROR: (CRON) Syntax error in crontab file for user .`, + `(${moment().toISOString()}) INFO: (CRON) Purged old log files during job 'jobID-${getStableShortId()}'.`, + `(${moment().toISOString()}) WARN: (CRON) Job 'jobID-${getStableShortId()}' exceeded timeout of ${Math.floor( + Math.random() * 3600 + )} seconds.`, + `(${moment().toISOString()}) INFO: (CRON) Executing job 'jobID-${getStableShortId()}' as user .`, + ], + sshd: [ + `${moment().toISOString()} INFO: sshd[${Math.floor( + Math.random() * 10000 + )}]: Accepted password for user from ${getIpAddress()} port ${ + 1024 + Math.floor(Math.random() * 50000) + }.`, + `${moment().toISOString()} WARN: sshd[${Math.floor( + Math.random() * 10000 + )}]: Failed password attempt for user from ${getIpAddress()} port ${ + 1024 + Math.floor(Math.random() * 50000) + }.`, + `${moment().toISOString()} INFO: sshd[${Math.floor( + Math.random() * 10000 + )}]: Connection closed by ${getIpAddress()} port ${ + 1024 + Math.floor(Math.random() * 50000) + }.`, + `${moment().toISOString()} ERROR: sshd[${Math.floor( + Math.random() * 10000 + )}]: Invalid public key for user .`, + `${moment().toISOString()} INFO: sshd[${Math.floor( + Math.random() * 10000 + )}]: Starting session for user .`, + `${moment().toISOString()} WARN: sshd[${Math.floor( + Math.random() * 10000 + )}]: Too many authentication failures from ${getIpAddress()}.`, + `${moment().toISOString()} INFO: sshd[${Math.floor( + Math.random() * 10000 + )}]: User disconnected.`, + `${moment().toISOString()} ERROR: sshd[${Math.floor( + Math.random() * 10000 + )}]: Attempt to use forbidden user .`, + `${moment().toISOString()} INFO: sshd[${Math.floor( + Math.random() * 10000 + )}]: Received SIGHUP signal. Reloading configuration.`, + `${moment().toISOString()} DEBUG: sshd[${Math.floor( + Math.random() * 10000 + )}]: Monitoring connections on port 22.`, + ], + systemd: [ + `${moment().toISOString()} INFO: systemd[${Math.floor( + Math.random() * 10000 + )}]: Started service .`, + `${moment().toISOString()} ERROR: systemd[${Math.floor( + Math.random() * 10000 + )}]: Failed to start service .`, + `${moment().toISOString()} INFO: systemd[${Math.floor( + Math.random() * 10000 + )}]: Stopped service .`, + `${moment().toISOString()} DEBUG: systemd[${Math.floor( + Math.random() * 10000 + )}]: Reloading daemon configuration.`, + `${moment().toISOString()} WARN: systemd[${Math.floor( + Math.random() * 10000 + )}]: Service restarted too many times.`, + `${moment().toISOString()} INFO: systemd[${Math.floor( + Math.random() * 10000 + )}]: Mounted .`, + `${moment().toISOString()} ERROR: systemd[${Math.floor( + Math.random() * 10000 + )}]: Unit entered failed state.`, + `${moment().toISOString()} INFO: systemd[${Math.floor( + Math.random() * 10000 + )}]: Timer triggered.`, + `${moment().toISOString()} WARN: systemd[${Math.floor( + Math.random() * 10000 + )}]: Service is inactive.`, + `${moment().toISOString()} DEBUG: systemd[${Math.floor( + Math.random() * 10000 + )}]: Service received SIGTERM.`, + ], + nginx: [ + `${moment().toISOString()} INFO: nginx[${Math.floor( + Math.random() * 10000 + )}]: Access log: ${getIpAddress()} - - [${moment().format( + 'DD/MMM/YYYY:HH:mm:ss Z' + )}] "GET /path-${getStableShortId()} HTTP/1.1" ${ + 200 + Math.floor(Math.random() * 100) + } ${Math.floor(Math.random() * 10000)}.`, + `${moment().toISOString()} ERROR: nginx[${Math.floor( + Math.random() * 10000 + )}]: 502 Bad Gateway for request to /path-${getStableShortId()}.`, + `${moment().toISOString()} WARN: nginx[${Math.floor( + Math.random() * 10000 + )}]: Upstream server timed out on /path-${getStableShortId()}.`, + `${moment().toISOString()} INFO: nginx[${Math.floor( + Math.random() * 10000 + )}]: Server restarted successfully.`, + `${moment().toISOString()} DEBUG: nginx[${Math.floor( + Math.random() * 10000 + )}]: Cache hit for /path-${getStableShortId()}.`, + ], + apache2: [ + `${moment().toISOString()} INFO: apache2[${Math.floor( + Math.random() * 10000 + )}]: GET /path-${getStableShortId()} HTTP/1.1" ${ + 200 + Math.floor(Math.random() * 100) + } ${Math.floor(Math.random() * 10000)} bytes.`, + `${moment().toISOString()} ERROR: apache2[${Math.floor( + Math.random() * 10000 + )}]: 500 Internal Server Error for request /path-${getStableShortId()}.`, + `${moment().toISOString()} WARN: apache2[${Math.floor( + Math.random() * 10000 + )}]: Worker process terminated unexpectedly.`, + `${moment().toISOString()} INFO: apache2[${Math.floor( + Math.random() * 10000 + )}]: Server restarted.`, + `${moment().toISOString()} DEBUG: apache2[${Math.floor( + Math.random() * 10000 + )}]: Keep-alive timeout on connection ${Math.floor(Math.random() * 10000)}.`, + ], + } as Record); + +export const KUBERNETES_SERVICES = [ + 'auth-service', + 'payment-service', + 'inventory-service', + 'ui-service', + 'notification-service', +]; + +export const getKubernetesMessages = () => + ({ + 'auth-service': [ + `User authenticated successfully at ${moment().toISOString()}.`, + `Failed login attempt for user at ${moment().toISOString()}.`, + `Token expired for user .`, + `Session started for user .`, + `Password reset requested by user .`, + `Invalid JWT token provided for user .`, + `New user registered at ${moment().toISOString()}.`, + `MFA challenge triggered for user .`, + `MFA challenge succeeded for user .`, + `MFA challenge failed for user .`, + `Access revoked for user .`, + `User deleted their account.`, + `Permission granted for resource .`, + `Permission denied for resource .`, + `Role updated for user .`, + `User logged out.`, + `Invalid credentials provided for user .`, + `Security alert triggered at ${moment().toISOString()} for user .`, + `Session expired for user .`, + `Password changed successfully for user .`, + ], + 'payment-service': [ + `Payment of $${(Math.random() * 1000).toFixed( + 2 + )} processed successfully at ${moment().toISOString()}.`, + `Card declined for transaction .`, + `Refund initiated for transaction .`, + `Refund of $${(Math.random() * 500).toFixed(2)} processed successfully.`, + `Payment gateway timeout during transaction .`, + `Fraudulent transaction detected at ${moment().toISOString()}.`, + `Payment pending approval for transaction .`, + `Payment gateway configuration error.`, + `Payment of $${(Math.random() * 200).toFixed( + 2 + )} canceled by user .`, + `Recurring payment of $${(Math.random() * 50).toFixed(2)} initiated.`, + `Subscription for renewed successfully.`, + `Subscription for canceled.`, + `Invoice generated.`, + `Invoice sent to user .`, + `Payment method added for user .`, + `Payment method removed for user .`, + `Credit limit exceeded for user .`, + `Insufficient funds for transaction .`, + `Transaction rollback initiated for .`, + `Chargeback received for transaction .`, + ], + 'inventory-service': [ + `Stock level updated for item : ${Math.floor( + Math.random() * 500 + )} units remaining.`, + `Item added to catalog at ${moment().toISOString()}.`, + `Item removed from catalog.`, + `Stock alert for item : Low inventory (${Math.floor( + Math.random() * 20 + )} units left).`, + `Stock replenished for item .`, + `Inventory check completed for warehouse .`, + `Item flagged as discontinued.`, + `Bulk update performed on inventory.`, + `Price updated for item .`, + `Warehouse status: Operational.`, + `Warehouse reported system failure.`, + `Item backordered.`, + `New shipment received for item .`, + `Item sold out.`, + `Item marked for promotion.`, + `Warehouse restocked.`, + `Stock audit started at ${moment().toISOString()}.`, + `Inventory discrepancy reported for item .`, + `Restock delayed for item .`, + `Item reserved for order .`, + ], + 'ui-service': [ + `Page rendered successfully at ${moment().toISOString()}.`, + `User clicked button .`, + `API call to completed in ${Math.floor( + Math.random() * 300 + )}ms.`, + `UI component loaded successfully.`, + `UI component failed to load.`, + `Session timeout for user .`, + `Error rendering component : Invalid data.`, + `User navigated to .`, + `CSS stylesheet loaded.`, + `JavaScript file executed.`, + `UI error at ${moment().toISOString()}: Cannot read property 'undefined'.`, + `Form submitted by user .`, + `Dialog displayed.`, + `Modal closed by user.`, + `Drag-and-drop interaction started.`, + `Drag-and-drop interaction completed.`, + `Keyboard shortcut activated: Ctrl+${String.fromCharCode( + 65 + Math.floor(Math.random() * 26) + )}.`, + `New notification displayed to user .`, + `UI settings updated by user .`, + `User logged out from UI.`, + ], + 'notification-service': [ + `Email sent to user .`, + `Push notification delivered to user .`, + `SMS sent to phone number .`, + `Email delivery failed for user .`, + `Push notification failed for user .`, + `SMS delivery failed for phone number .`, + `User opted out of notifications.`, + `New email template created.`, + `New push notification template created.`, + `New SMS template created.`, + `Batch email sent to ${Math.floor(Math.random() * 500)} recipients.`, + `Batch push notifications sent to ${Math.floor(Math.random() * 500)} recipients.`, + `Batch SMS sent to ${Math.floor(Math.random() * 500)} recipients.`, + `Template deleted.`, + `Notification settings updated for user .`, + `Email verification sent to user .`, + `Password reset notification sent to user .`, + `Marketing email sent to user .`, + `Reminder notification sent to user .`, + `System maintenance notification sent to all users.`, + ], + } as Record); + +export const getJavaMessages = () => [ '[main] com.example1.core.ApplicationCore - Critical failure: NullPointerException encountered during startup', - '[main] com.example.service.UserService - User registration completed for userId: 12345', + '[main] com.example1.core.ApplicationCore - Application started successfully in 3456ms', + '[main] com.example1.core.ApplicationCore - Configuring bean "dataSource" of type HikariCP', + '[main] com.example1.core.ApplicationCore - Memory usage threshold exceeded. GC invoked.', + '[main] com.example1.core.ApplicationCore - Shutting down gracefully on SIGTERM', + + '[main] com.example2.service.PaymentService - Payment processed successfully for orderId: ORD-' + + generateShortId(), + '[main] com.example2.service.PaymentService - Failed to process payment for orderId: ORD-' + + generateShortId() + + '. Reason: Insufficient funds.', + '[main] com.example2.service.PaymentService - Payment gateway timeout for orderId: ORD-' + + generateShortId(), + '[main] com.example2.service.PaymentService - Initiating refund for transactionId: TXN-' + + generateShortId(), + '[main] com.example2.service.PaymentService - Payment retry attempt started for orderId: ORD-' + + generateShortId(), + '[main] com.example3.util.JsonParser - Parsing JSON response from external API', - '[main] com.example4.security.AuthManager - Unauthorized access attempt detected for userId: 67890', + '[main] com.example3.util.JsonParser - Invalid JSON encountered: {"invalid_key":"missing_value"}', + '[main] com.example3.util.JsonParser - Successfully parsed JSON for userId: ' + + Math.floor(Math.random() * 10000), + '[main] com.example3.util.JsonParser - JSON parsing failed due to org.json.JSONException: Unterminated string', + '[main] com.example3.util.JsonParser - Fallback to default configuration triggered due to parsing error', + + '[main] com.example4.security.AuthManager - Unauthorized access attempt detected for userId: ' + + Math.floor(Math.random() * 100000), + '[main] com.example4.security.AuthManager - Password updated for userId: ' + + Math.floor(Math.random() * 100000), + '[main] com.example4.security.AuthManager - User account locked after 3 failed login attempts for userId: ' + + Math.floor(Math.random() * 100000), + '[main] com.example4.security.AuthManager - Token validation failed for token: TOKEN-' + + generateShortId(), + '[main] com.example4.security.AuthManager - User session terminated for userId: ' + + Math.floor(Math.random() * 100000), + '[main] com.example5.dao.UserDao - Database query failed: java.sql.SQLException: Timeout expired', + '[main] com.example5.dao.UserDao - Retrieved 10 results for query: SELECT * FROM users WHERE status = "active"', + '[main] com.example5.dao.UserDao - Connection pool exhausted. Waiting for available connection.', + '[main] com.example5.dao.UserDao - Insert operation succeeded for userId: ' + + Math.floor(Math.random() * 100000), + '[main] com.example5.dao.UserDao - Detected stale connection. Retrying operation.', + + '[main] com.example6.metrics.MetricsCollector - Reporting CPU usage: ' + + (Math.random() * 100).toFixed(2) + + '%', + '[main] com.example6.metrics.MetricsCollector - Application uptime: ' + + Math.floor(Math.random() * 86400) + + ' seconds', + '[main] com.example6.metrics.MetricsCollector - Memory usage: Heap=128MB Non-Heap=64MB', + '[main] com.example6.metrics.MetricsCollector - GC activity detected. Time taken: ' + + Math.floor(Math.random() * 100) + + 'ms', + '[main] com.example6.metrics.MetricsCollector - Collected metrics for 15 services', + + '[main] com.example7.messaging.MessageQueue - Message published to queue "orders" with messageId: MSG-' + + generateShortId(), + '[main] com.example7.messaging.MessageQueue - Consumer failed to process messageId: MSG-' + + generateShortId() + + '. Error: NullPointerException', + '[main] com.example7.messaging.MessageQueue - Queue "notifications" has 50 pending messages', + '[main] com.example7.messaging.MessageQueue - Retrying message delivery for messageId: MSG-' + + generateShortId(), + '[main] com.example7.messaging.MessageQueue - Dead-letter queue reached maximum size. Oldest messages purged.', + + '[main] com.example8.integration.ExternalServiceClient - HTTP 200: Successfully received response from "https://api.example.com/v1/resource"', + '[main] com.example8.integration.ExternalServiceClient - HTTP 500: Internal Server Error while accessing "https://api.example.com/v1/resource"', + '[main] com.example8.integration.ExternalServiceClient - Connection timeout occurred after 30 seconds', + '[main] com.example8.integration.ExternalServiceClient - Retrying request to endpoint "https://api.example.com/v1/resource"', + '[main] com.example8.integration.ExternalServiceClient - API key validation failed for key: APIKEY-' + + generateShortId(), ]; const IP_ADDRESSES = [ @@ -71,14 +410,25 @@ export const getCloudRegion = (index?: number) => getAtIndexOrRandom(CLOUD_REGIO export const getServiceName = (index?: number) => getAtIndexOrRandom(SERVICE_NAMES, index); export const getAgentName = (index?: number) => getAtIndexOrRandom(ELASTIC_AGENT_NAMES, index); -export const getJavaLog = () => - `${moment().format('YYYY-MM-DD HH:mm:ss,SSS')} ${getAtIndexOrRandom( - LOG_LEVELS - )} ${getAtIndexOrRandom(JAVA_LOG_MESSAGES)}`; +export const getJavaLogs = () => { + const javaLogMessages = getJavaMessages(); + return getRandomRange().map( + () => + `${moment().format('YYYY-MM-DD HH:mm:ss,SSS')} ${getAtIndexOrRandom( + LOG_LEVELS + )} ${getAtIndexOrRandom(javaLogMessages)}` + ); +}; + +export function getRandomRange() { + return Array.from({ length: Math.floor(Math.random() * 1000) + 1 }).fill(null); +} -export const getWebLog = () => { - const path = `/api/${noun()}/${verb()}`; - const bytes = randomInt(100, 4000); +export const getWebLogs = () => { + return getRandomRange().map(() => { + const path = `/api/${noun()}/${verb()}`; + const bytes = randomInt(100, 4000); - return `${ipv4()} - - [${moment().toISOString()}] "${httpMethod()} ${path} HTTP/1.1" ${httpStatusCode()} ${bytes} "-" "${userAgent()}"`; + return `${ipv4()} - - [${moment().toISOString()}] "${httpMethod()} ${path} HTTP/1.1" ${httpStatusCode()} ${bytes} "-" "${userAgent()}"`; + }); }; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/k8s_entities.ts b/packages/kbn-apm-synthtrace/src/scenarios/k8s_entities.ts index 7d94cc3180a7..8300d2058982 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/k8s_entities.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/k8s_entities.ts @@ -31,6 +31,7 @@ const CRON_JOB_ENTITY_ID = generateShortId(); const CRON_JOB_UID = generateShortId(); const NODE_ENTITY_ID = generateShortId(); const NODE_UID = generateShortId(); +const SERVICE_UID = generateShortId(); const scenario: Scenario> = async (runOptions) => { const { logger } = runOptions; @@ -45,7 +46,7 @@ const scenario: Scenario> = async (runOptions) => { .interval('1m') .rate(1) .generator((timestamp) => { - return [ + const commonEntities = [ entities.k8s .k8sClusterJobEntity({ schema, @@ -99,7 +100,7 @@ const scenario: Scenario> = async (runOptions) => { }) .timestamp(timestamp), entities.k8s - .k8sJobSetEntity({ + .k8sJobEntity({ clusterName: CLUSTER_NAME, name: 'job_set_foo', schema, @@ -133,10 +134,26 @@ const scenario: Scenario> = async (runOptions) => { }) .timestamp(timestamp), ]; + + if (schema === 'ecs') { + return [ + ...commonEntities, + entities.k8s + .k8sServiceEntity({ + schema, + clusterName: CLUSTER_NAME, + name: 'my_service', + entityId: SERVICE_UID, + }) + .timestamp(timestamp), + ]; + } + + return commonEntities; }); const ecsEntities = getK8sEntitiesEvents('ecs'); - const otelEntities = getK8sEntitiesEvents('semconv'); + const otelEntities = getK8sEntitiesEvents('otel'); return [ withClient( diff --git a/packages/kbn-apm-synthtrace/src/scenarios/many_entities.ts b/packages/kbn-apm-synthtrace/src/scenarios/many_entities.ts index 8b0d2afa5a97..4aa59d3ee840 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/many_entities.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/many_entities.ts @@ -100,7 +100,7 @@ const scenario: Scenario> = async (runOptions) => { }) .timestamp(timestamp), entities.k8s - .k8sJobSetEntity({ + .k8sJobEntity({ clusterName: CLUSTER_NAME, name: 'job_set_foo', schema, @@ -137,7 +137,7 @@ const scenario: Scenario> = async (runOptions) => { }); const ecsEntities = getK8sEntitiesEvents('ecs'); - const otelEntities = getK8sEntitiesEvents('semconv'); + const otelEntities = getK8sEntitiesEvents('otel'); const synthJavaTraces = entities.serviceEntity({ serviceName: 'synth_java', agentName: ['java'], diff --git a/packages/kbn-apm-synthtrace/src/scenarios/slash_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/slash_logs.ts new file mode 100644 index 000000000000..26c998f65866 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/slash_logs.ts @@ -0,0 +1,137 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { LogDocument, generateShortId, log } from '@kbn/apm-synthtrace-client'; +import { Scenario } from '../cli/scenario'; +import { withClient } from '../lib/utils/with_client'; +import { + getAgentName, + getCloudProvider, + getCloudRegion, + getIpAddress, + getJavaLogs, + getServiceName, + getWebLogs, + getKubernetesMessages, + getLinuxMessages, + KUBERNETES_SERVICES, + getStableShortId, + getRandomRange, +} from './helpers/logs_mock_data'; +import { getAtIndexOrRandom } from './helpers/get_at_index_or_random'; + +const LINUX_PROCESSES = ['cron', 'sshd', 'systemd', 'nginx', 'apache2']; + +const scenario: Scenario = async (runOptions) => { + const constructCommonMetadata = () => ({ + 'agent.name': getAgentName(), + 'cloud.provider': getCloudProvider(), + 'cloud.region': getCloudRegion(Math.floor(Math.random() * 3)), + 'cloud.availability_zone': `${getCloudRegion(0)}a`, + 'cloud.instance.id': generateShortId(), + 'cloud.project.id': generateShortId(), + }); + + const generateNginxLogs = (timestamp: number) => { + return getWebLogs().map((message) => { + return log + .createForIndex('logs') + .setHostIp(getIpAddress()) + .message(message) + .defaults({ + ...constructCommonMetadata(), + 'log.file.path': `/var/log/nginx/access-${getStableShortId()}.log`, + }) + .timestamp(timestamp); + }); + }; + + const generateSyslogData = (timestamp: number) => { + const messages: Record = getLinuxMessages(); + + return getRandomRange().map(() => { + const processName = getAtIndexOrRandom(LINUX_PROCESSES); + const message = getAtIndexOrRandom(messages[processName]); + return log + .createForIndex('logs') + .message(message) + .setHostIp(getIpAddress()) + .defaults({ + ...constructCommonMetadata(), + 'process.name': processName, + 'log.file.path': `/var/log/${processName}.log`, + }) + .timestamp(timestamp); + }); + }; + + const generateKubernetesLogs = (timestamp: number) => { + const messages: Record = getKubernetesMessages(); + + return getRandomRange().map(() => { + const service = getAtIndexOrRandom(KUBERNETES_SERVICES); + const isStringifiedJSON = Math.random() > 0.5; + const message = isStringifiedJSON + ? JSON.stringify({ + serviceName: service, + message: getAtIndexOrRandom(messages[service]), + }) + : getAtIndexOrRandom(messages[service]); + return log + .createForIndex('logs') + .message(message) + .setHostIp(getIpAddress()) + .defaults({ + ...constructCommonMetadata(), + 'kubernetes.namespace': 'default', + 'kubernetes.pod.name': `${service}-pod-${getStableShortId()}`, + 'kubernetes.container.name': `${service}-container`, + 'orchestrator.resource.name': service, + }) + .timestamp(timestamp); + }); + }; + + const generateUnparsedJavaLogs = (timestamp: number) => { + return getJavaLogs().map((message) => { + const serviceName = getServiceName(Math.floor(Math.random() * 3)); + return log + .createForIndex('logs') + .message(message) + .defaults({ + ...constructCommonMetadata(), + 'service.name': serviceName, + }) + .timestamp(timestamp); + }); + }; + + return { + generate: ({ range, clients: { logsEsClient } }) => { + const { logger } = runOptions; + + const nginxLogs = range.interval('1m').generator(generateNginxLogs); + const syslogData = range.interval('1m').generator(generateSyslogData); + const kubernetesLogs = range.interval('1m').generator(generateKubernetesLogs); + const unparsedJavaLogs = range.interval('1m').generator(generateUnparsedJavaLogs); + + return withClient( + logsEsClient, + logger.perf('generating_messy_logs', () => [ + nginxLogs, + syslogData, + kubernetesLogs, + unparsedJavaLogs, + ]) + ); + }, + }; +}; + +export default scenario; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/unstructured_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/unstructured_logs.ts index 704cfd21bbc0..b87fd7038a7d 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/unstructured_logs.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/unstructured_logs.ts @@ -12,7 +12,7 @@ import { Scenario } from '../cli/scenario'; import { IndexTemplateName } from '../lib/logs/custom_logsdb_index_templates'; import { withClient } from '../lib/utils/with_client'; import { parseLogsScenarioOpts } from './helpers/logs_scenario_opts_parser'; -import { getJavaLog, getWebLog } from './helpers/logs_mock_data'; +import { getJavaLogs, getWebLogs } from './helpers/logs_mock_data'; const scenario: Scenario = async (runOptions) => { const { isLogsDb } = parseLogsScenarioOpts(runOptions.scenarioOpts); @@ -23,19 +23,18 @@ const scenario: Scenario = async (runOptions) => { generate: ({ range, clients: { logsEsClient } }) => { const { logger } = runOptions; - const datasetJavaLogs = (timestamp: number) => - log.create({ isLogsDb }).dataset('java').message(getJavaLog()).timestamp(timestamp); - - const datasetWebLogs = (timestamp: number) => - log.create({ isLogsDb }).dataset('web').message(getWebLog()).timestamp(timestamp); - const logs = range .interval('1m') .rate(1) .generator((timestamp) => { - return Array(200) - .fill(0) - .flatMap((_, index) => [datasetJavaLogs(timestamp), datasetWebLogs(timestamp)]); + return [ + ...getJavaLogs().map((message) => + log.create({ isLogsDb }).dataset('java').message(message).timestamp(timestamp) + ), + ...getWebLogs().map((message) => + log.create({ isLogsDb }).dataset('web').message(message).timestamp(timestamp) + ), + ]; }); return withClient( diff --git a/packages/kbn-apm-types/kibana.jsonc b/packages/kbn-apm-types/kibana.jsonc index 26b4ec0b1cf7..d93053a79eed 100644 --- a/packages/kbn-apm-types/kibana.jsonc +++ b/packages/kbn-apm-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/apm-types", - "owner": "@elastic/obs-ux-infra_services-team" -} + "owner": [ + "@elastic/obs-ux-infra_services-team" + ], + "group": "observability", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-apm-utils/kibana.jsonc b/packages/kbn-apm-utils/kibana.jsonc index 2ee2a3b45335..26ac6576006b 100644 --- a/packages/kbn-apm-utils/kibana.jsonc +++ b/packages/kbn-apm-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/apm-utils", - "owner": "@elastic/obs-ux-infra_services-team" -} + "owner": [ + "@elastic/obs-ux-infra_services-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-avc-banner/kibana.jsonc b/packages/kbn-avc-banner/kibana.jsonc index 51269b1b2e76..ae9baa80fb92 100644 --- a/packages/kbn-avc-banner/kibana.jsonc +++ b/packages/kbn-avc-banner/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/avc-banner", - "owner": "@elastic/security-defend-workflows" -} + "owner": [ + "@elastic/security-defend-workflows" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-babel-preset/styled_components_files.js b/packages/kbn-babel-preset/styled_components_files.js index bcfbc7824ce4..89b71c06d9e8 100644 --- a/packages/kbn-babel-preset/styled_components_files.js +++ b/packages/kbn-babel-preset/styled_components_files.js @@ -15,7 +15,7 @@ module.exports = { USES_STYLED_COMPONENTS: [ /packages[\/\\]kbn-ui-shared-deps-(npm|src)[\/\\]/, /src[\/\\]plugins[\/\\](kibana_react)[\/\\]/, - /x-pack[\/\\]plugins[\/\\](observability_solution\/apm|beats_management|fleet|observability_solution\/infra|lists|observability_solution\/observability|observability_solution\/observability_shared|observability_solution\/exploratory_view|observability_solution\/slo|security_solution|timelines|observability_solution\/synthetics|observability_solution\/ux|observability_solution\/uptime)[\/\\]/, + /x-pack[\/\\]plugins[\/\\](observability_solution\/apm|beats_management|fleet|observability_solution\/infra|lists|observability_solution\/observability|observability_solution\/observability_shared|observability_solution\/exploratory_view|security_solution|timelines|observability_solution\/synthetics|observability_solution\/ux|observability_solution\/uptime)[\/\\]/, /x-pack[\/\\]test[\/\\]plugin_functional[\/\\]plugins[\/\\]resolver_test[\/\\]/, /x-pack[\/\\]packages[\/\\]elastic_assistant[\/\\]/, /x-pack[\/\\]packages[\/\\]security-solution[\/\\]ecs_data_quality_dashboard[\/\\]/, diff --git a/packages/kbn-babel-register/kibana.jsonc b/packages/kbn-babel-register/kibana.jsonc index 33dd730bc10b..7dc99b1386ed 100644 --- a/packages/kbn-babel-register/kibana.jsonc +++ b/packages/kbn-babel-register/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/babel-register", - "owner": "@elastic/kibana-operations", + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-babel-transform/kibana.jsonc b/packages/kbn-babel-transform/kibana.jsonc index 72b7cf1a9cc8..85fb2734581f 100644 --- a/packages/kbn-babel-transform/kibana.jsonc +++ b/packages/kbn-babel-transform/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/babel-transform", - "owner": "@elastic/kibana-operations", + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-bfetch-error/kibana.jsonc b/packages/kbn-bfetch-error/kibana.jsonc index 2cde90d13d99..c5f0f63bc8b1 100644 --- a/packages/kbn-bfetch-error/kibana.jsonc +++ b/packages/kbn-bfetch-error/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/bfetch-error", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-calculate-width-from-char-count/kibana.jsonc b/packages/kbn-calculate-width-from-char-count/kibana.jsonc index 216b12ddeac8..61005be7ed3b 100644 --- a/packages/kbn-calculate-width-from-char-count/kibana.jsonc +++ b/packages/kbn-calculate-width-from-char-count/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/calculate-width-from-char-count", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-cases-components/kibana.jsonc b/packages/kbn-cases-components/kibana.jsonc index 8fa02ddd80eb..0e144928125d 100644 --- a/packages/kbn-cases-components/kibana.jsonc +++ b/packages/kbn-cases-components/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/cases-components", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-cbor/kibana.jsonc b/packages/kbn-cbor/kibana.jsonc index 91ecbb2d27de..ed10c6b91c6e 100644 --- a/packages/kbn-cbor/kibana.jsonc +++ b/packages/kbn-cbor/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/cbor", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-cell-actions/kibana.jsonc b/packages/kbn-cell-actions/kibana.jsonc index e1ce1385436b..08d74e045d60 100644 --- a/packages/kbn-cell-actions/kibana.jsonc +++ b/packages/kbn-cell-actions/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/cell-actions", - "owner": "@elastic/security-threat-hunting-explore" -} + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-chart-icons/kibana.jsonc b/packages/kbn-chart-icons/kibana.jsonc index 95089968838f..4e81d688be8e 100644 --- a/packages/kbn-chart-icons/kibana.jsonc +++ b/packages/kbn-chart-icons/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/chart-icons", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json index 619bdd6c2932..45313933ab1b 100644 --- a/packages/kbn-check-mappings-update-cli/current_fields.json +++ b/packages/kbn-check-mappings-update-cli/current_fields.json @@ -511,6 +511,7 @@ ], "fleet-message-signing-keys": [], "fleet-package-policies": [ + "bump_agent_policy_revision", "created_at", "created_by", "description", @@ -532,6 +533,7 @@ "revision", "secret_references", "secret_references.id", + "supports_agentless", "updated_at", "updated_by", "vars" @@ -692,6 +694,7 @@ "version" ], "ingest-package-policies": [ + "bump_agent_policy_revision", "created_at", "created_by", "description", @@ -713,6 +716,7 @@ "revision", "secret_references", "secret_references.id", + "supports_agentless", "updated_at", "updated_by", "vars" diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index d6ec30393e09..17fb2c8cff1e 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -1715,6 +1715,9 @@ }, "fleet-package-policies": { "properties": { + "bump_agent_policy_revision": { + "type": "boolean" + }, "created_at": { "type": "date" }, @@ -1783,6 +1786,9 @@ } } }, + "supports_agentless": { + "type": "boolean" + }, "updated_at": { "type": "date" }, @@ -2300,6 +2306,9 @@ }, "ingest-package-policies": { "properties": { + "bump_agent_policy_revision": { + "type": "boolean" + }, "created_at": { "type": "date" }, @@ -2368,6 +2377,9 @@ } } }, + "supports_agentless": { + "type": "boolean" + }, "updated_at": { "type": "date" }, diff --git a/packages/kbn-ci-stats-core/kibana.jsonc b/packages/kbn-ci-stats-core/kibana.jsonc index f25ef3ae32e9..6d33547cf2fc 100644 --- a/packages/kbn-ci-stats-core/kibana.jsonc +++ b/packages/kbn-ci-stats-core/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/ci-stats-core", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-ci-stats-reporter/kibana.jsonc b/packages/kbn-ci-stats-reporter/kibana.jsonc index 71eff10133dd..773911cc5b66 100644 --- a/packages/kbn-ci-stats-reporter/kibana.jsonc +++ b/packages/kbn-ci-stats-reporter/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/ci-stats-reporter", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-cli-dev-mode/src/watcher.ts b/packages/kbn-cli-dev-mode/src/watcher.ts index 3c9763e0543a..6dc11371d958 100644 --- a/packages/kbn-cli-dev-mode/src/watcher.ts +++ b/packages/kbn-cli-dev-mode/src/watcher.ts @@ -26,7 +26,8 @@ const packageMatcher = makeMatcher([ /** * Any code that is outside of a package must match this in order to trigger a restart */ -const nonPackageMatcher = makeMatcher(['config/**/*.yml']); +const nonPackageMatcher = makeMatcher(['config/**/*.yml', 'plugins/**/server/**/*']); +const staticFileMatcher = makeMatcher(['plugins/**/kibana.json']); export interface Options { enabled: boolean; @@ -87,6 +88,10 @@ export class Watcher { if (result.type === 'non-package') { return nonPackageMatcher(result.repoRel) && fire(result.repoRel); } + + if (result.type === 'static') { + return staticFileMatcher(result.repoRel) && fire(result.repoRel); + } } }, { diff --git a/packages/kbn-code-owners/kibana.jsonc b/packages/kbn-code-owners/kibana.jsonc index 66d2e57ca15c..004515ce1c14 100644 --- a/packages/kbn-code-owners/kibana.jsonc +++ b/packages/kbn-code-owners/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/code-owners", - "owner": "@elastic/appex-qa", + "owner": [ + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "private", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-coloring/kibana.jsonc b/packages/kbn-coloring/kibana.jsonc index 54d8787c964f..44a9b9b36aa3 100644 --- a/packages/kbn-coloring/kibana.jsonc +++ b/packages/kbn-coloring/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/coloring", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-config-mocks/kibana.jsonc b/packages/kbn-config-mocks/kibana.jsonc index db330e90a69f..8ea9b31d03e5 100644 --- a/packages/kbn-config-mocks/kibana.jsonc +++ b/packages/kbn-config-mocks/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/config-mocks", - "owner": "@elastic/kibana-core" + "owner": "@elastic/kibana-core", + "group": "platform", + "visibility": "private" } diff --git a/packages/kbn-config-schema/kibana.jsonc b/packages/kbn-config-schema/kibana.jsonc index 9c936a1e3fa7..b2466516f9d6 100644 --- a/packages/kbn-config-schema/kibana.jsonc +++ b/packages/kbn-config-schema/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/config-schema", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-config/kibana.jsonc b/packages/kbn-config/kibana.jsonc index f16969225e90..c365d45cc3f7 100644 --- a/packages/kbn-config/kibana.jsonc +++ b/packages/kbn-config/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/config", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-content-management-utils/kibana.jsonc b/packages/kbn-content-management-utils/kibana.jsonc index 0b0fa95451cd..3125cc30da6a 100644 --- a/packages/kbn-content-management-utils/kibana.jsonc +++ b/packages/kbn-content-management-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/content-management-utils", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-crypto-browser/kibana.jsonc b/packages/kbn-crypto-browser/kibana.jsonc index 7bcbc106f23a..024e573ad474 100644 --- a/packages/kbn-crypto-browser/kibana.jsonc +++ b/packages/kbn-crypto-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/crypto-browser", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-crypto/kibana.jsonc b/packages/kbn-crypto/kibana.jsonc index c5f3a3e89edc..3f7ca7916d56 100644 --- a/packages/kbn-crypto/kibana.jsonc +++ b/packages/kbn-crypto/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/crypto", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-custom-icons/kibana.jsonc b/packages/kbn-custom-icons/kibana.jsonc index 7bd9eaa57e87..5daa04304baf 100644 --- a/packages/kbn-custom-icons/kibana.jsonc +++ b/packages/kbn-custom-icons/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/custom-icons", - "owner": "@elastic/obs-ux-logs-team" -} + "owner": [ + "@elastic/obs-ux-logs-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-custom-integrations/kibana.jsonc b/packages/kbn-custom-integrations/kibana.jsonc index b354b8e51cfe..0cfcf40aa13b 100644 --- a/packages/kbn-custom-integrations/kibana.jsonc +++ b/packages/kbn-custom-integrations/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/custom-integrations", - "owner": "@elastic/obs-ux-logs-team" -} + "owner": [ + "@elastic/obs-ux-logs-team" + ], + "group": "observability", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-cypress-config/kibana.jsonc b/packages/kbn-cypress-config/kibana.jsonc index ff6bf9e11ade..116561218e06 100644 --- a/packages/kbn-cypress-config/kibana.jsonc +++ b/packages/kbn-cypress-config/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/cypress-config", - "owner": "@elastic/kibana-operations", + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-data-service/kibana.jsonc b/packages/kbn-data-service/kibana.jsonc index 2d7bd2197085..4122ea6f04a0 100644 --- a/packages/kbn-data-service/kibana.jsonc +++ b/packages/kbn-data-service/kibana.jsonc @@ -4,5 +4,7 @@ "owner": [ "@elastic/kibana-visualizations", "@elastic/kibana-data-discovery" - ] -} + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-data-stream-adapter/kibana.jsonc b/packages/kbn-data-stream-adapter/kibana.jsonc index 43317dca0b91..655f6671200f 100644 --- a/packages/kbn-data-stream-adapter/kibana.jsonc +++ b/packages/kbn-data-stream-adapter/kibana.jsonc @@ -1,6 +1,9 @@ { "type": "shared-server", "id": "@kbn/data-stream-adapter", - "owner": "@elastic/security-threat-hunting", - "visibility": "shared" + "owner": [ + "@elastic/security-threat-hunting" + ], + "group": "security", + "visibility": "private" } diff --git a/packages/kbn-data-view-utils/kibana.jsonc b/packages/kbn-data-view-utils/kibana.jsonc index a5bd7b958e27..259845a58550 100644 --- a/packages/kbn-data-view-utils/kibana.jsonc +++ b/packages/kbn-data-view-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/data-view-utils", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-datemath/kibana.jsonc b/packages/kbn-datemath/kibana.jsonc index 43bbf53a816e..950c06e1fcd4 100644 --- a/packages/kbn-datemath/kibana.jsonc +++ b/packages/kbn-datemath/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/datemath", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-dependency-usage/README.md b/packages/kbn-dependency-usage/README.md new file mode 100644 index 000000000000..95d0b237bb14 --- /dev/null +++ b/packages/kbn-dependency-usage/README.md @@ -0,0 +1,153 @@ + +# @kbn/dependency-usage + +A CLI tool for analyzing dependencies across packages and plugins. This tool provides commands to check dependency usage, aggregate it, debug dependency graphs, and more. + +--- + +## Table of Contents +1. [Show all packages/plugins using a dependency](#show-all-packagesplugins-using-a-dependency) +2. [Show dependencies grouped by code owner](#show-dependencies-grouped-by-code-owner) +3. [List all dependencies for a package or directory](#list-all-dependencies-for-source-directory) +4. [Group by code owner with adjustable collapse depth](#group-by-code-owner-with-adjustable-collapse-depth) +5. [Show dependencies matching a pattern](#show-dependencies-matching-a-pattern) +6. [Verbose flag to debug dependency graph issues](#verbose-flag-to-debug-dependency-graph-issues) + +--- + + +### 1. Show all packages/plugins using a specific dependency + +Use this command to list all packages or plugins within a directory that use a specified dependency. + +```sh +bash scripts/dependency_usage.sh -d -p +``` +or +```sh +bash scripts/dependency_usage.sh --dependency-name --paths +``` + +**Example**: +```sh +bash scripts/dependency_usage.sh -d rxjs -p x-pack/plugins/security_solution +``` + +- `-d rxjs`: Specifies the dependency to look for (`rxjs`). +- `-p x-pack/plugins/security_solution`: Sets the directory to search within (`x-pack/plugins/security_solution`). + +--- + +### 2. Show dependencies grouped by code owner + +Group the dependencies used within a directory by code owner. + +```sh +bash scripts/dependency_usage.sh -p -g owner +``` +or +```sh +bash scripts/dependency_usage.sh --paths --group-by owner +``` + +**Example**: +```sh +bash scripts/dependency_usage.sh -p x-pack/plugins -g owner +``` + +- `-p x-pack/plugins`: Sets the directory to scan for plugins using this dependency. +- `-g owner`: Groups results by code owner. +- **Output**: Lists all dependencies for `x-pack/plugins`, organized by code owner. + +--- + +### 3. List all dependencies for source directory + +To display all dependencies used within a specific directory. + +```sh +bash scripts/dependency_usage.sh -p +``` +or +```sh +bash scripts/dependency_usage.sh --paths +``` + +**Example**: +```sh +bash scripts/dependency_usage.sh -p x-pack/plugins/security_solution +``` + +- `-p x-pack/plugins/security_solution`: Specifies the package or directory for which to list all dependencies. +- **Output**: Lists all dependencies for `x-pack/plugins/security_solution`. + +--- + +### 4. Group by code owner with adjustable collapse depth + +When a package or plugin has multiple subteams, use the `--collapse-depth` option to control how granular the grouping by code owner should be. + +#### Detailed Subteam Grouping +Shows all subteams within `security_solution`. + +```sh +bash scripts/dependency_usage.sh -p x-pack/plugins/security_solution -g owner --collapse-depth 4 +``` + +#### Collapsed Grouping +Groups the results under a higher-level owner (e.g., `security_solution` as a single group). + +```sh +bash scripts/dependency_usage.sh -p x-pack/plugins/security_solution -g owner --collapse-depth 1 +``` + +**Explanation**: +- `-p x-pack/plugins/security_solution`: Specifies the directory to scan. +- `-g owner`: Groups results by code owner. +- `--collapse-depth`: Defines the depth for grouping, where higher numbers show more granular subteams. +- **Output**: Lists dependencies grouped by code owner at different levels of depth based on the `--collapse-depth` value. + +--- + +### 5. Show dependencies matching a pattern + +Search for dependencies that match a specific pattern (such as `react-*`) within a package and output the results to a specified file. + +```sh +bash scripts/dependency_usage.sh -p -d '' -o +``` + +**Example**: +```sh +bash scripts/dependency_usage.sh -d 'react-*' -p x-pack/plugins/security_solution -o ./tmp/results.json +``` + +- `-p x-pack/plugins/security_solution`: Specifies the directory or package to search within. +- `-d 'react-*'`: Searches for dependencies that match the pattern `react-*`. +- `-o ./tmp/results.json`: Outputs the results to a specified file (`results.json` in the `./tmp` directory). +- **Output**: Saves a list of all dependencies matching `react-*` in `x-pack/plugins/security_solution` to `./tmp/results.json`. + +--- + +### 6. Verbose flag to debug dependency graph issues + +Enable verbose mode to log additional details for debugging dependency graphs. This includes generating a non-aggregated dependency graph in `.dependency-graph-log.json`. + +```sh +bash scripts/dependency_usage.sh -p -o -v +``` + +**Example**: +```sh +bash scripts/dependency_usage.sh -p x-pack/plugins/security_solution -o ./tmp/results.json +``` +- `-p x-pack/plugins/security_solution`: Specifies the target directory or package to analyze. +- `-o ./tmp/results.json`: Saves the output to the `results.json` file in the `./tmp` directory. +- `-v`: Enables verbose mode. + +**Output**: Saves a list of all dependencies in `x-pack/plugins/security_solution` to `./tmp/results.json`. Additionally, it logs a detailed, non aggregated dependency graph to `.dependency-graph-log.json` for debugging purposes. + +--- + +For further information on additional flags and options, refer to the script's help command. + diff --git a/packages/kbn-dependency-usage/jest.config.js b/packages/kbn-dependency-usage/jest.config.js new file mode 100644 index 000000000000..4a579a5ff94b --- /dev/null +++ b/packages/kbn-dependency-usage/jest.config.js @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +/* eslint-disable no-restricted-syntax */ +export default { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-dependency-usage'], +}; diff --git a/packages/kbn-dependency-usage/kibana.jsonc b/packages/kbn-dependency-usage/kibana.jsonc new file mode 100644 index 000000000000..b5d8f6261eee --- /dev/null +++ b/packages/kbn-dependency-usage/kibana.jsonc @@ -0,0 +1,6 @@ +{ + "devOnly": true, + "type": "shared-common", + "id": "@kbn/dependency-usage", + "owner": "@elastic/kibana-security" +} diff --git a/packages/kbn-dependency-usage/package.json b/packages/kbn-dependency-usage/package.json new file mode 100644 index 000000000000..631d01202858 --- /dev/null +++ b/packages/kbn-dependency-usage/package.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "name": "@kbn/dependency-usage", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0", + "type": "module", + "exports": { + "./src/*": "./src/*" + } +} \ No newline at end of file diff --git a/packages/kbn-dependency-usage/src/cli.test.ts b/packages/kbn-dependency-usage/src/cli.test.ts new file mode 100644 index 000000000000..5e40e4e39619 --- /dev/null +++ b/packages/kbn-dependency-usage/src/cli.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { identifyDependencyUsageWithCruiser } from './dependency_graph/providers/cruiser.ts'; +import { configureYargs } from './cli'; + +jest.mock('chalk', () => ({ + green: jest.fn((str) => str), + yellow: jest.fn((str) => str), + cyan: jest.fn((str) => str), + magenta: jest.fn((str) => str), + blue: jest.fn((str) => str), + bold: { magenta: jest.fn((str) => str), blue: jest.fn((str) => str) }, +})); + +jest.mock('./dependency_graph/providers/cruiser', () => ({ + identifyDependencyUsageWithCruiser: jest.fn(), +})); + +jest.mock('./cli', () => ({ + ...jest.requireActual('./cli'), + runCLI: jest.fn(), +})); + +describe('dependency-usage CLI', () => { + const parser = configureYargs() + .fail((message: string) => { + throw new Error(message); + }) + .exitProcess(false); + + beforeEach(() => { + jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should handle verbose option', () => { + const argv = parser.parse(['--paths', './plugins', '--verbose']); + expect(argv.verbose).toBe(true); + + expect(identifyDependencyUsageWithCruiser).toHaveBeenCalledWith( + expect.any(Array), + undefined, + expect.objectContaining({ isVerbose: true }) + ); + }); + + it('should group results by specified group-by option', () => { + const argv = parser.parse(['--paths', './src', '--group-by', 'owner']); + expect(argv['group-by']).toBe('owner'); + + expect(identifyDependencyUsageWithCruiser).toHaveBeenCalledWith( + expect.any(Array), + undefined, + expect.objectContaining({ groupBy: 'owner' }) + ); + }); + + it('should use default values when optional arguments are not provided', () => { + const argv = parser.parse([]); + expect(argv.paths).toEqual(['.']); + expect(argv['dependency-name']).toBeUndefined(); + expect(argv['collapse-depth']).toBe(1); + expect(argv.verbose).toBe(false); + }); + + it('should throw an error if summary is used without dependency-name', () => { + expect(() => { + parser.parse(['--summary', '--paths', './src']); + }).toThrow('Summary option can only be used when a dependency name is provided'); + }); + + it('should validate collapse-depth as a positive integer', () => { + expect(() => { + parser.parse(['--paths', './src', '--collapse-depth', '0']); + }).toThrow('Collapse depth must be a positive integer'); + }); + + it('should output results to specified output path', () => { + const argv = parser.parse(['--paths', './src', '--output-path', './output.json']); + expect(argv['output-path']).toBe('./output.json'); + }); + + it('should print results to console if no output path is specified', () => { + const argv = parser.parse(['--paths', './src']); + expect(argv['output-path']).toBeUndefined(); + }); +}); diff --git a/packages/kbn-dependency-usage/src/cli.ts b/packages/kbn-dependency-usage/src/cli.ts new file mode 100644 index 000000000000..674150fa4d91 --- /dev/null +++ b/packages/kbn-dependency-usage/src/cli.ts @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import nodePath from 'path'; +import yargs from 'yargs'; +import chalk from 'chalk'; +import fs from 'fs'; + +import { identifyDependencyUsageWithCruiser } from './dependency_graph/providers/cruiser.ts'; + +interface CLIArgs { + dependencyName?: string; + paths: string[]; + groupBy: string; + summary: boolean; + outputPath: string; + collapseDepth: number; + tool: string; + verbose: boolean; +} + +export const configureYargs = () => { + return yargs(process.argv.slice(2)) + .command( + '*', + chalk.green('Identify the usage of a dependency in the given paths and output as JSON'), + (y) => { + y.version(false) + .option('dependency-name', { + alias: 'd', + describe: chalk.yellow('The name of the dependency to search for'), + type: 'string', + demandOption: false, + }) + .option('paths', { + alias: 'p', + describe: chalk.cyan('The paths to search within (can be multiple)'), + type: 'string', + array: true, + default: ['.'], + }) + .option('group-by', { + alias: 'g', + describe: chalk.magenta('Group results by either owner or source (package/plugin)'), + choices: ['owner', 'source'], + }) + .option('summary', { + alias: 's', + describe: chalk.magenta( + 'Output a summary instead of full details. Applies only when a dependency name is provided' + ), + type: 'boolean', + }) + .option('collapse-depth', { + alias: 'c', + describe: chalk.blue('Specify the directory depth level for collapsing'), + type: 'number', + default: 1, + }) + .option('output-path', { + alias: 'o', + describe: chalk.blue('Specify the output file to save results as JSON'), + type: 'string', + }) + .option('verbose', { + alias: 'v', + describe: chalk.blue('Outputs verbose graph details to a file'), + type: 'boolean', + default: false, + }) + .check(({ summary, dependencyName, collapseDepth }: Partial) => { + if (summary && !dependencyName) { + throw new Error('Summary option can only be used when a dependency name is provided'); + } + + if (collapseDepth !== undefined && collapseDepth <= 0) { + throw new Error('Collapse depth must be a positive integer'); + } + + return true; + }) + .example( + '--dependency-name lodash --paths ./src ./lib', + chalk.blue( + 'Searches for "lodash" usage in the ./src and ./lib directories and outputs as JSON' + ) + ); + }, + async (argv: CLIArgs) => { + const { + dependencyName, + paths, + groupBy, + summary, + collapseDepth, + outputPath, + verbose: isVerbose, + } = argv; + if (dependencyName) { + console.log( + `Searching for dependency ${chalk.bold.magenta( + dependencyName + )} in paths: ${chalk.bold.magenta(paths.join(', '))}` + ); + } else { + console.log( + `Searching for dependencies in paths: ${chalk.bold.magenta(paths.join(', '))}` + ); + } + + if (collapseDepth > 1) { + console.log(`Dependencies will be collapsed to depth: ${chalk.bold.blue(collapseDepth)}`); + } + + try { + console.log(`${chalk.bold.magenta('cruiser')} is used for building dependency graph`); + + const result = await identifyDependencyUsageWithCruiser(paths, dependencyName, { + groupBy, + summary, + collapseDepth, + isVerbose, + }); + + if (outputPath) { + const isJsonFile = nodePath.extname(outputPath) === '.json'; + const outputFile = isJsonFile + ? outputPath + : nodePath.join(outputPath, 'dependency-usage.json'); + + const outputDir = nodePath.dirname(outputFile); + + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + fs.writeFile(outputFile, JSON.stringify(result, null, 2), (err) => { + if (err) { + console.error(chalk.red(`Failed to save results to ${outputFile}: ${err.message}`)); + } else { + console.log(chalk.green(`Results successfully saved to ${outputFile}`)); + } + }); + } else { + console.log(chalk.yellow('No output file specified, displaying results below:\n')); + console.log(JSON.stringify(result, null, 2)); + } + } catch (error) { + console.error('Error fetching dependency usage:', error.message); + } + } + ) + .help(); +}; + +export const runCLI = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + configureYargs().argv; +}; + +if (!process.env.JEST_WORKER_ID) { + runCLI(); +} diff --git a/packages/kbn-dependency-usage/src/dependency_graph/common/constants.ts b/packages/kbn-dependency-usage/src/dependency_graph/common/constants.ts new file mode 100644 index 000000000000..e13a69645032 --- /dev/null +++ b/packages/kbn-dependency-usage/src/dependency_graph/common/constants.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export const aggregationGroups = [ + 'x-pack/plugins', + 'x-pack/packages', + 'src/plugins', + 'packages', + 'src', + 'x-pack/test', + 'x-pack/test_serverless', +]; + +export const excludePaths = [ + '(^|/)target($|/)', + '^kbn', + '^@kbn', + '^.buildkite', + '^docs', + '^dev_docs', + '^examples', + '^scripts', + '^bazel', + '^x-pack/examples', + '^oas_docs', + '^api_docs', + '^kbn_pm', + '^.es', + '^.codeql', + '^.github', +]; diff --git a/packages/kbn-dependency-usage/src/dependency_graph/index.ts b/packages/kbn-dependency-usage/src/dependency_graph/index.ts new file mode 100644 index 000000000000..c02e90eb1ede --- /dev/null +++ b/packages/kbn-dependency-usage/src/dependency_graph/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { identifyDependencyUsageWithCruiser } from './providers/cruiser.ts'; diff --git a/packages/kbn-dependency-usage/src/dependency_graph/providers/cruiser.test.ts b/packages/kbn-dependency-usage/src/dependency_graph/providers/cruiser.test.ts new file mode 100644 index 000000000000..ed2004c462ab --- /dev/null +++ b/packages/kbn-dependency-usage/src/dependency_graph/providers/cruiser.test.ts @@ -0,0 +1,354 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { identifyDependencyUsageWithCruiser as identifyDependencyUsage } from './cruiser.ts'; +import { cruise } from 'dependency-cruiser'; + +import * as groupBy from '../../lib/group_by_owners.ts'; +import * as groupBySource from '../../lib/group_by_source.ts'; + +const codeOwners: Record = { + 'plugins/security': ['team_security'], + 'plugins/data_visualization': ['team_visualization'], + 'plugins/data_charts': ['team_visualization'], + 'plugins/analytics': ['team_analytics'], + 'plugins/notification': ['team_alerts', 'team_notifications'], + 'plugins/security_solution/public/entity_analytics/components': ['team_security_analytics'], + 'plugins/security_solution/public/entity_analytics/components/componentA.ts': [ + 'team_security_analytics', + ], + 'plugins/security_solution/public/entity_analytics/components/componentB.ts': [ + 'team_security_analytics', + ], + 'plugins/security_solution/server/lib/analytics/analytics.ts': ['team_security_analytics'], + 'plugins/security_solution/common/api/detection_engine': ['team_security_solution'], +}; + +jest.mock('dependency-cruiser', () => ({ + cruise: jest.fn(), +})); + +const mockCruiseResult = { + output: { + summary: { + violations: [ + { + from: 'plugins/security', + to: 'node_modules/rxjs', + }, + { + from: 'plugins/data_visualization', + to: 'node_modules/rxjs', + }, + { + from: 'plugins/data_charts', + to: 'node_modules/rxjs', + }, + { + from: 'plugins/analytics', + to: 'node_modules/rxjs', + }, + { + from: 'plugins/analytics', + to: 'node_modules/@hapi/boom', + }, + ], + }, + modules: [ + { + source: 'node_modules/rxjs', + dependents: [ + 'plugins/security/server/index.ts', + 'plugins/data_charts/public/charts.ts', + 'plugins/data_visualization/public/visualization.ts', + 'plugins/data_visualization/public/ingest.ts', + 'plugins/analytics/server/analytics.ts', + ], + }, + { + source: 'node_modules/@hapi/boom', + dependents: ['plugins/analytics'], + }, + ], + }, +}; + +jest.mock('../../lib/code_owners', () => ({ + getCodeOwnersForFile: jest.fn().mockImplementation((filePath: string) => codeOwners[filePath]), + getPathsWithOwnersReversed: () => ({}), +})); + +describe('identifyDependencyUsage', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should respect collapseDepth param', async () => { + (cruise as jest.Mock).mockResolvedValue(mockCruiseResult); + + await identifyDependencyUsage([], 'rxjs', { + groupBy: 'owner', + collapseDepth: 2, + summary: false, + }); + + await identifyDependencyUsage([], undefined, { + groupBy: 'owner', + collapseDepth: 1, + summary: false, + }); + + const [, configWithDepth2] = (cruise as jest.Mock).mock.calls[0]; + const [, configWithDepth1] = (cruise as jest.Mock).mock.calls[1]; + + expect(configWithDepth2.collapse).toMatchInlineSnapshot( + `"^(x-pack/plugins|x-pack/packages|src/plugins|packages|src|x-pack/test|x-pack/test_serverless)/([^/]+)/([^/]+)"` + ); + + expect(configWithDepth1.collapse).toMatchInlineSnapshot( + `"^(x-pack/plugins|x-pack/packages|src/plugins|packages|src|x-pack/test|x-pack/test_serverless)/([^/]+)|^node_modules/(@[^/]+/[^/]+|[^/]+)"` + ); + }); + + it('should group dependencies by codeowners', async () => { + (cruise as jest.Mock).mockResolvedValue(mockCruiseResult); + const groupFilesByOwnersSpy = jest.spyOn(groupBy, 'groupFilesByOwners'); + + const result = await identifyDependencyUsage([], undefined, { + groupBy: 'owner', + collapseDepth: 1, + summary: false, + }); + + expect(cruise).toHaveBeenCalled(); + expect(groupFilesByOwnersSpy).toHaveBeenCalledWith(mockCruiseResult.output.summary.violations); + + expect(result).toEqual({ + team_security: { + modules: ['plugins/security'], + deps: ['rxjs'], + teams: ['team_security'], + }, + team_visualization: { + modules: ['plugins/data_visualization', 'plugins/data_charts'], + deps: ['rxjs'], + teams: ['team_visualization'], + }, + team_analytics: { + modules: ['plugins/analytics'], + deps: ['rxjs', '@hapi/boom'], + teams: ['team_analytics'], + }, + }); + }); + + it('should group dependencies by source directory', async () => { + (cruise as jest.Mock).mockResolvedValue(mockCruiseResult); + const groupFilesByOwnersSpy = jest.spyOn(groupBySource, 'groupBySource'); + + const result = await identifyDependencyUsage([], undefined, { + collapseDepth: 1, + summary: false, + }); + + expect(cruise).toHaveBeenCalled(); + expect(groupFilesByOwnersSpy).toHaveBeenCalledWith(mockCruiseResult.output.summary.violations); + + expect(result).toEqual({ + 'plugins/security': ['rxjs'], + 'plugins/data_visualization': ['rxjs'], + 'plugins/data_charts': ['rxjs'], + 'plugins/analytics': ['rxjs', '@hapi/boom'], + }); + }); + + it('should search for specific dependency and return full dependents list', async () => { + (cruise as jest.Mock).mockResolvedValue(mockCruiseResult); + const result = await identifyDependencyUsage([], 'rxjs', { + collapseDepth: 1, + summary: false, + }); + + expect(cruise).toHaveBeenCalled(); + + expect(result).toEqual({ + modules: [ + 'plugins/security', + 'plugins/data_visualization', + 'plugins/data_charts', + 'plugins/analytics', + ], + dependents: { + rxjs: [ + 'plugins/security/server/index.ts', + 'plugins/data_charts/public/charts.ts', + 'plugins/data_visualization/public/visualization.ts', + 'plugins/data_visualization/public/ingest.ts', + 'plugins/analytics/server/analytics.ts', + ], + }, + }); + }); + + it('should search for specific dependency and return only summary', async () => { + (cruise as jest.Mock).mockResolvedValue(mockCruiseResult); + const result = await identifyDependencyUsage([], 'rxjs', { + collapseDepth: 1, + summary: true, + }); + + expect(cruise).toHaveBeenCalled(); + + expect(result).toEqual({ + modules: [ + 'plugins/security', + 'plugins/data_visualization', + 'plugins/data_charts', + 'plugins/analytics', + ], + }); + }); + + it('should handle empty cruise result', async () => { + (cruise as jest.Mock).mockResolvedValue({ + output: { summary: { violations: [] }, modules: [] }, + }); + + const result = await identifyDependencyUsage([], undefined, { + collapseDepth: 1, + summary: false, + }); + + expect(cruise).toHaveBeenCalled(); + expect(result).toEqual({}); + }); + + it('should handle no violations', async () => { + (cruise as jest.Mock).mockResolvedValue({ + output: { summary: { violations: [] }, modules: mockCruiseResult.output.modules }, + }); + + const result = await identifyDependencyUsage([], undefined, { + collapseDepth: 1, + summary: false, + }); + + expect(cruise).toHaveBeenCalled(); + expect(result).toEqual({}); + }); + + it('should return empty structure if specific dependency name does not exist', async () => { + (cruise as jest.Mock).mockResolvedValue({ + output: { summary: { violations: [] }, modules: mockCruiseResult.output.modules }, + }); + + const result = await identifyDependencyUsage([], 'nonexistent_dependency', { + collapseDepth: 1, + summary: false, + }); + + expect(cruise).toHaveBeenCalled(); + expect(result).toEqual({ + modules: [], + dependents: {}, + }); + }); + + it('should handle unknown ownership when grouping by owner', async () => { + const customCruiseResult = { + output: { + summary: { + violations: [ + { from: 'plugins/unknown_plugin', to: 'node_modules/some_module' }, + { from: 'plugins/security', to: 'node_modules/rxjs' }, + ], + }, + modules: [], + }, + }; + (cruise as jest.Mock).mockResolvedValue(customCruiseResult); + + const result = await identifyDependencyUsage([], undefined, { + groupBy: 'owner', + collapseDepth: 1, + summary: false, + }); + + expect(cruise).toHaveBeenCalled(); + expect(result).toEqual({ + unknown: { + modules: ['plugins/unknown_plugin'], + deps: ['some_module'], + teams: ['unknown'], + }, + team_security: { + modules: ['plugins/security'], + deps: ['rxjs'], + teams: ['team_security'], + }, + }); + }); + + it('should search for specific dependency and group by owner', async () => { + const customCruiseResult = { + output: { + summary: { + violations: [ + { + from: 'plugins/security_solution/public/entity_analytics/components/componentA.ts', + to: 'node_modules/lodash/fp.js', + }, + { + from: 'plugins/security_solution/public/entity_analytics/components/componentB.ts', + to: 'node_modules/lodash/partition.js', + }, + { + from: 'plugins/security_solution/server/lib/analytics/analytics.ts', + to: 'node_modules/lodash/partition.js', + }, + { + from: 'plugins/security_solution/server/lib/analytics/analytics.ts', + to: 'node_modules/lodash/cloneDeep.js', + }, + { + from: 'plugins/security_solution/common/api/detection_engine', + to: 'node_modules/lodash/sortBy.js', + }, + ], + }, + modules: [], + }, + }; + (cruise as jest.Mock).mockResolvedValue(customCruiseResult); + + const result = await identifyDependencyUsage([], 'lodash', { + groupBy: 'owner', + collapseDepth: 3, + summary: false, + }); + + expect(cruise).toHaveBeenCalled(); + expect(result).toEqual({ + team_security_analytics: { + modules: [ + 'plugins/security_solution/public/entity_analytics/components/componentA.ts', + 'plugins/security_solution/public/entity_analytics/components/componentB.ts', + 'plugins/security_solution/server/lib/analytics/analytics.ts', + ], + deps: ['lodash/fp.js', 'lodash/partition.js', 'lodash/cloneDeep.js'], + teams: ['team_security_analytics'], + }, + team_security_solution: { + modules: ['plugins/security_solution/common/api/detection_engine'], + deps: ['lodash/sortBy.js'], + teams: ['team_security_solution'], + }, + }); + }); +}); diff --git a/packages/kbn-dependency-usage/src/dependency_graph/providers/cruiser.ts b/packages/kbn-dependency-usage/src/dependency_graph/providers/cruiser.ts new file mode 100644 index 000000000000..33817605cf11 --- /dev/null +++ b/packages/kbn-dependency-usage/src/dependency_graph/providers/cruiser.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import chalk from 'chalk'; +import { cruise } from 'dependency-cruiser'; +import fs from 'fs'; +import nodePath from 'path'; + +import { groupFilesByOwners } from '../../lib/group_by_owners.ts'; +import { groupBySource } from '../../lib/group_by_source.ts'; +import { createCollapseRegexWithDepth } from '../../lib/collapse_with_depth.ts'; +import { aggregationGroups, excludePaths } from '../common/constants.ts'; + +interface DependencyGraphOptions { + isVerbose?: boolean; + summary?: boolean; + collapseDepth: number; + groupBy?: string; +} + +type PathsToAnalyze = string[]; +type DependencyName = string | undefined; + +const invokeDependencyCruiser = async ( + paths: PathsToAnalyze, + dependencyName: DependencyName, + { summary, collapseDepth }: Omit +) => { + const collapseByNodeModule = !dependencyName || (dependencyName && summary); + const collapseByNodeModuleRegex = '^node_modules/(@[^/]+/[^/]+|[^/]+)'; + const collapseRules = [createCollapseRegexWithDepth(aggregationGroups, collapseDepth)]; + + if (collapseByNodeModule) { + collapseRules.push(collapseByNodeModuleRegex); + } + + const captureRule = dependencyName + ? { + name: `dependency-usage ${dependencyName}`, + severity: 'info', + from: { pathNot: '^node_modules' }, + to: { path: dependencyName }, + } + : { + name: 'external-deps', + severity: 'info', + from: { path: paths.map((path) => `^${path}`) }, + to: { path: '^node_modules' }, + }; + + const result = await cruise(paths, { + ruleSet: { + // @ts-ignore + forbidden: [captureRule], + }, + doNotFollow: { + path: 'node_modules', + }, + extensions: ['.ts', '.tsx'], + focus: '^node_modules', + exclude: { + path: excludePaths, + }, + onlyReachable: paths.map((path) => `^${path}`).join('|'), + includeOnly: ['^node_modules', ...paths.map((path) => `^${path}`)], + validate: true, + collapse: collapseRules.join('|'), + }); + + return result; +}; + +export async function identifyDependencyUsageWithCruiser( + paths: PathsToAnalyze, + dependencyName: string | undefined, + { groupBy, summary, collapseDepth, isVerbose }: DependencyGraphOptions +) { + const result = await invokeDependencyCruiser(paths, dependencyName, { + summary, + collapseDepth, + }); + + if (typeof result.output === 'string') { + throw new Error('Unexpected string output from cruise result'); + } + + console.log( + `${chalk.green(`Successfully`)} built dependency graph using ${chalk.bold.magenta( + 'cruiser' + )}. Analyzing...` + ); + + if (isVerbose) { + const verboseLogPath = nodePath.join(process.cwd(), '.dependency-graph-log.json'); + + fs.writeFile(verboseLogPath, JSON.stringify(result, null, 2), (err) => { + if (err) { + console.error( + chalk.red(`Failed to save dependency graph log to ${verboseLogPath}: ${err.message}`) + ); + } else { + console.log(chalk.yellow(`Dependency graph log saved to ${verboseLogPath}`)); + } + }); + } + + const { violations } = result.output.summary; + + if (groupBy === 'owner') { + return groupFilesByOwners(violations); + } + + if (dependencyName) { + const dependencyRegex = new RegExp(`node_modules/${dependencyName}`); + + const dependentsList = result.output.modules.reduce>( + (acc, { source, dependents }) => { + if (!dependencyRegex.test(source)) { + return acc; + } + + const transformedDependencyName = source.split('/')[1]; + if (!acc[transformedDependencyName]) { + acc[transformedDependencyName] = []; + } + + acc[transformedDependencyName].push(...dependents); + + return acc; + }, + {} + ); + + return { + modules: [...new Set(violations.map(({ from }) => from))], + ...(!summary && { dependents: dependentsList }), + }; + } + + return groupBySource(violations); +} diff --git a/packages/kbn-dependency-usage/src/lib/code_owners.test.ts b/packages/kbn-dependency-usage/src/lib/code_owners.test.ts new file mode 100644 index 000000000000..e9c5c63ba2f9 --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/code_owners.test.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { getCodeOwnersForFile, PathWithOwners } from './code_owners'; + +describe('getCodeOwnersForFile', () => { + it('should return teams for exact file match', () => { + const reversedCodeowners = [ + { + path: 'src/file1.js', + teams: ['team_a'], + ignorePattern: { + test: (filePath: string) => ({ ignored: filePath === 'src/file1.js' }), + }, + }, + ] as PathWithOwners[]; + + const result = getCodeOwnersForFile('src/file1.js', reversedCodeowners); + expect(result).toEqual(['team_a']); + }); + + it('should return "unknown" if no ownership is found', () => { + const reversedCodeowners = [ + { + path: 'src/file1.js', + teams: ['team_a'], + ignorePattern: { test: (filePath: string) => ({ ignored: filePath === 'src/file1.js' }) }, + }, + ] as PathWithOwners[]; + + const result = getCodeOwnersForFile('src/unknown_file.js', reversedCodeowners); + expect(result).toEqual(['unknown']); + }); + + it('should return teams for partial match if no exact match exists', () => { + const reversedCodeowners = [ + { + path: 'src/folder', + teams: ['team_c'], + ignorePattern: { + test: (filePath: string) => ({ ignored: filePath.startsWith('src/folder') }), + }, + }, + ] as PathWithOwners[]; + + const result = getCodeOwnersForFile('src/folder/subfolder/file.js', reversedCodeowners); + expect(result).toEqual(['team_c']); + }); + + it('should handle root directory without ownership but with subdirectory owners', () => { + const reversedCodeowners = [ + { + path: 'folder/some/test', + teams: ['team_a'], + ignorePattern: { + test: (filePath: string) => ({ ignored: filePath.startsWith('folder/some/test') }), + }, + }, + { + path: 'folder/another/test', + teams: ['team_b'], + ignorePattern: { + test: (filePath: string) => ({ ignored: filePath.startsWith('folder/another/test') }), + }, + }, + ] as PathWithOwners[]; + + const result = getCodeOwnersForFile('folder', reversedCodeowners); + expect(result).toEqual(['team_a', 'team_b']); + }); + + it('should return all unique teams if multiple subdirectories match', () => { + const reversedCodeowners = [ + { + path: 'folder/some/test', + teams: ['team_a'], + ignorePattern: { + test: (filePath: string) => ({ ignored: filePath.startsWith('folder/some/test') }), + }, + }, + { + path: 'folder/another/test', + teams: ['team_b'], + ignorePattern: { + test: (filePath: string) => ({ ignored: filePath.startsWith('folder/another/test') }), + }, + }, + ] as PathWithOwners[]; + + const result = getCodeOwnersForFile('folder/another/test/file.js', reversedCodeowners); + expect(result).toEqual(['team_b']); + }); +}); diff --git a/packages/kbn-dependency-usage/src/lib/code_owners.ts b/packages/kbn-dependency-usage/src/lib/code_owners.ts new file mode 100644 index 000000000000..194dee7c8019 --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/code_owners.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +// @ts-ignore +import { REPO_ROOT } from '@kbn/repo-info'; +import { join as joinPath } from 'path'; +import { existsSync, readFileSync } from 'fs'; + +import type { Ignore } from 'ignore'; +import ignore from 'ignore'; + +export interface PathWithOwners { + path: string; + teams: string[]; + ignorePattern: Ignore; +} + +const existOrThrow = (targetFile: string) => { + if (existsSync(targetFile) === false) + throw Error(`Unable to determine code owners: file ${targetFile} Not Found`); +}; + +/** + * Get the .github/CODEOWNERS entries, prepared for path matching. + * The last matching CODEOWNERS entry has highest precedence: + * https://help.github.com/articles/about-codeowners/ + * so entries are returned in reversed order to later search for the first match. + */ +export function getPathsWithOwnersReversed(): PathWithOwners[] { + const codeownersPath = joinPath(REPO_ROOT, '.github', 'CODEOWNERS'); + existOrThrow(codeownersPath); + const codeownersContent = readFileSync(codeownersPath, { encoding: 'utf8', flag: 'r' }); + const codeownersLines = codeownersContent.split(/\r?\n/); + const codeowners = codeownersLines + .map((line) => line.trim()) + .filter((line) => line && line[0] !== '#'); + + const pathsWithOwners: PathWithOwners[] = codeowners.map((c) => { + const [path, ...ghTeams] = c.split(/\s+/); + const cleanedPath = path.replace(/\/$/, ''); // remove trailing slash + const parsedTeams = ghTeams + .map((t) => t.replace('@', '').split(',')) + .flat() + .filter((t) => t.startsWith('elastic')); + return { + path: cleanedPath, + teams: parsedTeams, + // register CODEOWNERS entries with the `ignores` lib for later path matching + ignorePattern: ignore().add([cleanedPath]), + }; + }); + + return pathsWithOwners.reverse(); +} + +export function getCodeOwnersForFile( + filePath: string, + reversedCodeowners?: PathWithOwners[] +): string[] { + const pathsWithOwners = reversedCodeowners ?? getPathsWithOwnersReversed(); + + const match = pathsWithOwners.find((p) => p.ignorePattern.test(filePath).ignored); + + if (!match?.teams.length) { + const allTeams = pathsWithOwners + .filter((p) => p.path.includes(filePath) && p.teams.length) + .map((p) => p.teams) + .flat(); + + if (!allTeams.length) { + return ['unknown']; + } + + return [...new Set(allTeams)]; + } + + return match?.teams ?? []; +} diff --git a/packages/kbn-dependency-usage/src/lib/collapse_with_depth.test.ts b/packages/kbn-dependency-usage/src/lib/collapse_with_depth.test.ts new file mode 100644 index 000000000000..f2f8c365fdd9 --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/collapse_with_depth.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { createCollapseRegexWithDepth } from './collapse_with_depth'; + +describe('createCollapseRegexWithDepth', () => { + it('should generate regex with a base path and depth of 0', () => { + const basePath = ['app/components']; + const depth = 0; + const regex = createCollapseRegexWithDepth(basePath, depth); + + expect(regex).toBe('^(app/components)'); + }); + + it('should generate regex with a base path and depth of 1', () => { + const basePath = ['src']; + const depth = 1; + const regex = createCollapseRegexWithDepth(basePath, depth); + + expect(regex).toBe('^(src)/([^/]+)'); + }); + + it('should generate regex with a base path and depth of 2', () => { + const basePath = ['src']; + const depth = 2; + const regex = createCollapseRegexWithDepth(basePath, depth); + + expect(regex).toBe('^(src)/([^/]+)/([^/]+)'); + }); +}); diff --git a/packages/kbn-dependency-usage/src/lib/collapse_with_depth.ts b/packages/kbn-dependency-usage/src/lib/collapse_with_depth.ts new file mode 100644 index 000000000000..6f0c42df0dcf --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/collapse_with_depth.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export function createCollapseRegexWithDepth(basePaths: string[], depth: number) { + let regex = `^(${basePaths.join('|')})`; + + for (let i = 0; i < depth; i++) { + regex += `/([^/]+)`; + } + + return regex; +} diff --git a/packages/kbn-dependency-usage/src/lib/group_by_owners.test.ts b/packages/kbn-dependency-usage/src/lib/group_by_owners.test.ts new file mode 100644 index 000000000000..224db092db44 --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/group_by_owners.test.ts @@ -0,0 +1,116 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { groupFilesByOwners } from './group_by_owners'; + +jest.mock('./code_owners', () => ({ + getPathsWithOwnersReversed: jest.fn(), + getCodeOwnersForFile: jest.fn((file: string) => { + const owners: Record = { + '/src/file1.js': ['team_a'], + '/src/file2.js': ['team_b'], + '/src/file3.js': ['team_a', 'team_c'], + }; + return owners[file]; + }), +})); + +describe('groupFilesByOwners', () => { + it('should group files by single owners correctly', () => { + const dependencies = [ + { from: '/src/file1.js', to: 'node_modules/module1' }, + { from: '/src/file2.js', to: 'node_modules/module2' }, + ]; + + const result = groupFilesByOwners(dependencies); + + expect(result).toEqual({ + team_a: { + modules: ['/src/file1.js'], + deps: ['module1'], + teams: ['team_a'], + }, + team_b: { + modules: ['/src/file2.js'], + deps: ['module2'], + teams: ['team_b'], + }, + }); + }); + + it('should group files with multiple owners under "multiple_teams"', () => { + const dependencies = [ + { from: '/src/file3.js', to: 'node_modules/module3' }, + { from: '/src/file3.js', to: 'node_modules/module4' }, + ]; + + const result = groupFilesByOwners(dependencies); + + expect(result).toEqual({ + multiple_teams: [ + { + modules: ['/src/file3.js'], + deps: ['module3', 'module4'], + teams: ['team_a', 'team_c'], + }, + ], + }); + }); + + it('should handle files with unknown owners', () => { + const dependencies = [{ from: '/src/file_unknown.js', to: 'node_modules/module_unknown' }]; + + const result = groupFilesByOwners(dependencies); + + expect(result).toEqual({ + unknown: { + modules: ['/src/file_unknown.js'], + deps: ['module_unknown'], + teams: ['unknown'], + }, + }); + }); + + it('should correctly handle mixed ownership scenarios', () => { + const dependencies = [ + { from: '/src/file1.js', to: 'node_modules/module1' }, + { from: '/src/file2.js', to: 'node_modules/module2' }, + { from: '/src/file3.js', to: 'node_modules/module3' }, + { from: '/src/file3.js', to: 'node_modules/module4' }, + { from: '/src/file_unknown.js', to: 'node_modules/module_unknown' }, + ]; + + const result = groupFilesByOwners(dependencies); + + expect(result).toEqual({ + team_a: { + modules: ['/src/file1.js'], + deps: ['module1'], + teams: ['team_a'], + }, + team_b: { + modules: ['/src/file2.js'], + deps: ['module2'], + teams: ['team_b'], + }, + multiple_teams: [ + { + modules: ['/src/file3.js'], + deps: ['module3', 'module4'], + teams: ['team_a', 'team_c'], + }, + ], + unknown: { + modules: ['/src/file_unknown.js'], + deps: ['module_unknown'], + teams: ['unknown'], + }, + }); + }); +}); diff --git a/packages/kbn-dependency-usage/src/lib/group_by_owners.ts b/packages/kbn-dependency-usage/src/lib/group_by_owners.ts new file mode 100644 index 000000000000..36a3a4ab5df7 --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/group_by_owners.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { getCodeOwnersForFile, getPathsWithOwnersReversed } from './code_owners.ts'; + +interface DependencyByOwnerEntry { + modules: T; + deps: T; + teams: T; +} + +const UNKNOWN_OWNER = 'unknown'; +const MULTIPLE_TEAMS_OWNER = 'multiple_teams'; + +export function groupFilesByOwners(dependencies: Array<{ from: string; to: string }>) { + const ownerFilesMap = new Map(); + const reversedCodeowners = getPathsWithOwnersReversed(); + + for (const dep of dependencies) { + const { from, to } = dep; + + const owners = getCodeOwnersForFile(from, reversedCodeowners) ?? [UNKNOWN_OWNER]; + const ownerKey = owners.length > 1 ? MULTIPLE_TEAMS_OWNER : owners[0]; + + if (ownerKey === MULTIPLE_TEAMS_OWNER) { + if (!ownerFilesMap.has(ownerKey)) { + ownerFilesMap.set(ownerKey, new Map()); + } + + const modulesMap = ownerFilesMap.get(ownerKey); + + if (!modulesMap.has(from)) { + modulesMap.set(from, { deps: new Set(), modules: new Set(), teams: new Set() }); + } + + const moduleEntry = modulesMap.get(from); + + moduleEntry.deps.add(to.replace(/^node_modules\//, '')); + moduleEntry.modules.add(from); + + for (const owner of owners) { + moduleEntry.teams.add(owner); + } + + continue; + } + + if (!ownerFilesMap.has(ownerKey)) { + ownerFilesMap.set(ownerKey, { deps: new Set(), modules: new Set(), teams: new Set(owners) }); + } + + ownerFilesMap.get(ownerKey).deps.add(to.replace(/^node_modules\//, '')); + ownerFilesMap.get(ownerKey).modules.add(from); + } + + const result: Record = {}; + + const transformRecord = (entry: DependencyByOwnerEntry>) => ({ + modules: Array.from(entry.modules), + deps: Array.from(entry.deps), + teams: Array.from(entry.teams), + }); + + for (const [key, ownerRecord] of ownerFilesMap.entries()) { + const isMultiTeamRecord = key === MULTIPLE_TEAMS_OWNER; + + if (isMultiTeamRecord) { + if (!Array.isArray(result[MULTIPLE_TEAMS_OWNER])) { + result[MULTIPLE_TEAMS_OWNER] = []; + } + + for (const [, multiTeamRecord] of ownerRecord.entries()) { + (result[key] as DependencyByOwnerEntry[]).push(transformRecord(multiTeamRecord)); + } + + continue; + } + + result[key] = transformRecord(ownerRecord); + } + + return result; +} diff --git a/packages/kbn-dependency-usage/src/lib/group_by_source.test.ts b/packages/kbn-dependency-usage/src/lib/group_by_source.test.ts new file mode 100644 index 000000000000..1ebce6936c1d --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/group_by_source.test.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { groupBySource } from './group_by_source.ts'; + +describe('groupBySource', () => { + it('should group dependencies by their source files', () => { + const dependencies = [ + { from: 'src/file1.js', to: 'node_modules/module1' }, + { from: 'src/file1.js', to: 'node_modules/module2' }, + { from: 'src/file2.js', to: 'node_modules/module3' }, + ]; + + const result = groupBySource(dependencies); + + expect(result).toEqual({ + 'src/file1.js': ['module1', 'module2'], + 'src/file2.js': ['module3'], + }); + }); + + it('should handle a single dependency', () => { + const dependencies = [{ from: 'src/file1.js', to: 'node_modules/module1' }]; + + const result = groupBySource(dependencies); + + expect(result).toEqual({ + 'src/file1.js': ['module1'], + }); + }); + + it('should handle multiple dependencies from the same source', () => { + const dependencies = [ + { from: 'src/file1.js', to: 'node_modules/module1' }, + { from: 'src/file1.js', to: 'node_modules/module2' }, + { from: 'src/file1.js', to: 'node_modules/module3' }, + ]; + + const result = groupBySource(dependencies); + + expect(result).toEqual({ + 'src/file1.js': ['module1', 'module2', 'module3'], + }); + }); + + it('should handle dependencies from different sources', () => { + const dependencies = [ + { from: 'src/file1.js', to: 'node_modules/module1' }, + { from: 'src/file2.js', to: 'node_modules/module2' }, + { from: 'src/file3.js', to: 'node_modules/module3' }, + ]; + + const result = groupBySource(dependencies); + + expect(result).toEqual({ + 'src/file1.js': ['module1'], + 'src/file2.js': ['module2'], + 'src/file3.js': ['module3'], + }); + }); + + it('should remove "node_modules/" prefix from dependencies', () => { + const dependencies = [ + { from: 'src/file1.js', to: 'node_modules/module1' }, + { from: 'src/file1.js', to: 'node_modules/module2' }, + ]; + + const result = groupBySource(dependencies); + + expect(result).toEqual({ + 'src/file1.js': ['module1', 'module2'], + }); + }); + + it('should return an empty object if there are no dependencies', () => { + const result = groupBySource([]); + + expect(result).toEqual({}); + }); +}); diff --git a/packages/kbn-dependency-usage/src/lib/group_by_source.ts b/packages/kbn-dependency-usage/src/lib/group_by_source.ts new file mode 100644 index 000000000000..9275e6fd8392 --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/group_by_source.ts @@ -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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export function groupBySource(dependencies: Array<{ from: string; to: string }>) { + const packageMap = new Map(); + + for (const dep of dependencies) { + const { from, to } = dep; + + if (!packageMap.has(from)) { + packageMap.set(from, new Set()); + } + + packageMap.get(from).add(to.replace(/^node_modules\//, '')); + } + + const result: Record = {}; + + for (const [key, value] of packageMap.entries()) { + result[key] = Array.from(value); + } + + return result; +} diff --git a/packages/kbn-dependency-usage/src/lib/index.ts b/packages/kbn-dependency-usage/src/lib/index.ts new file mode 100644 index 000000000000..ddf97e41d0bd --- /dev/null +++ b/packages/kbn-dependency-usage/src/lib/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { groupFilesByOwners } from './group_by_owners.ts'; +export { groupBySource } from './group_by_source.ts'; diff --git a/packages/kbn-dependency-usage/tsconfig.json b/packages/kbn-dependency-usage/tsconfig.json new file mode 100644 index 000000000000..96b87da389c3 --- /dev/null +++ b/packages/kbn-dependency-usage/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "esnext", + "moduleResolution": "node", + "outDir": "target/types", + "esModuleInterop": true, + "strict": true, + "resolveJsonModule": true, + "noEmit": true, + "allowImportingTsExtensions": true, + }, + "include": ["**/*.ts"], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/repo-info", + ], +} diff --git a/packages/kbn-dev-cli-errors/kibana.jsonc b/packages/kbn-dev-cli-errors/kibana.jsonc index 86fb72d378b1..d986ae83effa 100644 --- a/packages/kbn-dev-cli-errors/kibana.jsonc +++ b/packages/kbn-dev-cli-errors/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/dev-cli-errors", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-dev-cli-runner/kibana.jsonc b/packages/kbn-dev-cli-runner/kibana.jsonc index 0be99cae70fb..3877e9d959da 100644 --- a/packages/kbn-dev-cli-runner/kibana.jsonc +++ b/packages/kbn-dev-cli-runner/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/dev-cli-runner", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-dev-proc-runner/kibana.jsonc b/packages/kbn-dev-proc-runner/kibana.jsonc index 8f7a5ec07166..8353789db48f 100644 --- a/packages/kbn-dev-proc-runner/kibana.jsonc +++ b/packages/kbn-dev-proc-runner/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/dev-proc-runner", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-dev-utils/kibana.jsonc b/packages/kbn-dev-utils/kibana.jsonc index 7cb93b0f5a1d..d3beef7639ad 100644 --- a/packages/kbn-dev-utils/kibana.jsonc +++ b/packages/kbn-dev-utils/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/dev-utils", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-discover-contextual-components/kibana.jsonc b/packages/kbn-discover-contextual-components/kibana.jsonc index cfb9b1d5431e..1de6488bbd6b 100644 --- a/packages/kbn-discover-contextual-components/kibana.jsonc +++ b/packages/kbn-discover-contextual-components/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-browser", "id": "@kbn/discover-contextual-components", - "owner": ["@elastic/obs-ux-logs-team", "@elastic/kibana-data-discovery"] -} + "owner": [ + "@elastic/obs-ux-logs-team", + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-discover-utils/kibana.jsonc b/packages/kbn-discover-utils/kibana.jsonc index bf77a20bdb86..cbc57999c306 100644 --- a/packages/kbn-discover-utils/kibana.jsonc +++ b/packages/kbn-discover-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/discover-utils", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-discover-utils/src/hooks/use_pager.test.tsx b/packages/kbn-discover-utils/src/hooks/use_pager.test.tsx index 9e3b450e89cf..5eee2b760759 100644 --- a/packages/kbn-discover-utils/src/hooks/use_pager.test.tsx +++ b/packages/kbn-discover-utils/src/hooks/use_pager.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { usePager } from './use_pager'; describe('usePager', () => { diff --git a/packages/kbn-doc-links/kibana.jsonc b/packages/kbn-doc-links/kibana.jsonc index 6e4b13d060d2..adc558ab919b 100644 --- a/packages/kbn-doc-links/kibana.jsonc +++ b/packages/kbn-doc-links/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/doc-links", - "owner": "@elastic/docs" -} + "owner": [ + "@elastic/docs" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index a31e1f1641e8..44bd57ed8f3d 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -1001,5 +1001,8 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D context: `${KIBANA_DOCS}playground-context.html`, hiddenFields: `${KIBANA_DOCS}playground-query.html#playground-hidden-fields`, }, + inferenceManagement: { + inferenceAPIDocumentation: `${ELASTIC_WEBSITE_URL}docs/api/doc/elasticsearch/operation/operation-inference-put`, + }, }); }; diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index ac0f66d83b70..a344d2d694c0 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -676,6 +676,9 @@ export interface DocLinks { readonly context: string; readonly hiddenFields: string; }; + readonly inferenceManagement: { + readonly inferenceAPIDocumentation: string; + }; } export type BuildFlavor = 'serverless' | 'traditional'; diff --git a/packages/kbn-dom-drag-drop/kibana.jsonc b/packages/kbn-dom-drag-drop/kibana.jsonc index 6e54fddc2b75..c3108a22d633 100644 --- a/packages/kbn-dom-drag-drop/kibana.jsonc +++ b/packages/kbn-dom-drag-drop/kibana.jsonc @@ -4,5 +4,7 @@ "owner": [ "@elastic/kibana-visualizations", "@elastic/kibana-data-discovery" - ] -} + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-ebt-tools/kibana.jsonc b/packages/kbn-ebt-tools/kibana.jsonc index 8c063d20246e..e8280fd6e80a 100644 --- a/packages/kbn-ebt-tools/kibana.jsonc +++ b/packages/kbn-ebt-tools/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ebt-tools", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-elastic-agent-utils/kibana.jsonc b/packages/kbn-elastic-agent-utils/kibana.jsonc index cf8dc4c03f59..b954c36dffc8 100644 --- a/packages/kbn-elastic-agent-utils/kibana.jsonc +++ b/packages/kbn-elastic-agent-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/elastic-agent-utils", - "owner": "@elastic/obs-ux-logs-team" -} + "owner": [ + "@elastic/obs-ux-logs-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-es-archiver/kibana.jsonc b/packages/kbn-es-archiver/kibana.jsonc index ae651e3873d0..86049b70ab79 100644 --- a/packages/kbn-es-archiver/kibana.jsonc +++ b/packages/kbn-es-archiver/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "test-helper", "id": "@kbn/es-archiver", - "devOnly": true, - "owner": ["@elastic/kibana-operations", "@elastic/appex-qa"], -} + "owner": [ + "@elastic/kibana-operations", + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-es-errors/kibana.jsonc b/packages/kbn-es-errors/kibana.jsonc index aacc61f02c28..2adafb2fb3af 100644 --- a/packages/kbn-es-errors/kibana.jsonc +++ b/packages/kbn-es-errors/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/es-errors", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-es-query/kibana.jsonc b/packages/kbn-es-query/kibana.jsonc index 896ea93fe1f6..7bb7da8721d4 100644 --- a/packages/kbn-es-query/kibana.jsonc +++ b/packages/kbn-es-query/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/es-query", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-es-types/kibana.jsonc b/packages/kbn-es-types/kibana.jsonc index 2435d7666cf9..08dbeb9d1d88 100644 --- a/packages/kbn-es-types/kibana.jsonc +++ b/packages/kbn-es-types/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/es-types", - "owner": ["@elastic/kibana-core", "@elastic/obs-knowledge-team"] -} + "owner": [ + "@elastic/kibana-core", + "@elastic/obs-knowledge-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-es-types/src/search.ts b/packages/kbn-es-types/src/search.ts index 87f9dd15517c..d3675e04c266 100644 --- a/packages/kbn-es-types/src/search.ts +++ b/packages/kbn-es-types/src/search.ts @@ -695,5 +695,5 @@ export interface ESQLSearchParams { locale?: string; include_ccs_metadata?: boolean; dropNullColumns?: boolean; - params?: Array>; + params?: estypesWithoutBodyKey.ScalarValue[] | Array>; } diff --git a/packages/kbn-es/kibana.jsonc b/packages/kbn-es/kibana.jsonc index d575c727ef33..8c82b16952c0 100644 --- a/packages/kbn-es/kibana.jsonc +++ b/packages/kbn-es/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/es", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-eslint-plugin-imports/src/helpers/groups.ts b/packages/kbn-eslint-plugin-imports/src/helpers/groups.ts index a76251f02838..380078868988 100644 --- a/packages/kbn-eslint-plugin-imports/src/helpers/groups.ts +++ b/packages/kbn-eslint-plugin-imports/src/helpers/groups.ts @@ -8,18 +8,38 @@ */ import type { ModuleGroup, ModuleVisibility } from '@kbn/repo-info/types'; +import type { ModuleId } from '@kbn/repo-source-classifier'; /** * Checks whether a given ModuleGroup can import from another one - * @param importerGroup The group of the module that we are checking + * @param from The ModuleId object that defines the "import" statement * @param importedGroup The group of the imported module * @param importedVisibility The visibility of the imported module - * @returns true if importerGroup is allowed to import from importedGroup/Visibiliy + * @returns true if "from" is allowed to import from importedGroup/Visibility */ export function isImportableFrom( - importerGroup: ModuleGroup, + from: ModuleId, importedGroup: ModuleGroup, importedVisibility: ModuleVisibility ): boolean { - return importerGroup === importedGroup || importedVisibility === 'shared'; + return ( + (isDevOnly(from) && importedGroup === 'platform') || + from.group === importedGroup || + importedVisibility === 'shared' + ); +} + +/** + * Checks whether the given module is supposed to be used at dev/build/test time only + * @param module The module to check + * @returns true if the module is a dev-only module, false otherwise + * @see Package#isDevOnly (packages/kbn-repo-packages/modern/package.js) + */ +function isDevOnly(module: ModuleId) { + return ( + !module.manifest || + !!module.manifest?.devOnly || + module.manifest?.type === 'functional-tests' || + module.manifest?.type === 'test-helper' + ); } diff --git a/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_imports.test.ts b/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_imports.test.ts index dc4828603f73..73decfb9d865 100644 --- a/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_imports.test.ts +++ b/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_imports.test.ts @@ -11,18 +11,22 @@ import { RuleTester } from 'eslint'; import dedent from 'dedent'; import { NoGroupCrossingImportsRule } from './no_group_crossing_imports'; import { formatSuggestions } from '../helpers/report'; -import { ModuleGroup, ModuleVisibility } from '@kbn/repo-info/types'; +import type { ModuleGroup, ModuleVisibility } from '@kbn/repo-info/types'; +import type { KibanaPackageManifest } from '@kbn/repo-packages'; -const make = ( - fromGroup: ModuleGroup, - fromVisibility: ModuleVisibility, - toGroup: ModuleGroup, - toVisibility: ModuleVisibility, - imp = 'import' -) => ({ - filename: `${fromGroup}.${fromVisibility}.ts`, +interface ModuleInfo { + group: ModuleGroup; + visibility: ModuleVisibility; + type?: KibanaPackageManifest['type']; + devOnly?: boolean; +} + +const make = (from: ModuleInfo, to: ModuleInfo, imp = 'import') => ({ + filename: `${from.group}.${from.visibility}.${from.type ?? 'shared-common'}.${ + from.devOnly ?? 'false' + }.ts`, code: dedent` - ${imp} '${toGroup}.${toVisibility}' + ${imp} '${to.group}.${to.visibility}.${to.type ?? 'shared-common'}.${to.devOnly ?? 'false'}' `, }); @@ -46,12 +50,16 @@ jest.mock('../helpers/repo_source_classifier', () => { getRepoSourceClassifier() { return { classify(r: string | [string, string]) { - const [group, visibility] = + const [group, visibility, type, devOnly] = typeof r === 'string' ? (r.endsWith('.ts') ? r.slice(0, -3) : r).split('.') : r; return { pkgInfo: { pkgId: 'aPackage', }, + manifest: { + type, + devOnly: devOnly !== 'false', + }, group, visibility, }; @@ -94,19 +102,59 @@ for (const [name, tester] of [tsTester, babelTester]) { describe(name, () => { tester.run('@kbn/imports/no_group_crossing_imports', NoGroupCrossingImportsRule, { valid: [ - make('observability', 'private', 'observability', 'private'), - make('security', 'private', 'security', 'private'), - make('search', 'private', 'search', 'private'), - make('observability', 'private', 'platform', 'shared'), - make('security', 'private', 'common', 'shared'), - make('platform', 'shared', 'platform', 'shared'), - make('platform', 'shared', 'platform', 'private'), - make('common', 'shared', 'common', 'shared'), + make( + { group: 'observability', visibility: 'private' }, + { group: 'observability', visibility: 'private' } + ), + make( + { group: 'security', visibility: 'private' }, + { group: 'security', visibility: 'private' } + ), + make( + { group: 'search', visibility: 'private' }, + { group: 'search', visibility: 'private' } + ), + make( + { group: 'observability', visibility: 'private' }, + { group: 'platform', visibility: 'shared' } + ), + make( + { group: 'security', visibility: 'private' }, + { group: 'common', visibility: 'shared' } + ), + make( + { group: 'platform', visibility: 'shared' }, + { group: 'platform', visibility: 'shared' } + ), + make( + { group: 'platform', visibility: 'shared' }, + { group: 'platform', visibility: 'private' } + ), + make( + { group: 'security', visibility: 'private' }, + { group: 'platform', visibility: 'shared' } + ), + make( + { group: 'common', visibility: 'shared', devOnly: true }, + { group: 'platform', visibility: 'private' } + ), + make( + { group: 'common', visibility: 'shared', type: 'functional-tests' }, + { group: 'platform', visibility: 'private' } + ), + make( + { group: 'common', visibility: 'shared', type: 'test-helper' }, + { group: 'platform', visibility: 'private' } + ), + make({ group: 'common', visibility: 'shared' }, { group: 'common', visibility: 'shared' }), ], invalid: [ { - ...make('observability', 'private', 'security', 'private'), + ...make( + { group: 'observability', visibility: 'private' }, + { group: 'security', visibility: 'private' } + ), errors: [ { line: 1, @@ -117,7 +165,7 @@ for (const [name, tester] of [tsTester, babelTester]) { importedPackage: 'aPackage', importedGroup: 'security', importedVisibility: 'private', - sourcePath: 'observability.private.ts', + sourcePath: 'observability.private.shared-common.false.ts', suggestion: formatSuggestions([ `Please review the dependencies in your module's manifest (kibana.jsonc).`, `Relocate this module to a different group, and/or make sure it has the right 'visibility'.`, @@ -128,7 +176,10 @@ for (const [name, tester] of [tsTester, babelTester]) { ], }, { - ...make('security', 'private', 'platform', 'private'), + ...make( + { group: 'security', visibility: 'private' }, + { group: 'platform', visibility: 'private' } + ), errors: [ { line: 1, @@ -139,7 +190,7 @@ for (const [name, tester] of [tsTester, babelTester]) { importedPackage: 'aPackage', importedGroup: 'platform', importedVisibility: 'private', - sourcePath: 'security.private.ts', + sourcePath: 'security.private.shared-common.false.ts', suggestion: formatSuggestions([ `Please review the dependencies in your module's manifest (kibana.jsonc).`, `Relocate this module to a different group, and/or make sure it has the right 'visibility'.`, diff --git a/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_imports.ts b/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_imports.ts index 255973ab7460..fb262e88a02a 100644 --- a/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_imports.ts +++ b/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_imports.ts @@ -52,7 +52,7 @@ export const NoGroupCrossingImportsRule: Rule.RuleModule = { const imported = classifier.classify(result.absolute); - if (!isImportableFrom(self.group, imported.group, imported.visibility)) { + if (!isImportableFrom(self, imported.group, imported.visibility)) { context.report({ node: node as Node, messageId: 'ILLEGAL_IMPORT', diff --git a/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_manifests.ts b/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_manifests.ts index e68f7217905a..918412725cd2 100644 --- a/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_manifests.ts +++ b/packages/kbn-eslint-plugin-imports/src/rules/no_group_crossing_manifests.ts @@ -71,7 +71,7 @@ export const NoGroupCrossingManifestsRule: Rule.RuleModule = { if (dependency) { // at this point, we know the dependency is a plugin const { id, group, visibility } = dependency; - if (!isImportableFrom(moduleId.group, group, visibility)) { + if (!isImportableFrom(moduleId, group, visibility)) { offendingDependencies.push({ id, pluginId, group, visibility }); } } diff --git a/packages/kbn-esql-ast/kibana.jsonc b/packages/kbn-esql-ast/kibana.jsonc index 18ab1197119e..825235daef48 100644 --- a/packages/kbn-esql-ast/kibana.jsonc +++ b/packages/kbn-esql-ast/kibana.jsonc @@ -1,5 +1,9 @@ { - "type": "shared-common", - "id": "@kbn/esql-ast", - "owner": "@elastic/kibana-esql" - } \ No newline at end of file + "type": "shared-common", + "id": "@kbn/esql-ast", + "owner": [ + "@elastic/kibana-esql" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 b/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 index ad17de2984ad..28ba50ef3efe 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 @@ -87,8 +87,15 @@ WHERE : 'where' -> pushMode(EXPRESSION_MODE); // main section while preserving alphabetical order: // MYCOMMAND : 'mycommand' -> ... DEV_INLINESTATS : {this.isDevVersion()}? 'inlinestats' -> pushMode(EXPRESSION_MODE); -DEV_LOOKUP : {this.isDevVersion()}? 'lookup' -> pushMode(LOOKUP_MODE); +DEV_LOOKUP : {this.isDevVersion()}? 'lookup_🐔' -> pushMode(LOOKUP_MODE); DEV_METRICS : {this.isDevVersion()}? 'metrics' -> pushMode(METRICS_MODE); +// list of all JOIN commands +DEV_JOIN : {this.isDevVersion()}? 'join' -> pushMode(JOIN_MODE); +DEV_JOIN_FULL : {this.isDevVersion()}? 'full' -> pushMode(JOIN_MODE); +DEV_JOIN_LEFT : {this.isDevVersion()}? 'left' -> pushMode(JOIN_MODE); +DEV_JOIN_RIGHT : {this.isDevVersion()}? 'right' -> pushMode(JOIN_MODE); +DEV_JOIN_LOOKUP : {this.isDevVersion()}? 'lookup' -> pushMode(JOIN_MODE); + // // Catch-all for unrecognized commands - don't define any beyond this line @@ -107,8 +114,6 @@ WS : [ \r\n\t]+ -> channel(HIDDEN) ; -COLON : ':'; - // // Expression - used by most command // @@ -179,6 +184,7 @@ AND : 'and'; ASC : 'asc'; ASSIGN : '='; CAST_OP : '::'; +COLON : ':'; COMMA : ','; DESC : 'desc'; DOT : '.'; @@ -211,7 +217,6 @@ MINUS : '-'; ASTERISK : '*'; SLASH : '/'; PERCENT : '%'; -EXPRESSION_COLON : {this.isDevVersion()}? COLON -> type(COLON); NESTED_WHERE : WHERE -> type(WHERE); @@ -545,6 +550,31 @@ LOOKUP_FIELD_WS : WS -> channel(HIDDEN) ; +// +// JOIN-related commands +// +mode JOIN_MODE; +JOIN_PIPE : PIPE -> type(PIPE), popMode; +JOIN_JOIN : DEV_JOIN -> type(DEV_JOIN); +JOIN_AS : AS -> type(AS); +JOIN_ON : ON -> type(ON), popMode, pushMode(EXPRESSION_MODE); +USING : 'USING' -> popMode, pushMode(EXPRESSION_MODE); + +JOIN_UNQUOTED_IDENTIFER: UNQUOTED_IDENTIFIER -> type(UNQUOTED_IDENTIFIER); +JOIN_QUOTED_IDENTIFIER : QUOTED_IDENTIFIER -> type(QUOTED_IDENTIFIER); + +JOIN_LINE_COMMENT + : LINE_COMMENT -> channel(HIDDEN) + ; + +JOIN_MULTILINE_COMMENT + : MULTILINE_COMMENT -> channel(HIDDEN) + ; + +JOIN_WS + : WS -> channel(HIDDEN) + ; + // // METRICS command // diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.interp b/packages/kbn-esql-ast/src/antlr/esql_lexer.interp index 8f9c5956dddd..c83fdbe8847a 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.interp +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.interp @@ -23,7 +23,11 @@ null null null null -':' +null +null +null +null +null '|' null null @@ -33,6 +37,7 @@ null 'asc' '=' '::' +':' ',' 'desc' '.' @@ -113,6 +118,10 @@ null null null null +'USING' +null +null +null null null null @@ -141,11 +150,15 @@ WHERE DEV_INLINESTATS DEV_LOOKUP DEV_METRICS +DEV_JOIN +DEV_JOIN_FULL +DEV_JOIN_LEFT +DEV_JOIN_RIGHT +DEV_JOIN_LOOKUP UNKNOWN_CMD LINE_COMMENT MULTILINE_COMMENT WS -COLON PIPE QUOTED_STRING INTEGER_LITERAL @@ -155,6 +168,7 @@ AND ASC ASSIGN CAST_OP +COLON COMMA DESC DOT @@ -235,6 +249,10 @@ LOOKUP_WS LOOKUP_FIELD_LINE_COMMENT LOOKUP_FIELD_MULTILINE_COMMENT LOOKUP_FIELD_WS +USING +JOIN_LINE_COMMENT +JOIN_MULTILINE_COMMENT +JOIN_WS METRICS_LINE_COMMENT METRICS_MULTILINE_COMMENT METRICS_WS @@ -262,11 +280,15 @@ WHERE DEV_INLINESTATS DEV_LOOKUP DEV_METRICS +DEV_JOIN +DEV_JOIN_FULL +DEV_JOIN_LEFT +DEV_JOIN_RIGHT +DEV_JOIN_LOOKUP UNKNOWN_CMD LINE_COMMENT MULTILINE_COMMENT WS -COLON PIPE DIGIT LETTER @@ -286,6 +308,7 @@ AND ASC ASSIGN CAST_OP +COLON COMMA DESC DOT @@ -316,7 +339,6 @@ MINUS ASTERISK SLASH PERCENT -EXPRESSION_COLON NESTED_WHERE NAMED_OR_POSITIONAL_PARAM OPENING_BRACKET @@ -427,6 +449,16 @@ LOOKUP_FIELD_ID_PATTERN LOOKUP_FIELD_LINE_COMMENT LOOKUP_FIELD_MULTILINE_COMMENT LOOKUP_FIELD_WS +JOIN_PIPE +JOIN_JOIN +JOIN_AS +JOIN_ON +USING +JOIN_UNQUOTED_IDENTIFER +JOIN_QUOTED_IDENTIFIER +JOIN_LINE_COMMENT +JOIN_MULTILINE_COMMENT +JOIN_WS METRICS_PIPE METRICS_UNQUOTED_SOURCE METRICS_QUOTED_SOURCE @@ -461,8 +493,9 @@ SHOW_MODE SETTING_MODE LOOKUP_MODE LOOKUP_FIELD_MODE +JOIN_MODE METRICS_MODE CLOSING_METRICS_MODE atn: -[4, 0, 119, 1484, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 2, 198, 7, 198, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 4, 19, 580, 8, 19, 11, 19, 12, 19, 581, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 590, 8, 20, 10, 20, 12, 20, 593, 9, 20, 1, 20, 3, 20, 596, 8, 20, 1, 20, 3, 20, 599, 8, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 608, 8, 21, 10, 21, 12, 21, 611, 9, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 4, 22, 619, 8, 22, 11, 22, 12, 22, 620, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 3, 29, 642, 8, 29, 1, 29, 4, 29, 645, 8, 29, 11, 29, 12, 29, 646, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 3, 32, 656, 8, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 663, 8, 34, 1, 35, 1, 35, 1, 35, 5, 35, 668, 8, 35, 10, 35, 12, 35, 671, 9, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 679, 8, 35, 10, 35, 12, 35, 682, 9, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 3, 35, 689, 8, 35, 1, 35, 3, 35, 692, 8, 35, 3, 35, 694, 8, 35, 1, 36, 4, 36, 697, 8, 36, 11, 36, 12, 36, 698, 1, 37, 4, 37, 702, 8, 37, 11, 37, 12, 37, 703, 1, 37, 1, 37, 5, 37, 708, 8, 37, 10, 37, 12, 37, 711, 9, 37, 1, 37, 1, 37, 4, 37, 715, 8, 37, 11, 37, 12, 37, 716, 1, 37, 4, 37, 720, 8, 37, 11, 37, 12, 37, 721, 1, 37, 1, 37, 5, 37, 726, 8, 37, 10, 37, 12, 37, 729, 9, 37, 3, 37, 731, 8, 37, 1, 37, 1, 37, 1, 37, 1, 37, 4, 37, 737, 8, 37, 11, 37, 12, 37, 738, 1, 37, 1, 37, 3, 37, 743, 8, 37, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 3, 75, 874, 8, 75, 1, 75, 5, 75, 877, 8, 75, 10, 75, 12, 75, 880, 9, 75, 1, 75, 1, 75, 4, 75, 884, 8, 75, 11, 75, 12, 75, 885, 3, 75, 888, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 5, 78, 902, 8, 78, 10, 78, 12, 78, 905, 9, 78, 1, 78, 1, 78, 3, 78, 909, 8, 78, 1, 78, 4, 78, 912, 8, 78, 11, 78, 12, 78, 913, 3, 78, 916, 8, 78, 1, 79, 1, 79, 4, 79, 920, 8, 79, 11, 79, 12, 79, 921, 1, 79, 1, 79, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 3, 96, 999, 8, 96, 1, 97, 4, 97, 1002, 8, 97, 11, 97, 12, 97, 1003, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 3, 108, 1053, 8, 108, 1, 109, 1, 109, 3, 109, 1057, 8, 109, 1, 109, 5, 109, 1060, 8, 109, 10, 109, 12, 109, 1063, 9, 109, 1, 109, 1, 109, 3, 109, 1067, 8, 109, 1, 109, 4, 109, 1070, 8, 109, 11, 109, 12, 109, 1071, 3, 109, 1074, 8, 109, 1, 110, 1, 110, 4, 110, 1078, 8, 110, 11, 110, 12, 110, 1079, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 130, 4, 130, 1165, 8, 130, 11, 130, 12, 130, 1166, 1, 130, 1, 130, 3, 130, 1171, 8, 130, 1, 130, 4, 130, 1174, 8, 130, 11, 130, 12, 130, 1175, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 163, 4, 163, 1321, 8, 163, 11, 163, 12, 163, 1322, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 197, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 2, 609, 680, 0, 199, 15, 1, 17, 2, 19, 3, 21, 4, 23, 5, 25, 6, 27, 7, 29, 8, 31, 9, 33, 10, 35, 11, 37, 12, 39, 13, 41, 14, 43, 15, 45, 16, 47, 17, 49, 18, 51, 19, 53, 20, 55, 21, 57, 22, 59, 23, 61, 24, 63, 25, 65, 0, 67, 0, 69, 0, 71, 0, 73, 0, 75, 0, 77, 0, 79, 0, 81, 0, 83, 0, 85, 26, 87, 27, 89, 28, 91, 29, 93, 30, 95, 31, 97, 32, 99, 33, 101, 34, 103, 35, 105, 36, 107, 37, 109, 38, 111, 39, 113, 40, 115, 41, 117, 42, 119, 43, 121, 44, 123, 45, 125, 46, 127, 47, 129, 48, 131, 49, 133, 50, 135, 51, 137, 52, 139, 53, 141, 54, 143, 55, 145, 56, 147, 57, 149, 58, 151, 59, 153, 60, 155, 61, 157, 62, 159, 63, 161, 0, 163, 0, 165, 64, 167, 65, 169, 66, 171, 67, 173, 0, 175, 68, 177, 69, 179, 70, 181, 71, 183, 0, 185, 0, 187, 72, 189, 73, 191, 74, 193, 0, 195, 0, 197, 0, 199, 0, 201, 0, 203, 0, 205, 75, 207, 0, 209, 76, 211, 0, 213, 0, 215, 77, 217, 78, 219, 79, 221, 0, 223, 0, 225, 0, 227, 0, 229, 0, 231, 0, 233, 0, 235, 80, 237, 81, 239, 82, 241, 83, 243, 0, 245, 0, 247, 0, 249, 0, 251, 0, 253, 0, 255, 84, 257, 0, 259, 85, 261, 86, 263, 87, 265, 0, 267, 0, 269, 88, 271, 89, 273, 0, 275, 90, 277, 0, 279, 91, 281, 92, 283, 93, 285, 0, 287, 0, 289, 0, 291, 0, 293, 0, 295, 0, 297, 0, 299, 0, 301, 0, 303, 94, 305, 95, 307, 96, 309, 0, 311, 0, 313, 0, 315, 0, 317, 0, 319, 0, 321, 97, 323, 98, 325, 99, 327, 0, 329, 100, 331, 101, 333, 102, 335, 103, 337, 0, 339, 0, 341, 104, 343, 105, 345, 106, 347, 107, 349, 0, 351, 0, 353, 0, 355, 0, 357, 0, 359, 0, 361, 0, 363, 108, 365, 109, 367, 110, 369, 0, 371, 0, 373, 0, 375, 0, 377, 111, 379, 112, 381, 113, 383, 0, 385, 0, 387, 0, 389, 114, 391, 115, 393, 116, 395, 0, 397, 0, 399, 117, 401, 118, 403, 119, 405, 0, 407, 0, 409, 0, 411, 0, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 34, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1512, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 1, 63, 1, 0, 0, 0, 1, 85, 1, 0, 0, 0, 1, 87, 1, 0, 0, 0, 1, 89, 1, 0, 0, 0, 1, 91, 1, 0, 0, 0, 1, 93, 1, 0, 0, 0, 1, 95, 1, 0, 0, 0, 1, 97, 1, 0, 0, 0, 1, 99, 1, 0, 0, 0, 1, 101, 1, 0, 0, 0, 1, 103, 1, 0, 0, 0, 1, 105, 1, 0, 0, 0, 1, 107, 1, 0, 0, 0, 1, 109, 1, 0, 0, 0, 1, 111, 1, 0, 0, 0, 1, 113, 1, 0, 0, 0, 1, 115, 1, 0, 0, 0, 1, 117, 1, 0, 0, 0, 1, 119, 1, 0, 0, 0, 1, 121, 1, 0, 0, 0, 1, 123, 1, 0, 0, 0, 1, 125, 1, 0, 0, 0, 1, 127, 1, 0, 0, 0, 1, 129, 1, 0, 0, 0, 1, 131, 1, 0, 0, 0, 1, 133, 1, 0, 0, 0, 1, 135, 1, 0, 0, 0, 1, 137, 1, 0, 0, 0, 1, 139, 1, 0, 0, 0, 1, 141, 1, 0, 0, 0, 1, 143, 1, 0, 0, 0, 1, 145, 1, 0, 0, 0, 1, 147, 1, 0, 0, 0, 1, 149, 1, 0, 0, 0, 1, 151, 1, 0, 0, 0, 1, 153, 1, 0, 0, 0, 1, 155, 1, 0, 0, 0, 1, 157, 1, 0, 0, 0, 1, 159, 1, 0, 0, 0, 1, 161, 1, 0, 0, 0, 1, 163, 1, 0, 0, 0, 1, 165, 1, 0, 0, 0, 1, 167, 1, 0, 0, 0, 1, 169, 1, 0, 0, 0, 1, 171, 1, 0, 0, 0, 1, 175, 1, 0, 0, 0, 1, 177, 1, 0, 0, 0, 1, 179, 1, 0, 0, 0, 1, 181, 1, 0, 0, 0, 2, 183, 1, 0, 0, 0, 2, 185, 1, 0, 0, 0, 2, 187, 1, 0, 0, 0, 2, 189, 1, 0, 0, 0, 2, 191, 1, 0, 0, 0, 3, 193, 1, 0, 0, 0, 3, 195, 1, 0, 0, 0, 3, 197, 1, 0, 0, 0, 3, 199, 1, 0, 0, 0, 3, 201, 1, 0, 0, 0, 3, 203, 1, 0, 0, 0, 3, 205, 1, 0, 0, 0, 3, 209, 1, 0, 0, 0, 3, 211, 1, 0, 0, 0, 3, 213, 1, 0, 0, 0, 3, 215, 1, 0, 0, 0, 3, 217, 1, 0, 0, 0, 3, 219, 1, 0, 0, 0, 4, 221, 1, 0, 0, 0, 4, 223, 1, 0, 0, 0, 4, 225, 1, 0, 0, 0, 4, 227, 1, 0, 0, 0, 4, 229, 1, 0, 0, 0, 4, 235, 1, 0, 0, 0, 4, 237, 1, 0, 0, 0, 4, 239, 1, 0, 0, 0, 4, 241, 1, 0, 0, 0, 5, 243, 1, 0, 0, 0, 5, 245, 1, 0, 0, 0, 5, 247, 1, 0, 0, 0, 5, 249, 1, 0, 0, 0, 5, 251, 1, 0, 0, 0, 5, 253, 1, 0, 0, 0, 5, 255, 1, 0, 0, 0, 5, 257, 1, 0, 0, 0, 5, 259, 1, 0, 0, 0, 5, 261, 1, 0, 0, 0, 5, 263, 1, 0, 0, 0, 6, 265, 1, 0, 0, 0, 6, 267, 1, 0, 0, 0, 6, 269, 1, 0, 0, 0, 6, 271, 1, 0, 0, 0, 6, 275, 1, 0, 0, 0, 6, 277, 1, 0, 0, 0, 6, 279, 1, 0, 0, 0, 6, 281, 1, 0, 0, 0, 6, 283, 1, 0, 0, 0, 7, 285, 1, 0, 0, 0, 7, 287, 1, 0, 0, 0, 7, 289, 1, 0, 0, 0, 7, 291, 1, 0, 0, 0, 7, 293, 1, 0, 0, 0, 7, 295, 1, 0, 0, 0, 7, 297, 1, 0, 0, 0, 7, 299, 1, 0, 0, 0, 7, 301, 1, 0, 0, 0, 7, 303, 1, 0, 0, 0, 7, 305, 1, 0, 0, 0, 7, 307, 1, 0, 0, 0, 8, 309, 1, 0, 0, 0, 8, 311, 1, 0, 0, 0, 8, 313, 1, 0, 0, 0, 8, 315, 1, 0, 0, 0, 8, 317, 1, 0, 0, 0, 8, 319, 1, 0, 0, 0, 8, 321, 1, 0, 0, 0, 8, 323, 1, 0, 0, 0, 8, 325, 1, 0, 0, 0, 9, 327, 1, 0, 0, 0, 9, 329, 1, 0, 0, 0, 9, 331, 1, 0, 0, 0, 9, 333, 1, 0, 0, 0, 9, 335, 1, 0, 0, 0, 10, 337, 1, 0, 0, 0, 10, 339, 1, 0, 0, 0, 10, 341, 1, 0, 0, 0, 10, 343, 1, 0, 0, 0, 10, 345, 1, 0, 0, 0, 10, 347, 1, 0, 0, 0, 11, 349, 1, 0, 0, 0, 11, 351, 1, 0, 0, 0, 11, 353, 1, 0, 0, 0, 11, 355, 1, 0, 0, 0, 11, 357, 1, 0, 0, 0, 11, 359, 1, 0, 0, 0, 11, 361, 1, 0, 0, 0, 11, 363, 1, 0, 0, 0, 11, 365, 1, 0, 0, 0, 11, 367, 1, 0, 0, 0, 12, 369, 1, 0, 0, 0, 12, 371, 1, 0, 0, 0, 12, 373, 1, 0, 0, 0, 12, 375, 1, 0, 0, 0, 12, 377, 1, 0, 0, 0, 12, 379, 1, 0, 0, 0, 12, 381, 1, 0, 0, 0, 13, 383, 1, 0, 0, 0, 13, 385, 1, 0, 0, 0, 13, 387, 1, 0, 0, 0, 13, 389, 1, 0, 0, 0, 13, 391, 1, 0, 0, 0, 13, 393, 1, 0, 0, 0, 14, 395, 1, 0, 0, 0, 14, 397, 1, 0, 0, 0, 14, 399, 1, 0, 0, 0, 14, 401, 1, 0, 0, 0, 14, 403, 1, 0, 0, 0, 14, 405, 1, 0, 0, 0, 14, 407, 1, 0, 0, 0, 14, 409, 1, 0, 0, 0, 14, 411, 1, 0, 0, 0, 15, 413, 1, 0, 0, 0, 17, 423, 1, 0, 0, 0, 19, 430, 1, 0, 0, 0, 21, 439, 1, 0, 0, 0, 23, 446, 1, 0, 0, 0, 25, 456, 1, 0, 0, 0, 27, 463, 1, 0, 0, 0, 29, 470, 1, 0, 0, 0, 31, 477, 1, 0, 0, 0, 33, 485, 1, 0, 0, 0, 35, 497, 1, 0, 0, 0, 37, 506, 1, 0, 0, 0, 39, 512, 1, 0, 0, 0, 41, 519, 1, 0, 0, 0, 43, 526, 1, 0, 0, 0, 45, 534, 1, 0, 0, 0, 47, 542, 1, 0, 0, 0, 49, 557, 1, 0, 0, 0, 51, 567, 1, 0, 0, 0, 53, 579, 1, 0, 0, 0, 55, 585, 1, 0, 0, 0, 57, 602, 1, 0, 0, 0, 59, 618, 1, 0, 0, 0, 61, 624, 1, 0, 0, 0, 63, 626, 1, 0, 0, 0, 65, 630, 1, 0, 0, 0, 67, 632, 1, 0, 0, 0, 69, 634, 1, 0, 0, 0, 71, 637, 1, 0, 0, 0, 73, 639, 1, 0, 0, 0, 75, 648, 1, 0, 0, 0, 77, 650, 1, 0, 0, 0, 79, 655, 1, 0, 0, 0, 81, 657, 1, 0, 0, 0, 83, 662, 1, 0, 0, 0, 85, 693, 1, 0, 0, 0, 87, 696, 1, 0, 0, 0, 89, 742, 1, 0, 0, 0, 91, 744, 1, 0, 0, 0, 93, 747, 1, 0, 0, 0, 95, 751, 1, 0, 0, 0, 97, 755, 1, 0, 0, 0, 99, 757, 1, 0, 0, 0, 101, 760, 1, 0, 0, 0, 103, 762, 1, 0, 0, 0, 105, 767, 1, 0, 0, 0, 107, 769, 1, 0, 0, 0, 109, 775, 1, 0, 0, 0, 111, 781, 1, 0, 0, 0, 113, 784, 1, 0, 0, 0, 115, 787, 1, 0, 0, 0, 117, 792, 1, 0, 0, 0, 119, 797, 1, 0, 0, 0, 121, 799, 1, 0, 0, 0, 123, 803, 1, 0, 0, 0, 125, 808, 1, 0, 0, 0, 127, 814, 1, 0, 0, 0, 129, 817, 1, 0, 0, 0, 131, 819, 1, 0, 0, 0, 133, 825, 1, 0, 0, 0, 135, 827, 1, 0, 0, 0, 137, 832, 1, 0, 0, 0, 139, 835, 1, 0, 0, 0, 141, 838, 1, 0, 0, 0, 143, 841, 1, 0, 0, 0, 145, 843, 1, 0, 0, 0, 147, 846, 1, 0, 0, 0, 149, 848, 1, 0, 0, 0, 151, 851, 1, 0, 0, 0, 153, 853, 1, 0, 0, 0, 155, 855, 1, 0, 0, 0, 157, 857, 1, 0, 0, 0, 159, 859, 1, 0, 0, 0, 161, 861, 1, 0, 0, 0, 163, 866, 1, 0, 0, 0, 165, 887, 1, 0, 0, 0, 167, 889, 1, 0, 0, 0, 169, 894, 1, 0, 0, 0, 171, 915, 1, 0, 0, 0, 173, 917, 1, 0, 0, 0, 175, 925, 1, 0, 0, 0, 177, 927, 1, 0, 0, 0, 179, 931, 1, 0, 0, 0, 181, 935, 1, 0, 0, 0, 183, 939, 1, 0, 0, 0, 185, 944, 1, 0, 0, 0, 187, 949, 1, 0, 0, 0, 189, 953, 1, 0, 0, 0, 191, 957, 1, 0, 0, 0, 193, 961, 1, 0, 0, 0, 195, 966, 1, 0, 0, 0, 197, 970, 1, 0, 0, 0, 199, 974, 1, 0, 0, 0, 201, 978, 1, 0, 0, 0, 203, 982, 1, 0, 0, 0, 205, 986, 1, 0, 0, 0, 207, 998, 1, 0, 0, 0, 209, 1001, 1, 0, 0, 0, 211, 1005, 1, 0, 0, 0, 213, 1009, 1, 0, 0, 0, 215, 1013, 1, 0, 0, 0, 217, 1017, 1, 0, 0, 0, 219, 1021, 1, 0, 0, 0, 221, 1025, 1, 0, 0, 0, 223, 1030, 1, 0, 0, 0, 225, 1034, 1, 0, 0, 0, 227, 1038, 1, 0, 0, 0, 229, 1043, 1, 0, 0, 0, 231, 1052, 1, 0, 0, 0, 233, 1073, 1, 0, 0, 0, 235, 1077, 1, 0, 0, 0, 237, 1081, 1, 0, 0, 0, 239, 1085, 1, 0, 0, 0, 241, 1089, 1, 0, 0, 0, 243, 1093, 1, 0, 0, 0, 245, 1098, 1, 0, 0, 0, 247, 1102, 1, 0, 0, 0, 249, 1106, 1, 0, 0, 0, 251, 1110, 1, 0, 0, 0, 253, 1115, 1, 0, 0, 0, 255, 1120, 1, 0, 0, 0, 257, 1123, 1, 0, 0, 0, 259, 1127, 1, 0, 0, 0, 261, 1131, 1, 0, 0, 0, 263, 1135, 1, 0, 0, 0, 265, 1139, 1, 0, 0, 0, 267, 1144, 1, 0, 0, 0, 269, 1149, 1, 0, 0, 0, 271, 1154, 1, 0, 0, 0, 273, 1161, 1, 0, 0, 0, 275, 1170, 1, 0, 0, 0, 277, 1177, 1, 0, 0, 0, 279, 1181, 1, 0, 0, 0, 281, 1185, 1, 0, 0, 0, 283, 1189, 1, 0, 0, 0, 285, 1193, 1, 0, 0, 0, 287, 1199, 1, 0, 0, 0, 289, 1203, 1, 0, 0, 0, 291, 1207, 1, 0, 0, 0, 293, 1211, 1, 0, 0, 0, 295, 1215, 1, 0, 0, 0, 297, 1219, 1, 0, 0, 0, 299, 1223, 1, 0, 0, 0, 301, 1228, 1, 0, 0, 0, 303, 1233, 1, 0, 0, 0, 305, 1237, 1, 0, 0, 0, 307, 1241, 1, 0, 0, 0, 309, 1245, 1, 0, 0, 0, 311, 1250, 1, 0, 0, 0, 313, 1254, 1, 0, 0, 0, 315, 1259, 1, 0, 0, 0, 317, 1264, 1, 0, 0, 0, 319, 1268, 1, 0, 0, 0, 321, 1272, 1, 0, 0, 0, 323, 1276, 1, 0, 0, 0, 325, 1280, 1, 0, 0, 0, 327, 1284, 1, 0, 0, 0, 329, 1289, 1, 0, 0, 0, 331, 1294, 1, 0, 0, 0, 333, 1298, 1, 0, 0, 0, 335, 1302, 1, 0, 0, 0, 337, 1306, 1, 0, 0, 0, 339, 1311, 1, 0, 0, 0, 341, 1320, 1, 0, 0, 0, 343, 1324, 1, 0, 0, 0, 345, 1328, 1, 0, 0, 0, 347, 1332, 1, 0, 0, 0, 349, 1336, 1, 0, 0, 0, 351, 1341, 1, 0, 0, 0, 353, 1345, 1, 0, 0, 0, 355, 1349, 1, 0, 0, 0, 357, 1353, 1, 0, 0, 0, 359, 1358, 1, 0, 0, 0, 361, 1362, 1, 0, 0, 0, 363, 1366, 1, 0, 0, 0, 365, 1370, 1, 0, 0, 0, 367, 1374, 1, 0, 0, 0, 369, 1378, 1, 0, 0, 0, 371, 1384, 1, 0, 0, 0, 373, 1388, 1, 0, 0, 0, 375, 1392, 1, 0, 0, 0, 377, 1396, 1, 0, 0, 0, 379, 1400, 1, 0, 0, 0, 381, 1404, 1, 0, 0, 0, 383, 1408, 1, 0, 0, 0, 385, 1413, 1, 0, 0, 0, 387, 1419, 1, 0, 0, 0, 389, 1425, 1, 0, 0, 0, 391, 1429, 1, 0, 0, 0, 393, 1433, 1, 0, 0, 0, 395, 1437, 1, 0, 0, 0, 397, 1443, 1, 0, 0, 0, 399, 1449, 1, 0, 0, 0, 401, 1453, 1, 0, 0, 0, 403, 1457, 1, 0, 0, 0, 405, 1461, 1, 0, 0, 0, 407, 1467, 1, 0, 0, 0, 409, 1473, 1, 0, 0, 0, 411, 1479, 1, 0, 0, 0, 413, 414, 7, 0, 0, 0, 414, 415, 7, 1, 0, 0, 415, 416, 7, 2, 0, 0, 416, 417, 7, 2, 0, 0, 417, 418, 7, 3, 0, 0, 418, 419, 7, 4, 0, 0, 419, 420, 7, 5, 0, 0, 420, 421, 1, 0, 0, 0, 421, 422, 6, 0, 0, 0, 422, 16, 1, 0, 0, 0, 423, 424, 7, 0, 0, 0, 424, 425, 7, 6, 0, 0, 425, 426, 7, 7, 0, 0, 426, 427, 7, 8, 0, 0, 427, 428, 1, 0, 0, 0, 428, 429, 6, 1, 1, 0, 429, 18, 1, 0, 0, 0, 430, 431, 7, 3, 0, 0, 431, 432, 7, 9, 0, 0, 432, 433, 7, 6, 0, 0, 433, 434, 7, 1, 0, 0, 434, 435, 7, 4, 0, 0, 435, 436, 7, 10, 0, 0, 436, 437, 1, 0, 0, 0, 437, 438, 6, 2, 2, 0, 438, 20, 1, 0, 0, 0, 439, 440, 7, 3, 0, 0, 440, 441, 7, 11, 0, 0, 441, 442, 7, 12, 0, 0, 442, 443, 7, 13, 0, 0, 443, 444, 1, 0, 0, 0, 444, 445, 6, 3, 0, 0, 445, 22, 1, 0, 0, 0, 446, 447, 7, 3, 0, 0, 447, 448, 7, 14, 0, 0, 448, 449, 7, 8, 0, 0, 449, 450, 7, 13, 0, 0, 450, 451, 7, 12, 0, 0, 451, 452, 7, 1, 0, 0, 452, 453, 7, 9, 0, 0, 453, 454, 1, 0, 0, 0, 454, 455, 6, 4, 3, 0, 455, 24, 1, 0, 0, 0, 456, 457, 7, 15, 0, 0, 457, 458, 7, 6, 0, 0, 458, 459, 7, 7, 0, 0, 459, 460, 7, 16, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 6, 5, 4, 0, 462, 26, 1, 0, 0, 0, 463, 464, 7, 17, 0, 0, 464, 465, 7, 6, 0, 0, 465, 466, 7, 7, 0, 0, 466, 467, 7, 18, 0, 0, 467, 468, 1, 0, 0, 0, 468, 469, 6, 6, 0, 0, 469, 28, 1, 0, 0, 0, 470, 471, 7, 18, 0, 0, 471, 472, 7, 3, 0, 0, 472, 473, 7, 3, 0, 0, 473, 474, 7, 8, 0, 0, 474, 475, 1, 0, 0, 0, 475, 476, 6, 7, 1, 0, 476, 30, 1, 0, 0, 0, 477, 478, 7, 13, 0, 0, 478, 479, 7, 1, 0, 0, 479, 480, 7, 16, 0, 0, 480, 481, 7, 1, 0, 0, 481, 482, 7, 5, 0, 0, 482, 483, 1, 0, 0, 0, 483, 484, 6, 8, 0, 0, 484, 32, 1, 0, 0, 0, 485, 486, 7, 16, 0, 0, 486, 487, 7, 11, 0, 0, 487, 488, 5, 95, 0, 0, 488, 489, 7, 3, 0, 0, 489, 490, 7, 14, 0, 0, 490, 491, 7, 8, 0, 0, 491, 492, 7, 12, 0, 0, 492, 493, 7, 9, 0, 0, 493, 494, 7, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 496, 6, 9, 5, 0, 496, 34, 1, 0, 0, 0, 497, 498, 7, 6, 0, 0, 498, 499, 7, 3, 0, 0, 499, 500, 7, 9, 0, 0, 500, 501, 7, 12, 0, 0, 501, 502, 7, 16, 0, 0, 502, 503, 7, 3, 0, 0, 503, 504, 1, 0, 0, 0, 504, 505, 6, 10, 6, 0, 505, 36, 1, 0, 0, 0, 506, 507, 7, 6, 0, 0, 507, 508, 7, 7, 0, 0, 508, 509, 7, 19, 0, 0, 509, 510, 1, 0, 0, 0, 510, 511, 6, 11, 0, 0, 511, 38, 1, 0, 0, 0, 512, 513, 7, 2, 0, 0, 513, 514, 7, 10, 0, 0, 514, 515, 7, 7, 0, 0, 515, 516, 7, 19, 0, 0, 516, 517, 1, 0, 0, 0, 517, 518, 6, 12, 7, 0, 518, 40, 1, 0, 0, 0, 519, 520, 7, 2, 0, 0, 520, 521, 7, 7, 0, 0, 521, 522, 7, 6, 0, 0, 522, 523, 7, 5, 0, 0, 523, 524, 1, 0, 0, 0, 524, 525, 6, 13, 0, 0, 525, 42, 1, 0, 0, 0, 526, 527, 7, 2, 0, 0, 527, 528, 7, 5, 0, 0, 528, 529, 7, 12, 0, 0, 529, 530, 7, 5, 0, 0, 530, 531, 7, 2, 0, 0, 531, 532, 1, 0, 0, 0, 532, 533, 6, 14, 0, 0, 533, 44, 1, 0, 0, 0, 534, 535, 7, 19, 0, 0, 535, 536, 7, 10, 0, 0, 536, 537, 7, 3, 0, 0, 537, 538, 7, 6, 0, 0, 538, 539, 7, 3, 0, 0, 539, 540, 1, 0, 0, 0, 540, 541, 6, 15, 0, 0, 541, 46, 1, 0, 0, 0, 542, 543, 4, 16, 0, 0, 543, 544, 7, 1, 0, 0, 544, 545, 7, 9, 0, 0, 545, 546, 7, 13, 0, 0, 546, 547, 7, 1, 0, 0, 547, 548, 7, 9, 0, 0, 548, 549, 7, 3, 0, 0, 549, 550, 7, 2, 0, 0, 550, 551, 7, 5, 0, 0, 551, 552, 7, 12, 0, 0, 552, 553, 7, 5, 0, 0, 553, 554, 7, 2, 0, 0, 554, 555, 1, 0, 0, 0, 555, 556, 6, 16, 0, 0, 556, 48, 1, 0, 0, 0, 557, 558, 4, 17, 1, 0, 558, 559, 7, 13, 0, 0, 559, 560, 7, 7, 0, 0, 560, 561, 7, 7, 0, 0, 561, 562, 7, 18, 0, 0, 562, 563, 7, 20, 0, 0, 563, 564, 7, 8, 0, 0, 564, 565, 1, 0, 0, 0, 565, 566, 6, 17, 8, 0, 566, 50, 1, 0, 0, 0, 567, 568, 4, 18, 2, 0, 568, 569, 7, 16, 0, 0, 569, 570, 7, 3, 0, 0, 570, 571, 7, 5, 0, 0, 571, 572, 7, 6, 0, 0, 572, 573, 7, 1, 0, 0, 573, 574, 7, 4, 0, 0, 574, 575, 7, 2, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 6, 18, 9, 0, 577, 52, 1, 0, 0, 0, 578, 580, 8, 21, 0, 0, 579, 578, 1, 0, 0, 0, 580, 581, 1, 0, 0, 0, 581, 579, 1, 0, 0, 0, 581, 582, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 584, 6, 19, 0, 0, 584, 54, 1, 0, 0, 0, 585, 586, 5, 47, 0, 0, 586, 587, 5, 47, 0, 0, 587, 591, 1, 0, 0, 0, 588, 590, 8, 22, 0, 0, 589, 588, 1, 0, 0, 0, 590, 593, 1, 0, 0, 0, 591, 589, 1, 0, 0, 0, 591, 592, 1, 0, 0, 0, 592, 595, 1, 0, 0, 0, 593, 591, 1, 0, 0, 0, 594, 596, 5, 13, 0, 0, 595, 594, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 598, 1, 0, 0, 0, 597, 599, 5, 10, 0, 0, 598, 597, 1, 0, 0, 0, 598, 599, 1, 0, 0, 0, 599, 600, 1, 0, 0, 0, 600, 601, 6, 20, 10, 0, 601, 56, 1, 0, 0, 0, 602, 603, 5, 47, 0, 0, 603, 604, 5, 42, 0, 0, 604, 609, 1, 0, 0, 0, 605, 608, 3, 57, 21, 0, 606, 608, 9, 0, 0, 0, 607, 605, 1, 0, 0, 0, 607, 606, 1, 0, 0, 0, 608, 611, 1, 0, 0, 0, 609, 610, 1, 0, 0, 0, 609, 607, 1, 0, 0, 0, 610, 612, 1, 0, 0, 0, 611, 609, 1, 0, 0, 0, 612, 613, 5, 42, 0, 0, 613, 614, 5, 47, 0, 0, 614, 615, 1, 0, 0, 0, 615, 616, 6, 21, 10, 0, 616, 58, 1, 0, 0, 0, 617, 619, 7, 23, 0, 0, 618, 617, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 621, 622, 1, 0, 0, 0, 622, 623, 6, 22, 10, 0, 623, 60, 1, 0, 0, 0, 624, 625, 5, 58, 0, 0, 625, 62, 1, 0, 0, 0, 626, 627, 5, 124, 0, 0, 627, 628, 1, 0, 0, 0, 628, 629, 6, 24, 11, 0, 629, 64, 1, 0, 0, 0, 630, 631, 7, 24, 0, 0, 631, 66, 1, 0, 0, 0, 632, 633, 7, 25, 0, 0, 633, 68, 1, 0, 0, 0, 634, 635, 5, 92, 0, 0, 635, 636, 7, 26, 0, 0, 636, 70, 1, 0, 0, 0, 637, 638, 8, 27, 0, 0, 638, 72, 1, 0, 0, 0, 639, 641, 7, 3, 0, 0, 640, 642, 7, 28, 0, 0, 641, 640, 1, 0, 0, 0, 641, 642, 1, 0, 0, 0, 642, 644, 1, 0, 0, 0, 643, 645, 3, 65, 25, 0, 644, 643, 1, 0, 0, 0, 645, 646, 1, 0, 0, 0, 646, 644, 1, 0, 0, 0, 646, 647, 1, 0, 0, 0, 647, 74, 1, 0, 0, 0, 648, 649, 5, 64, 0, 0, 649, 76, 1, 0, 0, 0, 650, 651, 5, 96, 0, 0, 651, 78, 1, 0, 0, 0, 652, 656, 8, 29, 0, 0, 653, 654, 5, 96, 0, 0, 654, 656, 5, 96, 0, 0, 655, 652, 1, 0, 0, 0, 655, 653, 1, 0, 0, 0, 656, 80, 1, 0, 0, 0, 657, 658, 5, 95, 0, 0, 658, 82, 1, 0, 0, 0, 659, 663, 3, 67, 26, 0, 660, 663, 3, 65, 25, 0, 661, 663, 3, 81, 33, 0, 662, 659, 1, 0, 0, 0, 662, 660, 1, 0, 0, 0, 662, 661, 1, 0, 0, 0, 663, 84, 1, 0, 0, 0, 664, 669, 5, 34, 0, 0, 665, 668, 3, 69, 27, 0, 666, 668, 3, 71, 28, 0, 667, 665, 1, 0, 0, 0, 667, 666, 1, 0, 0, 0, 668, 671, 1, 0, 0, 0, 669, 667, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 672, 1, 0, 0, 0, 671, 669, 1, 0, 0, 0, 672, 694, 5, 34, 0, 0, 673, 674, 5, 34, 0, 0, 674, 675, 5, 34, 0, 0, 675, 676, 5, 34, 0, 0, 676, 680, 1, 0, 0, 0, 677, 679, 8, 22, 0, 0, 678, 677, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 34, 0, 0, 684, 685, 5, 34, 0, 0, 685, 686, 5, 34, 0, 0, 686, 688, 1, 0, 0, 0, 687, 689, 5, 34, 0, 0, 688, 687, 1, 0, 0, 0, 688, 689, 1, 0, 0, 0, 689, 691, 1, 0, 0, 0, 690, 692, 5, 34, 0, 0, 691, 690, 1, 0, 0, 0, 691, 692, 1, 0, 0, 0, 692, 694, 1, 0, 0, 0, 693, 664, 1, 0, 0, 0, 693, 673, 1, 0, 0, 0, 694, 86, 1, 0, 0, 0, 695, 697, 3, 65, 25, 0, 696, 695, 1, 0, 0, 0, 697, 698, 1, 0, 0, 0, 698, 696, 1, 0, 0, 0, 698, 699, 1, 0, 0, 0, 699, 88, 1, 0, 0, 0, 700, 702, 3, 65, 25, 0, 701, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 701, 1, 0, 0, 0, 703, 704, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 709, 3, 105, 45, 0, 706, 708, 3, 65, 25, 0, 707, 706, 1, 0, 0, 0, 708, 711, 1, 0, 0, 0, 709, 707, 1, 0, 0, 0, 709, 710, 1, 0, 0, 0, 710, 743, 1, 0, 0, 0, 711, 709, 1, 0, 0, 0, 712, 714, 3, 105, 45, 0, 713, 715, 3, 65, 25, 0, 714, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 714, 1, 0, 0, 0, 716, 717, 1, 0, 0, 0, 717, 743, 1, 0, 0, 0, 718, 720, 3, 65, 25, 0, 719, 718, 1, 0, 0, 0, 720, 721, 1, 0, 0, 0, 721, 719, 1, 0, 0, 0, 721, 722, 1, 0, 0, 0, 722, 730, 1, 0, 0, 0, 723, 727, 3, 105, 45, 0, 724, 726, 3, 65, 25, 0, 725, 724, 1, 0, 0, 0, 726, 729, 1, 0, 0, 0, 727, 725, 1, 0, 0, 0, 727, 728, 1, 0, 0, 0, 728, 731, 1, 0, 0, 0, 729, 727, 1, 0, 0, 0, 730, 723, 1, 0, 0, 0, 730, 731, 1, 0, 0, 0, 731, 732, 1, 0, 0, 0, 732, 733, 3, 73, 29, 0, 733, 743, 1, 0, 0, 0, 734, 736, 3, 105, 45, 0, 735, 737, 3, 65, 25, 0, 736, 735, 1, 0, 0, 0, 737, 738, 1, 0, 0, 0, 738, 736, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 3, 73, 29, 0, 741, 743, 1, 0, 0, 0, 742, 701, 1, 0, 0, 0, 742, 712, 1, 0, 0, 0, 742, 719, 1, 0, 0, 0, 742, 734, 1, 0, 0, 0, 743, 90, 1, 0, 0, 0, 744, 745, 7, 30, 0, 0, 745, 746, 7, 31, 0, 0, 746, 92, 1, 0, 0, 0, 747, 748, 7, 12, 0, 0, 748, 749, 7, 9, 0, 0, 749, 750, 7, 0, 0, 0, 750, 94, 1, 0, 0, 0, 751, 752, 7, 12, 0, 0, 752, 753, 7, 2, 0, 0, 753, 754, 7, 4, 0, 0, 754, 96, 1, 0, 0, 0, 755, 756, 5, 61, 0, 0, 756, 98, 1, 0, 0, 0, 757, 758, 5, 58, 0, 0, 758, 759, 5, 58, 0, 0, 759, 100, 1, 0, 0, 0, 760, 761, 5, 44, 0, 0, 761, 102, 1, 0, 0, 0, 762, 763, 7, 0, 0, 0, 763, 764, 7, 3, 0, 0, 764, 765, 7, 2, 0, 0, 765, 766, 7, 4, 0, 0, 766, 104, 1, 0, 0, 0, 767, 768, 5, 46, 0, 0, 768, 106, 1, 0, 0, 0, 769, 770, 7, 15, 0, 0, 770, 771, 7, 12, 0, 0, 771, 772, 7, 13, 0, 0, 772, 773, 7, 2, 0, 0, 773, 774, 7, 3, 0, 0, 774, 108, 1, 0, 0, 0, 775, 776, 7, 15, 0, 0, 776, 777, 7, 1, 0, 0, 777, 778, 7, 6, 0, 0, 778, 779, 7, 2, 0, 0, 779, 780, 7, 5, 0, 0, 780, 110, 1, 0, 0, 0, 781, 782, 7, 1, 0, 0, 782, 783, 7, 9, 0, 0, 783, 112, 1, 0, 0, 0, 784, 785, 7, 1, 0, 0, 785, 786, 7, 2, 0, 0, 786, 114, 1, 0, 0, 0, 787, 788, 7, 13, 0, 0, 788, 789, 7, 12, 0, 0, 789, 790, 7, 2, 0, 0, 790, 791, 7, 5, 0, 0, 791, 116, 1, 0, 0, 0, 792, 793, 7, 13, 0, 0, 793, 794, 7, 1, 0, 0, 794, 795, 7, 18, 0, 0, 795, 796, 7, 3, 0, 0, 796, 118, 1, 0, 0, 0, 797, 798, 5, 40, 0, 0, 798, 120, 1, 0, 0, 0, 799, 800, 7, 9, 0, 0, 800, 801, 7, 7, 0, 0, 801, 802, 7, 5, 0, 0, 802, 122, 1, 0, 0, 0, 803, 804, 7, 9, 0, 0, 804, 805, 7, 20, 0, 0, 805, 806, 7, 13, 0, 0, 806, 807, 7, 13, 0, 0, 807, 124, 1, 0, 0, 0, 808, 809, 7, 9, 0, 0, 809, 810, 7, 20, 0, 0, 810, 811, 7, 13, 0, 0, 811, 812, 7, 13, 0, 0, 812, 813, 7, 2, 0, 0, 813, 126, 1, 0, 0, 0, 814, 815, 7, 7, 0, 0, 815, 816, 7, 6, 0, 0, 816, 128, 1, 0, 0, 0, 817, 818, 5, 63, 0, 0, 818, 130, 1, 0, 0, 0, 819, 820, 7, 6, 0, 0, 820, 821, 7, 13, 0, 0, 821, 822, 7, 1, 0, 0, 822, 823, 7, 18, 0, 0, 823, 824, 7, 3, 0, 0, 824, 132, 1, 0, 0, 0, 825, 826, 5, 41, 0, 0, 826, 134, 1, 0, 0, 0, 827, 828, 7, 5, 0, 0, 828, 829, 7, 6, 0, 0, 829, 830, 7, 20, 0, 0, 830, 831, 7, 3, 0, 0, 831, 136, 1, 0, 0, 0, 832, 833, 5, 61, 0, 0, 833, 834, 5, 61, 0, 0, 834, 138, 1, 0, 0, 0, 835, 836, 5, 61, 0, 0, 836, 837, 5, 126, 0, 0, 837, 140, 1, 0, 0, 0, 838, 839, 5, 33, 0, 0, 839, 840, 5, 61, 0, 0, 840, 142, 1, 0, 0, 0, 841, 842, 5, 60, 0, 0, 842, 144, 1, 0, 0, 0, 843, 844, 5, 60, 0, 0, 844, 845, 5, 61, 0, 0, 845, 146, 1, 0, 0, 0, 846, 847, 5, 62, 0, 0, 847, 148, 1, 0, 0, 0, 848, 849, 5, 62, 0, 0, 849, 850, 5, 61, 0, 0, 850, 150, 1, 0, 0, 0, 851, 852, 5, 43, 0, 0, 852, 152, 1, 0, 0, 0, 853, 854, 5, 45, 0, 0, 854, 154, 1, 0, 0, 0, 855, 856, 5, 42, 0, 0, 856, 156, 1, 0, 0, 0, 857, 858, 5, 47, 0, 0, 858, 158, 1, 0, 0, 0, 859, 860, 5, 37, 0, 0, 860, 160, 1, 0, 0, 0, 861, 862, 4, 73, 3, 0, 862, 863, 3, 61, 23, 0, 863, 864, 1, 0, 0, 0, 864, 865, 6, 73, 12, 0, 865, 162, 1, 0, 0, 0, 866, 867, 3, 45, 15, 0, 867, 868, 1, 0, 0, 0, 868, 869, 6, 74, 13, 0, 869, 164, 1, 0, 0, 0, 870, 873, 3, 129, 57, 0, 871, 874, 3, 67, 26, 0, 872, 874, 3, 81, 33, 0, 873, 871, 1, 0, 0, 0, 873, 872, 1, 0, 0, 0, 874, 878, 1, 0, 0, 0, 875, 877, 3, 83, 34, 0, 876, 875, 1, 0, 0, 0, 877, 880, 1, 0, 0, 0, 878, 876, 1, 0, 0, 0, 878, 879, 1, 0, 0, 0, 879, 888, 1, 0, 0, 0, 880, 878, 1, 0, 0, 0, 881, 883, 3, 129, 57, 0, 882, 884, 3, 65, 25, 0, 883, 882, 1, 0, 0, 0, 884, 885, 1, 0, 0, 0, 885, 883, 1, 0, 0, 0, 885, 886, 1, 0, 0, 0, 886, 888, 1, 0, 0, 0, 887, 870, 1, 0, 0, 0, 887, 881, 1, 0, 0, 0, 888, 166, 1, 0, 0, 0, 889, 890, 5, 91, 0, 0, 890, 891, 1, 0, 0, 0, 891, 892, 6, 76, 0, 0, 892, 893, 6, 76, 0, 0, 893, 168, 1, 0, 0, 0, 894, 895, 5, 93, 0, 0, 895, 896, 1, 0, 0, 0, 896, 897, 6, 77, 11, 0, 897, 898, 6, 77, 11, 0, 898, 170, 1, 0, 0, 0, 899, 903, 3, 67, 26, 0, 900, 902, 3, 83, 34, 0, 901, 900, 1, 0, 0, 0, 902, 905, 1, 0, 0, 0, 903, 901, 1, 0, 0, 0, 903, 904, 1, 0, 0, 0, 904, 916, 1, 0, 0, 0, 905, 903, 1, 0, 0, 0, 906, 909, 3, 81, 33, 0, 907, 909, 3, 75, 30, 0, 908, 906, 1, 0, 0, 0, 908, 907, 1, 0, 0, 0, 909, 911, 1, 0, 0, 0, 910, 912, 3, 83, 34, 0, 911, 910, 1, 0, 0, 0, 912, 913, 1, 0, 0, 0, 913, 911, 1, 0, 0, 0, 913, 914, 1, 0, 0, 0, 914, 916, 1, 0, 0, 0, 915, 899, 1, 0, 0, 0, 915, 908, 1, 0, 0, 0, 916, 172, 1, 0, 0, 0, 917, 919, 3, 77, 31, 0, 918, 920, 3, 79, 32, 0, 919, 918, 1, 0, 0, 0, 920, 921, 1, 0, 0, 0, 921, 919, 1, 0, 0, 0, 921, 922, 1, 0, 0, 0, 922, 923, 1, 0, 0, 0, 923, 924, 3, 77, 31, 0, 924, 174, 1, 0, 0, 0, 925, 926, 3, 173, 79, 0, 926, 176, 1, 0, 0, 0, 927, 928, 3, 55, 20, 0, 928, 929, 1, 0, 0, 0, 929, 930, 6, 81, 10, 0, 930, 178, 1, 0, 0, 0, 931, 932, 3, 57, 21, 0, 932, 933, 1, 0, 0, 0, 933, 934, 6, 82, 10, 0, 934, 180, 1, 0, 0, 0, 935, 936, 3, 59, 22, 0, 936, 937, 1, 0, 0, 0, 937, 938, 6, 83, 10, 0, 938, 182, 1, 0, 0, 0, 939, 940, 3, 167, 76, 0, 940, 941, 1, 0, 0, 0, 941, 942, 6, 84, 14, 0, 942, 943, 6, 84, 15, 0, 943, 184, 1, 0, 0, 0, 944, 945, 3, 63, 24, 0, 945, 946, 1, 0, 0, 0, 946, 947, 6, 85, 16, 0, 947, 948, 6, 85, 11, 0, 948, 186, 1, 0, 0, 0, 949, 950, 3, 59, 22, 0, 950, 951, 1, 0, 0, 0, 951, 952, 6, 86, 10, 0, 952, 188, 1, 0, 0, 0, 953, 954, 3, 55, 20, 0, 954, 955, 1, 0, 0, 0, 955, 956, 6, 87, 10, 0, 956, 190, 1, 0, 0, 0, 957, 958, 3, 57, 21, 0, 958, 959, 1, 0, 0, 0, 959, 960, 6, 88, 10, 0, 960, 192, 1, 0, 0, 0, 961, 962, 3, 63, 24, 0, 962, 963, 1, 0, 0, 0, 963, 964, 6, 89, 16, 0, 964, 965, 6, 89, 11, 0, 965, 194, 1, 0, 0, 0, 966, 967, 3, 167, 76, 0, 967, 968, 1, 0, 0, 0, 968, 969, 6, 90, 14, 0, 969, 196, 1, 0, 0, 0, 970, 971, 3, 169, 77, 0, 971, 972, 1, 0, 0, 0, 972, 973, 6, 91, 17, 0, 973, 198, 1, 0, 0, 0, 974, 975, 3, 61, 23, 0, 975, 976, 1, 0, 0, 0, 976, 977, 6, 92, 12, 0, 977, 200, 1, 0, 0, 0, 978, 979, 3, 101, 43, 0, 979, 980, 1, 0, 0, 0, 980, 981, 6, 93, 18, 0, 981, 202, 1, 0, 0, 0, 982, 983, 3, 97, 41, 0, 983, 984, 1, 0, 0, 0, 984, 985, 6, 94, 19, 0, 985, 204, 1, 0, 0, 0, 986, 987, 7, 16, 0, 0, 987, 988, 7, 3, 0, 0, 988, 989, 7, 5, 0, 0, 989, 990, 7, 12, 0, 0, 990, 991, 7, 0, 0, 0, 991, 992, 7, 12, 0, 0, 992, 993, 7, 5, 0, 0, 993, 994, 7, 12, 0, 0, 994, 206, 1, 0, 0, 0, 995, 999, 8, 32, 0, 0, 996, 997, 5, 47, 0, 0, 997, 999, 8, 33, 0, 0, 998, 995, 1, 0, 0, 0, 998, 996, 1, 0, 0, 0, 999, 208, 1, 0, 0, 0, 1000, 1002, 3, 207, 96, 0, 1001, 1000, 1, 0, 0, 0, 1002, 1003, 1, 0, 0, 0, 1003, 1001, 1, 0, 0, 0, 1003, 1004, 1, 0, 0, 0, 1004, 210, 1, 0, 0, 0, 1005, 1006, 3, 209, 97, 0, 1006, 1007, 1, 0, 0, 0, 1007, 1008, 6, 98, 20, 0, 1008, 212, 1, 0, 0, 0, 1009, 1010, 3, 85, 35, 0, 1010, 1011, 1, 0, 0, 0, 1011, 1012, 6, 99, 21, 0, 1012, 214, 1, 0, 0, 0, 1013, 1014, 3, 55, 20, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1016, 6, 100, 10, 0, 1016, 216, 1, 0, 0, 0, 1017, 1018, 3, 57, 21, 0, 1018, 1019, 1, 0, 0, 0, 1019, 1020, 6, 101, 10, 0, 1020, 218, 1, 0, 0, 0, 1021, 1022, 3, 59, 22, 0, 1022, 1023, 1, 0, 0, 0, 1023, 1024, 6, 102, 10, 0, 1024, 220, 1, 0, 0, 0, 1025, 1026, 3, 63, 24, 0, 1026, 1027, 1, 0, 0, 0, 1027, 1028, 6, 103, 16, 0, 1028, 1029, 6, 103, 11, 0, 1029, 222, 1, 0, 0, 0, 1030, 1031, 3, 105, 45, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1033, 6, 104, 22, 0, 1033, 224, 1, 0, 0, 0, 1034, 1035, 3, 101, 43, 0, 1035, 1036, 1, 0, 0, 0, 1036, 1037, 6, 105, 18, 0, 1037, 226, 1, 0, 0, 0, 1038, 1039, 4, 106, 4, 0, 1039, 1040, 3, 129, 57, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1042, 6, 106, 23, 0, 1042, 228, 1, 0, 0, 0, 1043, 1044, 4, 107, 5, 0, 1044, 1045, 3, 165, 75, 0, 1045, 1046, 1, 0, 0, 0, 1046, 1047, 6, 107, 24, 0, 1047, 230, 1, 0, 0, 0, 1048, 1053, 3, 67, 26, 0, 1049, 1053, 3, 65, 25, 0, 1050, 1053, 3, 81, 33, 0, 1051, 1053, 3, 155, 70, 0, 1052, 1048, 1, 0, 0, 0, 1052, 1049, 1, 0, 0, 0, 1052, 1050, 1, 0, 0, 0, 1052, 1051, 1, 0, 0, 0, 1053, 232, 1, 0, 0, 0, 1054, 1057, 3, 67, 26, 0, 1055, 1057, 3, 155, 70, 0, 1056, 1054, 1, 0, 0, 0, 1056, 1055, 1, 0, 0, 0, 1057, 1061, 1, 0, 0, 0, 1058, 1060, 3, 231, 108, 0, 1059, 1058, 1, 0, 0, 0, 1060, 1063, 1, 0, 0, 0, 1061, 1059, 1, 0, 0, 0, 1061, 1062, 1, 0, 0, 0, 1062, 1074, 1, 0, 0, 0, 1063, 1061, 1, 0, 0, 0, 1064, 1067, 3, 81, 33, 0, 1065, 1067, 3, 75, 30, 0, 1066, 1064, 1, 0, 0, 0, 1066, 1065, 1, 0, 0, 0, 1067, 1069, 1, 0, 0, 0, 1068, 1070, 3, 231, 108, 0, 1069, 1068, 1, 0, 0, 0, 1070, 1071, 1, 0, 0, 0, 1071, 1069, 1, 0, 0, 0, 1071, 1072, 1, 0, 0, 0, 1072, 1074, 1, 0, 0, 0, 1073, 1056, 1, 0, 0, 0, 1073, 1066, 1, 0, 0, 0, 1074, 234, 1, 0, 0, 0, 1075, 1078, 3, 233, 109, 0, 1076, 1078, 3, 173, 79, 0, 1077, 1075, 1, 0, 0, 0, 1077, 1076, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1077, 1, 0, 0, 0, 1079, 1080, 1, 0, 0, 0, 1080, 236, 1, 0, 0, 0, 1081, 1082, 3, 55, 20, 0, 1082, 1083, 1, 0, 0, 0, 1083, 1084, 6, 111, 10, 0, 1084, 238, 1, 0, 0, 0, 1085, 1086, 3, 57, 21, 0, 1086, 1087, 1, 0, 0, 0, 1087, 1088, 6, 112, 10, 0, 1088, 240, 1, 0, 0, 0, 1089, 1090, 3, 59, 22, 0, 1090, 1091, 1, 0, 0, 0, 1091, 1092, 6, 113, 10, 0, 1092, 242, 1, 0, 0, 0, 1093, 1094, 3, 63, 24, 0, 1094, 1095, 1, 0, 0, 0, 1095, 1096, 6, 114, 16, 0, 1096, 1097, 6, 114, 11, 0, 1097, 244, 1, 0, 0, 0, 1098, 1099, 3, 97, 41, 0, 1099, 1100, 1, 0, 0, 0, 1100, 1101, 6, 115, 19, 0, 1101, 246, 1, 0, 0, 0, 1102, 1103, 3, 101, 43, 0, 1103, 1104, 1, 0, 0, 0, 1104, 1105, 6, 116, 18, 0, 1105, 248, 1, 0, 0, 0, 1106, 1107, 3, 105, 45, 0, 1107, 1108, 1, 0, 0, 0, 1108, 1109, 6, 117, 22, 0, 1109, 250, 1, 0, 0, 0, 1110, 1111, 4, 118, 6, 0, 1111, 1112, 3, 129, 57, 0, 1112, 1113, 1, 0, 0, 0, 1113, 1114, 6, 118, 23, 0, 1114, 252, 1, 0, 0, 0, 1115, 1116, 4, 119, 7, 0, 1116, 1117, 3, 165, 75, 0, 1117, 1118, 1, 0, 0, 0, 1118, 1119, 6, 119, 24, 0, 1119, 254, 1, 0, 0, 0, 1120, 1121, 7, 12, 0, 0, 1121, 1122, 7, 2, 0, 0, 1122, 256, 1, 0, 0, 0, 1123, 1124, 3, 235, 110, 0, 1124, 1125, 1, 0, 0, 0, 1125, 1126, 6, 121, 25, 0, 1126, 258, 1, 0, 0, 0, 1127, 1128, 3, 55, 20, 0, 1128, 1129, 1, 0, 0, 0, 1129, 1130, 6, 122, 10, 0, 1130, 260, 1, 0, 0, 0, 1131, 1132, 3, 57, 21, 0, 1132, 1133, 1, 0, 0, 0, 1133, 1134, 6, 123, 10, 0, 1134, 262, 1, 0, 0, 0, 1135, 1136, 3, 59, 22, 0, 1136, 1137, 1, 0, 0, 0, 1137, 1138, 6, 124, 10, 0, 1138, 264, 1, 0, 0, 0, 1139, 1140, 3, 63, 24, 0, 1140, 1141, 1, 0, 0, 0, 1141, 1142, 6, 125, 16, 0, 1142, 1143, 6, 125, 11, 0, 1143, 266, 1, 0, 0, 0, 1144, 1145, 3, 167, 76, 0, 1145, 1146, 1, 0, 0, 0, 1146, 1147, 6, 126, 14, 0, 1147, 1148, 6, 126, 26, 0, 1148, 268, 1, 0, 0, 0, 1149, 1150, 7, 7, 0, 0, 1150, 1151, 7, 9, 0, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 6, 127, 27, 0, 1153, 270, 1, 0, 0, 0, 1154, 1155, 7, 19, 0, 0, 1155, 1156, 7, 1, 0, 0, 1156, 1157, 7, 5, 0, 0, 1157, 1158, 7, 10, 0, 0, 1158, 1159, 1, 0, 0, 0, 1159, 1160, 6, 128, 27, 0, 1160, 272, 1, 0, 0, 0, 1161, 1162, 8, 34, 0, 0, 1162, 274, 1, 0, 0, 0, 1163, 1165, 3, 273, 129, 0, 1164, 1163, 1, 0, 0, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1164, 1, 0, 0, 0, 1166, 1167, 1, 0, 0, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1169, 3, 61, 23, 0, 1169, 1171, 1, 0, 0, 0, 1170, 1164, 1, 0, 0, 0, 1170, 1171, 1, 0, 0, 0, 1171, 1173, 1, 0, 0, 0, 1172, 1174, 3, 273, 129, 0, 1173, 1172, 1, 0, 0, 0, 1174, 1175, 1, 0, 0, 0, 1175, 1173, 1, 0, 0, 0, 1175, 1176, 1, 0, 0, 0, 1176, 276, 1, 0, 0, 0, 1177, 1178, 3, 275, 130, 0, 1178, 1179, 1, 0, 0, 0, 1179, 1180, 6, 131, 28, 0, 1180, 278, 1, 0, 0, 0, 1181, 1182, 3, 55, 20, 0, 1182, 1183, 1, 0, 0, 0, 1183, 1184, 6, 132, 10, 0, 1184, 280, 1, 0, 0, 0, 1185, 1186, 3, 57, 21, 0, 1186, 1187, 1, 0, 0, 0, 1187, 1188, 6, 133, 10, 0, 1188, 282, 1, 0, 0, 0, 1189, 1190, 3, 59, 22, 0, 1190, 1191, 1, 0, 0, 0, 1191, 1192, 6, 134, 10, 0, 1192, 284, 1, 0, 0, 0, 1193, 1194, 3, 63, 24, 0, 1194, 1195, 1, 0, 0, 0, 1195, 1196, 6, 135, 16, 0, 1196, 1197, 6, 135, 11, 0, 1197, 1198, 6, 135, 11, 0, 1198, 286, 1, 0, 0, 0, 1199, 1200, 3, 97, 41, 0, 1200, 1201, 1, 0, 0, 0, 1201, 1202, 6, 136, 19, 0, 1202, 288, 1, 0, 0, 0, 1203, 1204, 3, 101, 43, 0, 1204, 1205, 1, 0, 0, 0, 1205, 1206, 6, 137, 18, 0, 1206, 290, 1, 0, 0, 0, 1207, 1208, 3, 105, 45, 0, 1208, 1209, 1, 0, 0, 0, 1209, 1210, 6, 138, 22, 0, 1210, 292, 1, 0, 0, 0, 1211, 1212, 3, 271, 128, 0, 1212, 1213, 1, 0, 0, 0, 1213, 1214, 6, 139, 29, 0, 1214, 294, 1, 0, 0, 0, 1215, 1216, 3, 235, 110, 0, 1216, 1217, 1, 0, 0, 0, 1217, 1218, 6, 140, 25, 0, 1218, 296, 1, 0, 0, 0, 1219, 1220, 3, 175, 80, 0, 1220, 1221, 1, 0, 0, 0, 1221, 1222, 6, 141, 30, 0, 1222, 298, 1, 0, 0, 0, 1223, 1224, 4, 142, 8, 0, 1224, 1225, 3, 129, 57, 0, 1225, 1226, 1, 0, 0, 0, 1226, 1227, 6, 142, 23, 0, 1227, 300, 1, 0, 0, 0, 1228, 1229, 4, 143, 9, 0, 1229, 1230, 3, 165, 75, 0, 1230, 1231, 1, 0, 0, 0, 1231, 1232, 6, 143, 24, 0, 1232, 302, 1, 0, 0, 0, 1233, 1234, 3, 55, 20, 0, 1234, 1235, 1, 0, 0, 0, 1235, 1236, 6, 144, 10, 0, 1236, 304, 1, 0, 0, 0, 1237, 1238, 3, 57, 21, 0, 1238, 1239, 1, 0, 0, 0, 1239, 1240, 6, 145, 10, 0, 1240, 306, 1, 0, 0, 0, 1241, 1242, 3, 59, 22, 0, 1242, 1243, 1, 0, 0, 0, 1243, 1244, 6, 146, 10, 0, 1244, 308, 1, 0, 0, 0, 1245, 1246, 3, 63, 24, 0, 1246, 1247, 1, 0, 0, 0, 1247, 1248, 6, 147, 16, 0, 1248, 1249, 6, 147, 11, 0, 1249, 310, 1, 0, 0, 0, 1250, 1251, 3, 105, 45, 0, 1251, 1252, 1, 0, 0, 0, 1252, 1253, 6, 148, 22, 0, 1253, 312, 1, 0, 0, 0, 1254, 1255, 4, 149, 10, 0, 1255, 1256, 3, 129, 57, 0, 1256, 1257, 1, 0, 0, 0, 1257, 1258, 6, 149, 23, 0, 1258, 314, 1, 0, 0, 0, 1259, 1260, 4, 150, 11, 0, 1260, 1261, 3, 165, 75, 0, 1261, 1262, 1, 0, 0, 0, 1262, 1263, 6, 150, 24, 0, 1263, 316, 1, 0, 0, 0, 1264, 1265, 3, 175, 80, 0, 1265, 1266, 1, 0, 0, 0, 1266, 1267, 6, 151, 30, 0, 1267, 318, 1, 0, 0, 0, 1268, 1269, 3, 171, 78, 0, 1269, 1270, 1, 0, 0, 0, 1270, 1271, 6, 152, 31, 0, 1271, 320, 1, 0, 0, 0, 1272, 1273, 3, 55, 20, 0, 1273, 1274, 1, 0, 0, 0, 1274, 1275, 6, 153, 10, 0, 1275, 322, 1, 0, 0, 0, 1276, 1277, 3, 57, 21, 0, 1277, 1278, 1, 0, 0, 0, 1278, 1279, 6, 154, 10, 0, 1279, 324, 1, 0, 0, 0, 1280, 1281, 3, 59, 22, 0, 1281, 1282, 1, 0, 0, 0, 1282, 1283, 6, 155, 10, 0, 1283, 326, 1, 0, 0, 0, 1284, 1285, 3, 63, 24, 0, 1285, 1286, 1, 0, 0, 0, 1286, 1287, 6, 156, 16, 0, 1287, 1288, 6, 156, 11, 0, 1288, 328, 1, 0, 0, 0, 1289, 1290, 7, 1, 0, 0, 1290, 1291, 7, 9, 0, 0, 1291, 1292, 7, 15, 0, 0, 1292, 1293, 7, 7, 0, 0, 1293, 330, 1, 0, 0, 0, 1294, 1295, 3, 55, 20, 0, 1295, 1296, 1, 0, 0, 0, 1296, 1297, 6, 158, 10, 0, 1297, 332, 1, 0, 0, 0, 1298, 1299, 3, 57, 21, 0, 1299, 1300, 1, 0, 0, 0, 1300, 1301, 6, 159, 10, 0, 1301, 334, 1, 0, 0, 0, 1302, 1303, 3, 59, 22, 0, 1303, 1304, 1, 0, 0, 0, 1304, 1305, 6, 160, 10, 0, 1305, 336, 1, 0, 0, 0, 1306, 1307, 3, 169, 77, 0, 1307, 1308, 1, 0, 0, 0, 1308, 1309, 6, 161, 17, 0, 1309, 1310, 6, 161, 11, 0, 1310, 338, 1, 0, 0, 0, 1311, 1312, 3, 61, 23, 0, 1312, 1313, 1, 0, 0, 0, 1313, 1314, 6, 162, 12, 0, 1314, 340, 1, 0, 0, 0, 1315, 1321, 3, 75, 30, 0, 1316, 1321, 3, 65, 25, 0, 1317, 1321, 3, 105, 45, 0, 1318, 1321, 3, 67, 26, 0, 1319, 1321, 3, 81, 33, 0, 1320, 1315, 1, 0, 0, 0, 1320, 1316, 1, 0, 0, 0, 1320, 1317, 1, 0, 0, 0, 1320, 1318, 1, 0, 0, 0, 1320, 1319, 1, 0, 0, 0, 1321, 1322, 1, 0, 0, 0, 1322, 1320, 1, 0, 0, 0, 1322, 1323, 1, 0, 0, 0, 1323, 342, 1, 0, 0, 0, 1324, 1325, 3, 55, 20, 0, 1325, 1326, 1, 0, 0, 0, 1326, 1327, 6, 164, 10, 0, 1327, 344, 1, 0, 0, 0, 1328, 1329, 3, 57, 21, 0, 1329, 1330, 1, 0, 0, 0, 1330, 1331, 6, 165, 10, 0, 1331, 346, 1, 0, 0, 0, 1332, 1333, 3, 59, 22, 0, 1333, 1334, 1, 0, 0, 0, 1334, 1335, 6, 166, 10, 0, 1335, 348, 1, 0, 0, 0, 1336, 1337, 3, 63, 24, 0, 1337, 1338, 1, 0, 0, 0, 1338, 1339, 6, 167, 16, 0, 1339, 1340, 6, 167, 11, 0, 1340, 350, 1, 0, 0, 0, 1341, 1342, 3, 61, 23, 0, 1342, 1343, 1, 0, 0, 0, 1343, 1344, 6, 168, 12, 0, 1344, 352, 1, 0, 0, 0, 1345, 1346, 3, 101, 43, 0, 1346, 1347, 1, 0, 0, 0, 1347, 1348, 6, 169, 18, 0, 1348, 354, 1, 0, 0, 0, 1349, 1350, 3, 105, 45, 0, 1350, 1351, 1, 0, 0, 0, 1351, 1352, 6, 170, 22, 0, 1352, 356, 1, 0, 0, 0, 1353, 1354, 3, 269, 127, 0, 1354, 1355, 1, 0, 0, 0, 1355, 1356, 6, 171, 32, 0, 1356, 1357, 6, 171, 33, 0, 1357, 358, 1, 0, 0, 0, 1358, 1359, 3, 209, 97, 0, 1359, 1360, 1, 0, 0, 0, 1360, 1361, 6, 172, 20, 0, 1361, 360, 1, 0, 0, 0, 1362, 1363, 3, 85, 35, 0, 1363, 1364, 1, 0, 0, 0, 1364, 1365, 6, 173, 21, 0, 1365, 362, 1, 0, 0, 0, 1366, 1367, 3, 55, 20, 0, 1367, 1368, 1, 0, 0, 0, 1368, 1369, 6, 174, 10, 0, 1369, 364, 1, 0, 0, 0, 1370, 1371, 3, 57, 21, 0, 1371, 1372, 1, 0, 0, 0, 1372, 1373, 6, 175, 10, 0, 1373, 366, 1, 0, 0, 0, 1374, 1375, 3, 59, 22, 0, 1375, 1376, 1, 0, 0, 0, 1376, 1377, 6, 176, 10, 0, 1377, 368, 1, 0, 0, 0, 1378, 1379, 3, 63, 24, 0, 1379, 1380, 1, 0, 0, 0, 1380, 1381, 6, 177, 16, 0, 1381, 1382, 6, 177, 11, 0, 1382, 1383, 6, 177, 11, 0, 1383, 370, 1, 0, 0, 0, 1384, 1385, 3, 101, 43, 0, 1385, 1386, 1, 0, 0, 0, 1386, 1387, 6, 178, 18, 0, 1387, 372, 1, 0, 0, 0, 1388, 1389, 3, 105, 45, 0, 1389, 1390, 1, 0, 0, 0, 1390, 1391, 6, 179, 22, 0, 1391, 374, 1, 0, 0, 0, 1392, 1393, 3, 235, 110, 0, 1393, 1394, 1, 0, 0, 0, 1394, 1395, 6, 180, 25, 0, 1395, 376, 1, 0, 0, 0, 1396, 1397, 3, 55, 20, 0, 1397, 1398, 1, 0, 0, 0, 1398, 1399, 6, 181, 10, 0, 1399, 378, 1, 0, 0, 0, 1400, 1401, 3, 57, 21, 0, 1401, 1402, 1, 0, 0, 0, 1402, 1403, 6, 182, 10, 0, 1403, 380, 1, 0, 0, 0, 1404, 1405, 3, 59, 22, 0, 1405, 1406, 1, 0, 0, 0, 1406, 1407, 6, 183, 10, 0, 1407, 382, 1, 0, 0, 0, 1408, 1409, 3, 63, 24, 0, 1409, 1410, 1, 0, 0, 0, 1410, 1411, 6, 184, 16, 0, 1411, 1412, 6, 184, 11, 0, 1412, 384, 1, 0, 0, 0, 1413, 1414, 3, 209, 97, 0, 1414, 1415, 1, 0, 0, 0, 1415, 1416, 6, 185, 20, 0, 1416, 1417, 6, 185, 11, 0, 1417, 1418, 6, 185, 34, 0, 1418, 386, 1, 0, 0, 0, 1419, 1420, 3, 85, 35, 0, 1420, 1421, 1, 0, 0, 0, 1421, 1422, 6, 186, 21, 0, 1422, 1423, 6, 186, 11, 0, 1423, 1424, 6, 186, 34, 0, 1424, 388, 1, 0, 0, 0, 1425, 1426, 3, 55, 20, 0, 1426, 1427, 1, 0, 0, 0, 1427, 1428, 6, 187, 10, 0, 1428, 390, 1, 0, 0, 0, 1429, 1430, 3, 57, 21, 0, 1430, 1431, 1, 0, 0, 0, 1431, 1432, 6, 188, 10, 0, 1432, 392, 1, 0, 0, 0, 1433, 1434, 3, 59, 22, 0, 1434, 1435, 1, 0, 0, 0, 1435, 1436, 6, 189, 10, 0, 1436, 394, 1, 0, 0, 0, 1437, 1438, 3, 61, 23, 0, 1438, 1439, 1, 0, 0, 0, 1439, 1440, 6, 190, 12, 0, 1440, 1441, 6, 190, 11, 0, 1441, 1442, 6, 190, 9, 0, 1442, 396, 1, 0, 0, 0, 1443, 1444, 3, 101, 43, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 6, 191, 18, 0, 1446, 1447, 6, 191, 11, 0, 1447, 1448, 6, 191, 9, 0, 1448, 398, 1, 0, 0, 0, 1449, 1450, 3, 55, 20, 0, 1450, 1451, 1, 0, 0, 0, 1451, 1452, 6, 192, 10, 0, 1452, 400, 1, 0, 0, 0, 1453, 1454, 3, 57, 21, 0, 1454, 1455, 1, 0, 0, 0, 1455, 1456, 6, 193, 10, 0, 1456, 402, 1, 0, 0, 0, 1457, 1458, 3, 59, 22, 0, 1458, 1459, 1, 0, 0, 0, 1459, 1460, 6, 194, 10, 0, 1460, 404, 1, 0, 0, 0, 1461, 1462, 3, 175, 80, 0, 1462, 1463, 1, 0, 0, 0, 1463, 1464, 6, 195, 11, 0, 1464, 1465, 6, 195, 0, 0, 1465, 1466, 6, 195, 30, 0, 1466, 406, 1, 0, 0, 0, 1467, 1468, 3, 171, 78, 0, 1468, 1469, 1, 0, 0, 0, 1469, 1470, 6, 196, 11, 0, 1470, 1471, 6, 196, 0, 0, 1471, 1472, 6, 196, 31, 0, 1472, 408, 1, 0, 0, 0, 1473, 1474, 3, 91, 38, 0, 1474, 1475, 1, 0, 0, 0, 1475, 1476, 6, 197, 11, 0, 1476, 1477, 6, 197, 0, 0, 1477, 1478, 6, 197, 35, 0, 1478, 410, 1, 0, 0, 0, 1479, 1480, 3, 63, 24, 0, 1480, 1481, 1, 0, 0, 0, 1481, 1482, 6, 198, 16, 0, 1482, 1483, 6, 198, 11, 0, 1483, 412, 1, 0, 0, 0, 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 581, 591, 595, 598, 607, 609, 620, 641, 646, 655, 662, 667, 669, 680, 688, 691, 693, 698, 703, 709, 716, 721, 727, 730, 738, 742, 873, 878, 885, 887, 903, 908, 913, 915, 921, 998, 1003, 1052, 1056, 1061, 1066, 1071, 1073, 1077, 1079, 1166, 1170, 1175, 1320, 1322, 36, 5, 1, 0, 5, 4, 0, 5, 6, 0, 5, 2, 0, 5, 3, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 5, 11, 0, 5, 13, 0, 0, 1, 0, 4, 0, 0, 7, 24, 0, 7, 16, 0, 7, 65, 0, 5, 0, 0, 7, 25, 0, 7, 66, 0, 7, 34, 0, 7, 32, 0, 7, 76, 0, 7, 26, 0, 7, 36, 0, 7, 48, 0, 7, 64, 0, 7, 80, 0, 5, 10, 0, 5, 7, 0, 7, 90, 0, 7, 89, 0, 7, 68, 0, 7, 67, 0, 7, 88, 0, 5, 12, 0, 5, 14, 0, 7, 29, 0] \ No newline at end of file +[4, 0, 128, 1601, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 2, 198, 7, 198, 2, 199, 7, 199, 2, 200, 7, 200, 2, 201, 7, 201, 2, 202, 7, 202, 2, 203, 7, 203, 2, 204, 7, 204, 2, 205, 7, 205, 2, 206, 7, 206, 2, 207, 7, 207, 2, 208, 7, 208, 2, 209, 7, 209, 2, 210, 7, 210, 2, 211, 7, 211, 2, 212, 7, 212, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 4, 24, 654, 8, 24, 11, 24, 12, 24, 655, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 5, 25, 664, 8, 25, 10, 25, 12, 25, 667, 9, 25, 1, 25, 3, 25, 670, 8, 25, 1, 25, 3, 25, 673, 8, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 5, 26, 682, 8, 26, 10, 26, 12, 26, 685, 9, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 4, 27, 693, 8, 27, 11, 27, 12, 27, 694, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 3, 33, 714, 8, 33, 1, 33, 4, 33, 717, 8, 33, 11, 33, 12, 33, 718, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 3, 36, 728, 8, 36, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 3, 38, 735, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 740, 8, 39, 10, 39, 12, 39, 743, 9, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 5, 39, 751, 8, 39, 10, 39, 12, 39, 754, 9, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 3, 39, 761, 8, 39, 1, 39, 3, 39, 764, 8, 39, 3, 39, 766, 8, 39, 1, 40, 4, 40, 769, 8, 40, 11, 40, 12, 40, 770, 1, 41, 4, 41, 774, 8, 41, 11, 41, 12, 41, 775, 1, 41, 1, 41, 5, 41, 780, 8, 41, 10, 41, 12, 41, 783, 9, 41, 1, 41, 1, 41, 4, 41, 787, 8, 41, 11, 41, 12, 41, 788, 1, 41, 4, 41, 792, 8, 41, 11, 41, 12, 41, 793, 1, 41, 1, 41, 5, 41, 798, 8, 41, 10, 41, 12, 41, 801, 9, 41, 3, 41, 803, 8, 41, 1, 41, 1, 41, 1, 41, 1, 41, 4, 41, 809, 8, 41, 11, 41, 12, 41, 810, 1, 41, 1, 41, 3, 41, 815, 8, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 3, 79, 943, 8, 79, 1, 79, 5, 79, 946, 8, 79, 10, 79, 12, 79, 949, 9, 79, 1, 79, 1, 79, 4, 79, 953, 8, 79, 11, 79, 12, 79, 954, 3, 79, 957, 8, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 5, 82, 971, 8, 82, 10, 82, 12, 82, 974, 9, 82, 1, 82, 1, 82, 3, 82, 978, 8, 82, 1, 82, 4, 82, 981, 8, 82, 11, 82, 12, 82, 982, 3, 82, 985, 8, 82, 1, 83, 1, 83, 4, 83, 989, 8, 83, 11, 83, 12, 83, 990, 1, 83, 1, 83, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 3, 100, 1068, 8, 100, 1, 101, 4, 101, 1071, 8, 101, 11, 101, 12, 101, 1072, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 3, 112, 1122, 8, 112, 1, 113, 1, 113, 3, 113, 1126, 8, 113, 1, 113, 5, 113, 1129, 8, 113, 10, 113, 12, 113, 1132, 9, 113, 1, 113, 1, 113, 3, 113, 1136, 8, 113, 1, 113, 4, 113, 1139, 8, 113, 11, 113, 12, 113, 1140, 3, 113, 1143, 8, 113, 1, 114, 1, 114, 4, 114, 1147, 8, 114, 11, 114, 12, 114, 1148, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 134, 4, 134, 1234, 8, 134, 11, 134, 12, 134, 1235, 1, 134, 1, 134, 3, 134, 1240, 8, 134, 1, 134, 4, 134, 1243, 8, 134, 11, 134, 12, 134, 1244, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 4, 167, 1390, 8, 167, 11, 167, 12, 167, 1391, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 190, 1, 190, 1, 190, 1, 190, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 191, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 192, 1, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 198, 1, 198, 1, 198, 1, 198, 1, 198, 1, 199, 1, 199, 1, 199, 1, 199, 1, 199, 1, 199, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 200, 1, 201, 1, 201, 1, 201, 1, 201, 1, 202, 1, 202, 1, 202, 1, 202, 1, 203, 1, 203, 1, 203, 1, 203, 1, 204, 1, 204, 1, 204, 1, 204, 1, 204, 1, 204, 1, 205, 1, 205, 1, 205, 1, 205, 1, 205, 1, 205, 1, 206, 1, 206, 1, 206, 1, 206, 1, 207, 1, 207, 1, 207, 1, 207, 1, 208, 1, 208, 1, 208, 1, 208, 1, 209, 1, 209, 1, 209, 1, 209, 1, 209, 1, 209, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 210, 1, 211, 1, 211, 1, 211, 1, 211, 1, 211, 1, 211, 1, 212, 1, 212, 1, 212, 1, 212, 1, 212, 2, 683, 752, 0, 213, 16, 1, 18, 2, 20, 3, 22, 4, 24, 5, 26, 6, 28, 7, 30, 8, 32, 9, 34, 10, 36, 11, 38, 12, 40, 13, 42, 14, 44, 15, 46, 16, 48, 17, 50, 18, 52, 19, 54, 20, 56, 21, 58, 22, 60, 23, 62, 24, 64, 25, 66, 26, 68, 27, 70, 28, 72, 29, 74, 0, 76, 0, 78, 0, 80, 0, 82, 0, 84, 0, 86, 0, 88, 0, 90, 0, 92, 0, 94, 30, 96, 31, 98, 32, 100, 33, 102, 34, 104, 35, 106, 36, 108, 37, 110, 38, 112, 39, 114, 40, 116, 41, 118, 42, 120, 43, 122, 44, 124, 45, 126, 46, 128, 47, 130, 48, 132, 49, 134, 50, 136, 51, 138, 52, 140, 53, 142, 54, 144, 55, 146, 56, 148, 57, 150, 58, 152, 59, 154, 60, 156, 61, 158, 62, 160, 63, 162, 64, 164, 65, 166, 66, 168, 67, 170, 68, 172, 0, 174, 69, 176, 70, 178, 71, 180, 72, 182, 0, 184, 73, 186, 74, 188, 75, 190, 76, 192, 0, 194, 0, 196, 77, 198, 78, 200, 79, 202, 0, 204, 0, 206, 0, 208, 0, 210, 0, 212, 0, 214, 80, 216, 0, 218, 81, 220, 0, 222, 0, 224, 82, 226, 83, 228, 84, 230, 0, 232, 0, 234, 0, 236, 0, 238, 0, 240, 0, 242, 0, 244, 85, 246, 86, 248, 87, 250, 88, 252, 0, 254, 0, 256, 0, 258, 0, 260, 0, 262, 0, 264, 89, 266, 0, 268, 90, 270, 91, 272, 92, 274, 0, 276, 0, 278, 93, 280, 94, 282, 0, 284, 95, 286, 0, 288, 96, 290, 97, 292, 98, 294, 0, 296, 0, 298, 0, 300, 0, 302, 0, 304, 0, 306, 0, 308, 0, 310, 0, 312, 99, 314, 100, 316, 101, 318, 0, 320, 0, 322, 0, 324, 0, 326, 0, 328, 0, 330, 102, 332, 103, 334, 104, 336, 0, 338, 105, 340, 106, 342, 107, 344, 108, 346, 0, 348, 0, 350, 109, 352, 110, 354, 111, 356, 112, 358, 0, 360, 0, 362, 0, 364, 0, 366, 0, 368, 0, 370, 0, 372, 113, 374, 114, 376, 115, 378, 0, 380, 0, 382, 0, 384, 0, 386, 116, 388, 117, 390, 118, 392, 0, 394, 0, 396, 0, 398, 0, 400, 119, 402, 0, 404, 0, 406, 120, 408, 121, 410, 122, 412, 0, 414, 0, 416, 0, 418, 123, 420, 124, 422, 125, 424, 0, 426, 0, 428, 126, 430, 127, 432, 128, 434, 0, 436, 0, 438, 0, 440, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 36, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 85, 85, 117, 117, 2, 0, 74, 74, 106, 106, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 34, 44, 44, 47, 47, 58, 58, 61, 61, 91, 91, 93, 93, 124, 124, 2, 0, 42, 42, 47, 47, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1628, 0, 16, 1, 0, 0, 0, 0, 18, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 30, 1, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 36, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 40, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 64, 1, 0, 0, 0, 0, 66, 1, 0, 0, 0, 0, 68, 1, 0, 0, 0, 0, 70, 1, 0, 0, 0, 1, 72, 1, 0, 0, 0, 1, 94, 1, 0, 0, 0, 1, 96, 1, 0, 0, 0, 1, 98, 1, 0, 0, 0, 1, 100, 1, 0, 0, 0, 1, 102, 1, 0, 0, 0, 1, 104, 1, 0, 0, 0, 1, 106, 1, 0, 0, 0, 1, 108, 1, 0, 0, 0, 1, 110, 1, 0, 0, 0, 1, 112, 1, 0, 0, 0, 1, 114, 1, 0, 0, 0, 1, 116, 1, 0, 0, 0, 1, 118, 1, 0, 0, 0, 1, 120, 1, 0, 0, 0, 1, 122, 1, 0, 0, 0, 1, 124, 1, 0, 0, 0, 1, 126, 1, 0, 0, 0, 1, 128, 1, 0, 0, 0, 1, 130, 1, 0, 0, 0, 1, 132, 1, 0, 0, 0, 1, 134, 1, 0, 0, 0, 1, 136, 1, 0, 0, 0, 1, 138, 1, 0, 0, 0, 1, 140, 1, 0, 0, 0, 1, 142, 1, 0, 0, 0, 1, 144, 1, 0, 0, 0, 1, 146, 1, 0, 0, 0, 1, 148, 1, 0, 0, 0, 1, 150, 1, 0, 0, 0, 1, 152, 1, 0, 0, 0, 1, 154, 1, 0, 0, 0, 1, 156, 1, 0, 0, 0, 1, 158, 1, 0, 0, 0, 1, 160, 1, 0, 0, 0, 1, 162, 1, 0, 0, 0, 1, 164, 1, 0, 0, 0, 1, 166, 1, 0, 0, 0, 1, 168, 1, 0, 0, 0, 1, 170, 1, 0, 0, 0, 1, 172, 1, 0, 0, 0, 1, 174, 1, 0, 0, 0, 1, 176, 1, 0, 0, 0, 1, 178, 1, 0, 0, 0, 1, 180, 1, 0, 0, 0, 1, 184, 1, 0, 0, 0, 1, 186, 1, 0, 0, 0, 1, 188, 1, 0, 0, 0, 1, 190, 1, 0, 0, 0, 2, 192, 1, 0, 0, 0, 2, 194, 1, 0, 0, 0, 2, 196, 1, 0, 0, 0, 2, 198, 1, 0, 0, 0, 2, 200, 1, 0, 0, 0, 3, 202, 1, 0, 0, 0, 3, 204, 1, 0, 0, 0, 3, 206, 1, 0, 0, 0, 3, 208, 1, 0, 0, 0, 3, 210, 1, 0, 0, 0, 3, 212, 1, 0, 0, 0, 3, 214, 1, 0, 0, 0, 3, 218, 1, 0, 0, 0, 3, 220, 1, 0, 0, 0, 3, 222, 1, 0, 0, 0, 3, 224, 1, 0, 0, 0, 3, 226, 1, 0, 0, 0, 3, 228, 1, 0, 0, 0, 4, 230, 1, 0, 0, 0, 4, 232, 1, 0, 0, 0, 4, 234, 1, 0, 0, 0, 4, 236, 1, 0, 0, 0, 4, 238, 1, 0, 0, 0, 4, 244, 1, 0, 0, 0, 4, 246, 1, 0, 0, 0, 4, 248, 1, 0, 0, 0, 4, 250, 1, 0, 0, 0, 5, 252, 1, 0, 0, 0, 5, 254, 1, 0, 0, 0, 5, 256, 1, 0, 0, 0, 5, 258, 1, 0, 0, 0, 5, 260, 1, 0, 0, 0, 5, 262, 1, 0, 0, 0, 5, 264, 1, 0, 0, 0, 5, 266, 1, 0, 0, 0, 5, 268, 1, 0, 0, 0, 5, 270, 1, 0, 0, 0, 5, 272, 1, 0, 0, 0, 6, 274, 1, 0, 0, 0, 6, 276, 1, 0, 0, 0, 6, 278, 1, 0, 0, 0, 6, 280, 1, 0, 0, 0, 6, 284, 1, 0, 0, 0, 6, 286, 1, 0, 0, 0, 6, 288, 1, 0, 0, 0, 6, 290, 1, 0, 0, 0, 6, 292, 1, 0, 0, 0, 7, 294, 1, 0, 0, 0, 7, 296, 1, 0, 0, 0, 7, 298, 1, 0, 0, 0, 7, 300, 1, 0, 0, 0, 7, 302, 1, 0, 0, 0, 7, 304, 1, 0, 0, 0, 7, 306, 1, 0, 0, 0, 7, 308, 1, 0, 0, 0, 7, 310, 1, 0, 0, 0, 7, 312, 1, 0, 0, 0, 7, 314, 1, 0, 0, 0, 7, 316, 1, 0, 0, 0, 8, 318, 1, 0, 0, 0, 8, 320, 1, 0, 0, 0, 8, 322, 1, 0, 0, 0, 8, 324, 1, 0, 0, 0, 8, 326, 1, 0, 0, 0, 8, 328, 1, 0, 0, 0, 8, 330, 1, 0, 0, 0, 8, 332, 1, 0, 0, 0, 8, 334, 1, 0, 0, 0, 9, 336, 1, 0, 0, 0, 9, 338, 1, 0, 0, 0, 9, 340, 1, 0, 0, 0, 9, 342, 1, 0, 0, 0, 9, 344, 1, 0, 0, 0, 10, 346, 1, 0, 0, 0, 10, 348, 1, 0, 0, 0, 10, 350, 1, 0, 0, 0, 10, 352, 1, 0, 0, 0, 10, 354, 1, 0, 0, 0, 10, 356, 1, 0, 0, 0, 11, 358, 1, 0, 0, 0, 11, 360, 1, 0, 0, 0, 11, 362, 1, 0, 0, 0, 11, 364, 1, 0, 0, 0, 11, 366, 1, 0, 0, 0, 11, 368, 1, 0, 0, 0, 11, 370, 1, 0, 0, 0, 11, 372, 1, 0, 0, 0, 11, 374, 1, 0, 0, 0, 11, 376, 1, 0, 0, 0, 12, 378, 1, 0, 0, 0, 12, 380, 1, 0, 0, 0, 12, 382, 1, 0, 0, 0, 12, 384, 1, 0, 0, 0, 12, 386, 1, 0, 0, 0, 12, 388, 1, 0, 0, 0, 12, 390, 1, 0, 0, 0, 13, 392, 1, 0, 0, 0, 13, 394, 1, 0, 0, 0, 13, 396, 1, 0, 0, 0, 13, 398, 1, 0, 0, 0, 13, 400, 1, 0, 0, 0, 13, 402, 1, 0, 0, 0, 13, 404, 1, 0, 0, 0, 13, 406, 1, 0, 0, 0, 13, 408, 1, 0, 0, 0, 13, 410, 1, 0, 0, 0, 14, 412, 1, 0, 0, 0, 14, 414, 1, 0, 0, 0, 14, 416, 1, 0, 0, 0, 14, 418, 1, 0, 0, 0, 14, 420, 1, 0, 0, 0, 14, 422, 1, 0, 0, 0, 15, 424, 1, 0, 0, 0, 15, 426, 1, 0, 0, 0, 15, 428, 1, 0, 0, 0, 15, 430, 1, 0, 0, 0, 15, 432, 1, 0, 0, 0, 15, 434, 1, 0, 0, 0, 15, 436, 1, 0, 0, 0, 15, 438, 1, 0, 0, 0, 15, 440, 1, 0, 0, 0, 16, 442, 1, 0, 0, 0, 18, 452, 1, 0, 0, 0, 20, 459, 1, 0, 0, 0, 22, 468, 1, 0, 0, 0, 24, 475, 1, 0, 0, 0, 26, 485, 1, 0, 0, 0, 28, 492, 1, 0, 0, 0, 30, 499, 1, 0, 0, 0, 32, 506, 1, 0, 0, 0, 34, 514, 1, 0, 0, 0, 36, 526, 1, 0, 0, 0, 38, 535, 1, 0, 0, 0, 40, 541, 1, 0, 0, 0, 42, 548, 1, 0, 0, 0, 44, 555, 1, 0, 0, 0, 46, 563, 1, 0, 0, 0, 48, 571, 1, 0, 0, 0, 50, 586, 1, 0, 0, 0, 52, 598, 1, 0, 0, 0, 54, 609, 1, 0, 0, 0, 56, 617, 1, 0, 0, 0, 58, 625, 1, 0, 0, 0, 60, 633, 1, 0, 0, 0, 62, 642, 1, 0, 0, 0, 64, 653, 1, 0, 0, 0, 66, 659, 1, 0, 0, 0, 68, 676, 1, 0, 0, 0, 70, 692, 1, 0, 0, 0, 72, 698, 1, 0, 0, 0, 74, 702, 1, 0, 0, 0, 76, 704, 1, 0, 0, 0, 78, 706, 1, 0, 0, 0, 80, 709, 1, 0, 0, 0, 82, 711, 1, 0, 0, 0, 84, 720, 1, 0, 0, 0, 86, 722, 1, 0, 0, 0, 88, 727, 1, 0, 0, 0, 90, 729, 1, 0, 0, 0, 92, 734, 1, 0, 0, 0, 94, 765, 1, 0, 0, 0, 96, 768, 1, 0, 0, 0, 98, 814, 1, 0, 0, 0, 100, 816, 1, 0, 0, 0, 102, 819, 1, 0, 0, 0, 104, 823, 1, 0, 0, 0, 106, 827, 1, 0, 0, 0, 108, 829, 1, 0, 0, 0, 110, 832, 1, 0, 0, 0, 112, 834, 1, 0, 0, 0, 114, 836, 1, 0, 0, 0, 116, 841, 1, 0, 0, 0, 118, 843, 1, 0, 0, 0, 120, 849, 1, 0, 0, 0, 122, 855, 1, 0, 0, 0, 124, 858, 1, 0, 0, 0, 126, 861, 1, 0, 0, 0, 128, 866, 1, 0, 0, 0, 130, 871, 1, 0, 0, 0, 132, 873, 1, 0, 0, 0, 134, 877, 1, 0, 0, 0, 136, 882, 1, 0, 0, 0, 138, 888, 1, 0, 0, 0, 140, 891, 1, 0, 0, 0, 142, 893, 1, 0, 0, 0, 144, 899, 1, 0, 0, 0, 146, 901, 1, 0, 0, 0, 148, 906, 1, 0, 0, 0, 150, 909, 1, 0, 0, 0, 152, 912, 1, 0, 0, 0, 154, 915, 1, 0, 0, 0, 156, 917, 1, 0, 0, 0, 158, 920, 1, 0, 0, 0, 160, 922, 1, 0, 0, 0, 162, 925, 1, 0, 0, 0, 164, 927, 1, 0, 0, 0, 166, 929, 1, 0, 0, 0, 168, 931, 1, 0, 0, 0, 170, 933, 1, 0, 0, 0, 172, 935, 1, 0, 0, 0, 174, 956, 1, 0, 0, 0, 176, 958, 1, 0, 0, 0, 178, 963, 1, 0, 0, 0, 180, 984, 1, 0, 0, 0, 182, 986, 1, 0, 0, 0, 184, 994, 1, 0, 0, 0, 186, 996, 1, 0, 0, 0, 188, 1000, 1, 0, 0, 0, 190, 1004, 1, 0, 0, 0, 192, 1008, 1, 0, 0, 0, 194, 1013, 1, 0, 0, 0, 196, 1018, 1, 0, 0, 0, 198, 1022, 1, 0, 0, 0, 200, 1026, 1, 0, 0, 0, 202, 1030, 1, 0, 0, 0, 204, 1035, 1, 0, 0, 0, 206, 1039, 1, 0, 0, 0, 208, 1043, 1, 0, 0, 0, 210, 1047, 1, 0, 0, 0, 212, 1051, 1, 0, 0, 0, 214, 1055, 1, 0, 0, 0, 216, 1067, 1, 0, 0, 0, 218, 1070, 1, 0, 0, 0, 220, 1074, 1, 0, 0, 0, 222, 1078, 1, 0, 0, 0, 224, 1082, 1, 0, 0, 0, 226, 1086, 1, 0, 0, 0, 228, 1090, 1, 0, 0, 0, 230, 1094, 1, 0, 0, 0, 232, 1099, 1, 0, 0, 0, 234, 1103, 1, 0, 0, 0, 236, 1107, 1, 0, 0, 0, 238, 1112, 1, 0, 0, 0, 240, 1121, 1, 0, 0, 0, 242, 1142, 1, 0, 0, 0, 244, 1146, 1, 0, 0, 0, 246, 1150, 1, 0, 0, 0, 248, 1154, 1, 0, 0, 0, 250, 1158, 1, 0, 0, 0, 252, 1162, 1, 0, 0, 0, 254, 1167, 1, 0, 0, 0, 256, 1171, 1, 0, 0, 0, 258, 1175, 1, 0, 0, 0, 260, 1179, 1, 0, 0, 0, 262, 1184, 1, 0, 0, 0, 264, 1189, 1, 0, 0, 0, 266, 1192, 1, 0, 0, 0, 268, 1196, 1, 0, 0, 0, 270, 1200, 1, 0, 0, 0, 272, 1204, 1, 0, 0, 0, 274, 1208, 1, 0, 0, 0, 276, 1213, 1, 0, 0, 0, 278, 1218, 1, 0, 0, 0, 280, 1223, 1, 0, 0, 0, 282, 1230, 1, 0, 0, 0, 284, 1239, 1, 0, 0, 0, 286, 1246, 1, 0, 0, 0, 288, 1250, 1, 0, 0, 0, 290, 1254, 1, 0, 0, 0, 292, 1258, 1, 0, 0, 0, 294, 1262, 1, 0, 0, 0, 296, 1268, 1, 0, 0, 0, 298, 1272, 1, 0, 0, 0, 300, 1276, 1, 0, 0, 0, 302, 1280, 1, 0, 0, 0, 304, 1284, 1, 0, 0, 0, 306, 1288, 1, 0, 0, 0, 308, 1292, 1, 0, 0, 0, 310, 1297, 1, 0, 0, 0, 312, 1302, 1, 0, 0, 0, 314, 1306, 1, 0, 0, 0, 316, 1310, 1, 0, 0, 0, 318, 1314, 1, 0, 0, 0, 320, 1319, 1, 0, 0, 0, 322, 1323, 1, 0, 0, 0, 324, 1328, 1, 0, 0, 0, 326, 1333, 1, 0, 0, 0, 328, 1337, 1, 0, 0, 0, 330, 1341, 1, 0, 0, 0, 332, 1345, 1, 0, 0, 0, 334, 1349, 1, 0, 0, 0, 336, 1353, 1, 0, 0, 0, 338, 1358, 1, 0, 0, 0, 340, 1363, 1, 0, 0, 0, 342, 1367, 1, 0, 0, 0, 344, 1371, 1, 0, 0, 0, 346, 1375, 1, 0, 0, 0, 348, 1380, 1, 0, 0, 0, 350, 1389, 1, 0, 0, 0, 352, 1393, 1, 0, 0, 0, 354, 1397, 1, 0, 0, 0, 356, 1401, 1, 0, 0, 0, 358, 1405, 1, 0, 0, 0, 360, 1410, 1, 0, 0, 0, 362, 1414, 1, 0, 0, 0, 364, 1418, 1, 0, 0, 0, 366, 1422, 1, 0, 0, 0, 368, 1427, 1, 0, 0, 0, 370, 1431, 1, 0, 0, 0, 372, 1435, 1, 0, 0, 0, 374, 1439, 1, 0, 0, 0, 376, 1443, 1, 0, 0, 0, 378, 1447, 1, 0, 0, 0, 380, 1453, 1, 0, 0, 0, 382, 1457, 1, 0, 0, 0, 384, 1461, 1, 0, 0, 0, 386, 1465, 1, 0, 0, 0, 388, 1469, 1, 0, 0, 0, 390, 1473, 1, 0, 0, 0, 392, 1477, 1, 0, 0, 0, 394, 1482, 1, 0, 0, 0, 396, 1486, 1, 0, 0, 0, 398, 1490, 1, 0, 0, 0, 400, 1496, 1, 0, 0, 0, 402, 1505, 1, 0, 0, 0, 404, 1509, 1, 0, 0, 0, 406, 1513, 1, 0, 0, 0, 408, 1517, 1, 0, 0, 0, 410, 1521, 1, 0, 0, 0, 412, 1525, 1, 0, 0, 0, 414, 1530, 1, 0, 0, 0, 416, 1536, 1, 0, 0, 0, 418, 1542, 1, 0, 0, 0, 420, 1546, 1, 0, 0, 0, 422, 1550, 1, 0, 0, 0, 424, 1554, 1, 0, 0, 0, 426, 1560, 1, 0, 0, 0, 428, 1566, 1, 0, 0, 0, 430, 1570, 1, 0, 0, 0, 432, 1574, 1, 0, 0, 0, 434, 1578, 1, 0, 0, 0, 436, 1584, 1, 0, 0, 0, 438, 1590, 1, 0, 0, 0, 440, 1596, 1, 0, 0, 0, 442, 443, 7, 0, 0, 0, 443, 444, 7, 1, 0, 0, 444, 445, 7, 2, 0, 0, 445, 446, 7, 2, 0, 0, 446, 447, 7, 3, 0, 0, 447, 448, 7, 4, 0, 0, 448, 449, 7, 5, 0, 0, 449, 450, 1, 0, 0, 0, 450, 451, 6, 0, 0, 0, 451, 17, 1, 0, 0, 0, 452, 453, 7, 0, 0, 0, 453, 454, 7, 6, 0, 0, 454, 455, 7, 7, 0, 0, 455, 456, 7, 8, 0, 0, 456, 457, 1, 0, 0, 0, 457, 458, 6, 1, 1, 0, 458, 19, 1, 0, 0, 0, 459, 460, 7, 3, 0, 0, 460, 461, 7, 9, 0, 0, 461, 462, 7, 6, 0, 0, 462, 463, 7, 1, 0, 0, 463, 464, 7, 4, 0, 0, 464, 465, 7, 10, 0, 0, 465, 466, 1, 0, 0, 0, 466, 467, 6, 2, 2, 0, 467, 21, 1, 0, 0, 0, 468, 469, 7, 3, 0, 0, 469, 470, 7, 11, 0, 0, 470, 471, 7, 12, 0, 0, 471, 472, 7, 13, 0, 0, 472, 473, 1, 0, 0, 0, 473, 474, 6, 3, 0, 0, 474, 23, 1, 0, 0, 0, 475, 476, 7, 3, 0, 0, 476, 477, 7, 14, 0, 0, 477, 478, 7, 8, 0, 0, 478, 479, 7, 13, 0, 0, 479, 480, 7, 12, 0, 0, 480, 481, 7, 1, 0, 0, 481, 482, 7, 9, 0, 0, 482, 483, 1, 0, 0, 0, 483, 484, 6, 4, 3, 0, 484, 25, 1, 0, 0, 0, 485, 486, 7, 15, 0, 0, 486, 487, 7, 6, 0, 0, 487, 488, 7, 7, 0, 0, 488, 489, 7, 16, 0, 0, 489, 490, 1, 0, 0, 0, 490, 491, 6, 5, 4, 0, 491, 27, 1, 0, 0, 0, 492, 493, 7, 17, 0, 0, 493, 494, 7, 6, 0, 0, 494, 495, 7, 7, 0, 0, 495, 496, 7, 18, 0, 0, 496, 497, 1, 0, 0, 0, 497, 498, 6, 6, 0, 0, 498, 29, 1, 0, 0, 0, 499, 500, 7, 18, 0, 0, 500, 501, 7, 3, 0, 0, 501, 502, 7, 3, 0, 0, 502, 503, 7, 8, 0, 0, 503, 504, 1, 0, 0, 0, 504, 505, 6, 7, 1, 0, 505, 31, 1, 0, 0, 0, 506, 507, 7, 13, 0, 0, 507, 508, 7, 1, 0, 0, 508, 509, 7, 16, 0, 0, 509, 510, 7, 1, 0, 0, 510, 511, 7, 5, 0, 0, 511, 512, 1, 0, 0, 0, 512, 513, 6, 8, 0, 0, 513, 33, 1, 0, 0, 0, 514, 515, 7, 16, 0, 0, 515, 516, 7, 11, 0, 0, 516, 517, 5, 95, 0, 0, 517, 518, 7, 3, 0, 0, 518, 519, 7, 14, 0, 0, 519, 520, 7, 8, 0, 0, 520, 521, 7, 12, 0, 0, 521, 522, 7, 9, 0, 0, 522, 523, 7, 0, 0, 0, 523, 524, 1, 0, 0, 0, 524, 525, 6, 9, 5, 0, 525, 35, 1, 0, 0, 0, 526, 527, 7, 6, 0, 0, 527, 528, 7, 3, 0, 0, 528, 529, 7, 9, 0, 0, 529, 530, 7, 12, 0, 0, 530, 531, 7, 16, 0, 0, 531, 532, 7, 3, 0, 0, 532, 533, 1, 0, 0, 0, 533, 534, 6, 10, 6, 0, 534, 37, 1, 0, 0, 0, 535, 536, 7, 6, 0, 0, 536, 537, 7, 7, 0, 0, 537, 538, 7, 19, 0, 0, 538, 539, 1, 0, 0, 0, 539, 540, 6, 11, 0, 0, 540, 39, 1, 0, 0, 0, 541, 542, 7, 2, 0, 0, 542, 543, 7, 10, 0, 0, 543, 544, 7, 7, 0, 0, 544, 545, 7, 19, 0, 0, 545, 546, 1, 0, 0, 0, 546, 547, 6, 12, 7, 0, 547, 41, 1, 0, 0, 0, 548, 549, 7, 2, 0, 0, 549, 550, 7, 7, 0, 0, 550, 551, 7, 6, 0, 0, 551, 552, 7, 5, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 6, 13, 0, 0, 554, 43, 1, 0, 0, 0, 555, 556, 7, 2, 0, 0, 556, 557, 7, 5, 0, 0, 557, 558, 7, 12, 0, 0, 558, 559, 7, 5, 0, 0, 559, 560, 7, 2, 0, 0, 560, 561, 1, 0, 0, 0, 561, 562, 6, 14, 0, 0, 562, 45, 1, 0, 0, 0, 563, 564, 7, 19, 0, 0, 564, 565, 7, 10, 0, 0, 565, 566, 7, 3, 0, 0, 566, 567, 7, 6, 0, 0, 567, 568, 7, 3, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 6, 15, 0, 0, 570, 47, 1, 0, 0, 0, 571, 572, 4, 16, 0, 0, 572, 573, 7, 1, 0, 0, 573, 574, 7, 9, 0, 0, 574, 575, 7, 13, 0, 0, 575, 576, 7, 1, 0, 0, 576, 577, 7, 9, 0, 0, 577, 578, 7, 3, 0, 0, 578, 579, 7, 2, 0, 0, 579, 580, 7, 5, 0, 0, 580, 581, 7, 12, 0, 0, 581, 582, 7, 5, 0, 0, 582, 583, 7, 2, 0, 0, 583, 584, 1, 0, 0, 0, 584, 585, 6, 16, 0, 0, 585, 49, 1, 0, 0, 0, 586, 587, 4, 17, 1, 0, 587, 588, 7, 13, 0, 0, 588, 589, 7, 7, 0, 0, 589, 590, 7, 7, 0, 0, 590, 591, 7, 18, 0, 0, 591, 592, 7, 20, 0, 0, 592, 593, 7, 8, 0, 0, 593, 594, 5, 95, 0, 0, 594, 595, 5, 128020, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 6, 17, 8, 0, 597, 51, 1, 0, 0, 0, 598, 599, 4, 18, 2, 0, 599, 600, 7, 16, 0, 0, 600, 601, 7, 3, 0, 0, 601, 602, 7, 5, 0, 0, 602, 603, 7, 6, 0, 0, 603, 604, 7, 1, 0, 0, 604, 605, 7, 4, 0, 0, 605, 606, 7, 2, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 6, 18, 9, 0, 608, 53, 1, 0, 0, 0, 609, 610, 4, 19, 3, 0, 610, 611, 7, 21, 0, 0, 611, 612, 7, 7, 0, 0, 612, 613, 7, 1, 0, 0, 613, 614, 7, 9, 0, 0, 614, 615, 1, 0, 0, 0, 615, 616, 6, 19, 10, 0, 616, 55, 1, 0, 0, 0, 617, 618, 4, 20, 4, 0, 618, 619, 7, 15, 0, 0, 619, 620, 7, 20, 0, 0, 620, 621, 7, 13, 0, 0, 621, 622, 7, 13, 0, 0, 622, 623, 1, 0, 0, 0, 623, 624, 6, 20, 10, 0, 624, 57, 1, 0, 0, 0, 625, 626, 4, 21, 5, 0, 626, 627, 7, 13, 0, 0, 627, 628, 7, 3, 0, 0, 628, 629, 7, 15, 0, 0, 629, 630, 7, 5, 0, 0, 630, 631, 1, 0, 0, 0, 631, 632, 6, 21, 10, 0, 632, 59, 1, 0, 0, 0, 633, 634, 4, 22, 6, 0, 634, 635, 7, 6, 0, 0, 635, 636, 7, 1, 0, 0, 636, 637, 7, 17, 0, 0, 637, 638, 7, 10, 0, 0, 638, 639, 7, 5, 0, 0, 639, 640, 1, 0, 0, 0, 640, 641, 6, 22, 10, 0, 641, 61, 1, 0, 0, 0, 642, 643, 4, 23, 7, 0, 643, 644, 7, 13, 0, 0, 644, 645, 7, 7, 0, 0, 645, 646, 7, 7, 0, 0, 646, 647, 7, 18, 0, 0, 647, 648, 7, 20, 0, 0, 648, 649, 7, 8, 0, 0, 649, 650, 1, 0, 0, 0, 650, 651, 6, 23, 10, 0, 651, 63, 1, 0, 0, 0, 652, 654, 8, 22, 0, 0, 653, 652, 1, 0, 0, 0, 654, 655, 1, 0, 0, 0, 655, 653, 1, 0, 0, 0, 655, 656, 1, 0, 0, 0, 656, 657, 1, 0, 0, 0, 657, 658, 6, 24, 0, 0, 658, 65, 1, 0, 0, 0, 659, 660, 5, 47, 0, 0, 660, 661, 5, 47, 0, 0, 661, 665, 1, 0, 0, 0, 662, 664, 8, 23, 0, 0, 663, 662, 1, 0, 0, 0, 664, 667, 1, 0, 0, 0, 665, 663, 1, 0, 0, 0, 665, 666, 1, 0, 0, 0, 666, 669, 1, 0, 0, 0, 667, 665, 1, 0, 0, 0, 668, 670, 5, 13, 0, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 672, 1, 0, 0, 0, 671, 673, 5, 10, 0, 0, 672, 671, 1, 0, 0, 0, 672, 673, 1, 0, 0, 0, 673, 674, 1, 0, 0, 0, 674, 675, 6, 25, 11, 0, 675, 67, 1, 0, 0, 0, 676, 677, 5, 47, 0, 0, 677, 678, 5, 42, 0, 0, 678, 683, 1, 0, 0, 0, 679, 682, 3, 68, 26, 0, 680, 682, 9, 0, 0, 0, 681, 679, 1, 0, 0, 0, 681, 680, 1, 0, 0, 0, 682, 685, 1, 0, 0, 0, 683, 684, 1, 0, 0, 0, 683, 681, 1, 0, 0, 0, 684, 686, 1, 0, 0, 0, 685, 683, 1, 0, 0, 0, 686, 687, 5, 42, 0, 0, 687, 688, 5, 47, 0, 0, 688, 689, 1, 0, 0, 0, 689, 690, 6, 26, 11, 0, 690, 69, 1, 0, 0, 0, 691, 693, 7, 24, 0, 0, 692, 691, 1, 0, 0, 0, 693, 694, 1, 0, 0, 0, 694, 692, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 697, 6, 27, 11, 0, 697, 71, 1, 0, 0, 0, 698, 699, 5, 124, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 6, 28, 12, 0, 701, 73, 1, 0, 0, 0, 702, 703, 7, 25, 0, 0, 703, 75, 1, 0, 0, 0, 704, 705, 7, 26, 0, 0, 705, 77, 1, 0, 0, 0, 706, 707, 5, 92, 0, 0, 707, 708, 7, 27, 0, 0, 708, 79, 1, 0, 0, 0, 709, 710, 8, 28, 0, 0, 710, 81, 1, 0, 0, 0, 711, 713, 7, 3, 0, 0, 712, 714, 7, 29, 0, 0, 713, 712, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 716, 1, 0, 0, 0, 715, 717, 3, 74, 29, 0, 716, 715, 1, 0, 0, 0, 717, 718, 1, 0, 0, 0, 718, 716, 1, 0, 0, 0, 718, 719, 1, 0, 0, 0, 719, 83, 1, 0, 0, 0, 720, 721, 5, 64, 0, 0, 721, 85, 1, 0, 0, 0, 722, 723, 5, 96, 0, 0, 723, 87, 1, 0, 0, 0, 724, 728, 8, 30, 0, 0, 725, 726, 5, 96, 0, 0, 726, 728, 5, 96, 0, 0, 727, 724, 1, 0, 0, 0, 727, 725, 1, 0, 0, 0, 728, 89, 1, 0, 0, 0, 729, 730, 5, 95, 0, 0, 730, 91, 1, 0, 0, 0, 731, 735, 3, 76, 30, 0, 732, 735, 3, 74, 29, 0, 733, 735, 3, 90, 37, 0, 734, 731, 1, 0, 0, 0, 734, 732, 1, 0, 0, 0, 734, 733, 1, 0, 0, 0, 735, 93, 1, 0, 0, 0, 736, 741, 5, 34, 0, 0, 737, 740, 3, 78, 31, 0, 738, 740, 3, 80, 32, 0, 739, 737, 1, 0, 0, 0, 739, 738, 1, 0, 0, 0, 740, 743, 1, 0, 0, 0, 741, 739, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 744, 1, 0, 0, 0, 743, 741, 1, 0, 0, 0, 744, 766, 5, 34, 0, 0, 745, 746, 5, 34, 0, 0, 746, 747, 5, 34, 0, 0, 747, 748, 5, 34, 0, 0, 748, 752, 1, 0, 0, 0, 749, 751, 8, 23, 0, 0, 750, 749, 1, 0, 0, 0, 751, 754, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 752, 750, 1, 0, 0, 0, 753, 755, 1, 0, 0, 0, 754, 752, 1, 0, 0, 0, 755, 756, 5, 34, 0, 0, 756, 757, 5, 34, 0, 0, 757, 758, 5, 34, 0, 0, 758, 760, 1, 0, 0, 0, 759, 761, 5, 34, 0, 0, 760, 759, 1, 0, 0, 0, 760, 761, 1, 0, 0, 0, 761, 763, 1, 0, 0, 0, 762, 764, 5, 34, 0, 0, 763, 762, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 766, 1, 0, 0, 0, 765, 736, 1, 0, 0, 0, 765, 745, 1, 0, 0, 0, 766, 95, 1, 0, 0, 0, 767, 769, 3, 74, 29, 0, 768, 767, 1, 0, 0, 0, 769, 770, 1, 0, 0, 0, 770, 768, 1, 0, 0, 0, 770, 771, 1, 0, 0, 0, 771, 97, 1, 0, 0, 0, 772, 774, 3, 74, 29, 0, 773, 772, 1, 0, 0, 0, 774, 775, 1, 0, 0, 0, 775, 773, 1, 0, 0, 0, 775, 776, 1, 0, 0, 0, 776, 777, 1, 0, 0, 0, 777, 781, 3, 116, 50, 0, 778, 780, 3, 74, 29, 0, 779, 778, 1, 0, 0, 0, 780, 783, 1, 0, 0, 0, 781, 779, 1, 0, 0, 0, 781, 782, 1, 0, 0, 0, 782, 815, 1, 0, 0, 0, 783, 781, 1, 0, 0, 0, 784, 786, 3, 116, 50, 0, 785, 787, 3, 74, 29, 0, 786, 785, 1, 0, 0, 0, 787, 788, 1, 0, 0, 0, 788, 786, 1, 0, 0, 0, 788, 789, 1, 0, 0, 0, 789, 815, 1, 0, 0, 0, 790, 792, 3, 74, 29, 0, 791, 790, 1, 0, 0, 0, 792, 793, 1, 0, 0, 0, 793, 791, 1, 0, 0, 0, 793, 794, 1, 0, 0, 0, 794, 802, 1, 0, 0, 0, 795, 799, 3, 116, 50, 0, 796, 798, 3, 74, 29, 0, 797, 796, 1, 0, 0, 0, 798, 801, 1, 0, 0, 0, 799, 797, 1, 0, 0, 0, 799, 800, 1, 0, 0, 0, 800, 803, 1, 0, 0, 0, 801, 799, 1, 0, 0, 0, 802, 795, 1, 0, 0, 0, 802, 803, 1, 0, 0, 0, 803, 804, 1, 0, 0, 0, 804, 805, 3, 82, 33, 0, 805, 815, 1, 0, 0, 0, 806, 808, 3, 116, 50, 0, 807, 809, 3, 74, 29, 0, 808, 807, 1, 0, 0, 0, 809, 810, 1, 0, 0, 0, 810, 808, 1, 0, 0, 0, 810, 811, 1, 0, 0, 0, 811, 812, 1, 0, 0, 0, 812, 813, 3, 82, 33, 0, 813, 815, 1, 0, 0, 0, 814, 773, 1, 0, 0, 0, 814, 784, 1, 0, 0, 0, 814, 791, 1, 0, 0, 0, 814, 806, 1, 0, 0, 0, 815, 99, 1, 0, 0, 0, 816, 817, 7, 31, 0, 0, 817, 818, 7, 32, 0, 0, 818, 101, 1, 0, 0, 0, 819, 820, 7, 12, 0, 0, 820, 821, 7, 9, 0, 0, 821, 822, 7, 0, 0, 0, 822, 103, 1, 0, 0, 0, 823, 824, 7, 12, 0, 0, 824, 825, 7, 2, 0, 0, 825, 826, 7, 4, 0, 0, 826, 105, 1, 0, 0, 0, 827, 828, 5, 61, 0, 0, 828, 107, 1, 0, 0, 0, 829, 830, 5, 58, 0, 0, 830, 831, 5, 58, 0, 0, 831, 109, 1, 0, 0, 0, 832, 833, 5, 58, 0, 0, 833, 111, 1, 0, 0, 0, 834, 835, 5, 44, 0, 0, 835, 113, 1, 0, 0, 0, 836, 837, 7, 0, 0, 0, 837, 838, 7, 3, 0, 0, 838, 839, 7, 2, 0, 0, 839, 840, 7, 4, 0, 0, 840, 115, 1, 0, 0, 0, 841, 842, 5, 46, 0, 0, 842, 117, 1, 0, 0, 0, 843, 844, 7, 15, 0, 0, 844, 845, 7, 12, 0, 0, 845, 846, 7, 13, 0, 0, 846, 847, 7, 2, 0, 0, 847, 848, 7, 3, 0, 0, 848, 119, 1, 0, 0, 0, 849, 850, 7, 15, 0, 0, 850, 851, 7, 1, 0, 0, 851, 852, 7, 6, 0, 0, 852, 853, 7, 2, 0, 0, 853, 854, 7, 5, 0, 0, 854, 121, 1, 0, 0, 0, 855, 856, 7, 1, 0, 0, 856, 857, 7, 9, 0, 0, 857, 123, 1, 0, 0, 0, 858, 859, 7, 1, 0, 0, 859, 860, 7, 2, 0, 0, 860, 125, 1, 0, 0, 0, 861, 862, 7, 13, 0, 0, 862, 863, 7, 12, 0, 0, 863, 864, 7, 2, 0, 0, 864, 865, 7, 5, 0, 0, 865, 127, 1, 0, 0, 0, 866, 867, 7, 13, 0, 0, 867, 868, 7, 1, 0, 0, 868, 869, 7, 18, 0, 0, 869, 870, 7, 3, 0, 0, 870, 129, 1, 0, 0, 0, 871, 872, 5, 40, 0, 0, 872, 131, 1, 0, 0, 0, 873, 874, 7, 9, 0, 0, 874, 875, 7, 7, 0, 0, 875, 876, 7, 5, 0, 0, 876, 133, 1, 0, 0, 0, 877, 878, 7, 9, 0, 0, 878, 879, 7, 20, 0, 0, 879, 880, 7, 13, 0, 0, 880, 881, 7, 13, 0, 0, 881, 135, 1, 0, 0, 0, 882, 883, 7, 9, 0, 0, 883, 884, 7, 20, 0, 0, 884, 885, 7, 13, 0, 0, 885, 886, 7, 13, 0, 0, 886, 887, 7, 2, 0, 0, 887, 137, 1, 0, 0, 0, 888, 889, 7, 7, 0, 0, 889, 890, 7, 6, 0, 0, 890, 139, 1, 0, 0, 0, 891, 892, 5, 63, 0, 0, 892, 141, 1, 0, 0, 0, 893, 894, 7, 6, 0, 0, 894, 895, 7, 13, 0, 0, 895, 896, 7, 1, 0, 0, 896, 897, 7, 18, 0, 0, 897, 898, 7, 3, 0, 0, 898, 143, 1, 0, 0, 0, 899, 900, 5, 41, 0, 0, 900, 145, 1, 0, 0, 0, 901, 902, 7, 5, 0, 0, 902, 903, 7, 6, 0, 0, 903, 904, 7, 20, 0, 0, 904, 905, 7, 3, 0, 0, 905, 147, 1, 0, 0, 0, 906, 907, 5, 61, 0, 0, 907, 908, 5, 61, 0, 0, 908, 149, 1, 0, 0, 0, 909, 910, 5, 61, 0, 0, 910, 911, 5, 126, 0, 0, 911, 151, 1, 0, 0, 0, 912, 913, 5, 33, 0, 0, 913, 914, 5, 61, 0, 0, 914, 153, 1, 0, 0, 0, 915, 916, 5, 60, 0, 0, 916, 155, 1, 0, 0, 0, 917, 918, 5, 60, 0, 0, 918, 919, 5, 61, 0, 0, 919, 157, 1, 0, 0, 0, 920, 921, 5, 62, 0, 0, 921, 159, 1, 0, 0, 0, 922, 923, 5, 62, 0, 0, 923, 924, 5, 61, 0, 0, 924, 161, 1, 0, 0, 0, 925, 926, 5, 43, 0, 0, 926, 163, 1, 0, 0, 0, 927, 928, 5, 45, 0, 0, 928, 165, 1, 0, 0, 0, 929, 930, 5, 42, 0, 0, 930, 167, 1, 0, 0, 0, 931, 932, 5, 47, 0, 0, 932, 169, 1, 0, 0, 0, 933, 934, 5, 37, 0, 0, 934, 171, 1, 0, 0, 0, 935, 936, 3, 46, 15, 0, 936, 937, 1, 0, 0, 0, 937, 938, 6, 78, 13, 0, 938, 173, 1, 0, 0, 0, 939, 942, 3, 140, 62, 0, 940, 943, 3, 76, 30, 0, 941, 943, 3, 90, 37, 0, 942, 940, 1, 0, 0, 0, 942, 941, 1, 0, 0, 0, 943, 947, 1, 0, 0, 0, 944, 946, 3, 92, 38, 0, 945, 944, 1, 0, 0, 0, 946, 949, 1, 0, 0, 0, 947, 945, 1, 0, 0, 0, 947, 948, 1, 0, 0, 0, 948, 957, 1, 0, 0, 0, 949, 947, 1, 0, 0, 0, 950, 952, 3, 140, 62, 0, 951, 953, 3, 74, 29, 0, 952, 951, 1, 0, 0, 0, 953, 954, 1, 0, 0, 0, 954, 952, 1, 0, 0, 0, 954, 955, 1, 0, 0, 0, 955, 957, 1, 0, 0, 0, 956, 939, 1, 0, 0, 0, 956, 950, 1, 0, 0, 0, 957, 175, 1, 0, 0, 0, 958, 959, 5, 91, 0, 0, 959, 960, 1, 0, 0, 0, 960, 961, 6, 80, 0, 0, 961, 962, 6, 80, 0, 0, 962, 177, 1, 0, 0, 0, 963, 964, 5, 93, 0, 0, 964, 965, 1, 0, 0, 0, 965, 966, 6, 81, 12, 0, 966, 967, 6, 81, 12, 0, 967, 179, 1, 0, 0, 0, 968, 972, 3, 76, 30, 0, 969, 971, 3, 92, 38, 0, 970, 969, 1, 0, 0, 0, 971, 974, 1, 0, 0, 0, 972, 970, 1, 0, 0, 0, 972, 973, 1, 0, 0, 0, 973, 985, 1, 0, 0, 0, 974, 972, 1, 0, 0, 0, 975, 978, 3, 90, 37, 0, 976, 978, 3, 84, 34, 0, 977, 975, 1, 0, 0, 0, 977, 976, 1, 0, 0, 0, 978, 980, 1, 0, 0, 0, 979, 981, 3, 92, 38, 0, 980, 979, 1, 0, 0, 0, 981, 982, 1, 0, 0, 0, 982, 980, 1, 0, 0, 0, 982, 983, 1, 0, 0, 0, 983, 985, 1, 0, 0, 0, 984, 968, 1, 0, 0, 0, 984, 977, 1, 0, 0, 0, 985, 181, 1, 0, 0, 0, 986, 988, 3, 86, 35, 0, 987, 989, 3, 88, 36, 0, 988, 987, 1, 0, 0, 0, 989, 990, 1, 0, 0, 0, 990, 988, 1, 0, 0, 0, 990, 991, 1, 0, 0, 0, 991, 992, 1, 0, 0, 0, 992, 993, 3, 86, 35, 0, 993, 183, 1, 0, 0, 0, 994, 995, 3, 182, 83, 0, 995, 185, 1, 0, 0, 0, 996, 997, 3, 66, 25, 0, 997, 998, 1, 0, 0, 0, 998, 999, 6, 85, 11, 0, 999, 187, 1, 0, 0, 0, 1000, 1001, 3, 68, 26, 0, 1001, 1002, 1, 0, 0, 0, 1002, 1003, 6, 86, 11, 0, 1003, 189, 1, 0, 0, 0, 1004, 1005, 3, 70, 27, 0, 1005, 1006, 1, 0, 0, 0, 1006, 1007, 6, 87, 11, 0, 1007, 191, 1, 0, 0, 0, 1008, 1009, 3, 176, 80, 0, 1009, 1010, 1, 0, 0, 0, 1010, 1011, 6, 88, 14, 0, 1011, 1012, 6, 88, 15, 0, 1012, 193, 1, 0, 0, 0, 1013, 1014, 3, 72, 28, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1016, 6, 89, 16, 0, 1016, 1017, 6, 89, 12, 0, 1017, 195, 1, 0, 0, 0, 1018, 1019, 3, 70, 27, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1021, 6, 90, 11, 0, 1021, 197, 1, 0, 0, 0, 1022, 1023, 3, 66, 25, 0, 1023, 1024, 1, 0, 0, 0, 1024, 1025, 6, 91, 11, 0, 1025, 199, 1, 0, 0, 0, 1026, 1027, 3, 68, 26, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1029, 6, 92, 11, 0, 1029, 201, 1, 0, 0, 0, 1030, 1031, 3, 72, 28, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1033, 6, 93, 16, 0, 1033, 1034, 6, 93, 12, 0, 1034, 203, 1, 0, 0, 0, 1035, 1036, 3, 176, 80, 0, 1036, 1037, 1, 0, 0, 0, 1037, 1038, 6, 94, 14, 0, 1038, 205, 1, 0, 0, 0, 1039, 1040, 3, 178, 81, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1042, 6, 95, 17, 0, 1042, 207, 1, 0, 0, 0, 1043, 1044, 3, 110, 47, 0, 1044, 1045, 1, 0, 0, 0, 1045, 1046, 6, 96, 18, 0, 1046, 209, 1, 0, 0, 0, 1047, 1048, 3, 112, 48, 0, 1048, 1049, 1, 0, 0, 0, 1049, 1050, 6, 97, 19, 0, 1050, 211, 1, 0, 0, 0, 1051, 1052, 3, 106, 45, 0, 1052, 1053, 1, 0, 0, 0, 1053, 1054, 6, 98, 20, 0, 1054, 213, 1, 0, 0, 0, 1055, 1056, 7, 16, 0, 0, 1056, 1057, 7, 3, 0, 0, 1057, 1058, 7, 5, 0, 0, 1058, 1059, 7, 12, 0, 0, 1059, 1060, 7, 0, 0, 0, 1060, 1061, 7, 12, 0, 0, 1061, 1062, 7, 5, 0, 0, 1062, 1063, 7, 12, 0, 0, 1063, 215, 1, 0, 0, 0, 1064, 1068, 8, 33, 0, 0, 1065, 1066, 5, 47, 0, 0, 1066, 1068, 8, 34, 0, 0, 1067, 1064, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1068, 217, 1, 0, 0, 0, 1069, 1071, 3, 216, 100, 0, 1070, 1069, 1, 0, 0, 0, 1071, 1072, 1, 0, 0, 0, 1072, 1070, 1, 0, 0, 0, 1072, 1073, 1, 0, 0, 0, 1073, 219, 1, 0, 0, 0, 1074, 1075, 3, 218, 101, 0, 1075, 1076, 1, 0, 0, 0, 1076, 1077, 6, 102, 21, 0, 1077, 221, 1, 0, 0, 0, 1078, 1079, 3, 94, 39, 0, 1079, 1080, 1, 0, 0, 0, 1080, 1081, 6, 103, 22, 0, 1081, 223, 1, 0, 0, 0, 1082, 1083, 3, 66, 25, 0, 1083, 1084, 1, 0, 0, 0, 1084, 1085, 6, 104, 11, 0, 1085, 225, 1, 0, 0, 0, 1086, 1087, 3, 68, 26, 0, 1087, 1088, 1, 0, 0, 0, 1088, 1089, 6, 105, 11, 0, 1089, 227, 1, 0, 0, 0, 1090, 1091, 3, 70, 27, 0, 1091, 1092, 1, 0, 0, 0, 1092, 1093, 6, 106, 11, 0, 1093, 229, 1, 0, 0, 0, 1094, 1095, 3, 72, 28, 0, 1095, 1096, 1, 0, 0, 0, 1096, 1097, 6, 107, 16, 0, 1097, 1098, 6, 107, 12, 0, 1098, 231, 1, 0, 0, 0, 1099, 1100, 3, 116, 50, 0, 1100, 1101, 1, 0, 0, 0, 1101, 1102, 6, 108, 23, 0, 1102, 233, 1, 0, 0, 0, 1103, 1104, 3, 112, 48, 0, 1104, 1105, 1, 0, 0, 0, 1105, 1106, 6, 109, 19, 0, 1106, 235, 1, 0, 0, 0, 1107, 1108, 4, 110, 8, 0, 1108, 1109, 3, 140, 62, 0, 1109, 1110, 1, 0, 0, 0, 1110, 1111, 6, 110, 24, 0, 1111, 237, 1, 0, 0, 0, 1112, 1113, 4, 111, 9, 0, 1113, 1114, 3, 174, 79, 0, 1114, 1115, 1, 0, 0, 0, 1115, 1116, 6, 111, 25, 0, 1116, 239, 1, 0, 0, 0, 1117, 1122, 3, 76, 30, 0, 1118, 1122, 3, 74, 29, 0, 1119, 1122, 3, 90, 37, 0, 1120, 1122, 3, 166, 75, 0, 1121, 1117, 1, 0, 0, 0, 1121, 1118, 1, 0, 0, 0, 1121, 1119, 1, 0, 0, 0, 1121, 1120, 1, 0, 0, 0, 1122, 241, 1, 0, 0, 0, 1123, 1126, 3, 76, 30, 0, 1124, 1126, 3, 166, 75, 0, 1125, 1123, 1, 0, 0, 0, 1125, 1124, 1, 0, 0, 0, 1126, 1130, 1, 0, 0, 0, 1127, 1129, 3, 240, 112, 0, 1128, 1127, 1, 0, 0, 0, 1129, 1132, 1, 0, 0, 0, 1130, 1128, 1, 0, 0, 0, 1130, 1131, 1, 0, 0, 0, 1131, 1143, 1, 0, 0, 0, 1132, 1130, 1, 0, 0, 0, 1133, 1136, 3, 90, 37, 0, 1134, 1136, 3, 84, 34, 0, 1135, 1133, 1, 0, 0, 0, 1135, 1134, 1, 0, 0, 0, 1136, 1138, 1, 0, 0, 0, 1137, 1139, 3, 240, 112, 0, 1138, 1137, 1, 0, 0, 0, 1139, 1140, 1, 0, 0, 0, 1140, 1138, 1, 0, 0, 0, 1140, 1141, 1, 0, 0, 0, 1141, 1143, 1, 0, 0, 0, 1142, 1125, 1, 0, 0, 0, 1142, 1135, 1, 0, 0, 0, 1143, 243, 1, 0, 0, 0, 1144, 1147, 3, 242, 113, 0, 1145, 1147, 3, 182, 83, 0, 1146, 1144, 1, 0, 0, 0, 1146, 1145, 1, 0, 0, 0, 1147, 1148, 1, 0, 0, 0, 1148, 1146, 1, 0, 0, 0, 1148, 1149, 1, 0, 0, 0, 1149, 245, 1, 0, 0, 0, 1150, 1151, 3, 66, 25, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 6, 115, 11, 0, 1153, 247, 1, 0, 0, 0, 1154, 1155, 3, 68, 26, 0, 1155, 1156, 1, 0, 0, 0, 1156, 1157, 6, 116, 11, 0, 1157, 249, 1, 0, 0, 0, 1158, 1159, 3, 70, 27, 0, 1159, 1160, 1, 0, 0, 0, 1160, 1161, 6, 117, 11, 0, 1161, 251, 1, 0, 0, 0, 1162, 1163, 3, 72, 28, 0, 1163, 1164, 1, 0, 0, 0, 1164, 1165, 6, 118, 16, 0, 1165, 1166, 6, 118, 12, 0, 1166, 253, 1, 0, 0, 0, 1167, 1168, 3, 106, 45, 0, 1168, 1169, 1, 0, 0, 0, 1169, 1170, 6, 119, 20, 0, 1170, 255, 1, 0, 0, 0, 1171, 1172, 3, 112, 48, 0, 1172, 1173, 1, 0, 0, 0, 1173, 1174, 6, 120, 19, 0, 1174, 257, 1, 0, 0, 0, 1175, 1176, 3, 116, 50, 0, 1176, 1177, 1, 0, 0, 0, 1177, 1178, 6, 121, 23, 0, 1178, 259, 1, 0, 0, 0, 1179, 1180, 4, 122, 10, 0, 1180, 1181, 3, 140, 62, 0, 1181, 1182, 1, 0, 0, 0, 1182, 1183, 6, 122, 24, 0, 1183, 261, 1, 0, 0, 0, 1184, 1185, 4, 123, 11, 0, 1185, 1186, 3, 174, 79, 0, 1186, 1187, 1, 0, 0, 0, 1187, 1188, 6, 123, 25, 0, 1188, 263, 1, 0, 0, 0, 1189, 1190, 7, 12, 0, 0, 1190, 1191, 7, 2, 0, 0, 1191, 265, 1, 0, 0, 0, 1192, 1193, 3, 244, 114, 0, 1193, 1194, 1, 0, 0, 0, 1194, 1195, 6, 125, 26, 0, 1195, 267, 1, 0, 0, 0, 1196, 1197, 3, 66, 25, 0, 1197, 1198, 1, 0, 0, 0, 1198, 1199, 6, 126, 11, 0, 1199, 269, 1, 0, 0, 0, 1200, 1201, 3, 68, 26, 0, 1201, 1202, 1, 0, 0, 0, 1202, 1203, 6, 127, 11, 0, 1203, 271, 1, 0, 0, 0, 1204, 1205, 3, 70, 27, 0, 1205, 1206, 1, 0, 0, 0, 1206, 1207, 6, 128, 11, 0, 1207, 273, 1, 0, 0, 0, 1208, 1209, 3, 72, 28, 0, 1209, 1210, 1, 0, 0, 0, 1210, 1211, 6, 129, 16, 0, 1211, 1212, 6, 129, 12, 0, 1212, 275, 1, 0, 0, 0, 1213, 1214, 3, 176, 80, 0, 1214, 1215, 1, 0, 0, 0, 1215, 1216, 6, 130, 14, 0, 1216, 1217, 6, 130, 27, 0, 1217, 277, 1, 0, 0, 0, 1218, 1219, 7, 7, 0, 0, 1219, 1220, 7, 9, 0, 0, 1220, 1221, 1, 0, 0, 0, 1221, 1222, 6, 131, 28, 0, 1222, 279, 1, 0, 0, 0, 1223, 1224, 7, 19, 0, 0, 1224, 1225, 7, 1, 0, 0, 1225, 1226, 7, 5, 0, 0, 1226, 1227, 7, 10, 0, 0, 1227, 1228, 1, 0, 0, 0, 1228, 1229, 6, 132, 28, 0, 1229, 281, 1, 0, 0, 0, 1230, 1231, 8, 35, 0, 0, 1231, 283, 1, 0, 0, 0, 1232, 1234, 3, 282, 133, 0, 1233, 1232, 1, 0, 0, 0, 1234, 1235, 1, 0, 0, 0, 1235, 1233, 1, 0, 0, 0, 1235, 1236, 1, 0, 0, 0, 1236, 1237, 1, 0, 0, 0, 1237, 1238, 3, 110, 47, 0, 1238, 1240, 1, 0, 0, 0, 1239, 1233, 1, 0, 0, 0, 1239, 1240, 1, 0, 0, 0, 1240, 1242, 1, 0, 0, 0, 1241, 1243, 3, 282, 133, 0, 1242, 1241, 1, 0, 0, 0, 1243, 1244, 1, 0, 0, 0, 1244, 1242, 1, 0, 0, 0, 1244, 1245, 1, 0, 0, 0, 1245, 285, 1, 0, 0, 0, 1246, 1247, 3, 284, 134, 0, 1247, 1248, 1, 0, 0, 0, 1248, 1249, 6, 135, 29, 0, 1249, 287, 1, 0, 0, 0, 1250, 1251, 3, 66, 25, 0, 1251, 1252, 1, 0, 0, 0, 1252, 1253, 6, 136, 11, 0, 1253, 289, 1, 0, 0, 0, 1254, 1255, 3, 68, 26, 0, 1255, 1256, 1, 0, 0, 0, 1256, 1257, 6, 137, 11, 0, 1257, 291, 1, 0, 0, 0, 1258, 1259, 3, 70, 27, 0, 1259, 1260, 1, 0, 0, 0, 1260, 1261, 6, 138, 11, 0, 1261, 293, 1, 0, 0, 0, 1262, 1263, 3, 72, 28, 0, 1263, 1264, 1, 0, 0, 0, 1264, 1265, 6, 139, 16, 0, 1265, 1266, 6, 139, 12, 0, 1266, 1267, 6, 139, 12, 0, 1267, 295, 1, 0, 0, 0, 1268, 1269, 3, 106, 45, 0, 1269, 1270, 1, 0, 0, 0, 1270, 1271, 6, 140, 20, 0, 1271, 297, 1, 0, 0, 0, 1272, 1273, 3, 112, 48, 0, 1273, 1274, 1, 0, 0, 0, 1274, 1275, 6, 141, 19, 0, 1275, 299, 1, 0, 0, 0, 1276, 1277, 3, 116, 50, 0, 1277, 1278, 1, 0, 0, 0, 1278, 1279, 6, 142, 23, 0, 1279, 301, 1, 0, 0, 0, 1280, 1281, 3, 280, 132, 0, 1281, 1282, 1, 0, 0, 0, 1282, 1283, 6, 143, 30, 0, 1283, 303, 1, 0, 0, 0, 1284, 1285, 3, 244, 114, 0, 1285, 1286, 1, 0, 0, 0, 1286, 1287, 6, 144, 26, 0, 1287, 305, 1, 0, 0, 0, 1288, 1289, 3, 184, 84, 0, 1289, 1290, 1, 0, 0, 0, 1290, 1291, 6, 145, 31, 0, 1291, 307, 1, 0, 0, 0, 1292, 1293, 4, 146, 12, 0, 1293, 1294, 3, 140, 62, 0, 1294, 1295, 1, 0, 0, 0, 1295, 1296, 6, 146, 24, 0, 1296, 309, 1, 0, 0, 0, 1297, 1298, 4, 147, 13, 0, 1298, 1299, 3, 174, 79, 0, 1299, 1300, 1, 0, 0, 0, 1300, 1301, 6, 147, 25, 0, 1301, 311, 1, 0, 0, 0, 1302, 1303, 3, 66, 25, 0, 1303, 1304, 1, 0, 0, 0, 1304, 1305, 6, 148, 11, 0, 1305, 313, 1, 0, 0, 0, 1306, 1307, 3, 68, 26, 0, 1307, 1308, 1, 0, 0, 0, 1308, 1309, 6, 149, 11, 0, 1309, 315, 1, 0, 0, 0, 1310, 1311, 3, 70, 27, 0, 1311, 1312, 1, 0, 0, 0, 1312, 1313, 6, 150, 11, 0, 1313, 317, 1, 0, 0, 0, 1314, 1315, 3, 72, 28, 0, 1315, 1316, 1, 0, 0, 0, 1316, 1317, 6, 151, 16, 0, 1317, 1318, 6, 151, 12, 0, 1318, 319, 1, 0, 0, 0, 1319, 1320, 3, 116, 50, 0, 1320, 1321, 1, 0, 0, 0, 1321, 1322, 6, 152, 23, 0, 1322, 321, 1, 0, 0, 0, 1323, 1324, 4, 153, 14, 0, 1324, 1325, 3, 140, 62, 0, 1325, 1326, 1, 0, 0, 0, 1326, 1327, 6, 153, 24, 0, 1327, 323, 1, 0, 0, 0, 1328, 1329, 4, 154, 15, 0, 1329, 1330, 3, 174, 79, 0, 1330, 1331, 1, 0, 0, 0, 1331, 1332, 6, 154, 25, 0, 1332, 325, 1, 0, 0, 0, 1333, 1334, 3, 184, 84, 0, 1334, 1335, 1, 0, 0, 0, 1335, 1336, 6, 155, 31, 0, 1336, 327, 1, 0, 0, 0, 1337, 1338, 3, 180, 82, 0, 1338, 1339, 1, 0, 0, 0, 1339, 1340, 6, 156, 32, 0, 1340, 329, 1, 0, 0, 0, 1341, 1342, 3, 66, 25, 0, 1342, 1343, 1, 0, 0, 0, 1343, 1344, 6, 157, 11, 0, 1344, 331, 1, 0, 0, 0, 1345, 1346, 3, 68, 26, 0, 1346, 1347, 1, 0, 0, 0, 1347, 1348, 6, 158, 11, 0, 1348, 333, 1, 0, 0, 0, 1349, 1350, 3, 70, 27, 0, 1350, 1351, 1, 0, 0, 0, 1351, 1352, 6, 159, 11, 0, 1352, 335, 1, 0, 0, 0, 1353, 1354, 3, 72, 28, 0, 1354, 1355, 1, 0, 0, 0, 1355, 1356, 6, 160, 16, 0, 1356, 1357, 6, 160, 12, 0, 1357, 337, 1, 0, 0, 0, 1358, 1359, 7, 1, 0, 0, 1359, 1360, 7, 9, 0, 0, 1360, 1361, 7, 15, 0, 0, 1361, 1362, 7, 7, 0, 0, 1362, 339, 1, 0, 0, 0, 1363, 1364, 3, 66, 25, 0, 1364, 1365, 1, 0, 0, 0, 1365, 1366, 6, 162, 11, 0, 1366, 341, 1, 0, 0, 0, 1367, 1368, 3, 68, 26, 0, 1368, 1369, 1, 0, 0, 0, 1369, 1370, 6, 163, 11, 0, 1370, 343, 1, 0, 0, 0, 1371, 1372, 3, 70, 27, 0, 1372, 1373, 1, 0, 0, 0, 1373, 1374, 6, 164, 11, 0, 1374, 345, 1, 0, 0, 0, 1375, 1376, 3, 178, 81, 0, 1376, 1377, 1, 0, 0, 0, 1377, 1378, 6, 165, 17, 0, 1378, 1379, 6, 165, 12, 0, 1379, 347, 1, 0, 0, 0, 1380, 1381, 3, 110, 47, 0, 1381, 1382, 1, 0, 0, 0, 1382, 1383, 6, 166, 18, 0, 1383, 349, 1, 0, 0, 0, 1384, 1390, 3, 84, 34, 0, 1385, 1390, 3, 74, 29, 0, 1386, 1390, 3, 116, 50, 0, 1387, 1390, 3, 76, 30, 0, 1388, 1390, 3, 90, 37, 0, 1389, 1384, 1, 0, 0, 0, 1389, 1385, 1, 0, 0, 0, 1389, 1386, 1, 0, 0, 0, 1389, 1387, 1, 0, 0, 0, 1389, 1388, 1, 0, 0, 0, 1390, 1391, 1, 0, 0, 0, 1391, 1389, 1, 0, 0, 0, 1391, 1392, 1, 0, 0, 0, 1392, 351, 1, 0, 0, 0, 1393, 1394, 3, 66, 25, 0, 1394, 1395, 1, 0, 0, 0, 1395, 1396, 6, 168, 11, 0, 1396, 353, 1, 0, 0, 0, 1397, 1398, 3, 68, 26, 0, 1398, 1399, 1, 0, 0, 0, 1399, 1400, 6, 169, 11, 0, 1400, 355, 1, 0, 0, 0, 1401, 1402, 3, 70, 27, 0, 1402, 1403, 1, 0, 0, 0, 1403, 1404, 6, 170, 11, 0, 1404, 357, 1, 0, 0, 0, 1405, 1406, 3, 72, 28, 0, 1406, 1407, 1, 0, 0, 0, 1407, 1408, 6, 171, 16, 0, 1408, 1409, 6, 171, 12, 0, 1409, 359, 1, 0, 0, 0, 1410, 1411, 3, 110, 47, 0, 1411, 1412, 1, 0, 0, 0, 1412, 1413, 6, 172, 18, 0, 1413, 361, 1, 0, 0, 0, 1414, 1415, 3, 112, 48, 0, 1415, 1416, 1, 0, 0, 0, 1416, 1417, 6, 173, 19, 0, 1417, 363, 1, 0, 0, 0, 1418, 1419, 3, 116, 50, 0, 1419, 1420, 1, 0, 0, 0, 1420, 1421, 6, 174, 23, 0, 1421, 365, 1, 0, 0, 0, 1422, 1423, 3, 278, 131, 0, 1423, 1424, 1, 0, 0, 0, 1424, 1425, 6, 175, 33, 0, 1425, 1426, 6, 175, 34, 0, 1426, 367, 1, 0, 0, 0, 1427, 1428, 3, 218, 101, 0, 1428, 1429, 1, 0, 0, 0, 1429, 1430, 6, 176, 21, 0, 1430, 369, 1, 0, 0, 0, 1431, 1432, 3, 94, 39, 0, 1432, 1433, 1, 0, 0, 0, 1433, 1434, 6, 177, 22, 0, 1434, 371, 1, 0, 0, 0, 1435, 1436, 3, 66, 25, 0, 1436, 1437, 1, 0, 0, 0, 1437, 1438, 6, 178, 11, 0, 1438, 373, 1, 0, 0, 0, 1439, 1440, 3, 68, 26, 0, 1440, 1441, 1, 0, 0, 0, 1441, 1442, 6, 179, 11, 0, 1442, 375, 1, 0, 0, 0, 1443, 1444, 3, 70, 27, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 6, 180, 11, 0, 1446, 377, 1, 0, 0, 0, 1447, 1448, 3, 72, 28, 0, 1448, 1449, 1, 0, 0, 0, 1449, 1450, 6, 181, 16, 0, 1450, 1451, 6, 181, 12, 0, 1451, 1452, 6, 181, 12, 0, 1452, 379, 1, 0, 0, 0, 1453, 1454, 3, 112, 48, 0, 1454, 1455, 1, 0, 0, 0, 1455, 1456, 6, 182, 19, 0, 1456, 381, 1, 0, 0, 0, 1457, 1458, 3, 116, 50, 0, 1458, 1459, 1, 0, 0, 0, 1459, 1460, 6, 183, 23, 0, 1460, 383, 1, 0, 0, 0, 1461, 1462, 3, 244, 114, 0, 1462, 1463, 1, 0, 0, 0, 1463, 1464, 6, 184, 26, 0, 1464, 385, 1, 0, 0, 0, 1465, 1466, 3, 66, 25, 0, 1466, 1467, 1, 0, 0, 0, 1467, 1468, 6, 185, 11, 0, 1468, 387, 1, 0, 0, 0, 1469, 1470, 3, 68, 26, 0, 1470, 1471, 1, 0, 0, 0, 1471, 1472, 6, 186, 11, 0, 1472, 389, 1, 0, 0, 0, 1473, 1474, 3, 70, 27, 0, 1474, 1475, 1, 0, 0, 0, 1475, 1476, 6, 187, 11, 0, 1476, 391, 1, 0, 0, 0, 1477, 1478, 3, 72, 28, 0, 1478, 1479, 1, 0, 0, 0, 1479, 1480, 6, 188, 16, 0, 1480, 1481, 6, 188, 12, 0, 1481, 393, 1, 0, 0, 0, 1482, 1483, 3, 54, 19, 0, 1483, 1484, 1, 0, 0, 0, 1484, 1485, 6, 189, 35, 0, 1485, 395, 1, 0, 0, 0, 1486, 1487, 3, 264, 124, 0, 1487, 1488, 1, 0, 0, 0, 1488, 1489, 6, 190, 36, 0, 1489, 397, 1, 0, 0, 0, 1490, 1491, 3, 278, 131, 0, 1491, 1492, 1, 0, 0, 0, 1492, 1493, 6, 191, 33, 0, 1493, 1494, 6, 191, 12, 0, 1494, 1495, 6, 191, 0, 0, 1495, 399, 1, 0, 0, 0, 1496, 1497, 7, 20, 0, 0, 1497, 1498, 7, 2, 0, 0, 1498, 1499, 7, 1, 0, 0, 1499, 1500, 7, 9, 0, 0, 1500, 1501, 7, 17, 0, 0, 1501, 1502, 1, 0, 0, 0, 1502, 1503, 6, 192, 12, 0, 1503, 1504, 6, 192, 0, 0, 1504, 401, 1, 0, 0, 0, 1505, 1506, 3, 180, 82, 0, 1506, 1507, 1, 0, 0, 0, 1507, 1508, 6, 193, 32, 0, 1508, 403, 1, 0, 0, 0, 1509, 1510, 3, 184, 84, 0, 1510, 1511, 1, 0, 0, 0, 1511, 1512, 6, 194, 31, 0, 1512, 405, 1, 0, 0, 0, 1513, 1514, 3, 66, 25, 0, 1514, 1515, 1, 0, 0, 0, 1515, 1516, 6, 195, 11, 0, 1516, 407, 1, 0, 0, 0, 1517, 1518, 3, 68, 26, 0, 1518, 1519, 1, 0, 0, 0, 1519, 1520, 6, 196, 11, 0, 1520, 409, 1, 0, 0, 0, 1521, 1522, 3, 70, 27, 0, 1522, 1523, 1, 0, 0, 0, 1523, 1524, 6, 197, 11, 0, 1524, 411, 1, 0, 0, 0, 1525, 1526, 3, 72, 28, 0, 1526, 1527, 1, 0, 0, 0, 1527, 1528, 6, 198, 16, 0, 1528, 1529, 6, 198, 12, 0, 1529, 413, 1, 0, 0, 0, 1530, 1531, 3, 218, 101, 0, 1531, 1532, 1, 0, 0, 0, 1532, 1533, 6, 199, 21, 0, 1533, 1534, 6, 199, 12, 0, 1534, 1535, 6, 199, 37, 0, 1535, 415, 1, 0, 0, 0, 1536, 1537, 3, 94, 39, 0, 1537, 1538, 1, 0, 0, 0, 1538, 1539, 6, 200, 22, 0, 1539, 1540, 6, 200, 12, 0, 1540, 1541, 6, 200, 37, 0, 1541, 417, 1, 0, 0, 0, 1542, 1543, 3, 66, 25, 0, 1543, 1544, 1, 0, 0, 0, 1544, 1545, 6, 201, 11, 0, 1545, 419, 1, 0, 0, 0, 1546, 1547, 3, 68, 26, 0, 1547, 1548, 1, 0, 0, 0, 1548, 1549, 6, 202, 11, 0, 1549, 421, 1, 0, 0, 0, 1550, 1551, 3, 70, 27, 0, 1551, 1552, 1, 0, 0, 0, 1552, 1553, 6, 203, 11, 0, 1553, 423, 1, 0, 0, 0, 1554, 1555, 3, 110, 47, 0, 1555, 1556, 1, 0, 0, 0, 1556, 1557, 6, 204, 18, 0, 1557, 1558, 6, 204, 12, 0, 1558, 1559, 6, 204, 9, 0, 1559, 425, 1, 0, 0, 0, 1560, 1561, 3, 112, 48, 0, 1561, 1562, 1, 0, 0, 0, 1562, 1563, 6, 205, 19, 0, 1563, 1564, 6, 205, 12, 0, 1564, 1565, 6, 205, 9, 0, 1565, 427, 1, 0, 0, 0, 1566, 1567, 3, 66, 25, 0, 1567, 1568, 1, 0, 0, 0, 1568, 1569, 6, 206, 11, 0, 1569, 429, 1, 0, 0, 0, 1570, 1571, 3, 68, 26, 0, 1571, 1572, 1, 0, 0, 0, 1572, 1573, 6, 207, 11, 0, 1573, 431, 1, 0, 0, 0, 1574, 1575, 3, 70, 27, 0, 1575, 1576, 1, 0, 0, 0, 1576, 1577, 6, 208, 11, 0, 1577, 433, 1, 0, 0, 0, 1578, 1579, 3, 184, 84, 0, 1579, 1580, 1, 0, 0, 0, 1580, 1581, 6, 209, 12, 0, 1581, 1582, 6, 209, 0, 0, 1582, 1583, 6, 209, 31, 0, 1583, 435, 1, 0, 0, 0, 1584, 1585, 3, 180, 82, 0, 1585, 1586, 1, 0, 0, 0, 1586, 1587, 6, 210, 12, 0, 1587, 1588, 6, 210, 0, 0, 1588, 1589, 6, 210, 32, 0, 1589, 437, 1, 0, 0, 0, 1590, 1591, 3, 100, 42, 0, 1591, 1592, 1, 0, 0, 0, 1592, 1593, 6, 211, 12, 0, 1593, 1594, 6, 211, 0, 0, 1594, 1595, 6, 211, 38, 0, 1595, 439, 1, 0, 0, 0, 1596, 1597, 3, 72, 28, 0, 1597, 1598, 1, 0, 0, 0, 1598, 1599, 6, 212, 16, 0, 1599, 1600, 6, 212, 12, 0, 1600, 441, 1, 0, 0, 0, 66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 655, 665, 669, 672, 681, 683, 694, 713, 718, 727, 734, 739, 741, 752, 760, 763, 765, 770, 775, 781, 788, 793, 799, 802, 810, 814, 942, 947, 954, 956, 972, 977, 982, 984, 990, 1067, 1072, 1121, 1125, 1130, 1135, 1140, 1142, 1146, 1148, 1235, 1239, 1244, 1389, 1391, 39, 5, 1, 0, 5, 4, 0, 5, 6, 0, 5, 2, 0, 5, 3, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 5, 11, 0, 5, 14, 0, 5, 13, 0, 0, 1, 0, 4, 0, 0, 7, 16, 0, 7, 70, 0, 5, 0, 0, 7, 29, 0, 7, 71, 0, 7, 38, 0, 7, 39, 0, 7, 36, 0, 7, 81, 0, 7, 30, 0, 7, 41, 0, 7, 53, 0, 7, 69, 0, 7, 85, 0, 5, 10, 0, 5, 7, 0, 7, 95, 0, 7, 94, 0, 7, 73, 0, 7, 72, 0, 7, 93, 0, 5, 12, 0, 7, 20, 0, 7, 89, 0, 5, 15, 0, 7, 33, 0] \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens b/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens index 3dd1a2c75403..b1a16987dd8c 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens @@ -17,106 +17,115 @@ WHERE=16 DEV_INLINESTATS=17 DEV_LOOKUP=18 DEV_METRICS=19 -UNKNOWN_CMD=20 -LINE_COMMENT=21 -MULTILINE_COMMENT=22 -WS=23 -COLON=24 -PIPE=25 -QUOTED_STRING=26 -INTEGER_LITERAL=27 -DECIMAL_LITERAL=28 -BY=29 -AND=30 -ASC=31 -ASSIGN=32 -CAST_OP=33 -COMMA=34 -DESC=35 -DOT=36 -FALSE=37 -FIRST=38 -IN=39 -IS=40 -LAST=41 -LIKE=42 -LP=43 -NOT=44 -NULL=45 -NULLS=46 -OR=47 -PARAM=48 -RLIKE=49 -RP=50 -TRUE=51 -EQ=52 -CIEQ=53 -NEQ=54 -LT=55 -LTE=56 -GT=57 -GTE=58 -PLUS=59 -MINUS=60 -ASTERISK=61 -SLASH=62 -PERCENT=63 -NAMED_OR_POSITIONAL_PARAM=64 -OPENING_BRACKET=65 -CLOSING_BRACKET=66 -UNQUOTED_IDENTIFIER=67 -QUOTED_IDENTIFIER=68 -EXPR_LINE_COMMENT=69 -EXPR_MULTILINE_COMMENT=70 -EXPR_WS=71 -EXPLAIN_WS=72 -EXPLAIN_LINE_COMMENT=73 -EXPLAIN_MULTILINE_COMMENT=74 -METADATA=75 -UNQUOTED_SOURCE=76 -FROM_LINE_COMMENT=77 -FROM_MULTILINE_COMMENT=78 -FROM_WS=79 -ID_PATTERN=80 -PROJECT_LINE_COMMENT=81 -PROJECT_MULTILINE_COMMENT=82 -PROJECT_WS=83 -AS=84 -RENAME_LINE_COMMENT=85 -RENAME_MULTILINE_COMMENT=86 -RENAME_WS=87 -ON=88 -WITH=89 -ENRICH_POLICY_NAME=90 -ENRICH_LINE_COMMENT=91 -ENRICH_MULTILINE_COMMENT=92 -ENRICH_WS=93 -ENRICH_FIELD_LINE_COMMENT=94 -ENRICH_FIELD_MULTILINE_COMMENT=95 -ENRICH_FIELD_WS=96 -MVEXPAND_LINE_COMMENT=97 -MVEXPAND_MULTILINE_COMMENT=98 -MVEXPAND_WS=99 -INFO=100 -SHOW_LINE_COMMENT=101 -SHOW_MULTILINE_COMMENT=102 -SHOW_WS=103 -SETTING=104 -SETTING_LINE_COMMENT=105 -SETTTING_MULTILINE_COMMENT=106 -SETTING_WS=107 -LOOKUP_LINE_COMMENT=108 -LOOKUP_MULTILINE_COMMENT=109 -LOOKUP_WS=110 -LOOKUP_FIELD_LINE_COMMENT=111 -LOOKUP_FIELD_MULTILINE_COMMENT=112 -LOOKUP_FIELD_WS=113 -METRICS_LINE_COMMENT=114 -METRICS_MULTILINE_COMMENT=115 -METRICS_WS=116 -CLOSING_METRICS_LINE_COMMENT=117 -CLOSING_METRICS_MULTILINE_COMMENT=118 -CLOSING_METRICS_WS=119 +DEV_JOIN=20 +DEV_JOIN_FULL=21 +DEV_JOIN_LEFT=22 +DEV_JOIN_RIGHT=23 +DEV_JOIN_LOOKUP=24 +UNKNOWN_CMD=25 +LINE_COMMENT=26 +MULTILINE_COMMENT=27 +WS=28 +PIPE=29 +QUOTED_STRING=30 +INTEGER_LITERAL=31 +DECIMAL_LITERAL=32 +BY=33 +AND=34 +ASC=35 +ASSIGN=36 +CAST_OP=37 +COLON=38 +COMMA=39 +DESC=40 +DOT=41 +FALSE=42 +FIRST=43 +IN=44 +IS=45 +LAST=46 +LIKE=47 +LP=48 +NOT=49 +NULL=50 +NULLS=51 +OR=52 +PARAM=53 +RLIKE=54 +RP=55 +TRUE=56 +EQ=57 +CIEQ=58 +NEQ=59 +LT=60 +LTE=61 +GT=62 +GTE=63 +PLUS=64 +MINUS=65 +ASTERISK=66 +SLASH=67 +PERCENT=68 +NAMED_OR_POSITIONAL_PARAM=69 +OPENING_BRACKET=70 +CLOSING_BRACKET=71 +UNQUOTED_IDENTIFIER=72 +QUOTED_IDENTIFIER=73 +EXPR_LINE_COMMENT=74 +EXPR_MULTILINE_COMMENT=75 +EXPR_WS=76 +EXPLAIN_WS=77 +EXPLAIN_LINE_COMMENT=78 +EXPLAIN_MULTILINE_COMMENT=79 +METADATA=80 +UNQUOTED_SOURCE=81 +FROM_LINE_COMMENT=82 +FROM_MULTILINE_COMMENT=83 +FROM_WS=84 +ID_PATTERN=85 +PROJECT_LINE_COMMENT=86 +PROJECT_MULTILINE_COMMENT=87 +PROJECT_WS=88 +AS=89 +RENAME_LINE_COMMENT=90 +RENAME_MULTILINE_COMMENT=91 +RENAME_WS=92 +ON=93 +WITH=94 +ENRICH_POLICY_NAME=95 +ENRICH_LINE_COMMENT=96 +ENRICH_MULTILINE_COMMENT=97 +ENRICH_WS=98 +ENRICH_FIELD_LINE_COMMENT=99 +ENRICH_FIELD_MULTILINE_COMMENT=100 +ENRICH_FIELD_WS=101 +MVEXPAND_LINE_COMMENT=102 +MVEXPAND_MULTILINE_COMMENT=103 +MVEXPAND_WS=104 +INFO=105 +SHOW_LINE_COMMENT=106 +SHOW_MULTILINE_COMMENT=107 +SHOW_WS=108 +SETTING=109 +SETTING_LINE_COMMENT=110 +SETTTING_MULTILINE_COMMENT=111 +SETTING_WS=112 +LOOKUP_LINE_COMMENT=113 +LOOKUP_MULTILINE_COMMENT=114 +LOOKUP_WS=115 +LOOKUP_FIELD_LINE_COMMENT=116 +LOOKUP_FIELD_MULTILINE_COMMENT=117 +LOOKUP_FIELD_WS=118 +USING=119 +JOIN_LINE_COMMENT=120 +JOIN_MULTILINE_COMMENT=121 +JOIN_WS=122 +METRICS_LINE_COMMENT=123 +METRICS_MULTILINE_COMMENT=124 +METRICS_WS=125 +CLOSING_METRICS_LINE_COMMENT=126 +CLOSING_METRICS_MULTILINE_COMMENT=127 +CLOSING_METRICS_WS=128 'dissect'=1 'drop'=2 'enrich'=3 @@ -133,46 +142,47 @@ CLOSING_METRICS_WS=119 'sort'=14 'stats'=15 'where'=16 -':'=24 -'|'=25 -'by'=29 -'and'=30 -'asc'=31 -'='=32 -'::'=33 -','=34 -'desc'=35 -'.'=36 -'false'=37 -'first'=38 -'in'=39 -'is'=40 -'last'=41 -'like'=42 -'('=43 -'not'=44 -'null'=45 -'nulls'=46 -'or'=47 -'?'=48 -'rlike'=49 -')'=50 -'true'=51 -'=='=52 -'=~'=53 -'!='=54 -'<'=55 -'<='=56 -'>'=57 -'>='=58 -'+'=59 -'-'=60 -'*'=61 -'/'=62 -'%'=63 -']'=66 -'metadata'=75 -'as'=84 -'on'=88 -'with'=89 -'info'=100 +'|'=29 +'by'=33 +'and'=34 +'asc'=35 +'='=36 +'::'=37 +':'=38 +','=39 +'desc'=40 +'.'=41 +'false'=42 +'first'=43 +'in'=44 +'is'=45 +'last'=46 +'like'=47 +'('=48 +'not'=49 +'null'=50 +'nulls'=51 +'or'=52 +'?'=53 +'rlike'=54 +')'=55 +'true'=56 +'=='=57 +'=~'=58 +'!='=59 +'<'=60 +'<='=61 +'>'=62 +'>='=63 +'+'=64 +'-'=65 +'*'=66 +'/'=67 +'%'=68 +']'=71 +'metadata'=80 +'as'=89 +'on'=93 +'with'=94 +'info'=105 +'USING'=119 diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.ts b/packages/kbn-esql-ast/src/antlr/esql_lexer.ts index 54546fef8590..5c25d0233576 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.ts @@ -42,106 +42,115 @@ export default class esql_lexer extends lexer_config { public static readonly DEV_INLINESTATS = 17; public static readonly DEV_LOOKUP = 18; public static readonly DEV_METRICS = 19; - public static readonly UNKNOWN_CMD = 20; - public static readonly LINE_COMMENT = 21; - public static readonly MULTILINE_COMMENT = 22; - public static readonly WS = 23; - public static readonly COLON = 24; - public static readonly PIPE = 25; - public static readonly QUOTED_STRING = 26; - public static readonly INTEGER_LITERAL = 27; - public static readonly DECIMAL_LITERAL = 28; - public static readonly BY = 29; - public static readonly AND = 30; - public static readonly ASC = 31; - public static readonly ASSIGN = 32; - public static readonly CAST_OP = 33; - public static readonly COMMA = 34; - public static readonly DESC = 35; - public static readonly DOT = 36; - public static readonly FALSE = 37; - public static readonly FIRST = 38; - public static readonly IN = 39; - public static readonly IS = 40; - public static readonly LAST = 41; - public static readonly LIKE = 42; - public static readonly LP = 43; - public static readonly NOT = 44; - public static readonly NULL = 45; - public static readonly NULLS = 46; - public static readonly OR = 47; - public static readonly PARAM = 48; - public static readonly RLIKE = 49; - public static readonly RP = 50; - public static readonly TRUE = 51; - public static readonly EQ = 52; - public static readonly CIEQ = 53; - public static readonly NEQ = 54; - public static readonly LT = 55; - public static readonly LTE = 56; - public static readonly GT = 57; - public static readonly GTE = 58; - public static readonly PLUS = 59; - public static readonly MINUS = 60; - public static readonly ASTERISK = 61; - public static readonly SLASH = 62; - public static readonly PERCENT = 63; - public static readonly NAMED_OR_POSITIONAL_PARAM = 64; - public static readonly OPENING_BRACKET = 65; - public static readonly CLOSING_BRACKET = 66; - public static readonly UNQUOTED_IDENTIFIER = 67; - public static readonly QUOTED_IDENTIFIER = 68; - public static readonly EXPR_LINE_COMMENT = 69; - public static readonly EXPR_MULTILINE_COMMENT = 70; - public static readonly EXPR_WS = 71; - public static readonly EXPLAIN_WS = 72; - public static readonly EXPLAIN_LINE_COMMENT = 73; - public static readonly EXPLAIN_MULTILINE_COMMENT = 74; - public static readonly METADATA = 75; - public static readonly UNQUOTED_SOURCE = 76; - public static readonly FROM_LINE_COMMENT = 77; - public static readonly FROM_MULTILINE_COMMENT = 78; - public static readonly FROM_WS = 79; - public static readonly ID_PATTERN = 80; - public static readonly PROJECT_LINE_COMMENT = 81; - public static readonly PROJECT_MULTILINE_COMMENT = 82; - public static readonly PROJECT_WS = 83; - public static readonly AS = 84; - public static readonly RENAME_LINE_COMMENT = 85; - public static readonly RENAME_MULTILINE_COMMENT = 86; - public static readonly RENAME_WS = 87; - public static readonly ON = 88; - public static readonly WITH = 89; - public static readonly ENRICH_POLICY_NAME = 90; - public static readonly ENRICH_LINE_COMMENT = 91; - public static readonly ENRICH_MULTILINE_COMMENT = 92; - public static readonly ENRICH_WS = 93; - public static readonly ENRICH_FIELD_LINE_COMMENT = 94; - public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 95; - public static readonly ENRICH_FIELD_WS = 96; - public static readonly MVEXPAND_LINE_COMMENT = 97; - public static readonly MVEXPAND_MULTILINE_COMMENT = 98; - public static readonly MVEXPAND_WS = 99; - public static readonly INFO = 100; - public static readonly SHOW_LINE_COMMENT = 101; - public static readonly SHOW_MULTILINE_COMMENT = 102; - public static readonly SHOW_WS = 103; - public static readonly SETTING = 104; - public static readonly SETTING_LINE_COMMENT = 105; - public static readonly SETTTING_MULTILINE_COMMENT = 106; - public static readonly SETTING_WS = 107; - public static readonly LOOKUP_LINE_COMMENT = 108; - public static readonly LOOKUP_MULTILINE_COMMENT = 109; - public static readonly LOOKUP_WS = 110; - public static readonly LOOKUP_FIELD_LINE_COMMENT = 111; - public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 112; - public static readonly LOOKUP_FIELD_WS = 113; - public static readonly METRICS_LINE_COMMENT = 114; - public static readonly METRICS_MULTILINE_COMMENT = 115; - public static readonly METRICS_WS = 116; - public static readonly CLOSING_METRICS_LINE_COMMENT = 117; - public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 118; - public static readonly CLOSING_METRICS_WS = 119; + public static readonly DEV_JOIN = 20; + public static readonly DEV_JOIN_FULL = 21; + public static readonly DEV_JOIN_LEFT = 22; + public static readonly DEV_JOIN_RIGHT = 23; + public static readonly DEV_JOIN_LOOKUP = 24; + public static readonly UNKNOWN_CMD = 25; + public static readonly LINE_COMMENT = 26; + public static readonly MULTILINE_COMMENT = 27; + public static readonly WS = 28; + public static readonly PIPE = 29; + public static readonly QUOTED_STRING = 30; + public static readonly INTEGER_LITERAL = 31; + public static readonly DECIMAL_LITERAL = 32; + public static readonly BY = 33; + public static readonly AND = 34; + public static readonly ASC = 35; + public static readonly ASSIGN = 36; + public static readonly CAST_OP = 37; + public static readonly COLON = 38; + public static readonly COMMA = 39; + public static readonly DESC = 40; + public static readonly DOT = 41; + public static readonly FALSE = 42; + public static readonly FIRST = 43; + public static readonly IN = 44; + public static readonly IS = 45; + public static readonly LAST = 46; + public static readonly LIKE = 47; + public static readonly LP = 48; + public static readonly NOT = 49; + public static readonly NULL = 50; + public static readonly NULLS = 51; + public static readonly OR = 52; + public static readonly PARAM = 53; + public static readonly RLIKE = 54; + public static readonly RP = 55; + public static readonly TRUE = 56; + public static readonly EQ = 57; + public static readonly CIEQ = 58; + public static readonly NEQ = 59; + public static readonly LT = 60; + public static readonly LTE = 61; + public static readonly GT = 62; + public static readonly GTE = 63; + public static readonly PLUS = 64; + public static readonly MINUS = 65; + public static readonly ASTERISK = 66; + public static readonly SLASH = 67; + public static readonly PERCENT = 68; + public static readonly NAMED_OR_POSITIONAL_PARAM = 69; + public static readonly OPENING_BRACKET = 70; + public static readonly CLOSING_BRACKET = 71; + public static readonly UNQUOTED_IDENTIFIER = 72; + public static readonly QUOTED_IDENTIFIER = 73; + public static readonly EXPR_LINE_COMMENT = 74; + public static readonly EXPR_MULTILINE_COMMENT = 75; + public static readonly EXPR_WS = 76; + public static readonly EXPLAIN_WS = 77; + public static readonly EXPLAIN_LINE_COMMENT = 78; + public static readonly EXPLAIN_MULTILINE_COMMENT = 79; + public static readonly METADATA = 80; + public static readonly UNQUOTED_SOURCE = 81; + public static readonly FROM_LINE_COMMENT = 82; + public static readonly FROM_MULTILINE_COMMENT = 83; + public static readonly FROM_WS = 84; + public static readonly ID_PATTERN = 85; + public static readonly PROJECT_LINE_COMMENT = 86; + public static readonly PROJECT_MULTILINE_COMMENT = 87; + public static readonly PROJECT_WS = 88; + public static readonly AS = 89; + public static readonly RENAME_LINE_COMMENT = 90; + public static readonly RENAME_MULTILINE_COMMENT = 91; + public static readonly RENAME_WS = 92; + public static readonly ON = 93; + public static readonly WITH = 94; + public static readonly ENRICH_POLICY_NAME = 95; + public static readonly ENRICH_LINE_COMMENT = 96; + public static readonly ENRICH_MULTILINE_COMMENT = 97; + public static readonly ENRICH_WS = 98; + public static readonly ENRICH_FIELD_LINE_COMMENT = 99; + public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 100; + public static readonly ENRICH_FIELD_WS = 101; + public static readonly MVEXPAND_LINE_COMMENT = 102; + public static readonly MVEXPAND_MULTILINE_COMMENT = 103; + public static readonly MVEXPAND_WS = 104; + public static readonly INFO = 105; + public static readonly SHOW_LINE_COMMENT = 106; + public static readonly SHOW_MULTILINE_COMMENT = 107; + public static readonly SHOW_WS = 108; + public static readonly SETTING = 109; + public static readonly SETTING_LINE_COMMENT = 110; + public static readonly SETTTING_MULTILINE_COMMENT = 111; + public static readonly SETTING_WS = 112; + public static readonly LOOKUP_LINE_COMMENT = 113; + public static readonly LOOKUP_MULTILINE_COMMENT = 114; + public static readonly LOOKUP_WS = 115; + public static readonly LOOKUP_FIELD_LINE_COMMENT = 116; + public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 117; + public static readonly LOOKUP_FIELD_WS = 118; + public static readonly USING = 119; + public static readonly JOIN_LINE_COMMENT = 120; + public static readonly JOIN_MULTILINE_COMMENT = 121; + public static readonly JOIN_WS = 122; + public static readonly METRICS_LINE_COMMENT = 123; + public static readonly METRICS_MULTILINE_COMMENT = 124; + public static readonly METRICS_WS = 125; + public static readonly CLOSING_METRICS_LINE_COMMENT = 126; + public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 127; + public static readonly CLOSING_METRICS_WS = 128; public static readonly EOF = Token.EOF; public static readonly EXPRESSION_MODE = 1; public static readonly EXPLAIN_MODE = 2; @@ -155,8 +164,9 @@ export default class esql_lexer extends lexer_config { public static readonly SETTING_MODE = 10; public static readonly LOOKUP_MODE = 11; public static readonly LOOKUP_FIELD_MODE = 12; - public static readonly METRICS_MODE = 13; - public static readonly CLOSING_METRICS_MODE = 14; + public static readonly JOIN_MODE = 13; + public static readonly METRICS_MODE = 14; + public static readonly CLOSING_METRICS_MODE = 15; public static readonly channelNames: string[] = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ]; public static readonly literalNames: (string | null)[] = [ null, "'dissect'", @@ -172,32 +182,35 @@ export default class esql_lexer extends lexer_config { null, null, null, null, null, null, - "':'", "'|'", + null, null, + null, null, + null, "'|'", null, null, null, "'by'", "'and'", "'asc'", "'='", "'::'", - "','", "'desc'", - "'.'", "'false'", - "'first'", "'in'", - "'is'", "'last'", - "'like'", "'('", - "'not'", "'null'", - "'nulls'", "'or'", - "'?'", "'rlike'", - "')'", "'true'", - "'=='", "'=~'", - "'!='", "'<'", - "'<='", "'>'", - "'>='", "'+'", - "'-'", "'*'", - "'/'", "'%'", + "':'", "','", + "'desc'", "'.'", + "'false'", "'first'", + "'in'", "'is'", + "'last'", "'like'", + "'('", "'not'", + "'null'", "'nulls'", + "'or'", "'?'", + "'rlike'", "')'", + "'true'", "'=='", + "'=~'", "'!='", + "'<'", "'<='", + "'>'", "'>='", + "'+'", "'-'", + "'*'", "'/'", + "'%'", null, + null, "']'", null, null, - "']'", null, null, null, null, null, null, null, - null, "'metadata'", + "'metadata'", null, null, null, null, null, null, @@ -210,7 +223,14 @@ export default class esql_lexer extends lexer_config { null, null, null, null, null, null, - "'info'" ]; + "'info'", null, + null, null, + null, null, + null, null, + null, null, + null, null, + null, null, + "'USING'" ]; public static readonly symbolicNames: (string | null)[] = [ null, "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", @@ -223,30 +243,36 @@ export default class esql_lexer extends lexer_config { "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_METRICS", + "DEV_JOIN", + "DEV_JOIN_FULL", + "DEV_JOIN_LEFT", + "DEV_JOIN_RIGHT", + "DEV_JOIN_LOOKUP", "UNKNOWN_CMD", "LINE_COMMENT", "MULTILINE_COMMENT", - "WS", "COLON", - "PIPE", "QUOTED_STRING", + "WS", "PIPE", + "QUOTED_STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", - "COMMA", "DESC", - "DOT", "FALSE", - "FIRST", "IN", - "IS", "LAST", - "LIKE", "LP", - "NOT", "NULL", - "NULLS", "OR", - "PARAM", "RLIKE", - "RP", "TRUE", - "EQ", "CIEQ", - "NEQ", "LT", - "LTE", "GT", - "GTE", "PLUS", - "MINUS", "ASTERISK", + "COLON", "COMMA", + "DESC", "DOT", + "FALSE", "FIRST", + "IN", "IS", + "LAST", "LIKE", + "LP", "NOT", + "NULL", "NULLS", + "OR", "PARAM", + "RLIKE", "RP", + "TRUE", "EQ", + "CIEQ", "NEQ", + "LT", "LTE", + "GT", "GTE", + "PLUS", "MINUS", + "ASTERISK", "SLASH", "PERCENT", "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", @@ -295,6 +321,9 @@ export default class esql_lexer extends lexer_config { "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", + "USING", "JOIN_LINE_COMMENT", + "JOIN_MULTILINE_COMMENT", + "JOIN_WS", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", "METRICS_WS", @@ -307,20 +336,21 @@ export default class esql_lexer extends lexer_config { "ENRICH_MODE", "ENRICH_FIELD_MODE", "MVEXPAND_MODE", "SHOW_MODE", "SETTING_MODE", "LOOKUP_MODE", - "LOOKUP_FIELD_MODE", "METRICS_MODE", - "CLOSING_METRICS_MODE", ]; + "LOOKUP_FIELD_MODE", "JOIN_MODE", + "METRICS_MODE", "CLOSING_METRICS_MODE", ]; public static readonly ruleNames: string[] = [ "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", "FROM", "GROK", "KEEP", "LIMIT", "MV_EXPAND", "RENAME", "ROW", "SHOW", "SORT", "STATS", "WHERE", - "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_METRICS", "UNKNOWN_CMD", "LINE_COMMENT", - "MULTILINE_COMMENT", "WS", "COLON", "PIPE", "DIGIT", "LETTER", "ESCAPE_SEQUENCE", + "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_METRICS", "DEV_JOIN", "DEV_JOIN_FULL", + "DEV_JOIN_LEFT", "DEV_JOIN_RIGHT", "DEV_JOIN_LOOKUP", "UNKNOWN_CMD", "LINE_COMMENT", + "MULTILINE_COMMENT", "WS", "PIPE", "DIGIT", "LETTER", "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "ASPERAND", "BACKQUOTE", "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", "QUOTED_STRING", "INTEGER_LITERAL", - "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", "COMMA", "DESC", - "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", "LP", "NOT", "NULL", - "NULLS", "OR", "PARAM", "RLIKE", "RP", "TRUE", "EQ", "CIEQ", "NEQ", "LT", - "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "EXPRESSION_COLON", + "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", "COLON", "COMMA", + "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", "LP", "NOT", + "NULL", "NULLS", "OR", "PARAM", "RLIKE", "RP", "TRUE", "EQ", "CIEQ", "NEQ", + "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "NESTED_WHERE", "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", "QUOTED_ID", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "EXPLAIN_OPENING_BRACKET", "EXPLAIN_PIPE", @@ -349,11 +379,14 @@ export default class esql_lexer extends lexer_config { "LOOKUP_UNQUOTED_SOURCE", "LOOKUP_QUOTED_SOURCE", "LOOKUP_LINE_COMMENT", "LOOKUP_MULTILINE_COMMENT", "LOOKUP_WS", "LOOKUP_FIELD_PIPE", "LOOKUP_FIELD_COMMA", "LOOKUP_FIELD_DOT", "LOOKUP_FIELD_ID_PATTERN", "LOOKUP_FIELD_LINE_COMMENT", - "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", "METRICS_PIPE", "METRICS_UNQUOTED_SOURCE", - "METRICS_QUOTED_SOURCE", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", - "METRICS_WS", "CLOSING_METRICS_COLON", "CLOSING_METRICS_COMMA", "CLOSING_METRICS_LINE_COMMENT", - "CLOSING_METRICS_MULTILINE_COMMENT", "CLOSING_METRICS_WS", "CLOSING_METRICS_QUOTED_IDENTIFIER", - "CLOSING_METRICS_UNQUOTED_IDENTIFIER", "CLOSING_METRICS_BY", "CLOSING_METRICS_PIPE", + "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", "JOIN_PIPE", "JOIN_JOIN", + "JOIN_AS", "JOIN_ON", "USING", "JOIN_UNQUOTED_IDENTIFER", "JOIN_QUOTED_IDENTIFIER", + "JOIN_LINE_COMMENT", "JOIN_MULTILINE_COMMENT", "JOIN_WS", "METRICS_PIPE", + "METRICS_UNQUOTED_SOURCE", "METRICS_QUOTED_SOURCE", "METRICS_LINE_COMMENT", + "METRICS_MULTILINE_COMMENT", "METRICS_WS", "CLOSING_METRICS_COLON", "CLOSING_METRICS_COMMA", + "CLOSING_METRICS_LINE_COMMENT", "CLOSING_METRICS_MULTILINE_COMMENT", "CLOSING_METRICS_WS", + "CLOSING_METRICS_QUOTED_IDENTIFIER", "CLOSING_METRICS_UNQUOTED_IDENTIFIER", + "CLOSING_METRICS_BY", "CLOSING_METRICS_PIPE", ]; @@ -383,23 +416,31 @@ export default class esql_lexer extends lexer_config { return this.DEV_LOOKUP_sempred(localctx, predIndex); case 18: return this.DEV_METRICS_sempred(localctx, predIndex); - case 73: - return this.EXPRESSION_COLON_sempred(localctx, predIndex); - case 106: + case 19: + return this.DEV_JOIN_sempred(localctx, predIndex); + case 20: + return this.DEV_JOIN_FULL_sempred(localctx, predIndex); + case 21: + return this.DEV_JOIN_LEFT_sempred(localctx, predIndex); + case 22: + return this.DEV_JOIN_RIGHT_sempred(localctx, predIndex); + case 23: + return this.DEV_JOIN_LOOKUP_sempred(localctx, predIndex); + case 110: return this.PROJECT_PARAM_sempred(localctx, predIndex); - case 107: + case 111: return this.PROJECT_NAMED_OR_POSITIONAL_PARAM_sempred(localctx, predIndex); - case 118: + case 122: return this.RENAME_PARAM_sempred(localctx, predIndex); - case 119: + case 123: return this.RENAME_NAMED_OR_POSITIONAL_PARAM_sempred(localctx, predIndex); - case 142: + case 146: return this.ENRICH_FIELD_PARAM_sempred(localctx, predIndex); - case 143: + case 147: return this.ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM_sempred(localctx, predIndex); - case 149: + case 153: return this.MVEXPAND_PARAM_sempred(localctx, predIndex); - case 150: + case 154: return this.MVEXPAND_NAMED_OR_POSITIONAL_PARAM_sempred(localctx, predIndex); } return true; @@ -425,86 +466,114 @@ export default class esql_lexer extends lexer_config { } return true; } - private EXPRESSION_COLON_sempred(localctx: RuleContext, predIndex: number): boolean { + private DEV_JOIN_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 3: return this.isDevVersion(); } return true; } - private PROJECT_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private DEV_JOIN_FULL_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 4: return this.isDevVersion(); } return true; } - private PROJECT_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private DEV_JOIN_LEFT_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 5: return this.isDevVersion(); } return true; } - private RENAME_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private DEV_JOIN_RIGHT_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 6: return this.isDevVersion(); } return true; } - private RENAME_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private DEV_JOIN_LOOKUP_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 7: return this.isDevVersion(); } return true; } - private ENRICH_FIELD_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private PROJECT_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 8: return this.isDevVersion(); } return true; } - private ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private PROJECT_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 9: return this.isDevVersion(); } return true; } - private MVEXPAND_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private RENAME_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 10: return this.isDevVersion(); } return true; } - private MVEXPAND_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + private RENAME_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { switch (predIndex) { case 11: return this.isDevVersion(); } return true; } + private ENRICH_FIELD_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + switch (predIndex) { + case 12: + return this.isDevVersion(); + } + return true; + } + private ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + switch (predIndex) { + case 13: + return this.isDevVersion(); + } + return true; + } + private MVEXPAND_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + switch (predIndex) { + case 14: + return this.isDevVersion(); + } + return true; + } + private MVEXPAND_NAMED_OR_POSITIONAL_PARAM_sempred(localctx: RuleContext, predIndex: number): boolean { + switch (predIndex) { + case 15: + return this.isDevVersion(); + } + return true; + } - public static readonly _serializedATN: number[] = [4,0,119,1484,6,-1,6, - -1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,2,0, - 7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9, - 7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7, - 16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23, - 2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2, - 31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38, - 7,38,2,39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7, - 45,2,46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7,52, - 2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2,59,7,59,2, - 60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7,65,2,66,7,66,2,67, - 7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,71,2,72,7,72,2,73,7,73,2,74,7, - 74,2,75,7,75,2,76,7,76,2,77,7,77,2,78,7,78,2,79,7,79,2,80,7,80,2,81,7,81, - 2,82,7,82,2,83,7,83,2,84,7,84,2,85,7,85,2,86,7,86,2,87,7,87,2,88,7,88,2, - 89,7,89,2,90,7,90,2,91,7,91,2,92,7,92,2,93,7,93,2,94,7,94,2,95,7,95,2,96, - 7,96,2,97,7,97,2,98,7,98,2,99,7,99,2,100,7,100,2,101,7,101,2,102,7,102, + public static readonly _serializedATN: number[] = [4,0,128,1601,6,-1,6, + -1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1, + 2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8, + 2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16, + 7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7, + 23,2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30, + 2,31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2, + 38,7,38,2,39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45, + 7,45,2,46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7, + 52,2,53,7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2,59,7,59, + 2,60,7,60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7,65,2,66,7,66,2, + 67,7,67,2,68,7,68,2,69,7,69,2,70,7,70,2,71,7,71,2,72,7,72,2,73,7,73,2,74, + 7,74,2,75,7,75,2,76,7,76,2,77,7,77,2,78,7,78,2,79,7,79,2,80,7,80,2,81,7, + 81,2,82,7,82,2,83,7,83,2,84,7,84,2,85,7,85,2,86,7,86,2,87,7,87,2,88,7,88, + 2,89,7,89,2,90,7,90,2,91,7,91,2,92,7,92,2,93,7,93,2,94,7,94,2,95,7,95,2, + 96,7,96,2,97,7,97,2,98,7,98,2,99,7,99,2,100,7,100,2,101,7,101,2,102,7,102, 2,103,7,103,2,104,7,104,2,105,7,105,2,106,7,106,2,107,7,107,2,108,7,108, 2,109,7,109,2,110,7,110,2,111,7,111,2,112,7,112,2,113,7,113,2,114,7,114, 2,115,7,115,2,116,7,116,2,117,7,117,2,118,7,118,2,119,7,119,2,120,7,120, @@ -521,484 +590,526 @@ export default class esql_lexer extends lexer_config { 2,181,7,181,2,182,7,182,2,183,7,183,2,184,7,184,2,185,7,185,2,186,7,186, 2,187,7,187,2,188,7,188,2,189,7,189,2,190,7,190,2,191,7,191,2,192,7,192, 2,193,7,193,2,194,7,194,2,195,7,195,2,196,7,196,2,197,7,197,2,198,7,198, - 1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2, - 1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4, - 1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6, - 1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8, - 1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10, - 1,10,1,10,1,10,1,10,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1, - 12,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14, - 1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,16,1,16,1,16,1, - 16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,17, - 1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1, - 18,1,18,1,18,1,18,1,19,4,19,580,8,19,11,19,12,19,581,1,19,1,19,1,20,1,20, - 1,20,1,20,5,20,590,8,20,10,20,12,20,593,9,20,1,20,3,20,596,8,20,1,20,3, - 20,599,8,20,1,20,1,20,1,21,1,21,1,21,1,21,1,21,5,21,608,8,21,10,21,12,21, - 611,9,21,1,21,1,21,1,21,1,21,1,21,1,22,4,22,619,8,22,11,22,12,22,620,1, - 22,1,22,1,23,1,23,1,24,1,24,1,24,1,24,1,25,1,25,1,26,1,26,1,27,1,27,1,27, - 1,28,1,28,1,29,1,29,3,29,642,8,29,1,29,4,29,645,8,29,11,29,12,29,646,1, - 30,1,30,1,31,1,31,1,32,1,32,1,32,3,32,656,8,32,1,33,1,33,1,34,1,34,1,34, - 3,34,663,8,34,1,35,1,35,1,35,5,35,668,8,35,10,35,12,35,671,9,35,1,35,1, - 35,1,35,1,35,1,35,1,35,5,35,679,8,35,10,35,12,35,682,9,35,1,35,1,35,1,35, - 1,35,1,35,3,35,689,8,35,1,35,3,35,692,8,35,3,35,694,8,35,1,36,4,36,697, - 8,36,11,36,12,36,698,1,37,4,37,702,8,37,11,37,12,37,703,1,37,1,37,5,37, - 708,8,37,10,37,12,37,711,9,37,1,37,1,37,4,37,715,8,37,11,37,12,37,716,1, - 37,4,37,720,8,37,11,37,12,37,721,1,37,1,37,5,37,726,8,37,10,37,12,37,729, - 9,37,3,37,731,8,37,1,37,1,37,1,37,1,37,4,37,737,8,37,11,37,12,37,738,1, - 37,1,37,3,37,743,8,37,1,38,1,38,1,38,1,39,1,39,1,39,1,39,1,40,1,40,1,40, - 1,40,1,41,1,41,1,42,1,42,1,42,1,43,1,43,1,44,1,44,1,44,1,44,1,44,1,45,1, - 45,1,46,1,46,1,46,1,46,1,46,1,46,1,47,1,47,1,47,1,47,1,47,1,47,1,48,1,48, - 1,48,1,49,1,49,1,49,1,50,1,50,1,50,1,50,1,50,1,51,1,51,1,51,1,51,1,51,1, - 52,1,52,1,53,1,53,1,53,1,53,1,54,1,54,1,54,1,54,1,54,1,55,1,55,1,55,1,55, - 1,55,1,55,1,56,1,56,1,56,1,57,1,57,1,58,1,58,1,58,1,58,1,58,1,58,1,59,1, - 59,1,60,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,62,1,62,1,62,1,63,1,63,1,63, - 1,64,1,64,1,65,1,65,1,65,1,66,1,66,1,67,1,67,1,67,1,68,1,68,1,69,1,69,1, - 70,1,70,1,71,1,71,1,72,1,72,1,73,1,73,1,73,1,73,1,73,1,74,1,74,1,74,1,74, - 1,75,1,75,1,75,3,75,874,8,75,1,75,5,75,877,8,75,10,75,12,75,880,9,75,1, - 75,1,75,4,75,884,8,75,11,75,12,75,885,3,75,888,8,75,1,76,1,76,1,76,1,76, - 1,76,1,77,1,77,1,77,1,77,1,77,1,78,1,78,5,78,902,8,78,10,78,12,78,905,9, - 78,1,78,1,78,3,78,909,8,78,1,78,4,78,912,8,78,11,78,12,78,913,3,78,916, - 8,78,1,79,1,79,4,79,920,8,79,11,79,12,79,921,1,79,1,79,1,80,1,80,1,81,1, - 81,1,81,1,81,1,82,1,82,1,82,1,82,1,83,1,83,1,83,1,83,1,84,1,84,1,84,1,84, - 1,84,1,85,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,1,87,1,87,1,87,1,87,1, - 88,1,88,1,88,1,88,1,89,1,89,1,89,1,89,1,89,1,90,1,90,1,90,1,90,1,91,1,91, - 1,91,1,91,1,92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1,94,1,94,1,94,1,94,1, - 95,1,95,1,95,1,95,1,95,1,95,1,95,1,95,1,95,1,96,1,96,1,96,3,96,999,8,96, - 1,97,4,97,1002,8,97,11,97,12,97,1003,1,98,1,98,1,98,1,98,1,99,1,99,1,99, - 1,99,1,100,1,100,1,100,1,100,1,101,1,101,1,101,1,101,1,102,1,102,1,102, - 1,102,1,103,1,103,1,103,1,103,1,103,1,104,1,104,1,104,1,104,1,105,1,105, - 1,105,1,105,1,106,1,106,1,106,1,106,1,106,1,107,1,107,1,107,1,107,1,107, - 1,108,1,108,1,108,1,108,3,108,1053,8,108,1,109,1,109,3,109,1057,8,109,1, - 109,5,109,1060,8,109,10,109,12,109,1063,9,109,1,109,1,109,3,109,1067,8, - 109,1,109,4,109,1070,8,109,11,109,12,109,1071,3,109,1074,8,109,1,110,1, - 110,4,110,1078,8,110,11,110,12,110,1079,1,111,1,111,1,111,1,111,1,112,1, - 112,1,112,1,112,1,113,1,113,1,113,1,113,1,114,1,114,1,114,1,114,1,114,1, - 115,1,115,1,115,1,115,1,116,1,116,1,116,1,116,1,117,1,117,1,117,1,117,1, - 118,1,118,1,118,1,118,1,118,1,119,1,119,1,119,1,119,1,119,1,120,1,120,1, - 120,1,121,1,121,1,121,1,121,1,122,1,122,1,122,1,122,1,123,1,123,1,123,1, - 123,1,124,1,124,1,124,1,124,1,125,1,125,1,125,1,125,1,125,1,126,1,126,1, - 126,1,126,1,126,1,127,1,127,1,127,1,127,1,127,1,128,1,128,1,128,1,128,1, - 128,1,128,1,128,1,129,1,129,1,130,4,130,1165,8,130,11,130,12,130,1166,1, - 130,1,130,3,130,1171,8,130,1,130,4,130,1174,8,130,11,130,12,130,1175,1, - 131,1,131,1,131,1,131,1,132,1,132,1,132,1,132,1,133,1,133,1,133,1,133,1, - 134,1,134,1,134,1,134,1,135,1,135,1,135,1,135,1,135,1,135,1,136,1,136,1, - 136,1,136,1,137,1,137,1,137,1,137,1,138,1,138,1,138,1,138,1,139,1,139,1, - 139,1,139,1,140,1,140,1,140,1,140,1,141,1,141,1,141,1,141,1,142,1,142,1, - 142,1,142,1,142,1,143,1,143,1,143,1,143,1,143,1,144,1,144,1,144,1,144,1, - 145,1,145,1,145,1,145,1,146,1,146,1,146,1,146,1,147,1,147,1,147,1,147,1, - 147,1,148,1,148,1,148,1,148,1,149,1,149,1,149,1,149,1,149,1,150,1,150,1, - 150,1,150,1,150,1,151,1,151,1,151,1,151,1,152,1,152,1,152,1,152,1,153,1, - 153,1,153,1,153,1,154,1,154,1,154,1,154,1,155,1,155,1,155,1,155,1,156,1, - 156,1,156,1,156,1,156,1,157,1,157,1,157,1,157,1,157,1,158,1,158,1,158,1, - 158,1,159,1,159,1,159,1,159,1,160,1,160,1,160,1,160,1,161,1,161,1,161,1, - 161,1,161,1,162,1,162,1,162,1,162,1,163,1,163,1,163,1,163,1,163,4,163,1321, - 8,163,11,163,12,163,1322,1,164,1,164,1,164,1,164,1,165,1,165,1,165,1,165, - 1,166,1,166,1,166,1,166,1,167,1,167,1,167,1,167,1,167,1,168,1,168,1,168, - 1,168,1,169,1,169,1,169,1,169,1,170,1,170,1,170,1,170,1,171,1,171,1,171, - 1,171,1,171,1,172,1,172,1,172,1,172,1,173,1,173,1,173,1,173,1,174,1,174, - 1,174,1,174,1,175,1,175,1,175,1,175,1,176,1,176,1,176,1,176,1,177,1,177, - 1,177,1,177,1,177,1,177,1,178,1,178,1,178,1,178,1,179,1,179,1,179,1,179, - 1,180,1,180,1,180,1,180,1,181,1,181,1,181,1,181,1,182,1,182,1,182,1,182, - 1,183,1,183,1,183,1,183,1,184,1,184,1,184,1,184,1,184,1,185,1,185,1,185, - 1,185,1,185,1,185,1,186,1,186,1,186,1,186,1,186,1,186,1,187,1,187,1,187, - 1,187,1,188,1,188,1,188,1,188,1,189,1,189,1,189,1,189,1,190,1,190,1,190, - 1,190,1,190,1,190,1,191,1,191,1,191,1,191,1,191,1,191,1,192,1,192,1,192, - 1,192,1,193,1,193,1,193,1,193,1,194,1,194,1,194,1,194,1,195,1,195,1,195, - 1,195,1,195,1,195,1,196,1,196,1,196,1,196,1,196,1,196,1,197,1,197,1,197, - 1,197,1,197,1,197,1,198,1,198,1,198,1,198,1,198,2,609,680,0,199,15,1,17, - 2,19,3,21,4,23,5,25,6,27,7,29,8,31,9,33,10,35,11,37,12,39,13,41,14,43,15, - 45,16,47,17,49,18,51,19,53,20,55,21,57,22,59,23,61,24,63,25,65,0,67,0,69, - 0,71,0,73,0,75,0,77,0,79,0,81,0,83,0,85,26,87,27,89,28,91,29,93,30,95,31, - 97,32,99,33,101,34,103,35,105,36,107,37,109,38,111,39,113,40,115,41,117, - 42,119,43,121,44,123,45,125,46,127,47,129,48,131,49,133,50,135,51,137,52, - 139,53,141,54,143,55,145,56,147,57,149,58,151,59,153,60,155,61,157,62,159, - 63,161,0,163,0,165,64,167,65,169,66,171,67,173,0,175,68,177,69,179,70,181, - 71,183,0,185,0,187,72,189,73,191,74,193,0,195,0,197,0,199,0,201,0,203,0, - 205,75,207,0,209,76,211,0,213,0,215,77,217,78,219,79,221,0,223,0,225,0, - 227,0,229,0,231,0,233,0,235,80,237,81,239,82,241,83,243,0,245,0,247,0,249, - 0,251,0,253,0,255,84,257,0,259,85,261,86,263,87,265,0,267,0,269,88,271, - 89,273,0,275,90,277,0,279,91,281,92,283,93,285,0,287,0,289,0,291,0,293, - 0,295,0,297,0,299,0,301,0,303,94,305,95,307,96,309,0,311,0,313,0,315,0, - 317,0,319,0,321,97,323,98,325,99,327,0,329,100,331,101,333,102,335,103, - 337,0,339,0,341,104,343,105,345,106,347,107,349,0,351,0,353,0,355,0,357, - 0,359,0,361,0,363,108,365,109,367,110,369,0,371,0,373,0,375,0,377,111,379, - 112,381,113,383,0,385,0,387,0,389,114,391,115,393,116,395,0,397,0,399,117, - 401,118,403,119,405,0,407,0,409,0,411,0,15,0,1,2,3,4,5,6,7,8,9,10,11,12, - 13,14,35,2,0,68,68,100,100,2,0,73,73,105,105,2,0,83,83,115,115,2,0,69,69, - 101,101,2,0,67,67,99,99,2,0,84,84,116,116,2,0,82,82,114,114,2,0,79,79,111, - 111,2,0,80,80,112,112,2,0,78,78,110,110,2,0,72,72,104,104,2,0,86,86,118, - 118,2,0,65,65,97,97,2,0,76,76,108,108,2,0,88,88,120,120,2,0,70,70,102,102, - 2,0,77,77,109,109,2,0,71,71,103,103,2,0,75,75,107,107,2,0,87,87,119,119, - 2,0,85,85,117,117,6,0,9,10,13,13,32,32,47,47,91,91,93,93,2,0,10,10,13,13, - 3,0,9,10,13,13,32,32,1,0,48,57,2,0,65,90,97,122,8,0,34,34,78,78,82,82,84, - 84,92,92,110,110,114,114,116,116,4,0,10,10,13,13,34,34,92,92,2,0,43,43, - 45,45,1,0,96,96,2,0,66,66,98,98,2,0,89,89,121,121,11,0,9,10,13,13,32,32, - 34,34,44,44,47,47,58,58,61,61,91,91,93,93,124,124,2,0,42,42,47,47,11,0, - 9,10,13,13,32,32,34,35,44,44,47,47,58,58,60,60,62,63,92,92,124,124,1512, - 0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1, - 0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0, - 0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1, - 0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0, - 0,59,1,0,0,0,0,61,1,0,0,0,1,63,1,0,0,0,1,85,1,0,0,0,1,87,1,0,0,0,1,89,1, - 0,0,0,1,91,1,0,0,0,1,93,1,0,0,0,1,95,1,0,0,0,1,97,1,0,0,0,1,99,1,0,0,0, - 1,101,1,0,0,0,1,103,1,0,0,0,1,105,1,0,0,0,1,107,1,0,0,0,1,109,1,0,0,0,1, - 111,1,0,0,0,1,113,1,0,0,0,1,115,1,0,0,0,1,117,1,0,0,0,1,119,1,0,0,0,1,121, - 1,0,0,0,1,123,1,0,0,0,1,125,1,0,0,0,1,127,1,0,0,0,1,129,1,0,0,0,1,131,1, - 0,0,0,1,133,1,0,0,0,1,135,1,0,0,0,1,137,1,0,0,0,1,139,1,0,0,0,1,141,1,0, - 0,0,1,143,1,0,0,0,1,145,1,0,0,0,1,147,1,0,0,0,1,149,1,0,0,0,1,151,1,0,0, - 0,1,153,1,0,0,0,1,155,1,0,0,0,1,157,1,0,0,0,1,159,1,0,0,0,1,161,1,0,0,0, - 1,163,1,0,0,0,1,165,1,0,0,0,1,167,1,0,0,0,1,169,1,0,0,0,1,171,1,0,0,0,1, - 175,1,0,0,0,1,177,1,0,0,0,1,179,1,0,0,0,1,181,1,0,0,0,2,183,1,0,0,0,2,185, - 1,0,0,0,2,187,1,0,0,0,2,189,1,0,0,0,2,191,1,0,0,0,3,193,1,0,0,0,3,195,1, - 0,0,0,3,197,1,0,0,0,3,199,1,0,0,0,3,201,1,0,0,0,3,203,1,0,0,0,3,205,1,0, - 0,0,3,209,1,0,0,0,3,211,1,0,0,0,3,213,1,0,0,0,3,215,1,0,0,0,3,217,1,0,0, - 0,3,219,1,0,0,0,4,221,1,0,0,0,4,223,1,0,0,0,4,225,1,0,0,0,4,227,1,0,0,0, - 4,229,1,0,0,0,4,235,1,0,0,0,4,237,1,0,0,0,4,239,1,0,0,0,4,241,1,0,0,0,5, - 243,1,0,0,0,5,245,1,0,0,0,5,247,1,0,0,0,5,249,1,0,0,0,5,251,1,0,0,0,5,253, - 1,0,0,0,5,255,1,0,0,0,5,257,1,0,0,0,5,259,1,0,0,0,5,261,1,0,0,0,5,263,1, - 0,0,0,6,265,1,0,0,0,6,267,1,0,0,0,6,269,1,0,0,0,6,271,1,0,0,0,6,275,1,0, - 0,0,6,277,1,0,0,0,6,279,1,0,0,0,6,281,1,0,0,0,6,283,1,0,0,0,7,285,1,0,0, - 0,7,287,1,0,0,0,7,289,1,0,0,0,7,291,1,0,0,0,7,293,1,0,0,0,7,295,1,0,0,0, - 7,297,1,0,0,0,7,299,1,0,0,0,7,301,1,0,0,0,7,303,1,0,0,0,7,305,1,0,0,0,7, - 307,1,0,0,0,8,309,1,0,0,0,8,311,1,0,0,0,8,313,1,0,0,0,8,315,1,0,0,0,8,317, - 1,0,0,0,8,319,1,0,0,0,8,321,1,0,0,0,8,323,1,0,0,0,8,325,1,0,0,0,9,327,1, - 0,0,0,9,329,1,0,0,0,9,331,1,0,0,0,9,333,1,0,0,0,9,335,1,0,0,0,10,337,1, - 0,0,0,10,339,1,0,0,0,10,341,1,0,0,0,10,343,1,0,0,0,10,345,1,0,0,0,10,347, - 1,0,0,0,11,349,1,0,0,0,11,351,1,0,0,0,11,353,1,0,0,0,11,355,1,0,0,0,11, - 357,1,0,0,0,11,359,1,0,0,0,11,361,1,0,0,0,11,363,1,0,0,0,11,365,1,0,0,0, - 11,367,1,0,0,0,12,369,1,0,0,0,12,371,1,0,0,0,12,373,1,0,0,0,12,375,1,0, - 0,0,12,377,1,0,0,0,12,379,1,0,0,0,12,381,1,0,0,0,13,383,1,0,0,0,13,385, - 1,0,0,0,13,387,1,0,0,0,13,389,1,0,0,0,13,391,1,0,0,0,13,393,1,0,0,0,14, - 395,1,0,0,0,14,397,1,0,0,0,14,399,1,0,0,0,14,401,1,0,0,0,14,403,1,0,0,0, - 14,405,1,0,0,0,14,407,1,0,0,0,14,409,1,0,0,0,14,411,1,0,0,0,15,413,1,0, - 0,0,17,423,1,0,0,0,19,430,1,0,0,0,21,439,1,0,0,0,23,446,1,0,0,0,25,456, - 1,0,0,0,27,463,1,0,0,0,29,470,1,0,0,0,31,477,1,0,0,0,33,485,1,0,0,0,35, - 497,1,0,0,0,37,506,1,0,0,0,39,512,1,0,0,0,41,519,1,0,0,0,43,526,1,0,0,0, - 45,534,1,0,0,0,47,542,1,0,0,0,49,557,1,0,0,0,51,567,1,0,0,0,53,579,1,0, - 0,0,55,585,1,0,0,0,57,602,1,0,0,0,59,618,1,0,0,0,61,624,1,0,0,0,63,626, - 1,0,0,0,65,630,1,0,0,0,67,632,1,0,0,0,69,634,1,0,0,0,71,637,1,0,0,0,73, - 639,1,0,0,0,75,648,1,0,0,0,77,650,1,0,0,0,79,655,1,0,0,0,81,657,1,0,0,0, - 83,662,1,0,0,0,85,693,1,0,0,0,87,696,1,0,0,0,89,742,1,0,0,0,91,744,1,0, - 0,0,93,747,1,0,0,0,95,751,1,0,0,0,97,755,1,0,0,0,99,757,1,0,0,0,101,760, - 1,0,0,0,103,762,1,0,0,0,105,767,1,0,0,0,107,769,1,0,0,0,109,775,1,0,0,0, - 111,781,1,0,0,0,113,784,1,0,0,0,115,787,1,0,0,0,117,792,1,0,0,0,119,797, - 1,0,0,0,121,799,1,0,0,0,123,803,1,0,0,0,125,808,1,0,0,0,127,814,1,0,0,0, - 129,817,1,0,0,0,131,819,1,0,0,0,133,825,1,0,0,0,135,827,1,0,0,0,137,832, - 1,0,0,0,139,835,1,0,0,0,141,838,1,0,0,0,143,841,1,0,0,0,145,843,1,0,0,0, - 147,846,1,0,0,0,149,848,1,0,0,0,151,851,1,0,0,0,153,853,1,0,0,0,155,855, - 1,0,0,0,157,857,1,0,0,0,159,859,1,0,0,0,161,861,1,0,0,0,163,866,1,0,0,0, - 165,887,1,0,0,0,167,889,1,0,0,0,169,894,1,0,0,0,171,915,1,0,0,0,173,917, - 1,0,0,0,175,925,1,0,0,0,177,927,1,0,0,0,179,931,1,0,0,0,181,935,1,0,0,0, - 183,939,1,0,0,0,185,944,1,0,0,0,187,949,1,0,0,0,189,953,1,0,0,0,191,957, - 1,0,0,0,193,961,1,0,0,0,195,966,1,0,0,0,197,970,1,0,0,0,199,974,1,0,0,0, - 201,978,1,0,0,0,203,982,1,0,0,0,205,986,1,0,0,0,207,998,1,0,0,0,209,1001, - 1,0,0,0,211,1005,1,0,0,0,213,1009,1,0,0,0,215,1013,1,0,0,0,217,1017,1,0, - 0,0,219,1021,1,0,0,0,221,1025,1,0,0,0,223,1030,1,0,0,0,225,1034,1,0,0,0, - 227,1038,1,0,0,0,229,1043,1,0,0,0,231,1052,1,0,0,0,233,1073,1,0,0,0,235, - 1077,1,0,0,0,237,1081,1,0,0,0,239,1085,1,0,0,0,241,1089,1,0,0,0,243,1093, - 1,0,0,0,245,1098,1,0,0,0,247,1102,1,0,0,0,249,1106,1,0,0,0,251,1110,1,0, - 0,0,253,1115,1,0,0,0,255,1120,1,0,0,0,257,1123,1,0,0,0,259,1127,1,0,0,0, - 261,1131,1,0,0,0,263,1135,1,0,0,0,265,1139,1,0,0,0,267,1144,1,0,0,0,269, - 1149,1,0,0,0,271,1154,1,0,0,0,273,1161,1,0,0,0,275,1170,1,0,0,0,277,1177, - 1,0,0,0,279,1181,1,0,0,0,281,1185,1,0,0,0,283,1189,1,0,0,0,285,1193,1,0, - 0,0,287,1199,1,0,0,0,289,1203,1,0,0,0,291,1207,1,0,0,0,293,1211,1,0,0,0, - 295,1215,1,0,0,0,297,1219,1,0,0,0,299,1223,1,0,0,0,301,1228,1,0,0,0,303, - 1233,1,0,0,0,305,1237,1,0,0,0,307,1241,1,0,0,0,309,1245,1,0,0,0,311,1250, - 1,0,0,0,313,1254,1,0,0,0,315,1259,1,0,0,0,317,1264,1,0,0,0,319,1268,1,0, - 0,0,321,1272,1,0,0,0,323,1276,1,0,0,0,325,1280,1,0,0,0,327,1284,1,0,0,0, - 329,1289,1,0,0,0,331,1294,1,0,0,0,333,1298,1,0,0,0,335,1302,1,0,0,0,337, - 1306,1,0,0,0,339,1311,1,0,0,0,341,1320,1,0,0,0,343,1324,1,0,0,0,345,1328, - 1,0,0,0,347,1332,1,0,0,0,349,1336,1,0,0,0,351,1341,1,0,0,0,353,1345,1,0, - 0,0,355,1349,1,0,0,0,357,1353,1,0,0,0,359,1358,1,0,0,0,361,1362,1,0,0,0, - 363,1366,1,0,0,0,365,1370,1,0,0,0,367,1374,1,0,0,0,369,1378,1,0,0,0,371, - 1384,1,0,0,0,373,1388,1,0,0,0,375,1392,1,0,0,0,377,1396,1,0,0,0,379,1400, - 1,0,0,0,381,1404,1,0,0,0,383,1408,1,0,0,0,385,1413,1,0,0,0,387,1419,1,0, - 0,0,389,1425,1,0,0,0,391,1429,1,0,0,0,393,1433,1,0,0,0,395,1437,1,0,0,0, - 397,1443,1,0,0,0,399,1449,1,0,0,0,401,1453,1,0,0,0,403,1457,1,0,0,0,405, - 1461,1,0,0,0,407,1467,1,0,0,0,409,1473,1,0,0,0,411,1479,1,0,0,0,413,414, - 7,0,0,0,414,415,7,1,0,0,415,416,7,2,0,0,416,417,7,2,0,0,417,418,7,3,0,0, - 418,419,7,4,0,0,419,420,7,5,0,0,420,421,1,0,0,0,421,422,6,0,0,0,422,16, - 1,0,0,0,423,424,7,0,0,0,424,425,7,6,0,0,425,426,7,7,0,0,426,427,7,8,0,0, - 427,428,1,0,0,0,428,429,6,1,1,0,429,18,1,0,0,0,430,431,7,3,0,0,431,432, - 7,9,0,0,432,433,7,6,0,0,433,434,7,1,0,0,434,435,7,4,0,0,435,436,7,10,0, - 0,436,437,1,0,0,0,437,438,6,2,2,0,438,20,1,0,0,0,439,440,7,3,0,0,440,441, - 7,11,0,0,441,442,7,12,0,0,442,443,7,13,0,0,443,444,1,0,0,0,444,445,6,3, - 0,0,445,22,1,0,0,0,446,447,7,3,0,0,447,448,7,14,0,0,448,449,7,8,0,0,449, - 450,7,13,0,0,450,451,7,12,0,0,451,452,7,1,0,0,452,453,7,9,0,0,453,454,1, - 0,0,0,454,455,6,4,3,0,455,24,1,0,0,0,456,457,7,15,0,0,457,458,7,6,0,0,458, - 459,7,7,0,0,459,460,7,16,0,0,460,461,1,0,0,0,461,462,6,5,4,0,462,26,1,0, - 0,0,463,464,7,17,0,0,464,465,7,6,0,0,465,466,7,7,0,0,466,467,7,18,0,0,467, - 468,1,0,0,0,468,469,6,6,0,0,469,28,1,0,0,0,470,471,7,18,0,0,471,472,7,3, - 0,0,472,473,7,3,0,0,473,474,7,8,0,0,474,475,1,0,0,0,475,476,6,7,1,0,476, - 30,1,0,0,0,477,478,7,13,0,0,478,479,7,1,0,0,479,480,7,16,0,0,480,481,7, - 1,0,0,481,482,7,5,0,0,482,483,1,0,0,0,483,484,6,8,0,0,484,32,1,0,0,0,485, - 486,7,16,0,0,486,487,7,11,0,0,487,488,5,95,0,0,488,489,7,3,0,0,489,490, - 7,14,0,0,490,491,7,8,0,0,491,492,7,12,0,0,492,493,7,9,0,0,493,494,7,0,0, - 0,494,495,1,0,0,0,495,496,6,9,5,0,496,34,1,0,0,0,497,498,7,6,0,0,498,499, - 7,3,0,0,499,500,7,9,0,0,500,501,7,12,0,0,501,502,7,16,0,0,502,503,7,3,0, - 0,503,504,1,0,0,0,504,505,6,10,6,0,505,36,1,0,0,0,506,507,7,6,0,0,507,508, - 7,7,0,0,508,509,7,19,0,0,509,510,1,0,0,0,510,511,6,11,0,0,511,38,1,0,0, - 0,512,513,7,2,0,0,513,514,7,10,0,0,514,515,7,7,0,0,515,516,7,19,0,0,516, - 517,1,0,0,0,517,518,6,12,7,0,518,40,1,0,0,0,519,520,7,2,0,0,520,521,7,7, - 0,0,521,522,7,6,0,0,522,523,7,5,0,0,523,524,1,0,0,0,524,525,6,13,0,0,525, - 42,1,0,0,0,526,527,7,2,0,0,527,528,7,5,0,0,528,529,7,12,0,0,529,530,7,5, - 0,0,530,531,7,2,0,0,531,532,1,0,0,0,532,533,6,14,0,0,533,44,1,0,0,0,534, - 535,7,19,0,0,535,536,7,10,0,0,536,537,7,3,0,0,537,538,7,6,0,0,538,539,7, - 3,0,0,539,540,1,0,0,0,540,541,6,15,0,0,541,46,1,0,0,0,542,543,4,16,0,0, - 543,544,7,1,0,0,544,545,7,9,0,0,545,546,7,13,0,0,546,547,7,1,0,0,547,548, - 7,9,0,0,548,549,7,3,0,0,549,550,7,2,0,0,550,551,7,5,0,0,551,552,7,12,0, - 0,552,553,7,5,0,0,553,554,7,2,0,0,554,555,1,0,0,0,555,556,6,16,0,0,556, - 48,1,0,0,0,557,558,4,17,1,0,558,559,7,13,0,0,559,560,7,7,0,0,560,561,7, - 7,0,0,561,562,7,18,0,0,562,563,7,20,0,0,563,564,7,8,0,0,564,565,1,0,0,0, - 565,566,6,17,8,0,566,50,1,0,0,0,567,568,4,18,2,0,568,569,7,16,0,0,569,570, - 7,3,0,0,570,571,7,5,0,0,571,572,7,6,0,0,572,573,7,1,0,0,573,574,7,4,0,0, - 574,575,7,2,0,0,575,576,1,0,0,0,576,577,6,18,9,0,577,52,1,0,0,0,578,580, - 8,21,0,0,579,578,1,0,0,0,580,581,1,0,0,0,581,579,1,0,0,0,581,582,1,0,0, - 0,582,583,1,0,0,0,583,584,6,19,0,0,584,54,1,0,0,0,585,586,5,47,0,0,586, - 587,5,47,0,0,587,591,1,0,0,0,588,590,8,22,0,0,589,588,1,0,0,0,590,593,1, - 0,0,0,591,589,1,0,0,0,591,592,1,0,0,0,592,595,1,0,0,0,593,591,1,0,0,0,594, - 596,5,13,0,0,595,594,1,0,0,0,595,596,1,0,0,0,596,598,1,0,0,0,597,599,5, - 10,0,0,598,597,1,0,0,0,598,599,1,0,0,0,599,600,1,0,0,0,600,601,6,20,10, - 0,601,56,1,0,0,0,602,603,5,47,0,0,603,604,5,42,0,0,604,609,1,0,0,0,605, - 608,3,57,21,0,606,608,9,0,0,0,607,605,1,0,0,0,607,606,1,0,0,0,608,611,1, - 0,0,0,609,610,1,0,0,0,609,607,1,0,0,0,610,612,1,0,0,0,611,609,1,0,0,0,612, - 613,5,42,0,0,613,614,5,47,0,0,614,615,1,0,0,0,615,616,6,21,10,0,616,58, - 1,0,0,0,617,619,7,23,0,0,618,617,1,0,0,0,619,620,1,0,0,0,620,618,1,0,0, - 0,620,621,1,0,0,0,621,622,1,0,0,0,622,623,6,22,10,0,623,60,1,0,0,0,624, - 625,5,58,0,0,625,62,1,0,0,0,626,627,5,124,0,0,627,628,1,0,0,0,628,629,6, - 24,11,0,629,64,1,0,0,0,630,631,7,24,0,0,631,66,1,0,0,0,632,633,7,25,0,0, - 633,68,1,0,0,0,634,635,5,92,0,0,635,636,7,26,0,0,636,70,1,0,0,0,637,638, - 8,27,0,0,638,72,1,0,0,0,639,641,7,3,0,0,640,642,7,28,0,0,641,640,1,0,0, - 0,641,642,1,0,0,0,642,644,1,0,0,0,643,645,3,65,25,0,644,643,1,0,0,0,645, - 646,1,0,0,0,646,644,1,0,0,0,646,647,1,0,0,0,647,74,1,0,0,0,648,649,5,64, - 0,0,649,76,1,0,0,0,650,651,5,96,0,0,651,78,1,0,0,0,652,656,8,29,0,0,653, - 654,5,96,0,0,654,656,5,96,0,0,655,652,1,0,0,0,655,653,1,0,0,0,656,80,1, - 0,0,0,657,658,5,95,0,0,658,82,1,0,0,0,659,663,3,67,26,0,660,663,3,65,25, - 0,661,663,3,81,33,0,662,659,1,0,0,0,662,660,1,0,0,0,662,661,1,0,0,0,663, - 84,1,0,0,0,664,669,5,34,0,0,665,668,3,69,27,0,666,668,3,71,28,0,667,665, - 1,0,0,0,667,666,1,0,0,0,668,671,1,0,0,0,669,667,1,0,0,0,669,670,1,0,0,0, - 670,672,1,0,0,0,671,669,1,0,0,0,672,694,5,34,0,0,673,674,5,34,0,0,674,675, - 5,34,0,0,675,676,5,34,0,0,676,680,1,0,0,0,677,679,8,22,0,0,678,677,1,0, - 0,0,679,682,1,0,0,0,680,681,1,0,0,0,680,678,1,0,0,0,681,683,1,0,0,0,682, - 680,1,0,0,0,683,684,5,34,0,0,684,685,5,34,0,0,685,686,5,34,0,0,686,688, - 1,0,0,0,687,689,5,34,0,0,688,687,1,0,0,0,688,689,1,0,0,0,689,691,1,0,0, - 0,690,692,5,34,0,0,691,690,1,0,0,0,691,692,1,0,0,0,692,694,1,0,0,0,693, - 664,1,0,0,0,693,673,1,0,0,0,694,86,1,0,0,0,695,697,3,65,25,0,696,695,1, - 0,0,0,697,698,1,0,0,0,698,696,1,0,0,0,698,699,1,0,0,0,699,88,1,0,0,0,700, - 702,3,65,25,0,701,700,1,0,0,0,702,703,1,0,0,0,703,701,1,0,0,0,703,704,1, - 0,0,0,704,705,1,0,0,0,705,709,3,105,45,0,706,708,3,65,25,0,707,706,1,0, - 0,0,708,711,1,0,0,0,709,707,1,0,0,0,709,710,1,0,0,0,710,743,1,0,0,0,711, - 709,1,0,0,0,712,714,3,105,45,0,713,715,3,65,25,0,714,713,1,0,0,0,715,716, - 1,0,0,0,716,714,1,0,0,0,716,717,1,0,0,0,717,743,1,0,0,0,718,720,3,65,25, - 0,719,718,1,0,0,0,720,721,1,0,0,0,721,719,1,0,0,0,721,722,1,0,0,0,722,730, - 1,0,0,0,723,727,3,105,45,0,724,726,3,65,25,0,725,724,1,0,0,0,726,729,1, - 0,0,0,727,725,1,0,0,0,727,728,1,0,0,0,728,731,1,0,0,0,729,727,1,0,0,0,730, - 723,1,0,0,0,730,731,1,0,0,0,731,732,1,0,0,0,732,733,3,73,29,0,733,743,1, - 0,0,0,734,736,3,105,45,0,735,737,3,65,25,0,736,735,1,0,0,0,737,738,1,0, - 0,0,738,736,1,0,0,0,738,739,1,0,0,0,739,740,1,0,0,0,740,741,3,73,29,0,741, - 743,1,0,0,0,742,701,1,0,0,0,742,712,1,0,0,0,742,719,1,0,0,0,742,734,1,0, - 0,0,743,90,1,0,0,0,744,745,7,30,0,0,745,746,7,31,0,0,746,92,1,0,0,0,747, - 748,7,12,0,0,748,749,7,9,0,0,749,750,7,0,0,0,750,94,1,0,0,0,751,752,7,12, - 0,0,752,753,7,2,0,0,753,754,7,4,0,0,754,96,1,0,0,0,755,756,5,61,0,0,756, - 98,1,0,0,0,757,758,5,58,0,0,758,759,5,58,0,0,759,100,1,0,0,0,760,761,5, - 44,0,0,761,102,1,0,0,0,762,763,7,0,0,0,763,764,7,3,0,0,764,765,7,2,0,0, - 765,766,7,4,0,0,766,104,1,0,0,0,767,768,5,46,0,0,768,106,1,0,0,0,769,770, - 7,15,0,0,770,771,7,12,0,0,771,772,7,13,0,0,772,773,7,2,0,0,773,774,7,3, - 0,0,774,108,1,0,0,0,775,776,7,15,0,0,776,777,7,1,0,0,777,778,7,6,0,0,778, - 779,7,2,0,0,779,780,7,5,0,0,780,110,1,0,0,0,781,782,7,1,0,0,782,783,7,9, - 0,0,783,112,1,0,0,0,784,785,7,1,0,0,785,786,7,2,0,0,786,114,1,0,0,0,787, - 788,7,13,0,0,788,789,7,12,0,0,789,790,7,2,0,0,790,791,7,5,0,0,791,116,1, - 0,0,0,792,793,7,13,0,0,793,794,7,1,0,0,794,795,7,18,0,0,795,796,7,3,0,0, - 796,118,1,0,0,0,797,798,5,40,0,0,798,120,1,0,0,0,799,800,7,9,0,0,800,801, - 7,7,0,0,801,802,7,5,0,0,802,122,1,0,0,0,803,804,7,9,0,0,804,805,7,20,0, - 0,805,806,7,13,0,0,806,807,7,13,0,0,807,124,1,0,0,0,808,809,7,9,0,0,809, - 810,7,20,0,0,810,811,7,13,0,0,811,812,7,13,0,0,812,813,7,2,0,0,813,126, - 1,0,0,0,814,815,7,7,0,0,815,816,7,6,0,0,816,128,1,0,0,0,817,818,5,63,0, - 0,818,130,1,0,0,0,819,820,7,6,0,0,820,821,7,13,0,0,821,822,7,1,0,0,822, - 823,7,18,0,0,823,824,7,3,0,0,824,132,1,0,0,0,825,826,5,41,0,0,826,134,1, - 0,0,0,827,828,7,5,0,0,828,829,7,6,0,0,829,830,7,20,0,0,830,831,7,3,0,0, - 831,136,1,0,0,0,832,833,5,61,0,0,833,834,5,61,0,0,834,138,1,0,0,0,835,836, - 5,61,0,0,836,837,5,126,0,0,837,140,1,0,0,0,838,839,5,33,0,0,839,840,5,61, - 0,0,840,142,1,0,0,0,841,842,5,60,0,0,842,144,1,0,0,0,843,844,5,60,0,0,844, - 845,5,61,0,0,845,146,1,0,0,0,846,847,5,62,0,0,847,148,1,0,0,0,848,849,5, - 62,0,0,849,850,5,61,0,0,850,150,1,0,0,0,851,852,5,43,0,0,852,152,1,0,0, - 0,853,854,5,45,0,0,854,154,1,0,0,0,855,856,5,42,0,0,856,156,1,0,0,0,857, - 858,5,47,0,0,858,158,1,0,0,0,859,860,5,37,0,0,860,160,1,0,0,0,861,862,4, - 73,3,0,862,863,3,61,23,0,863,864,1,0,0,0,864,865,6,73,12,0,865,162,1,0, - 0,0,866,867,3,45,15,0,867,868,1,0,0,0,868,869,6,74,13,0,869,164,1,0,0,0, - 870,873,3,129,57,0,871,874,3,67,26,0,872,874,3,81,33,0,873,871,1,0,0,0, - 873,872,1,0,0,0,874,878,1,0,0,0,875,877,3,83,34,0,876,875,1,0,0,0,877,880, - 1,0,0,0,878,876,1,0,0,0,878,879,1,0,0,0,879,888,1,0,0,0,880,878,1,0,0,0, - 881,883,3,129,57,0,882,884,3,65,25,0,883,882,1,0,0,0,884,885,1,0,0,0,885, - 883,1,0,0,0,885,886,1,0,0,0,886,888,1,0,0,0,887,870,1,0,0,0,887,881,1,0, - 0,0,888,166,1,0,0,0,889,890,5,91,0,0,890,891,1,0,0,0,891,892,6,76,0,0,892, - 893,6,76,0,0,893,168,1,0,0,0,894,895,5,93,0,0,895,896,1,0,0,0,896,897,6, - 77,11,0,897,898,6,77,11,0,898,170,1,0,0,0,899,903,3,67,26,0,900,902,3,83, - 34,0,901,900,1,0,0,0,902,905,1,0,0,0,903,901,1,0,0,0,903,904,1,0,0,0,904, - 916,1,0,0,0,905,903,1,0,0,0,906,909,3,81,33,0,907,909,3,75,30,0,908,906, - 1,0,0,0,908,907,1,0,0,0,909,911,1,0,0,0,910,912,3,83,34,0,911,910,1,0,0, - 0,912,913,1,0,0,0,913,911,1,0,0,0,913,914,1,0,0,0,914,916,1,0,0,0,915,899, - 1,0,0,0,915,908,1,0,0,0,916,172,1,0,0,0,917,919,3,77,31,0,918,920,3,79, - 32,0,919,918,1,0,0,0,920,921,1,0,0,0,921,919,1,0,0,0,921,922,1,0,0,0,922, - 923,1,0,0,0,923,924,3,77,31,0,924,174,1,0,0,0,925,926,3,173,79,0,926,176, - 1,0,0,0,927,928,3,55,20,0,928,929,1,0,0,0,929,930,6,81,10,0,930,178,1,0, - 0,0,931,932,3,57,21,0,932,933,1,0,0,0,933,934,6,82,10,0,934,180,1,0,0,0, - 935,936,3,59,22,0,936,937,1,0,0,0,937,938,6,83,10,0,938,182,1,0,0,0,939, - 940,3,167,76,0,940,941,1,0,0,0,941,942,6,84,14,0,942,943,6,84,15,0,943, - 184,1,0,0,0,944,945,3,63,24,0,945,946,1,0,0,0,946,947,6,85,16,0,947,948, - 6,85,11,0,948,186,1,0,0,0,949,950,3,59,22,0,950,951,1,0,0,0,951,952,6,86, - 10,0,952,188,1,0,0,0,953,954,3,55,20,0,954,955,1,0,0,0,955,956,6,87,10, - 0,956,190,1,0,0,0,957,958,3,57,21,0,958,959,1,0,0,0,959,960,6,88,10,0,960, - 192,1,0,0,0,961,962,3,63,24,0,962,963,1,0,0,0,963,964,6,89,16,0,964,965, - 6,89,11,0,965,194,1,0,0,0,966,967,3,167,76,0,967,968,1,0,0,0,968,969,6, - 90,14,0,969,196,1,0,0,0,970,971,3,169,77,0,971,972,1,0,0,0,972,973,6,91, - 17,0,973,198,1,0,0,0,974,975,3,61,23,0,975,976,1,0,0,0,976,977,6,92,12, - 0,977,200,1,0,0,0,978,979,3,101,43,0,979,980,1,0,0,0,980,981,6,93,18,0, - 981,202,1,0,0,0,982,983,3,97,41,0,983,984,1,0,0,0,984,985,6,94,19,0,985, - 204,1,0,0,0,986,987,7,16,0,0,987,988,7,3,0,0,988,989,7,5,0,0,989,990,7, - 12,0,0,990,991,7,0,0,0,991,992,7,12,0,0,992,993,7,5,0,0,993,994,7,12,0, - 0,994,206,1,0,0,0,995,999,8,32,0,0,996,997,5,47,0,0,997,999,8,33,0,0,998, - 995,1,0,0,0,998,996,1,0,0,0,999,208,1,0,0,0,1000,1002,3,207,96,0,1001,1000, - 1,0,0,0,1002,1003,1,0,0,0,1003,1001,1,0,0,0,1003,1004,1,0,0,0,1004,210, - 1,0,0,0,1005,1006,3,209,97,0,1006,1007,1,0,0,0,1007,1008,6,98,20,0,1008, - 212,1,0,0,0,1009,1010,3,85,35,0,1010,1011,1,0,0,0,1011,1012,6,99,21,0,1012, - 214,1,0,0,0,1013,1014,3,55,20,0,1014,1015,1,0,0,0,1015,1016,6,100,10,0, - 1016,216,1,0,0,0,1017,1018,3,57,21,0,1018,1019,1,0,0,0,1019,1020,6,101, - 10,0,1020,218,1,0,0,0,1021,1022,3,59,22,0,1022,1023,1,0,0,0,1023,1024,6, - 102,10,0,1024,220,1,0,0,0,1025,1026,3,63,24,0,1026,1027,1,0,0,0,1027,1028, - 6,103,16,0,1028,1029,6,103,11,0,1029,222,1,0,0,0,1030,1031,3,105,45,0,1031, - 1032,1,0,0,0,1032,1033,6,104,22,0,1033,224,1,0,0,0,1034,1035,3,101,43,0, - 1035,1036,1,0,0,0,1036,1037,6,105,18,0,1037,226,1,0,0,0,1038,1039,4,106, - 4,0,1039,1040,3,129,57,0,1040,1041,1,0,0,0,1041,1042,6,106,23,0,1042,228, - 1,0,0,0,1043,1044,4,107,5,0,1044,1045,3,165,75,0,1045,1046,1,0,0,0,1046, - 1047,6,107,24,0,1047,230,1,0,0,0,1048,1053,3,67,26,0,1049,1053,3,65,25, - 0,1050,1053,3,81,33,0,1051,1053,3,155,70,0,1052,1048,1,0,0,0,1052,1049, - 1,0,0,0,1052,1050,1,0,0,0,1052,1051,1,0,0,0,1053,232,1,0,0,0,1054,1057, - 3,67,26,0,1055,1057,3,155,70,0,1056,1054,1,0,0,0,1056,1055,1,0,0,0,1057, - 1061,1,0,0,0,1058,1060,3,231,108,0,1059,1058,1,0,0,0,1060,1063,1,0,0,0, - 1061,1059,1,0,0,0,1061,1062,1,0,0,0,1062,1074,1,0,0,0,1063,1061,1,0,0,0, - 1064,1067,3,81,33,0,1065,1067,3,75,30,0,1066,1064,1,0,0,0,1066,1065,1,0, - 0,0,1067,1069,1,0,0,0,1068,1070,3,231,108,0,1069,1068,1,0,0,0,1070,1071, - 1,0,0,0,1071,1069,1,0,0,0,1071,1072,1,0,0,0,1072,1074,1,0,0,0,1073,1056, - 1,0,0,0,1073,1066,1,0,0,0,1074,234,1,0,0,0,1075,1078,3,233,109,0,1076,1078, - 3,173,79,0,1077,1075,1,0,0,0,1077,1076,1,0,0,0,1078,1079,1,0,0,0,1079,1077, - 1,0,0,0,1079,1080,1,0,0,0,1080,236,1,0,0,0,1081,1082,3,55,20,0,1082,1083, - 1,0,0,0,1083,1084,6,111,10,0,1084,238,1,0,0,0,1085,1086,3,57,21,0,1086, - 1087,1,0,0,0,1087,1088,6,112,10,0,1088,240,1,0,0,0,1089,1090,3,59,22,0, - 1090,1091,1,0,0,0,1091,1092,6,113,10,0,1092,242,1,0,0,0,1093,1094,3,63, - 24,0,1094,1095,1,0,0,0,1095,1096,6,114,16,0,1096,1097,6,114,11,0,1097,244, - 1,0,0,0,1098,1099,3,97,41,0,1099,1100,1,0,0,0,1100,1101,6,115,19,0,1101, - 246,1,0,0,0,1102,1103,3,101,43,0,1103,1104,1,0,0,0,1104,1105,6,116,18,0, - 1105,248,1,0,0,0,1106,1107,3,105,45,0,1107,1108,1,0,0,0,1108,1109,6,117, - 22,0,1109,250,1,0,0,0,1110,1111,4,118,6,0,1111,1112,3,129,57,0,1112,1113, - 1,0,0,0,1113,1114,6,118,23,0,1114,252,1,0,0,0,1115,1116,4,119,7,0,1116, - 1117,3,165,75,0,1117,1118,1,0,0,0,1118,1119,6,119,24,0,1119,254,1,0,0,0, - 1120,1121,7,12,0,0,1121,1122,7,2,0,0,1122,256,1,0,0,0,1123,1124,3,235,110, - 0,1124,1125,1,0,0,0,1125,1126,6,121,25,0,1126,258,1,0,0,0,1127,1128,3,55, - 20,0,1128,1129,1,0,0,0,1129,1130,6,122,10,0,1130,260,1,0,0,0,1131,1132, - 3,57,21,0,1132,1133,1,0,0,0,1133,1134,6,123,10,0,1134,262,1,0,0,0,1135, - 1136,3,59,22,0,1136,1137,1,0,0,0,1137,1138,6,124,10,0,1138,264,1,0,0,0, - 1139,1140,3,63,24,0,1140,1141,1,0,0,0,1141,1142,6,125,16,0,1142,1143,6, - 125,11,0,1143,266,1,0,0,0,1144,1145,3,167,76,0,1145,1146,1,0,0,0,1146,1147, - 6,126,14,0,1147,1148,6,126,26,0,1148,268,1,0,0,0,1149,1150,7,7,0,0,1150, - 1151,7,9,0,0,1151,1152,1,0,0,0,1152,1153,6,127,27,0,1153,270,1,0,0,0,1154, - 1155,7,19,0,0,1155,1156,7,1,0,0,1156,1157,7,5,0,0,1157,1158,7,10,0,0,1158, - 1159,1,0,0,0,1159,1160,6,128,27,0,1160,272,1,0,0,0,1161,1162,8,34,0,0,1162, - 274,1,0,0,0,1163,1165,3,273,129,0,1164,1163,1,0,0,0,1165,1166,1,0,0,0,1166, - 1164,1,0,0,0,1166,1167,1,0,0,0,1167,1168,1,0,0,0,1168,1169,3,61,23,0,1169, - 1171,1,0,0,0,1170,1164,1,0,0,0,1170,1171,1,0,0,0,1171,1173,1,0,0,0,1172, - 1174,3,273,129,0,1173,1172,1,0,0,0,1174,1175,1,0,0,0,1175,1173,1,0,0,0, - 1175,1176,1,0,0,0,1176,276,1,0,0,0,1177,1178,3,275,130,0,1178,1179,1,0, - 0,0,1179,1180,6,131,28,0,1180,278,1,0,0,0,1181,1182,3,55,20,0,1182,1183, - 1,0,0,0,1183,1184,6,132,10,0,1184,280,1,0,0,0,1185,1186,3,57,21,0,1186, - 1187,1,0,0,0,1187,1188,6,133,10,0,1188,282,1,0,0,0,1189,1190,3,59,22,0, - 1190,1191,1,0,0,0,1191,1192,6,134,10,0,1192,284,1,0,0,0,1193,1194,3,63, - 24,0,1194,1195,1,0,0,0,1195,1196,6,135,16,0,1196,1197,6,135,11,0,1197,1198, - 6,135,11,0,1198,286,1,0,0,0,1199,1200,3,97,41,0,1200,1201,1,0,0,0,1201, - 1202,6,136,19,0,1202,288,1,0,0,0,1203,1204,3,101,43,0,1204,1205,1,0,0,0, - 1205,1206,6,137,18,0,1206,290,1,0,0,0,1207,1208,3,105,45,0,1208,1209,1, - 0,0,0,1209,1210,6,138,22,0,1210,292,1,0,0,0,1211,1212,3,271,128,0,1212, - 1213,1,0,0,0,1213,1214,6,139,29,0,1214,294,1,0,0,0,1215,1216,3,235,110, - 0,1216,1217,1,0,0,0,1217,1218,6,140,25,0,1218,296,1,0,0,0,1219,1220,3,175, - 80,0,1220,1221,1,0,0,0,1221,1222,6,141,30,0,1222,298,1,0,0,0,1223,1224, - 4,142,8,0,1224,1225,3,129,57,0,1225,1226,1,0,0,0,1226,1227,6,142,23,0,1227, - 300,1,0,0,0,1228,1229,4,143,9,0,1229,1230,3,165,75,0,1230,1231,1,0,0,0, - 1231,1232,6,143,24,0,1232,302,1,0,0,0,1233,1234,3,55,20,0,1234,1235,1,0, - 0,0,1235,1236,6,144,10,0,1236,304,1,0,0,0,1237,1238,3,57,21,0,1238,1239, - 1,0,0,0,1239,1240,6,145,10,0,1240,306,1,0,0,0,1241,1242,3,59,22,0,1242, - 1243,1,0,0,0,1243,1244,6,146,10,0,1244,308,1,0,0,0,1245,1246,3,63,24,0, - 1246,1247,1,0,0,0,1247,1248,6,147,16,0,1248,1249,6,147,11,0,1249,310,1, - 0,0,0,1250,1251,3,105,45,0,1251,1252,1,0,0,0,1252,1253,6,148,22,0,1253, - 312,1,0,0,0,1254,1255,4,149,10,0,1255,1256,3,129,57,0,1256,1257,1,0,0,0, - 1257,1258,6,149,23,0,1258,314,1,0,0,0,1259,1260,4,150,11,0,1260,1261,3, - 165,75,0,1261,1262,1,0,0,0,1262,1263,6,150,24,0,1263,316,1,0,0,0,1264,1265, - 3,175,80,0,1265,1266,1,0,0,0,1266,1267,6,151,30,0,1267,318,1,0,0,0,1268, - 1269,3,171,78,0,1269,1270,1,0,0,0,1270,1271,6,152,31,0,1271,320,1,0,0,0, - 1272,1273,3,55,20,0,1273,1274,1,0,0,0,1274,1275,6,153,10,0,1275,322,1,0, - 0,0,1276,1277,3,57,21,0,1277,1278,1,0,0,0,1278,1279,6,154,10,0,1279,324, - 1,0,0,0,1280,1281,3,59,22,0,1281,1282,1,0,0,0,1282,1283,6,155,10,0,1283, - 326,1,0,0,0,1284,1285,3,63,24,0,1285,1286,1,0,0,0,1286,1287,6,156,16,0, - 1287,1288,6,156,11,0,1288,328,1,0,0,0,1289,1290,7,1,0,0,1290,1291,7,9,0, - 0,1291,1292,7,15,0,0,1292,1293,7,7,0,0,1293,330,1,0,0,0,1294,1295,3,55, - 20,0,1295,1296,1,0,0,0,1296,1297,6,158,10,0,1297,332,1,0,0,0,1298,1299, - 3,57,21,0,1299,1300,1,0,0,0,1300,1301,6,159,10,0,1301,334,1,0,0,0,1302, - 1303,3,59,22,0,1303,1304,1,0,0,0,1304,1305,6,160,10,0,1305,336,1,0,0,0, - 1306,1307,3,169,77,0,1307,1308,1,0,0,0,1308,1309,6,161,17,0,1309,1310,6, - 161,11,0,1310,338,1,0,0,0,1311,1312,3,61,23,0,1312,1313,1,0,0,0,1313,1314, - 6,162,12,0,1314,340,1,0,0,0,1315,1321,3,75,30,0,1316,1321,3,65,25,0,1317, - 1321,3,105,45,0,1318,1321,3,67,26,0,1319,1321,3,81,33,0,1320,1315,1,0,0, - 0,1320,1316,1,0,0,0,1320,1317,1,0,0,0,1320,1318,1,0,0,0,1320,1319,1,0,0, - 0,1321,1322,1,0,0,0,1322,1320,1,0,0,0,1322,1323,1,0,0,0,1323,342,1,0,0, - 0,1324,1325,3,55,20,0,1325,1326,1,0,0,0,1326,1327,6,164,10,0,1327,344,1, - 0,0,0,1328,1329,3,57,21,0,1329,1330,1,0,0,0,1330,1331,6,165,10,0,1331,346, - 1,0,0,0,1332,1333,3,59,22,0,1333,1334,1,0,0,0,1334,1335,6,166,10,0,1335, - 348,1,0,0,0,1336,1337,3,63,24,0,1337,1338,1,0,0,0,1338,1339,6,167,16,0, - 1339,1340,6,167,11,0,1340,350,1,0,0,0,1341,1342,3,61,23,0,1342,1343,1,0, - 0,0,1343,1344,6,168,12,0,1344,352,1,0,0,0,1345,1346,3,101,43,0,1346,1347, - 1,0,0,0,1347,1348,6,169,18,0,1348,354,1,0,0,0,1349,1350,3,105,45,0,1350, - 1351,1,0,0,0,1351,1352,6,170,22,0,1352,356,1,0,0,0,1353,1354,3,269,127, - 0,1354,1355,1,0,0,0,1355,1356,6,171,32,0,1356,1357,6,171,33,0,1357,358, - 1,0,0,0,1358,1359,3,209,97,0,1359,1360,1,0,0,0,1360,1361,6,172,20,0,1361, - 360,1,0,0,0,1362,1363,3,85,35,0,1363,1364,1,0,0,0,1364,1365,6,173,21,0, - 1365,362,1,0,0,0,1366,1367,3,55,20,0,1367,1368,1,0,0,0,1368,1369,6,174, - 10,0,1369,364,1,0,0,0,1370,1371,3,57,21,0,1371,1372,1,0,0,0,1372,1373,6, - 175,10,0,1373,366,1,0,0,0,1374,1375,3,59,22,0,1375,1376,1,0,0,0,1376,1377, - 6,176,10,0,1377,368,1,0,0,0,1378,1379,3,63,24,0,1379,1380,1,0,0,0,1380, - 1381,6,177,16,0,1381,1382,6,177,11,0,1382,1383,6,177,11,0,1383,370,1,0, - 0,0,1384,1385,3,101,43,0,1385,1386,1,0,0,0,1386,1387,6,178,18,0,1387,372, - 1,0,0,0,1388,1389,3,105,45,0,1389,1390,1,0,0,0,1390,1391,6,179,22,0,1391, - 374,1,0,0,0,1392,1393,3,235,110,0,1393,1394,1,0,0,0,1394,1395,6,180,25, - 0,1395,376,1,0,0,0,1396,1397,3,55,20,0,1397,1398,1,0,0,0,1398,1399,6,181, - 10,0,1399,378,1,0,0,0,1400,1401,3,57,21,0,1401,1402,1,0,0,0,1402,1403,6, - 182,10,0,1403,380,1,0,0,0,1404,1405,3,59,22,0,1405,1406,1,0,0,0,1406,1407, - 6,183,10,0,1407,382,1,0,0,0,1408,1409,3,63,24,0,1409,1410,1,0,0,0,1410, - 1411,6,184,16,0,1411,1412,6,184,11,0,1412,384,1,0,0,0,1413,1414,3,209,97, - 0,1414,1415,1,0,0,0,1415,1416,6,185,20,0,1416,1417,6,185,11,0,1417,1418, - 6,185,34,0,1418,386,1,0,0,0,1419,1420,3,85,35,0,1420,1421,1,0,0,0,1421, - 1422,6,186,21,0,1422,1423,6,186,11,0,1423,1424,6,186,34,0,1424,388,1,0, - 0,0,1425,1426,3,55,20,0,1426,1427,1,0,0,0,1427,1428,6,187,10,0,1428,390, - 1,0,0,0,1429,1430,3,57,21,0,1430,1431,1,0,0,0,1431,1432,6,188,10,0,1432, - 392,1,0,0,0,1433,1434,3,59,22,0,1434,1435,1,0,0,0,1435,1436,6,189,10,0, - 1436,394,1,0,0,0,1437,1438,3,61,23,0,1438,1439,1,0,0,0,1439,1440,6,190, - 12,0,1440,1441,6,190,11,0,1441,1442,6,190,9,0,1442,396,1,0,0,0,1443,1444, - 3,101,43,0,1444,1445,1,0,0,0,1445,1446,6,191,18,0,1446,1447,6,191,11,0, - 1447,1448,6,191,9,0,1448,398,1,0,0,0,1449,1450,3,55,20,0,1450,1451,1,0, - 0,0,1451,1452,6,192,10,0,1452,400,1,0,0,0,1453,1454,3,57,21,0,1454,1455, - 1,0,0,0,1455,1456,6,193,10,0,1456,402,1,0,0,0,1457,1458,3,59,22,0,1458, - 1459,1,0,0,0,1459,1460,6,194,10,0,1460,404,1,0,0,0,1461,1462,3,175,80,0, - 1462,1463,1,0,0,0,1463,1464,6,195,11,0,1464,1465,6,195,0,0,1465,1466,6, - 195,30,0,1466,406,1,0,0,0,1467,1468,3,171,78,0,1468,1469,1,0,0,0,1469,1470, - 6,196,11,0,1470,1471,6,196,0,0,1471,1472,6,196,31,0,1472,408,1,0,0,0,1473, - 1474,3,91,38,0,1474,1475,1,0,0,0,1475,1476,6,197,11,0,1476,1477,6,197,0, - 0,1477,1478,6,197,35,0,1478,410,1,0,0,0,1479,1480,3,63,24,0,1480,1481,1, - 0,0,0,1481,1482,6,198,16,0,1482,1483,6,198,11,0,1483,412,1,0,0,0,65,0,1, - 2,3,4,5,6,7,8,9,10,11,12,13,14,581,591,595,598,607,609,620,641,646,655, - 662,667,669,680,688,691,693,698,703,709,716,721,727,730,738,742,873,878, - 885,887,903,908,913,915,921,998,1003,1052,1056,1061,1066,1071,1073,1077, - 1079,1166,1170,1175,1320,1322,36,5,1,0,5,4,0,5,6,0,5,2,0,5,3,0,5,8,0,5, - 5,0,5,9,0,5,11,0,5,13,0,0,1,0,4,0,0,7,24,0,7,16,0,7,65,0,5,0,0,7,25,0,7, - 66,0,7,34,0,7,32,0,7,76,0,7,26,0,7,36,0,7,48,0,7,64,0,7,80,0,5,10,0,5,7, - 0,7,90,0,7,89,0,7,68,0,7,67,0,7,88,0,5,12,0,5,14,0,7,29,0]; + 2,199,7,199,2,200,7,200,2,201,7,201,2,202,7,202,2,203,7,203,2,204,7,204, + 2,205,7,205,2,206,7,206,2,207,7,207,2,208,7,208,2,209,7,209,2,210,7,210, + 2,211,7,211,2,212,7,212,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3, + 1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5, + 1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8, + 1,8,1,8,1,8,1,8,1,8,1,8,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9, + 1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,11,1,11,1,11,1,11,1,11,1, + 11,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,13, + 1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,1, + 15,1,15,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16, + 1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1, + 18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,18,1,19,1,19,1,19,1,19, + 1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,20,1,21,1,21,1, + 21,1,21,1,21,1,21,1,21,1,21,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22, + 1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,23,1,24,4,24,654,8,24,11, + 24,12,24,655,1,24,1,24,1,25,1,25,1,25,1,25,5,25,664,8,25,10,25,12,25,667, + 9,25,1,25,3,25,670,8,25,1,25,3,25,673,8,25,1,25,1,25,1,26,1,26,1,26,1,26, + 1,26,5,26,682,8,26,10,26,12,26,685,9,26,1,26,1,26,1,26,1,26,1,26,1,27,4, + 27,693,8,27,11,27,12,27,694,1,27,1,27,1,28,1,28,1,28,1,28,1,29,1,29,1,30, + 1,30,1,31,1,31,1,31,1,32,1,32,1,33,1,33,3,33,714,8,33,1,33,4,33,717,8,33, + 11,33,12,33,718,1,34,1,34,1,35,1,35,1,36,1,36,1,36,3,36,728,8,36,1,37,1, + 37,1,38,1,38,1,38,3,38,735,8,38,1,39,1,39,1,39,5,39,740,8,39,10,39,12,39, + 743,9,39,1,39,1,39,1,39,1,39,1,39,1,39,5,39,751,8,39,10,39,12,39,754,9, + 39,1,39,1,39,1,39,1,39,1,39,3,39,761,8,39,1,39,3,39,764,8,39,3,39,766,8, + 39,1,40,4,40,769,8,40,11,40,12,40,770,1,41,4,41,774,8,41,11,41,12,41,775, + 1,41,1,41,5,41,780,8,41,10,41,12,41,783,9,41,1,41,1,41,4,41,787,8,41,11, + 41,12,41,788,1,41,4,41,792,8,41,11,41,12,41,793,1,41,1,41,5,41,798,8,41, + 10,41,12,41,801,9,41,3,41,803,8,41,1,41,1,41,1,41,1,41,4,41,809,8,41,11, + 41,12,41,810,1,41,1,41,3,41,815,8,41,1,42,1,42,1,42,1,43,1,43,1,43,1,43, + 1,44,1,44,1,44,1,44,1,45,1,45,1,46,1,46,1,46,1,47,1,47,1,48,1,48,1,49,1, + 49,1,49,1,49,1,49,1,50,1,50,1,51,1,51,1,51,1,51,1,51,1,51,1,52,1,52,1,52, + 1,52,1,52,1,52,1,53,1,53,1,53,1,54,1,54,1,54,1,55,1,55,1,55,1,55,1,55,1, + 56,1,56,1,56,1,56,1,56,1,57,1,57,1,58,1,58,1,58,1,58,1,59,1,59,1,59,1,59, + 1,59,1,60,1,60,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,62,1,62,1,63,1,63,1, + 63,1,63,1,63,1,63,1,64,1,64,1,65,1,65,1,65,1,65,1,65,1,66,1,66,1,66,1,67, + 1,67,1,67,1,68,1,68,1,68,1,69,1,69,1,70,1,70,1,70,1,71,1,71,1,72,1,72,1, + 72,1,73,1,73,1,74,1,74,1,75,1,75,1,76,1,76,1,77,1,77,1,78,1,78,1,78,1,78, + 1,79,1,79,1,79,3,79,943,8,79,1,79,5,79,946,8,79,10,79,12,79,949,9,79,1, + 79,1,79,4,79,953,8,79,11,79,12,79,954,3,79,957,8,79,1,80,1,80,1,80,1,80, + 1,80,1,81,1,81,1,81,1,81,1,81,1,82,1,82,5,82,971,8,82,10,82,12,82,974,9, + 82,1,82,1,82,3,82,978,8,82,1,82,4,82,981,8,82,11,82,12,82,982,3,82,985, + 8,82,1,83,1,83,4,83,989,8,83,11,83,12,83,990,1,83,1,83,1,84,1,84,1,85,1, + 85,1,85,1,85,1,86,1,86,1,86,1,86,1,87,1,87,1,87,1,87,1,88,1,88,1,88,1,88, + 1,88,1,89,1,89,1,89,1,89,1,89,1,90,1,90,1,90,1,90,1,91,1,91,1,91,1,91,1, + 92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1,93,1,94,1,94,1,94,1,94,1,95,1,95, + 1,95,1,95,1,96,1,96,1,96,1,96,1,97,1,97,1,97,1,97,1,98,1,98,1,98,1,98,1, + 99,1,99,1,99,1,99,1,99,1,99,1,99,1,99,1,99,1,100,1,100,1,100,3,100,1068, + 8,100,1,101,4,101,1071,8,101,11,101,12,101,1072,1,102,1,102,1,102,1,102, + 1,103,1,103,1,103,1,103,1,104,1,104,1,104,1,104,1,105,1,105,1,105,1,105, + 1,106,1,106,1,106,1,106,1,107,1,107,1,107,1,107,1,107,1,108,1,108,1,108, + 1,108,1,109,1,109,1,109,1,109,1,110,1,110,1,110,1,110,1,110,1,111,1,111, + 1,111,1,111,1,111,1,112,1,112,1,112,1,112,3,112,1122,8,112,1,113,1,113, + 3,113,1126,8,113,1,113,5,113,1129,8,113,10,113,12,113,1132,9,113,1,113, + 1,113,3,113,1136,8,113,1,113,4,113,1139,8,113,11,113,12,113,1140,3,113, + 1143,8,113,1,114,1,114,4,114,1147,8,114,11,114,12,114,1148,1,115,1,115, + 1,115,1,115,1,116,1,116,1,116,1,116,1,117,1,117,1,117,1,117,1,118,1,118, + 1,118,1,118,1,118,1,119,1,119,1,119,1,119,1,120,1,120,1,120,1,120,1,121, + 1,121,1,121,1,121,1,122,1,122,1,122,1,122,1,122,1,123,1,123,1,123,1,123, + 1,123,1,124,1,124,1,124,1,125,1,125,1,125,1,125,1,126,1,126,1,126,1,126, + 1,127,1,127,1,127,1,127,1,128,1,128,1,128,1,128,1,129,1,129,1,129,1,129, + 1,129,1,130,1,130,1,130,1,130,1,130,1,131,1,131,1,131,1,131,1,131,1,132, + 1,132,1,132,1,132,1,132,1,132,1,132,1,133,1,133,1,134,4,134,1234,8,134, + 11,134,12,134,1235,1,134,1,134,3,134,1240,8,134,1,134,4,134,1243,8,134, + 11,134,12,134,1244,1,135,1,135,1,135,1,135,1,136,1,136,1,136,1,136,1,137, + 1,137,1,137,1,137,1,138,1,138,1,138,1,138,1,139,1,139,1,139,1,139,1,139, + 1,139,1,140,1,140,1,140,1,140,1,141,1,141,1,141,1,141,1,142,1,142,1,142, + 1,142,1,143,1,143,1,143,1,143,1,144,1,144,1,144,1,144,1,145,1,145,1,145, + 1,145,1,146,1,146,1,146,1,146,1,146,1,147,1,147,1,147,1,147,1,147,1,148, + 1,148,1,148,1,148,1,149,1,149,1,149,1,149,1,150,1,150,1,150,1,150,1,151, + 1,151,1,151,1,151,1,151,1,152,1,152,1,152,1,152,1,153,1,153,1,153,1,153, + 1,153,1,154,1,154,1,154,1,154,1,154,1,155,1,155,1,155,1,155,1,156,1,156, + 1,156,1,156,1,157,1,157,1,157,1,157,1,158,1,158,1,158,1,158,1,159,1,159, + 1,159,1,159,1,160,1,160,1,160,1,160,1,160,1,161,1,161,1,161,1,161,1,161, + 1,162,1,162,1,162,1,162,1,163,1,163,1,163,1,163,1,164,1,164,1,164,1,164, + 1,165,1,165,1,165,1,165,1,165,1,166,1,166,1,166,1,166,1,167,1,167,1,167, + 1,167,1,167,4,167,1390,8,167,11,167,12,167,1391,1,168,1,168,1,168,1,168, + 1,169,1,169,1,169,1,169,1,170,1,170,1,170,1,170,1,171,1,171,1,171,1,171, + 1,171,1,172,1,172,1,172,1,172,1,173,1,173,1,173,1,173,1,174,1,174,1,174, + 1,174,1,175,1,175,1,175,1,175,1,175,1,176,1,176,1,176,1,176,1,177,1,177, + 1,177,1,177,1,178,1,178,1,178,1,178,1,179,1,179,1,179,1,179,1,180,1,180, + 1,180,1,180,1,181,1,181,1,181,1,181,1,181,1,181,1,182,1,182,1,182,1,182, + 1,183,1,183,1,183,1,183,1,184,1,184,1,184,1,184,1,185,1,185,1,185,1,185, + 1,186,1,186,1,186,1,186,1,187,1,187,1,187,1,187,1,188,1,188,1,188,1,188, + 1,188,1,189,1,189,1,189,1,189,1,190,1,190,1,190,1,190,1,191,1,191,1,191, + 1,191,1,191,1,191,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192,1,192, + 1,193,1,193,1,193,1,193,1,194,1,194,1,194,1,194,1,195,1,195,1,195,1,195, + 1,196,1,196,1,196,1,196,1,197,1,197,1,197,1,197,1,198,1,198,1,198,1,198, + 1,198,1,199,1,199,1,199,1,199,1,199,1,199,1,200,1,200,1,200,1,200,1,200, + 1,200,1,201,1,201,1,201,1,201,1,202,1,202,1,202,1,202,1,203,1,203,1,203, + 1,203,1,204,1,204,1,204,1,204,1,204,1,204,1,205,1,205,1,205,1,205,1,205, + 1,205,1,206,1,206,1,206,1,206,1,207,1,207,1,207,1,207,1,208,1,208,1,208, + 1,208,1,209,1,209,1,209,1,209,1,209,1,209,1,210,1,210,1,210,1,210,1,210, + 1,210,1,211,1,211,1,211,1,211,1,211,1,211,1,212,1,212,1,212,1,212,1,212, + 2,683,752,0,213,16,1,18,2,20,3,22,4,24,5,26,6,28,7,30,8,32,9,34,10,36,11, + 38,12,40,13,42,14,44,15,46,16,48,17,50,18,52,19,54,20,56,21,58,22,60,23, + 62,24,64,25,66,26,68,27,70,28,72,29,74,0,76,0,78,0,80,0,82,0,84,0,86,0, + 88,0,90,0,92,0,94,30,96,31,98,32,100,33,102,34,104,35,106,36,108,37,110, + 38,112,39,114,40,116,41,118,42,120,43,122,44,124,45,126,46,128,47,130,48, + 132,49,134,50,136,51,138,52,140,53,142,54,144,55,146,56,148,57,150,58,152, + 59,154,60,156,61,158,62,160,63,162,64,164,65,166,66,168,67,170,68,172,0, + 174,69,176,70,178,71,180,72,182,0,184,73,186,74,188,75,190,76,192,0,194, + 0,196,77,198,78,200,79,202,0,204,0,206,0,208,0,210,0,212,0,214,80,216,0, + 218,81,220,0,222,0,224,82,226,83,228,84,230,0,232,0,234,0,236,0,238,0,240, + 0,242,0,244,85,246,86,248,87,250,88,252,0,254,0,256,0,258,0,260,0,262,0, + 264,89,266,0,268,90,270,91,272,92,274,0,276,0,278,93,280,94,282,0,284,95, + 286,0,288,96,290,97,292,98,294,0,296,0,298,0,300,0,302,0,304,0,306,0,308, + 0,310,0,312,99,314,100,316,101,318,0,320,0,322,0,324,0,326,0,328,0,330, + 102,332,103,334,104,336,0,338,105,340,106,342,107,344,108,346,0,348,0,350, + 109,352,110,354,111,356,112,358,0,360,0,362,0,364,0,366,0,368,0,370,0,372, + 113,374,114,376,115,378,0,380,0,382,0,384,0,386,116,388,117,390,118,392, + 0,394,0,396,0,398,0,400,119,402,0,404,0,406,120,408,121,410,122,412,0,414, + 0,416,0,418,123,420,124,422,125,424,0,426,0,428,126,430,127,432,128,434, + 0,436,0,438,0,440,0,16,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,36,2,0,68, + 68,100,100,2,0,73,73,105,105,2,0,83,83,115,115,2,0,69,69,101,101,2,0,67, + 67,99,99,2,0,84,84,116,116,2,0,82,82,114,114,2,0,79,79,111,111,2,0,80,80, + 112,112,2,0,78,78,110,110,2,0,72,72,104,104,2,0,86,86,118,118,2,0,65,65, + 97,97,2,0,76,76,108,108,2,0,88,88,120,120,2,0,70,70,102,102,2,0,77,77,109, + 109,2,0,71,71,103,103,2,0,75,75,107,107,2,0,87,87,119,119,2,0,85,85,117, + 117,2,0,74,74,106,106,6,0,9,10,13,13,32,32,47,47,91,91,93,93,2,0,10,10, + 13,13,3,0,9,10,13,13,32,32,1,0,48,57,2,0,65,90,97,122,8,0,34,34,78,78,82, + 82,84,84,92,92,110,110,114,114,116,116,4,0,10,10,13,13,34,34,92,92,2,0, + 43,43,45,45,1,0,96,96,2,0,66,66,98,98,2,0,89,89,121,121,11,0,9,10,13,13, + 32,32,34,34,44,44,47,47,58,58,61,61,91,91,93,93,124,124,2,0,42,42,47,47, + 11,0,9,10,13,13,32,32,34,35,44,44,47,47,58,58,60,60,62,63,92,92,124,124, + 1628,0,16,1,0,0,0,0,18,1,0,0,0,0,20,1,0,0,0,0,22,1,0,0,0,0,24,1,0,0,0,0, + 26,1,0,0,0,0,28,1,0,0,0,0,30,1,0,0,0,0,32,1,0,0,0,0,34,1,0,0,0,0,36,1,0, + 0,0,0,38,1,0,0,0,0,40,1,0,0,0,0,42,1,0,0,0,0,44,1,0,0,0,0,46,1,0,0,0,0, + 48,1,0,0,0,0,50,1,0,0,0,0,52,1,0,0,0,0,54,1,0,0,0,0,56,1,0,0,0,0,58,1,0, + 0,0,0,60,1,0,0,0,0,62,1,0,0,0,0,64,1,0,0,0,0,66,1,0,0,0,0,68,1,0,0,0,0, + 70,1,0,0,0,1,72,1,0,0,0,1,94,1,0,0,0,1,96,1,0,0,0,1,98,1,0,0,0,1,100,1, + 0,0,0,1,102,1,0,0,0,1,104,1,0,0,0,1,106,1,0,0,0,1,108,1,0,0,0,1,110,1,0, + 0,0,1,112,1,0,0,0,1,114,1,0,0,0,1,116,1,0,0,0,1,118,1,0,0,0,1,120,1,0,0, + 0,1,122,1,0,0,0,1,124,1,0,0,0,1,126,1,0,0,0,1,128,1,0,0,0,1,130,1,0,0,0, + 1,132,1,0,0,0,1,134,1,0,0,0,1,136,1,0,0,0,1,138,1,0,0,0,1,140,1,0,0,0,1, + 142,1,0,0,0,1,144,1,0,0,0,1,146,1,0,0,0,1,148,1,0,0,0,1,150,1,0,0,0,1,152, + 1,0,0,0,1,154,1,0,0,0,1,156,1,0,0,0,1,158,1,0,0,0,1,160,1,0,0,0,1,162,1, + 0,0,0,1,164,1,0,0,0,1,166,1,0,0,0,1,168,1,0,0,0,1,170,1,0,0,0,1,172,1,0, + 0,0,1,174,1,0,0,0,1,176,1,0,0,0,1,178,1,0,0,0,1,180,1,0,0,0,1,184,1,0,0, + 0,1,186,1,0,0,0,1,188,1,0,0,0,1,190,1,0,0,0,2,192,1,0,0,0,2,194,1,0,0,0, + 2,196,1,0,0,0,2,198,1,0,0,0,2,200,1,0,0,0,3,202,1,0,0,0,3,204,1,0,0,0,3, + 206,1,0,0,0,3,208,1,0,0,0,3,210,1,0,0,0,3,212,1,0,0,0,3,214,1,0,0,0,3,218, + 1,0,0,0,3,220,1,0,0,0,3,222,1,0,0,0,3,224,1,0,0,0,3,226,1,0,0,0,3,228,1, + 0,0,0,4,230,1,0,0,0,4,232,1,0,0,0,4,234,1,0,0,0,4,236,1,0,0,0,4,238,1,0, + 0,0,4,244,1,0,0,0,4,246,1,0,0,0,4,248,1,0,0,0,4,250,1,0,0,0,5,252,1,0,0, + 0,5,254,1,0,0,0,5,256,1,0,0,0,5,258,1,0,0,0,5,260,1,0,0,0,5,262,1,0,0,0, + 5,264,1,0,0,0,5,266,1,0,0,0,5,268,1,0,0,0,5,270,1,0,0,0,5,272,1,0,0,0,6, + 274,1,0,0,0,6,276,1,0,0,0,6,278,1,0,0,0,6,280,1,0,0,0,6,284,1,0,0,0,6,286, + 1,0,0,0,6,288,1,0,0,0,6,290,1,0,0,0,6,292,1,0,0,0,7,294,1,0,0,0,7,296,1, + 0,0,0,7,298,1,0,0,0,7,300,1,0,0,0,7,302,1,0,0,0,7,304,1,0,0,0,7,306,1,0, + 0,0,7,308,1,0,0,0,7,310,1,0,0,0,7,312,1,0,0,0,7,314,1,0,0,0,7,316,1,0,0, + 0,8,318,1,0,0,0,8,320,1,0,0,0,8,322,1,0,0,0,8,324,1,0,0,0,8,326,1,0,0,0, + 8,328,1,0,0,0,8,330,1,0,0,0,8,332,1,0,0,0,8,334,1,0,0,0,9,336,1,0,0,0,9, + 338,1,0,0,0,9,340,1,0,0,0,9,342,1,0,0,0,9,344,1,0,0,0,10,346,1,0,0,0,10, + 348,1,0,0,0,10,350,1,0,0,0,10,352,1,0,0,0,10,354,1,0,0,0,10,356,1,0,0,0, + 11,358,1,0,0,0,11,360,1,0,0,0,11,362,1,0,0,0,11,364,1,0,0,0,11,366,1,0, + 0,0,11,368,1,0,0,0,11,370,1,0,0,0,11,372,1,0,0,0,11,374,1,0,0,0,11,376, + 1,0,0,0,12,378,1,0,0,0,12,380,1,0,0,0,12,382,1,0,0,0,12,384,1,0,0,0,12, + 386,1,0,0,0,12,388,1,0,0,0,12,390,1,0,0,0,13,392,1,0,0,0,13,394,1,0,0,0, + 13,396,1,0,0,0,13,398,1,0,0,0,13,400,1,0,0,0,13,402,1,0,0,0,13,404,1,0, + 0,0,13,406,1,0,0,0,13,408,1,0,0,0,13,410,1,0,0,0,14,412,1,0,0,0,14,414, + 1,0,0,0,14,416,1,0,0,0,14,418,1,0,0,0,14,420,1,0,0,0,14,422,1,0,0,0,15, + 424,1,0,0,0,15,426,1,0,0,0,15,428,1,0,0,0,15,430,1,0,0,0,15,432,1,0,0,0, + 15,434,1,0,0,0,15,436,1,0,0,0,15,438,1,0,0,0,15,440,1,0,0,0,16,442,1,0, + 0,0,18,452,1,0,0,0,20,459,1,0,0,0,22,468,1,0,0,0,24,475,1,0,0,0,26,485, + 1,0,0,0,28,492,1,0,0,0,30,499,1,0,0,0,32,506,1,0,0,0,34,514,1,0,0,0,36, + 526,1,0,0,0,38,535,1,0,0,0,40,541,1,0,0,0,42,548,1,0,0,0,44,555,1,0,0,0, + 46,563,1,0,0,0,48,571,1,0,0,0,50,586,1,0,0,0,52,598,1,0,0,0,54,609,1,0, + 0,0,56,617,1,0,0,0,58,625,1,0,0,0,60,633,1,0,0,0,62,642,1,0,0,0,64,653, + 1,0,0,0,66,659,1,0,0,0,68,676,1,0,0,0,70,692,1,0,0,0,72,698,1,0,0,0,74, + 702,1,0,0,0,76,704,1,0,0,0,78,706,1,0,0,0,80,709,1,0,0,0,82,711,1,0,0,0, + 84,720,1,0,0,0,86,722,1,0,0,0,88,727,1,0,0,0,90,729,1,0,0,0,92,734,1,0, + 0,0,94,765,1,0,0,0,96,768,1,0,0,0,98,814,1,0,0,0,100,816,1,0,0,0,102,819, + 1,0,0,0,104,823,1,0,0,0,106,827,1,0,0,0,108,829,1,0,0,0,110,832,1,0,0,0, + 112,834,1,0,0,0,114,836,1,0,0,0,116,841,1,0,0,0,118,843,1,0,0,0,120,849, + 1,0,0,0,122,855,1,0,0,0,124,858,1,0,0,0,126,861,1,0,0,0,128,866,1,0,0,0, + 130,871,1,0,0,0,132,873,1,0,0,0,134,877,1,0,0,0,136,882,1,0,0,0,138,888, + 1,0,0,0,140,891,1,0,0,0,142,893,1,0,0,0,144,899,1,0,0,0,146,901,1,0,0,0, + 148,906,1,0,0,0,150,909,1,0,0,0,152,912,1,0,0,0,154,915,1,0,0,0,156,917, + 1,0,0,0,158,920,1,0,0,0,160,922,1,0,0,0,162,925,1,0,0,0,164,927,1,0,0,0, + 166,929,1,0,0,0,168,931,1,0,0,0,170,933,1,0,0,0,172,935,1,0,0,0,174,956, + 1,0,0,0,176,958,1,0,0,0,178,963,1,0,0,0,180,984,1,0,0,0,182,986,1,0,0,0, + 184,994,1,0,0,0,186,996,1,0,0,0,188,1000,1,0,0,0,190,1004,1,0,0,0,192,1008, + 1,0,0,0,194,1013,1,0,0,0,196,1018,1,0,0,0,198,1022,1,0,0,0,200,1026,1,0, + 0,0,202,1030,1,0,0,0,204,1035,1,0,0,0,206,1039,1,0,0,0,208,1043,1,0,0,0, + 210,1047,1,0,0,0,212,1051,1,0,0,0,214,1055,1,0,0,0,216,1067,1,0,0,0,218, + 1070,1,0,0,0,220,1074,1,0,0,0,222,1078,1,0,0,0,224,1082,1,0,0,0,226,1086, + 1,0,0,0,228,1090,1,0,0,0,230,1094,1,0,0,0,232,1099,1,0,0,0,234,1103,1,0, + 0,0,236,1107,1,0,0,0,238,1112,1,0,0,0,240,1121,1,0,0,0,242,1142,1,0,0,0, + 244,1146,1,0,0,0,246,1150,1,0,0,0,248,1154,1,0,0,0,250,1158,1,0,0,0,252, + 1162,1,0,0,0,254,1167,1,0,0,0,256,1171,1,0,0,0,258,1175,1,0,0,0,260,1179, + 1,0,0,0,262,1184,1,0,0,0,264,1189,1,0,0,0,266,1192,1,0,0,0,268,1196,1,0, + 0,0,270,1200,1,0,0,0,272,1204,1,0,0,0,274,1208,1,0,0,0,276,1213,1,0,0,0, + 278,1218,1,0,0,0,280,1223,1,0,0,0,282,1230,1,0,0,0,284,1239,1,0,0,0,286, + 1246,1,0,0,0,288,1250,1,0,0,0,290,1254,1,0,0,0,292,1258,1,0,0,0,294,1262, + 1,0,0,0,296,1268,1,0,0,0,298,1272,1,0,0,0,300,1276,1,0,0,0,302,1280,1,0, + 0,0,304,1284,1,0,0,0,306,1288,1,0,0,0,308,1292,1,0,0,0,310,1297,1,0,0,0, + 312,1302,1,0,0,0,314,1306,1,0,0,0,316,1310,1,0,0,0,318,1314,1,0,0,0,320, + 1319,1,0,0,0,322,1323,1,0,0,0,324,1328,1,0,0,0,326,1333,1,0,0,0,328,1337, + 1,0,0,0,330,1341,1,0,0,0,332,1345,1,0,0,0,334,1349,1,0,0,0,336,1353,1,0, + 0,0,338,1358,1,0,0,0,340,1363,1,0,0,0,342,1367,1,0,0,0,344,1371,1,0,0,0, + 346,1375,1,0,0,0,348,1380,1,0,0,0,350,1389,1,0,0,0,352,1393,1,0,0,0,354, + 1397,1,0,0,0,356,1401,1,0,0,0,358,1405,1,0,0,0,360,1410,1,0,0,0,362,1414, + 1,0,0,0,364,1418,1,0,0,0,366,1422,1,0,0,0,368,1427,1,0,0,0,370,1431,1,0, + 0,0,372,1435,1,0,0,0,374,1439,1,0,0,0,376,1443,1,0,0,0,378,1447,1,0,0,0, + 380,1453,1,0,0,0,382,1457,1,0,0,0,384,1461,1,0,0,0,386,1465,1,0,0,0,388, + 1469,1,0,0,0,390,1473,1,0,0,0,392,1477,1,0,0,0,394,1482,1,0,0,0,396,1486, + 1,0,0,0,398,1490,1,0,0,0,400,1496,1,0,0,0,402,1505,1,0,0,0,404,1509,1,0, + 0,0,406,1513,1,0,0,0,408,1517,1,0,0,0,410,1521,1,0,0,0,412,1525,1,0,0,0, + 414,1530,1,0,0,0,416,1536,1,0,0,0,418,1542,1,0,0,0,420,1546,1,0,0,0,422, + 1550,1,0,0,0,424,1554,1,0,0,0,426,1560,1,0,0,0,428,1566,1,0,0,0,430,1570, + 1,0,0,0,432,1574,1,0,0,0,434,1578,1,0,0,0,436,1584,1,0,0,0,438,1590,1,0, + 0,0,440,1596,1,0,0,0,442,443,7,0,0,0,443,444,7,1,0,0,444,445,7,2,0,0,445, + 446,7,2,0,0,446,447,7,3,0,0,447,448,7,4,0,0,448,449,7,5,0,0,449,450,1,0, + 0,0,450,451,6,0,0,0,451,17,1,0,0,0,452,453,7,0,0,0,453,454,7,6,0,0,454, + 455,7,7,0,0,455,456,7,8,0,0,456,457,1,0,0,0,457,458,6,1,1,0,458,19,1,0, + 0,0,459,460,7,3,0,0,460,461,7,9,0,0,461,462,7,6,0,0,462,463,7,1,0,0,463, + 464,7,4,0,0,464,465,7,10,0,0,465,466,1,0,0,0,466,467,6,2,2,0,467,21,1,0, + 0,0,468,469,7,3,0,0,469,470,7,11,0,0,470,471,7,12,0,0,471,472,7,13,0,0, + 472,473,1,0,0,0,473,474,6,3,0,0,474,23,1,0,0,0,475,476,7,3,0,0,476,477, + 7,14,0,0,477,478,7,8,0,0,478,479,7,13,0,0,479,480,7,12,0,0,480,481,7,1, + 0,0,481,482,7,9,0,0,482,483,1,0,0,0,483,484,6,4,3,0,484,25,1,0,0,0,485, + 486,7,15,0,0,486,487,7,6,0,0,487,488,7,7,0,0,488,489,7,16,0,0,489,490,1, + 0,0,0,490,491,6,5,4,0,491,27,1,0,0,0,492,493,7,17,0,0,493,494,7,6,0,0,494, + 495,7,7,0,0,495,496,7,18,0,0,496,497,1,0,0,0,497,498,6,6,0,0,498,29,1,0, + 0,0,499,500,7,18,0,0,500,501,7,3,0,0,501,502,7,3,0,0,502,503,7,8,0,0,503, + 504,1,0,0,0,504,505,6,7,1,0,505,31,1,0,0,0,506,507,7,13,0,0,507,508,7,1, + 0,0,508,509,7,16,0,0,509,510,7,1,0,0,510,511,7,5,0,0,511,512,1,0,0,0,512, + 513,6,8,0,0,513,33,1,0,0,0,514,515,7,16,0,0,515,516,7,11,0,0,516,517,5, + 95,0,0,517,518,7,3,0,0,518,519,7,14,0,0,519,520,7,8,0,0,520,521,7,12,0, + 0,521,522,7,9,0,0,522,523,7,0,0,0,523,524,1,0,0,0,524,525,6,9,5,0,525,35, + 1,0,0,0,526,527,7,6,0,0,527,528,7,3,0,0,528,529,7,9,0,0,529,530,7,12,0, + 0,530,531,7,16,0,0,531,532,7,3,0,0,532,533,1,0,0,0,533,534,6,10,6,0,534, + 37,1,0,0,0,535,536,7,6,0,0,536,537,7,7,0,0,537,538,7,19,0,0,538,539,1,0, + 0,0,539,540,6,11,0,0,540,39,1,0,0,0,541,542,7,2,0,0,542,543,7,10,0,0,543, + 544,7,7,0,0,544,545,7,19,0,0,545,546,1,0,0,0,546,547,6,12,7,0,547,41,1, + 0,0,0,548,549,7,2,0,0,549,550,7,7,0,0,550,551,7,6,0,0,551,552,7,5,0,0,552, + 553,1,0,0,0,553,554,6,13,0,0,554,43,1,0,0,0,555,556,7,2,0,0,556,557,7,5, + 0,0,557,558,7,12,0,0,558,559,7,5,0,0,559,560,7,2,0,0,560,561,1,0,0,0,561, + 562,6,14,0,0,562,45,1,0,0,0,563,564,7,19,0,0,564,565,7,10,0,0,565,566,7, + 3,0,0,566,567,7,6,0,0,567,568,7,3,0,0,568,569,1,0,0,0,569,570,6,15,0,0, + 570,47,1,0,0,0,571,572,4,16,0,0,572,573,7,1,0,0,573,574,7,9,0,0,574,575, + 7,13,0,0,575,576,7,1,0,0,576,577,7,9,0,0,577,578,7,3,0,0,578,579,7,2,0, + 0,579,580,7,5,0,0,580,581,7,12,0,0,581,582,7,5,0,0,582,583,7,2,0,0,583, + 584,1,0,0,0,584,585,6,16,0,0,585,49,1,0,0,0,586,587,4,17,1,0,587,588,7, + 13,0,0,588,589,7,7,0,0,589,590,7,7,0,0,590,591,7,18,0,0,591,592,7,20,0, + 0,592,593,7,8,0,0,593,594,5,95,0,0,594,595,5,128020,0,0,595,596,1,0,0,0, + 596,597,6,17,8,0,597,51,1,0,0,0,598,599,4,18,2,0,599,600,7,16,0,0,600,601, + 7,3,0,0,601,602,7,5,0,0,602,603,7,6,0,0,603,604,7,1,0,0,604,605,7,4,0,0, + 605,606,7,2,0,0,606,607,1,0,0,0,607,608,6,18,9,0,608,53,1,0,0,0,609,610, + 4,19,3,0,610,611,7,21,0,0,611,612,7,7,0,0,612,613,7,1,0,0,613,614,7,9,0, + 0,614,615,1,0,0,0,615,616,6,19,10,0,616,55,1,0,0,0,617,618,4,20,4,0,618, + 619,7,15,0,0,619,620,7,20,0,0,620,621,7,13,0,0,621,622,7,13,0,0,622,623, + 1,0,0,0,623,624,6,20,10,0,624,57,1,0,0,0,625,626,4,21,5,0,626,627,7,13, + 0,0,627,628,7,3,0,0,628,629,7,15,0,0,629,630,7,5,0,0,630,631,1,0,0,0,631, + 632,6,21,10,0,632,59,1,0,0,0,633,634,4,22,6,0,634,635,7,6,0,0,635,636,7, + 1,0,0,636,637,7,17,0,0,637,638,7,10,0,0,638,639,7,5,0,0,639,640,1,0,0,0, + 640,641,6,22,10,0,641,61,1,0,0,0,642,643,4,23,7,0,643,644,7,13,0,0,644, + 645,7,7,0,0,645,646,7,7,0,0,646,647,7,18,0,0,647,648,7,20,0,0,648,649,7, + 8,0,0,649,650,1,0,0,0,650,651,6,23,10,0,651,63,1,0,0,0,652,654,8,22,0,0, + 653,652,1,0,0,0,654,655,1,0,0,0,655,653,1,0,0,0,655,656,1,0,0,0,656,657, + 1,0,0,0,657,658,6,24,0,0,658,65,1,0,0,0,659,660,5,47,0,0,660,661,5,47,0, + 0,661,665,1,0,0,0,662,664,8,23,0,0,663,662,1,0,0,0,664,667,1,0,0,0,665, + 663,1,0,0,0,665,666,1,0,0,0,666,669,1,0,0,0,667,665,1,0,0,0,668,670,5,13, + 0,0,669,668,1,0,0,0,669,670,1,0,0,0,670,672,1,0,0,0,671,673,5,10,0,0,672, + 671,1,0,0,0,672,673,1,0,0,0,673,674,1,0,0,0,674,675,6,25,11,0,675,67,1, + 0,0,0,676,677,5,47,0,0,677,678,5,42,0,0,678,683,1,0,0,0,679,682,3,68,26, + 0,680,682,9,0,0,0,681,679,1,0,0,0,681,680,1,0,0,0,682,685,1,0,0,0,683,684, + 1,0,0,0,683,681,1,0,0,0,684,686,1,0,0,0,685,683,1,0,0,0,686,687,5,42,0, + 0,687,688,5,47,0,0,688,689,1,0,0,0,689,690,6,26,11,0,690,69,1,0,0,0,691, + 693,7,24,0,0,692,691,1,0,0,0,693,694,1,0,0,0,694,692,1,0,0,0,694,695,1, + 0,0,0,695,696,1,0,0,0,696,697,6,27,11,0,697,71,1,0,0,0,698,699,5,124,0, + 0,699,700,1,0,0,0,700,701,6,28,12,0,701,73,1,0,0,0,702,703,7,25,0,0,703, + 75,1,0,0,0,704,705,7,26,0,0,705,77,1,0,0,0,706,707,5,92,0,0,707,708,7,27, + 0,0,708,79,1,0,0,0,709,710,8,28,0,0,710,81,1,0,0,0,711,713,7,3,0,0,712, + 714,7,29,0,0,713,712,1,0,0,0,713,714,1,0,0,0,714,716,1,0,0,0,715,717,3, + 74,29,0,716,715,1,0,0,0,717,718,1,0,0,0,718,716,1,0,0,0,718,719,1,0,0,0, + 719,83,1,0,0,0,720,721,5,64,0,0,721,85,1,0,0,0,722,723,5,96,0,0,723,87, + 1,0,0,0,724,728,8,30,0,0,725,726,5,96,0,0,726,728,5,96,0,0,727,724,1,0, + 0,0,727,725,1,0,0,0,728,89,1,0,0,0,729,730,5,95,0,0,730,91,1,0,0,0,731, + 735,3,76,30,0,732,735,3,74,29,0,733,735,3,90,37,0,734,731,1,0,0,0,734,732, + 1,0,0,0,734,733,1,0,0,0,735,93,1,0,0,0,736,741,5,34,0,0,737,740,3,78,31, + 0,738,740,3,80,32,0,739,737,1,0,0,0,739,738,1,0,0,0,740,743,1,0,0,0,741, + 739,1,0,0,0,741,742,1,0,0,0,742,744,1,0,0,0,743,741,1,0,0,0,744,766,5,34, + 0,0,745,746,5,34,0,0,746,747,5,34,0,0,747,748,5,34,0,0,748,752,1,0,0,0, + 749,751,8,23,0,0,750,749,1,0,0,0,751,754,1,0,0,0,752,753,1,0,0,0,752,750, + 1,0,0,0,753,755,1,0,0,0,754,752,1,0,0,0,755,756,5,34,0,0,756,757,5,34,0, + 0,757,758,5,34,0,0,758,760,1,0,0,0,759,761,5,34,0,0,760,759,1,0,0,0,760, + 761,1,0,0,0,761,763,1,0,0,0,762,764,5,34,0,0,763,762,1,0,0,0,763,764,1, + 0,0,0,764,766,1,0,0,0,765,736,1,0,0,0,765,745,1,0,0,0,766,95,1,0,0,0,767, + 769,3,74,29,0,768,767,1,0,0,0,769,770,1,0,0,0,770,768,1,0,0,0,770,771,1, + 0,0,0,771,97,1,0,0,0,772,774,3,74,29,0,773,772,1,0,0,0,774,775,1,0,0,0, + 775,773,1,0,0,0,775,776,1,0,0,0,776,777,1,0,0,0,777,781,3,116,50,0,778, + 780,3,74,29,0,779,778,1,0,0,0,780,783,1,0,0,0,781,779,1,0,0,0,781,782,1, + 0,0,0,782,815,1,0,0,0,783,781,1,0,0,0,784,786,3,116,50,0,785,787,3,74,29, + 0,786,785,1,0,0,0,787,788,1,0,0,0,788,786,1,0,0,0,788,789,1,0,0,0,789,815, + 1,0,0,0,790,792,3,74,29,0,791,790,1,0,0,0,792,793,1,0,0,0,793,791,1,0,0, + 0,793,794,1,0,0,0,794,802,1,0,0,0,795,799,3,116,50,0,796,798,3,74,29,0, + 797,796,1,0,0,0,798,801,1,0,0,0,799,797,1,0,0,0,799,800,1,0,0,0,800,803, + 1,0,0,0,801,799,1,0,0,0,802,795,1,0,0,0,802,803,1,0,0,0,803,804,1,0,0,0, + 804,805,3,82,33,0,805,815,1,0,0,0,806,808,3,116,50,0,807,809,3,74,29,0, + 808,807,1,0,0,0,809,810,1,0,0,0,810,808,1,0,0,0,810,811,1,0,0,0,811,812, + 1,0,0,0,812,813,3,82,33,0,813,815,1,0,0,0,814,773,1,0,0,0,814,784,1,0,0, + 0,814,791,1,0,0,0,814,806,1,0,0,0,815,99,1,0,0,0,816,817,7,31,0,0,817,818, + 7,32,0,0,818,101,1,0,0,0,819,820,7,12,0,0,820,821,7,9,0,0,821,822,7,0,0, + 0,822,103,1,0,0,0,823,824,7,12,0,0,824,825,7,2,0,0,825,826,7,4,0,0,826, + 105,1,0,0,0,827,828,5,61,0,0,828,107,1,0,0,0,829,830,5,58,0,0,830,831,5, + 58,0,0,831,109,1,0,0,0,832,833,5,58,0,0,833,111,1,0,0,0,834,835,5,44,0, + 0,835,113,1,0,0,0,836,837,7,0,0,0,837,838,7,3,0,0,838,839,7,2,0,0,839,840, + 7,4,0,0,840,115,1,0,0,0,841,842,5,46,0,0,842,117,1,0,0,0,843,844,7,15,0, + 0,844,845,7,12,0,0,845,846,7,13,0,0,846,847,7,2,0,0,847,848,7,3,0,0,848, + 119,1,0,0,0,849,850,7,15,0,0,850,851,7,1,0,0,851,852,7,6,0,0,852,853,7, + 2,0,0,853,854,7,5,0,0,854,121,1,0,0,0,855,856,7,1,0,0,856,857,7,9,0,0,857, + 123,1,0,0,0,858,859,7,1,0,0,859,860,7,2,0,0,860,125,1,0,0,0,861,862,7,13, + 0,0,862,863,7,12,0,0,863,864,7,2,0,0,864,865,7,5,0,0,865,127,1,0,0,0,866, + 867,7,13,0,0,867,868,7,1,0,0,868,869,7,18,0,0,869,870,7,3,0,0,870,129,1, + 0,0,0,871,872,5,40,0,0,872,131,1,0,0,0,873,874,7,9,0,0,874,875,7,7,0,0, + 875,876,7,5,0,0,876,133,1,0,0,0,877,878,7,9,0,0,878,879,7,20,0,0,879,880, + 7,13,0,0,880,881,7,13,0,0,881,135,1,0,0,0,882,883,7,9,0,0,883,884,7,20, + 0,0,884,885,7,13,0,0,885,886,7,13,0,0,886,887,7,2,0,0,887,137,1,0,0,0,888, + 889,7,7,0,0,889,890,7,6,0,0,890,139,1,0,0,0,891,892,5,63,0,0,892,141,1, + 0,0,0,893,894,7,6,0,0,894,895,7,13,0,0,895,896,7,1,0,0,896,897,7,18,0,0, + 897,898,7,3,0,0,898,143,1,0,0,0,899,900,5,41,0,0,900,145,1,0,0,0,901,902, + 7,5,0,0,902,903,7,6,0,0,903,904,7,20,0,0,904,905,7,3,0,0,905,147,1,0,0, + 0,906,907,5,61,0,0,907,908,5,61,0,0,908,149,1,0,0,0,909,910,5,61,0,0,910, + 911,5,126,0,0,911,151,1,0,0,0,912,913,5,33,0,0,913,914,5,61,0,0,914,153, + 1,0,0,0,915,916,5,60,0,0,916,155,1,0,0,0,917,918,5,60,0,0,918,919,5,61, + 0,0,919,157,1,0,0,0,920,921,5,62,0,0,921,159,1,0,0,0,922,923,5,62,0,0,923, + 924,5,61,0,0,924,161,1,0,0,0,925,926,5,43,0,0,926,163,1,0,0,0,927,928,5, + 45,0,0,928,165,1,0,0,0,929,930,5,42,0,0,930,167,1,0,0,0,931,932,5,47,0, + 0,932,169,1,0,0,0,933,934,5,37,0,0,934,171,1,0,0,0,935,936,3,46,15,0,936, + 937,1,0,0,0,937,938,6,78,13,0,938,173,1,0,0,0,939,942,3,140,62,0,940,943, + 3,76,30,0,941,943,3,90,37,0,942,940,1,0,0,0,942,941,1,0,0,0,943,947,1,0, + 0,0,944,946,3,92,38,0,945,944,1,0,0,0,946,949,1,0,0,0,947,945,1,0,0,0,947, + 948,1,0,0,0,948,957,1,0,0,0,949,947,1,0,0,0,950,952,3,140,62,0,951,953, + 3,74,29,0,952,951,1,0,0,0,953,954,1,0,0,0,954,952,1,0,0,0,954,955,1,0,0, + 0,955,957,1,0,0,0,956,939,1,0,0,0,956,950,1,0,0,0,957,175,1,0,0,0,958,959, + 5,91,0,0,959,960,1,0,0,0,960,961,6,80,0,0,961,962,6,80,0,0,962,177,1,0, + 0,0,963,964,5,93,0,0,964,965,1,0,0,0,965,966,6,81,12,0,966,967,6,81,12, + 0,967,179,1,0,0,0,968,972,3,76,30,0,969,971,3,92,38,0,970,969,1,0,0,0,971, + 974,1,0,0,0,972,970,1,0,0,0,972,973,1,0,0,0,973,985,1,0,0,0,974,972,1,0, + 0,0,975,978,3,90,37,0,976,978,3,84,34,0,977,975,1,0,0,0,977,976,1,0,0,0, + 978,980,1,0,0,0,979,981,3,92,38,0,980,979,1,0,0,0,981,982,1,0,0,0,982,980, + 1,0,0,0,982,983,1,0,0,0,983,985,1,0,0,0,984,968,1,0,0,0,984,977,1,0,0,0, + 985,181,1,0,0,0,986,988,3,86,35,0,987,989,3,88,36,0,988,987,1,0,0,0,989, + 990,1,0,0,0,990,988,1,0,0,0,990,991,1,0,0,0,991,992,1,0,0,0,992,993,3,86, + 35,0,993,183,1,0,0,0,994,995,3,182,83,0,995,185,1,0,0,0,996,997,3,66,25, + 0,997,998,1,0,0,0,998,999,6,85,11,0,999,187,1,0,0,0,1000,1001,3,68,26,0, + 1001,1002,1,0,0,0,1002,1003,6,86,11,0,1003,189,1,0,0,0,1004,1005,3,70,27, + 0,1005,1006,1,0,0,0,1006,1007,6,87,11,0,1007,191,1,0,0,0,1008,1009,3,176, + 80,0,1009,1010,1,0,0,0,1010,1011,6,88,14,0,1011,1012,6,88,15,0,1012,193, + 1,0,0,0,1013,1014,3,72,28,0,1014,1015,1,0,0,0,1015,1016,6,89,16,0,1016, + 1017,6,89,12,0,1017,195,1,0,0,0,1018,1019,3,70,27,0,1019,1020,1,0,0,0,1020, + 1021,6,90,11,0,1021,197,1,0,0,0,1022,1023,3,66,25,0,1023,1024,1,0,0,0,1024, + 1025,6,91,11,0,1025,199,1,0,0,0,1026,1027,3,68,26,0,1027,1028,1,0,0,0,1028, + 1029,6,92,11,0,1029,201,1,0,0,0,1030,1031,3,72,28,0,1031,1032,1,0,0,0,1032, + 1033,6,93,16,0,1033,1034,6,93,12,0,1034,203,1,0,0,0,1035,1036,3,176,80, + 0,1036,1037,1,0,0,0,1037,1038,6,94,14,0,1038,205,1,0,0,0,1039,1040,3,178, + 81,0,1040,1041,1,0,0,0,1041,1042,6,95,17,0,1042,207,1,0,0,0,1043,1044,3, + 110,47,0,1044,1045,1,0,0,0,1045,1046,6,96,18,0,1046,209,1,0,0,0,1047,1048, + 3,112,48,0,1048,1049,1,0,0,0,1049,1050,6,97,19,0,1050,211,1,0,0,0,1051, + 1052,3,106,45,0,1052,1053,1,0,0,0,1053,1054,6,98,20,0,1054,213,1,0,0,0, + 1055,1056,7,16,0,0,1056,1057,7,3,0,0,1057,1058,7,5,0,0,1058,1059,7,12,0, + 0,1059,1060,7,0,0,0,1060,1061,7,12,0,0,1061,1062,7,5,0,0,1062,1063,7,12, + 0,0,1063,215,1,0,0,0,1064,1068,8,33,0,0,1065,1066,5,47,0,0,1066,1068,8, + 34,0,0,1067,1064,1,0,0,0,1067,1065,1,0,0,0,1068,217,1,0,0,0,1069,1071,3, + 216,100,0,1070,1069,1,0,0,0,1071,1072,1,0,0,0,1072,1070,1,0,0,0,1072,1073, + 1,0,0,0,1073,219,1,0,0,0,1074,1075,3,218,101,0,1075,1076,1,0,0,0,1076,1077, + 6,102,21,0,1077,221,1,0,0,0,1078,1079,3,94,39,0,1079,1080,1,0,0,0,1080, + 1081,6,103,22,0,1081,223,1,0,0,0,1082,1083,3,66,25,0,1083,1084,1,0,0,0, + 1084,1085,6,104,11,0,1085,225,1,0,0,0,1086,1087,3,68,26,0,1087,1088,1,0, + 0,0,1088,1089,6,105,11,0,1089,227,1,0,0,0,1090,1091,3,70,27,0,1091,1092, + 1,0,0,0,1092,1093,6,106,11,0,1093,229,1,0,0,0,1094,1095,3,72,28,0,1095, + 1096,1,0,0,0,1096,1097,6,107,16,0,1097,1098,6,107,12,0,1098,231,1,0,0,0, + 1099,1100,3,116,50,0,1100,1101,1,0,0,0,1101,1102,6,108,23,0,1102,233,1, + 0,0,0,1103,1104,3,112,48,0,1104,1105,1,0,0,0,1105,1106,6,109,19,0,1106, + 235,1,0,0,0,1107,1108,4,110,8,0,1108,1109,3,140,62,0,1109,1110,1,0,0,0, + 1110,1111,6,110,24,0,1111,237,1,0,0,0,1112,1113,4,111,9,0,1113,1114,3,174, + 79,0,1114,1115,1,0,0,0,1115,1116,6,111,25,0,1116,239,1,0,0,0,1117,1122, + 3,76,30,0,1118,1122,3,74,29,0,1119,1122,3,90,37,0,1120,1122,3,166,75,0, + 1121,1117,1,0,0,0,1121,1118,1,0,0,0,1121,1119,1,0,0,0,1121,1120,1,0,0,0, + 1122,241,1,0,0,0,1123,1126,3,76,30,0,1124,1126,3,166,75,0,1125,1123,1,0, + 0,0,1125,1124,1,0,0,0,1126,1130,1,0,0,0,1127,1129,3,240,112,0,1128,1127, + 1,0,0,0,1129,1132,1,0,0,0,1130,1128,1,0,0,0,1130,1131,1,0,0,0,1131,1143, + 1,0,0,0,1132,1130,1,0,0,0,1133,1136,3,90,37,0,1134,1136,3,84,34,0,1135, + 1133,1,0,0,0,1135,1134,1,0,0,0,1136,1138,1,0,0,0,1137,1139,3,240,112,0, + 1138,1137,1,0,0,0,1139,1140,1,0,0,0,1140,1138,1,0,0,0,1140,1141,1,0,0,0, + 1141,1143,1,0,0,0,1142,1125,1,0,0,0,1142,1135,1,0,0,0,1143,243,1,0,0,0, + 1144,1147,3,242,113,0,1145,1147,3,182,83,0,1146,1144,1,0,0,0,1146,1145, + 1,0,0,0,1147,1148,1,0,0,0,1148,1146,1,0,0,0,1148,1149,1,0,0,0,1149,245, + 1,0,0,0,1150,1151,3,66,25,0,1151,1152,1,0,0,0,1152,1153,6,115,11,0,1153, + 247,1,0,0,0,1154,1155,3,68,26,0,1155,1156,1,0,0,0,1156,1157,6,116,11,0, + 1157,249,1,0,0,0,1158,1159,3,70,27,0,1159,1160,1,0,0,0,1160,1161,6,117, + 11,0,1161,251,1,0,0,0,1162,1163,3,72,28,0,1163,1164,1,0,0,0,1164,1165,6, + 118,16,0,1165,1166,6,118,12,0,1166,253,1,0,0,0,1167,1168,3,106,45,0,1168, + 1169,1,0,0,0,1169,1170,6,119,20,0,1170,255,1,0,0,0,1171,1172,3,112,48,0, + 1172,1173,1,0,0,0,1173,1174,6,120,19,0,1174,257,1,0,0,0,1175,1176,3,116, + 50,0,1176,1177,1,0,0,0,1177,1178,6,121,23,0,1178,259,1,0,0,0,1179,1180, + 4,122,10,0,1180,1181,3,140,62,0,1181,1182,1,0,0,0,1182,1183,6,122,24,0, + 1183,261,1,0,0,0,1184,1185,4,123,11,0,1185,1186,3,174,79,0,1186,1187,1, + 0,0,0,1187,1188,6,123,25,0,1188,263,1,0,0,0,1189,1190,7,12,0,0,1190,1191, + 7,2,0,0,1191,265,1,0,0,0,1192,1193,3,244,114,0,1193,1194,1,0,0,0,1194,1195, + 6,125,26,0,1195,267,1,0,0,0,1196,1197,3,66,25,0,1197,1198,1,0,0,0,1198, + 1199,6,126,11,0,1199,269,1,0,0,0,1200,1201,3,68,26,0,1201,1202,1,0,0,0, + 1202,1203,6,127,11,0,1203,271,1,0,0,0,1204,1205,3,70,27,0,1205,1206,1,0, + 0,0,1206,1207,6,128,11,0,1207,273,1,0,0,0,1208,1209,3,72,28,0,1209,1210, + 1,0,0,0,1210,1211,6,129,16,0,1211,1212,6,129,12,0,1212,275,1,0,0,0,1213, + 1214,3,176,80,0,1214,1215,1,0,0,0,1215,1216,6,130,14,0,1216,1217,6,130, + 27,0,1217,277,1,0,0,0,1218,1219,7,7,0,0,1219,1220,7,9,0,0,1220,1221,1,0, + 0,0,1221,1222,6,131,28,0,1222,279,1,0,0,0,1223,1224,7,19,0,0,1224,1225, + 7,1,0,0,1225,1226,7,5,0,0,1226,1227,7,10,0,0,1227,1228,1,0,0,0,1228,1229, + 6,132,28,0,1229,281,1,0,0,0,1230,1231,8,35,0,0,1231,283,1,0,0,0,1232,1234, + 3,282,133,0,1233,1232,1,0,0,0,1234,1235,1,0,0,0,1235,1233,1,0,0,0,1235, + 1236,1,0,0,0,1236,1237,1,0,0,0,1237,1238,3,110,47,0,1238,1240,1,0,0,0,1239, + 1233,1,0,0,0,1239,1240,1,0,0,0,1240,1242,1,0,0,0,1241,1243,3,282,133,0, + 1242,1241,1,0,0,0,1243,1244,1,0,0,0,1244,1242,1,0,0,0,1244,1245,1,0,0,0, + 1245,285,1,0,0,0,1246,1247,3,284,134,0,1247,1248,1,0,0,0,1248,1249,6,135, + 29,0,1249,287,1,0,0,0,1250,1251,3,66,25,0,1251,1252,1,0,0,0,1252,1253,6, + 136,11,0,1253,289,1,0,0,0,1254,1255,3,68,26,0,1255,1256,1,0,0,0,1256,1257, + 6,137,11,0,1257,291,1,0,0,0,1258,1259,3,70,27,0,1259,1260,1,0,0,0,1260, + 1261,6,138,11,0,1261,293,1,0,0,0,1262,1263,3,72,28,0,1263,1264,1,0,0,0, + 1264,1265,6,139,16,0,1265,1266,6,139,12,0,1266,1267,6,139,12,0,1267,295, + 1,0,0,0,1268,1269,3,106,45,0,1269,1270,1,0,0,0,1270,1271,6,140,20,0,1271, + 297,1,0,0,0,1272,1273,3,112,48,0,1273,1274,1,0,0,0,1274,1275,6,141,19,0, + 1275,299,1,0,0,0,1276,1277,3,116,50,0,1277,1278,1,0,0,0,1278,1279,6,142, + 23,0,1279,301,1,0,0,0,1280,1281,3,280,132,0,1281,1282,1,0,0,0,1282,1283, + 6,143,30,0,1283,303,1,0,0,0,1284,1285,3,244,114,0,1285,1286,1,0,0,0,1286, + 1287,6,144,26,0,1287,305,1,0,0,0,1288,1289,3,184,84,0,1289,1290,1,0,0,0, + 1290,1291,6,145,31,0,1291,307,1,0,0,0,1292,1293,4,146,12,0,1293,1294,3, + 140,62,0,1294,1295,1,0,0,0,1295,1296,6,146,24,0,1296,309,1,0,0,0,1297,1298, + 4,147,13,0,1298,1299,3,174,79,0,1299,1300,1,0,0,0,1300,1301,6,147,25,0, + 1301,311,1,0,0,0,1302,1303,3,66,25,0,1303,1304,1,0,0,0,1304,1305,6,148, + 11,0,1305,313,1,0,0,0,1306,1307,3,68,26,0,1307,1308,1,0,0,0,1308,1309,6, + 149,11,0,1309,315,1,0,0,0,1310,1311,3,70,27,0,1311,1312,1,0,0,0,1312,1313, + 6,150,11,0,1313,317,1,0,0,0,1314,1315,3,72,28,0,1315,1316,1,0,0,0,1316, + 1317,6,151,16,0,1317,1318,6,151,12,0,1318,319,1,0,0,0,1319,1320,3,116,50, + 0,1320,1321,1,0,0,0,1321,1322,6,152,23,0,1322,321,1,0,0,0,1323,1324,4,153, + 14,0,1324,1325,3,140,62,0,1325,1326,1,0,0,0,1326,1327,6,153,24,0,1327,323, + 1,0,0,0,1328,1329,4,154,15,0,1329,1330,3,174,79,0,1330,1331,1,0,0,0,1331, + 1332,6,154,25,0,1332,325,1,0,0,0,1333,1334,3,184,84,0,1334,1335,1,0,0,0, + 1335,1336,6,155,31,0,1336,327,1,0,0,0,1337,1338,3,180,82,0,1338,1339,1, + 0,0,0,1339,1340,6,156,32,0,1340,329,1,0,0,0,1341,1342,3,66,25,0,1342,1343, + 1,0,0,0,1343,1344,6,157,11,0,1344,331,1,0,0,0,1345,1346,3,68,26,0,1346, + 1347,1,0,0,0,1347,1348,6,158,11,0,1348,333,1,0,0,0,1349,1350,3,70,27,0, + 1350,1351,1,0,0,0,1351,1352,6,159,11,0,1352,335,1,0,0,0,1353,1354,3,72, + 28,0,1354,1355,1,0,0,0,1355,1356,6,160,16,0,1356,1357,6,160,12,0,1357,337, + 1,0,0,0,1358,1359,7,1,0,0,1359,1360,7,9,0,0,1360,1361,7,15,0,0,1361,1362, + 7,7,0,0,1362,339,1,0,0,0,1363,1364,3,66,25,0,1364,1365,1,0,0,0,1365,1366, + 6,162,11,0,1366,341,1,0,0,0,1367,1368,3,68,26,0,1368,1369,1,0,0,0,1369, + 1370,6,163,11,0,1370,343,1,0,0,0,1371,1372,3,70,27,0,1372,1373,1,0,0,0, + 1373,1374,6,164,11,0,1374,345,1,0,0,0,1375,1376,3,178,81,0,1376,1377,1, + 0,0,0,1377,1378,6,165,17,0,1378,1379,6,165,12,0,1379,347,1,0,0,0,1380,1381, + 3,110,47,0,1381,1382,1,0,0,0,1382,1383,6,166,18,0,1383,349,1,0,0,0,1384, + 1390,3,84,34,0,1385,1390,3,74,29,0,1386,1390,3,116,50,0,1387,1390,3,76, + 30,0,1388,1390,3,90,37,0,1389,1384,1,0,0,0,1389,1385,1,0,0,0,1389,1386, + 1,0,0,0,1389,1387,1,0,0,0,1389,1388,1,0,0,0,1390,1391,1,0,0,0,1391,1389, + 1,0,0,0,1391,1392,1,0,0,0,1392,351,1,0,0,0,1393,1394,3,66,25,0,1394,1395, + 1,0,0,0,1395,1396,6,168,11,0,1396,353,1,0,0,0,1397,1398,3,68,26,0,1398, + 1399,1,0,0,0,1399,1400,6,169,11,0,1400,355,1,0,0,0,1401,1402,3,70,27,0, + 1402,1403,1,0,0,0,1403,1404,6,170,11,0,1404,357,1,0,0,0,1405,1406,3,72, + 28,0,1406,1407,1,0,0,0,1407,1408,6,171,16,0,1408,1409,6,171,12,0,1409,359, + 1,0,0,0,1410,1411,3,110,47,0,1411,1412,1,0,0,0,1412,1413,6,172,18,0,1413, + 361,1,0,0,0,1414,1415,3,112,48,0,1415,1416,1,0,0,0,1416,1417,6,173,19,0, + 1417,363,1,0,0,0,1418,1419,3,116,50,0,1419,1420,1,0,0,0,1420,1421,6,174, + 23,0,1421,365,1,0,0,0,1422,1423,3,278,131,0,1423,1424,1,0,0,0,1424,1425, + 6,175,33,0,1425,1426,6,175,34,0,1426,367,1,0,0,0,1427,1428,3,218,101,0, + 1428,1429,1,0,0,0,1429,1430,6,176,21,0,1430,369,1,0,0,0,1431,1432,3,94, + 39,0,1432,1433,1,0,0,0,1433,1434,6,177,22,0,1434,371,1,0,0,0,1435,1436, + 3,66,25,0,1436,1437,1,0,0,0,1437,1438,6,178,11,0,1438,373,1,0,0,0,1439, + 1440,3,68,26,0,1440,1441,1,0,0,0,1441,1442,6,179,11,0,1442,375,1,0,0,0, + 1443,1444,3,70,27,0,1444,1445,1,0,0,0,1445,1446,6,180,11,0,1446,377,1,0, + 0,0,1447,1448,3,72,28,0,1448,1449,1,0,0,0,1449,1450,6,181,16,0,1450,1451, + 6,181,12,0,1451,1452,6,181,12,0,1452,379,1,0,0,0,1453,1454,3,112,48,0,1454, + 1455,1,0,0,0,1455,1456,6,182,19,0,1456,381,1,0,0,0,1457,1458,3,116,50,0, + 1458,1459,1,0,0,0,1459,1460,6,183,23,0,1460,383,1,0,0,0,1461,1462,3,244, + 114,0,1462,1463,1,0,0,0,1463,1464,6,184,26,0,1464,385,1,0,0,0,1465,1466, + 3,66,25,0,1466,1467,1,0,0,0,1467,1468,6,185,11,0,1468,387,1,0,0,0,1469, + 1470,3,68,26,0,1470,1471,1,0,0,0,1471,1472,6,186,11,0,1472,389,1,0,0,0, + 1473,1474,3,70,27,0,1474,1475,1,0,0,0,1475,1476,6,187,11,0,1476,391,1,0, + 0,0,1477,1478,3,72,28,0,1478,1479,1,0,0,0,1479,1480,6,188,16,0,1480,1481, + 6,188,12,0,1481,393,1,0,0,0,1482,1483,3,54,19,0,1483,1484,1,0,0,0,1484, + 1485,6,189,35,0,1485,395,1,0,0,0,1486,1487,3,264,124,0,1487,1488,1,0,0, + 0,1488,1489,6,190,36,0,1489,397,1,0,0,0,1490,1491,3,278,131,0,1491,1492, + 1,0,0,0,1492,1493,6,191,33,0,1493,1494,6,191,12,0,1494,1495,6,191,0,0,1495, + 399,1,0,0,0,1496,1497,7,20,0,0,1497,1498,7,2,0,0,1498,1499,7,1,0,0,1499, + 1500,7,9,0,0,1500,1501,7,17,0,0,1501,1502,1,0,0,0,1502,1503,6,192,12,0, + 1503,1504,6,192,0,0,1504,401,1,0,0,0,1505,1506,3,180,82,0,1506,1507,1,0, + 0,0,1507,1508,6,193,32,0,1508,403,1,0,0,0,1509,1510,3,184,84,0,1510,1511, + 1,0,0,0,1511,1512,6,194,31,0,1512,405,1,0,0,0,1513,1514,3,66,25,0,1514, + 1515,1,0,0,0,1515,1516,6,195,11,0,1516,407,1,0,0,0,1517,1518,3,68,26,0, + 1518,1519,1,0,0,0,1519,1520,6,196,11,0,1520,409,1,0,0,0,1521,1522,3,70, + 27,0,1522,1523,1,0,0,0,1523,1524,6,197,11,0,1524,411,1,0,0,0,1525,1526, + 3,72,28,0,1526,1527,1,0,0,0,1527,1528,6,198,16,0,1528,1529,6,198,12,0,1529, + 413,1,0,0,0,1530,1531,3,218,101,0,1531,1532,1,0,0,0,1532,1533,6,199,21, + 0,1533,1534,6,199,12,0,1534,1535,6,199,37,0,1535,415,1,0,0,0,1536,1537, + 3,94,39,0,1537,1538,1,0,0,0,1538,1539,6,200,22,0,1539,1540,6,200,12,0,1540, + 1541,6,200,37,0,1541,417,1,0,0,0,1542,1543,3,66,25,0,1543,1544,1,0,0,0, + 1544,1545,6,201,11,0,1545,419,1,0,0,0,1546,1547,3,68,26,0,1547,1548,1,0, + 0,0,1548,1549,6,202,11,0,1549,421,1,0,0,0,1550,1551,3,70,27,0,1551,1552, + 1,0,0,0,1552,1553,6,203,11,0,1553,423,1,0,0,0,1554,1555,3,110,47,0,1555, + 1556,1,0,0,0,1556,1557,6,204,18,0,1557,1558,6,204,12,0,1558,1559,6,204, + 9,0,1559,425,1,0,0,0,1560,1561,3,112,48,0,1561,1562,1,0,0,0,1562,1563,6, + 205,19,0,1563,1564,6,205,12,0,1564,1565,6,205,9,0,1565,427,1,0,0,0,1566, + 1567,3,66,25,0,1567,1568,1,0,0,0,1568,1569,6,206,11,0,1569,429,1,0,0,0, + 1570,1571,3,68,26,0,1571,1572,1,0,0,0,1572,1573,6,207,11,0,1573,431,1,0, + 0,0,1574,1575,3,70,27,0,1575,1576,1,0,0,0,1576,1577,6,208,11,0,1577,433, + 1,0,0,0,1578,1579,3,184,84,0,1579,1580,1,0,0,0,1580,1581,6,209,12,0,1581, + 1582,6,209,0,0,1582,1583,6,209,31,0,1583,435,1,0,0,0,1584,1585,3,180,82, + 0,1585,1586,1,0,0,0,1586,1587,6,210,12,0,1587,1588,6,210,0,0,1588,1589, + 6,210,32,0,1589,437,1,0,0,0,1590,1591,3,100,42,0,1591,1592,1,0,0,0,1592, + 1593,6,211,12,0,1593,1594,6,211,0,0,1594,1595,6,211,38,0,1595,439,1,0,0, + 0,1596,1597,3,72,28,0,1597,1598,1,0,0,0,1598,1599,6,212,16,0,1599,1600, + 6,212,12,0,1600,441,1,0,0,0,66,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,655, + 665,669,672,681,683,694,713,718,727,734,739,741,752,760,763,765,770,775, + 781,788,793,799,802,810,814,942,947,954,956,972,977,982,984,990,1067,1072, + 1121,1125,1130,1135,1140,1142,1146,1148,1235,1239,1244,1389,1391,39,5,1, + 0,5,4,0,5,6,0,5,2,0,5,3,0,5,8,0,5,5,0,5,9,0,5,11,0,5,14,0,5,13,0,0,1,0, + 4,0,0,7,16,0,7,70,0,5,0,0,7,29,0,7,71,0,7,38,0,7,39,0,7,36,0,7,81,0,7,30, + 0,7,41,0,7,53,0,7,69,0,7,85,0,5,10,0,5,7,0,7,95,0,7,94,0,7,73,0,7,72,0, + 7,93,0,5,12,0,7,20,0,7,89,0,5,15,0,7,33,0]; private static __ATN: ATN; public static get _ATN(): ATN { diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.g4 b/packages/kbn-esql-ast/src/antlr/esql_parser.g4 index 6a76e32d28f3..0857f14f9d0f 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.g4 +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.g4 @@ -56,6 +56,7 @@ processingCommand // in development | {this.isDevVersion()}? inlinestatsCommand | {this.isDevVersion()}? lookupCommand + | {this.isDevVersion()}? joinCommand ; whereCommand @@ -70,7 +71,7 @@ booleanExpression | left=booleanExpression operator=OR right=booleanExpression #logicalBinary | valueExpression (NOT)? IN LP valueExpression (COMMA valueExpression)* RP #logicalIn | valueExpression IS NOT? NULL #isNull - | {this.isDevVersion()}? matchBooleanExpression #matchExpression + | matchBooleanExpression #matchExpression ; regexBooleanExpression @@ -323,4 +324,20 @@ lookupCommand inlinestatsCommand : DEV_INLINESTATS stats=aggFields (BY grouping=fields)? + ; + +joinCommand + : type=(DEV_JOIN_LOOKUP | DEV_JOIN_LEFT | DEV_JOIN_RIGHT)? DEV_JOIN joinTarget joinCondition + ; + +joinTarget + : index=identifier (AS alias=identifier)? + ; + +joinCondition + : ON joinPredicate (COMMA joinPredicate)* + ; + +joinPredicate + : valueExpression ; \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.interp b/packages/kbn-esql-ast/src/antlr/esql_parser.interp index a2b339f378f1..50493f584fe4 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.interp +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.interp @@ -23,7 +23,11 @@ null null null null -':' +null +null +null +null +null '|' null null @@ -33,6 +37,7 @@ null 'asc' '=' '::' +':' ',' 'desc' '.' @@ -113,6 +118,10 @@ null null null null +'USING' +null +null +null null null null @@ -141,11 +150,15 @@ WHERE DEV_INLINESTATS DEV_LOOKUP DEV_METRICS +DEV_JOIN +DEV_JOIN_FULL +DEV_JOIN_LEFT +DEV_JOIN_RIGHT +DEV_JOIN_LOOKUP UNKNOWN_CMD LINE_COMMENT MULTILINE_COMMENT WS -COLON PIPE QUOTED_STRING INTEGER_LITERAL @@ -155,6 +168,7 @@ AND ASC ASSIGN CAST_OP +COLON COMMA DESC DOT @@ -235,6 +249,10 @@ LOOKUP_WS LOOKUP_FIELD_LINE_COMMENT LOOKUP_FIELD_MULTILINE_COMMENT LOOKUP_FIELD_WS +USING +JOIN_LINE_COMMENT +JOIN_MULTILINE_COMMENT +JOIN_WS METRICS_LINE_COMMENT METRICS_MULTILINE_COMMENT METRICS_WS @@ -305,7 +323,11 @@ enrichCommand enrichWithClause lookupCommand inlinestatsCommand +joinCommand +joinTarget +joinCondition +joinPredicate atn: -[4, 1, 119, 603, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 134, 8, 1, 10, 1, 12, 1, 137, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 145, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 163, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 175, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 182, 8, 5, 10, 5, 12, 5, 185, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 192, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 198, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 206, 8, 5, 10, 5, 12, 5, 209, 9, 5, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 220, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 225, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 236, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 242, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 250, 8, 9, 10, 9, 12, 9, 253, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 263, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 268, 8, 10, 10, 10, 12, 10, 271, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 279, 8, 11, 10, 11, 12, 11, 282, 9, 11, 3, 11, 284, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 298, 8, 15, 10, 15, 12, 15, 301, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 306, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 314, 8, 17, 10, 17, 12, 17, 317, 9, 17, 1, 17, 3, 17, 320, 8, 17, 1, 18, 1, 18, 1, 18, 3, 18, 325, 8, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 3, 21, 335, 8, 21, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 341, 8, 22, 10, 22, 12, 22, 344, 9, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 354, 8, 24, 10, 24, 12, 24, 357, 9, 24, 1, 24, 3, 24, 360, 8, 24, 1, 24, 1, 24, 3, 24, 364, 8, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 3, 26, 371, 8, 26, 1, 26, 1, 26, 3, 26, 375, 8, 26, 1, 27, 1, 27, 1, 27, 5, 27, 380, 8, 27, 10, 27, 12, 27, 383, 9, 27, 1, 28, 1, 28, 1, 28, 3, 28, 388, 8, 28, 1, 29, 1, 29, 1, 29, 5, 29, 393, 8, 29, 10, 29, 12, 29, 396, 9, 29, 1, 30, 1, 30, 1, 30, 5, 30, 401, 8, 30, 10, 30, 12, 30, 404, 9, 30, 1, 31, 1, 31, 1, 31, 5, 31, 409, 8, 31, 10, 31, 12, 31, 412, 9, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 3, 33, 419, 8, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 434, 8, 34, 10, 34, 12, 34, 437, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 445, 8, 34, 10, 34, 12, 34, 448, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 456, 8, 34, 10, 34, 12, 34, 459, 9, 34, 1, 34, 1, 34, 3, 34, 463, 8, 34, 1, 35, 1, 35, 3, 35, 467, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 472, 8, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 5, 38, 481, 8, 38, 10, 38, 12, 38, 484, 9, 38, 1, 39, 1, 39, 3, 39, 488, 8, 39, 1, 39, 1, 39, 3, 39, 492, 8, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 504, 8, 42, 10, 42, 12, 42, 507, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 517, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 5, 47, 529, 8, 47, 10, 47, 12, 47, 532, 9, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, 3, 50, 542, 8, 50, 1, 51, 3, 51, 545, 8, 51, 1, 51, 1, 51, 1, 52, 3, 52, 550, 8, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 572, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 5, 58, 578, 8, 58, 10, 58, 12, 58, 581, 9, 58, 3, 58, 583, 8, 58, 1, 59, 1, 59, 1, 59, 3, 59, 588, 8, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 601, 8, 61, 1, 61, 0, 4, 2, 10, 18, 20, 62, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 0, 8, 1, 0, 59, 60, 1, 0, 61, 63, 2, 0, 26, 26, 76, 76, 1, 0, 67, 68, 2, 0, 31, 31, 35, 35, 2, 0, 38, 38, 41, 41, 2, 0, 37, 37, 51, 51, 2, 0, 52, 52, 54, 58, 628, 0, 124, 1, 0, 0, 0, 2, 127, 1, 0, 0, 0, 4, 144, 1, 0, 0, 0, 6, 162, 1, 0, 0, 0, 8, 164, 1, 0, 0, 0, 10, 197, 1, 0, 0, 0, 12, 224, 1, 0, 0, 0, 14, 226, 1, 0, 0, 0, 16, 235, 1, 0, 0, 0, 18, 241, 1, 0, 0, 0, 20, 262, 1, 0, 0, 0, 22, 272, 1, 0, 0, 0, 24, 287, 1, 0, 0, 0, 26, 289, 1, 0, 0, 0, 28, 291, 1, 0, 0, 0, 30, 294, 1, 0, 0, 0, 32, 305, 1, 0, 0, 0, 34, 309, 1, 0, 0, 0, 36, 324, 1, 0, 0, 0, 38, 328, 1, 0, 0, 0, 40, 330, 1, 0, 0, 0, 42, 334, 1, 0, 0, 0, 44, 336, 1, 0, 0, 0, 46, 345, 1, 0, 0, 0, 48, 349, 1, 0, 0, 0, 50, 365, 1, 0, 0, 0, 52, 368, 1, 0, 0, 0, 54, 376, 1, 0, 0, 0, 56, 384, 1, 0, 0, 0, 58, 389, 1, 0, 0, 0, 60, 397, 1, 0, 0, 0, 62, 405, 1, 0, 0, 0, 64, 413, 1, 0, 0, 0, 66, 418, 1, 0, 0, 0, 68, 462, 1, 0, 0, 0, 70, 466, 1, 0, 0, 0, 72, 471, 1, 0, 0, 0, 74, 473, 1, 0, 0, 0, 76, 476, 1, 0, 0, 0, 78, 485, 1, 0, 0, 0, 80, 493, 1, 0, 0, 0, 82, 496, 1, 0, 0, 0, 84, 499, 1, 0, 0, 0, 86, 508, 1, 0, 0, 0, 88, 512, 1, 0, 0, 0, 90, 518, 1, 0, 0, 0, 92, 522, 1, 0, 0, 0, 94, 525, 1, 0, 0, 0, 96, 533, 1, 0, 0, 0, 98, 537, 1, 0, 0, 0, 100, 541, 1, 0, 0, 0, 102, 544, 1, 0, 0, 0, 104, 549, 1, 0, 0, 0, 106, 553, 1, 0, 0, 0, 108, 555, 1, 0, 0, 0, 110, 557, 1, 0, 0, 0, 112, 560, 1, 0, 0, 0, 114, 564, 1, 0, 0, 0, 116, 567, 1, 0, 0, 0, 118, 587, 1, 0, 0, 0, 120, 591, 1, 0, 0, 0, 122, 596, 1, 0, 0, 0, 124, 125, 3, 2, 1, 0, 125, 126, 5, 0, 0, 1, 126, 1, 1, 0, 0, 0, 127, 128, 6, 1, -1, 0, 128, 129, 3, 4, 2, 0, 129, 135, 1, 0, 0, 0, 130, 131, 10, 1, 0, 0, 131, 132, 5, 25, 0, 0, 132, 134, 3, 6, 3, 0, 133, 130, 1, 0, 0, 0, 134, 137, 1, 0, 0, 0, 135, 133, 1, 0, 0, 0, 135, 136, 1, 0, 0, 0, 136, 3, 1, 0, 0, 0, 137, 135, 1, 0, 0, 0, 138, 145, 3, 110, 55, 0, 139, 145, 3, 34, 17, 0, 140, 145, 3, 28, 14, 0, 141, 145, 3, 114, 57, 0, 142, 143, 4, 2, 1, 0, 143, 145, 3, 48, 24, 0, 144, 138, 1, 0, 0, 0, 144, 139, 1, 0, 0, 0, 144, 140, 1, 0, 0, 0, 144, 141, 1, 0, 0, 0, 144, 142, 1, 0, 0, 0, 145, 5, 1, 0, 0, 0, 146, 163, 3, 50, 25, 0, 147, 163, 3, 8, 4, 0, 148, 163, 3, 80, 40, 0, 149, 163, 3, 74, 37, 0, 150, 163, 3, 52, 26, 0, 151, 163, 3, 76, 38, 0, 152, 163, 3, 82, 41, 0, 153, 163, 3, 84, 42, 0, 154, 163, 3, 88, 44, 0, 155, 163, 3, 90, 45, 0, 156, 163, 3, 116, 58, 0, 157, 163, 3, 92, 46, 0, 158, 159, 4, 3, 2, 0, 159, 163, 3, 122, 61, 0, 160, 161, 4, 3, 3, 0, 161, 163, 3, 120, 60, 0, 162, 146, 1, 0, 0, 0, 162, 147, 1, 0, 0, 0, 162, 148, 1, 0, 0, 0, 162, 149, 1, 0, 0, 0, 162, 150, 1, 0, 0, 0, 162, 151, 1, 0, 0, 0, 162, 152, 1, 0, 0, 0, 162, 153, 1, 0, 0, 0, 162, 154, 1, 0, 0, 0, 162, 155, 1, 0, 0, 0, 162, 156, 1, 0, 0, 0, 162, 157, 1, 0, 0, 0, 162, 158, 1, 0, 0, 0, 162, 160, 1, 0, 0, 0, 163, 7, 1, 0, 0, 0, 164, 165, 5, 16, 0, 0, 165, 166, 3, 10, 5, 0, 166, 9, 1, 0, 0, 0, 167, 168, 6, 5, -1, 0, 168, 169, 5, 44, 0, 0, 169, 198, 3, 10, 5, 8, 170, 198, 3, 16, 8, 0, 171, 198, 3, 12, 6, 0, 172, 174, 3, 16, 8, 0, 173, 175, 5, 44, 0, 0, 174, 173, 1, 0, 0, 0, 174, 175, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 177, 5, 39, 0, 0, 177, 178, 5, 43, 0, 0, 178, 183, 3, 16, 8, 0, 179, 180, 5, 34, 0, 0, 180, 182, 3, 16, 8, 0, 181, 179, 1, 0, 0, 0, 182, 185, 1, 0, 0, 0, 183, 181, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184, 186, 1, 0, 0, 0, 185, 183, 1, 0, 0, 0, 186, 187, 5, 50, 0, 0, 187, 198, 1, 0, 0, 0, 188, 189, 3, 16, 8, 0, 189, 191, 5, 40, 0, 0, 190, 192, 5, 44, 0, 0, 191, 190, 1, 0, 0, 0, 191, 192, 1, 0, 0, 0, 192, 193, 1, 0, 0, 0, 193, 194, 5, 45, 0, 0, 194, 198, 1, 0, 0, 0, 195, 196, 4, 5, 4, 0, 196, 198, 3, 14, 7, 0, 197, 167, 1, 0, 0, 0, 197, 170, 1, 0, 0, 0, 197, 171, 1, 0, 0, 0, 197, 172, 1, 0, 0, 0, 197, 188, 1, 0, 0, 0, 197, 195, 1, 0, 0, 0, 198, 207, 1, 0, 0, 0, 199, 200, 10, 5, 0, 0, 200, 201, 5, 30, 0, 0, 201, 206, 3, 10, 5, 6, 202, 203, 10, 4, 0, 0, 203, 204, 5, 47, 0, 0, 204, 206, 3, 10, 5, 5, 205, 199, 1, 0, 0, 0, 205, 202, 1, 0, 0, 0, 206, 209, 1, 0, 0, 0, 207, 205, 1, 0, 0, 0, 207, 208, 1, 0, 0, 0, 208, 11, 1, 0, 0, 0, 209, 207, 1, 0, 0, 0, 210, 212, 3, 16, 8, 0, 211, 213, 5, 44, 0, 0, 212, 211, 1, 0, 0, 0, 212, 213, 1, 0, 0, 0, 213, 214, 1, 0, 0, 0, 214, 215, 5, 42, 0, 0, 215, 216, 3, 106, 53, 0, 216, 225, 1, 0, 0, 0, 217, 219, 3, 16, 8, 0, 218, 220, 5, 44, 0, 0, 219, 218, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, 221, 1, 0, 0, 0, 221, 222, 5, 49, 0, 0, 222, 223, 3, 106, 53, 0, 223, 225, 1, 0, 0, 0, 224, 210, 1, 0, 0, 0, 224, 217, 1, 0, 0, 0, 225, 13, 1, 0, 0, 0, 226, 227, 3, 58, 29, 0, 227, 228, 5, 24, 0, 0, 228, 229, 3, 68, 34, 0, 229, 15, 1, 0, 0, 0, 230, 236, 3, 18, 9, 0, 231, 232, 3, 18, 9, 0, 232, 233, 3, 108, 54, 0, 233, 234, 3, 18, 9, 0, 234, 236, 1, 0, 0, 0, 235, 230, 1, 0, 0, 0, 235, 231, 1, 0, 0, 0, 236, 17, 1, 0, 0, 0, 237, 238, 6, 9, -1, 0, 238, 242, 3, 20, 10, 0, 239, 240, 7, 0, 0, 0, 240, 242, 3, 18, 9, 3, 241, 237, 1, 0, 0, 0, 241, 239, 1, 0, 0, 0, 242, 251, 1, 0, 0, 0, 243, 244, 10, 2, 0, 0, 244, 245, 7, 1, 0, 0, 245, 250, 3, 18, 9, 3, 246, 247, 10, 1, 0, 0, 247, 248, 7, 0, 0, 0, 248, 250, 3, 18, 9, 2, 249, 243, 1, 0, 0, 0, 249, 246, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 19, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 6, 10, -1, 0, 255, 263, 3, 68, 34, 0, 256, 263, 3, 58, 29, 0, 257, 263, 3, 22, 11, 0, 258, 259, 5, 43, 0, 0, 259, 260, 3, 10, 5, 0, 260, 261, 5, 50, 0, 0, 261, 263, 1, 0, 0, 0, 262, 254, 1, 0, 0, 0, 262, 256, 1, 0, 0, 0, 262, 257, 1, 0, 0, 0, 262, 258, 1, 0, 0, 0, 263, 269, 1, 0, 0, 0, 264, 265, 10, 1, 0, 0, 265, 266, 5, 33, 0, 0, 266, 268, 3, 26, 13, 0, 267, 264, 1, 0, 0, 0, 268, 271, 1, 0, 0, 0, 269, 267, 1, 0, 0, 0, 269, 270, 1, 0, 0, 0, 270, 21, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 272, 273, 3, 24, 12, 0, 273, 283, 5, 43, 0, 0, 274, 284, 5, 61, 0, 0, 275, 280, 3, 10, 5, 0, 276, 277, 5, 34, 0, 0, 277, 279, 3, 10, 5, 0, 278, 276, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 284, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 274, 1, 0, 0, 0, 283, 275, 1, 0, 0, 0, 283, 284, 1, 0, 0, 0, 284, 285, 1, 0, 0, 0, 285, 286, 5, 50, 0, 0, 286, 23, 1, 0, 0, 0, 287, 288, 3, 72, 36, 0, 288, 25, 1, 0, 0, 0, 289, 290, 3, 64, 32, 0, 290, 27, 1, 0, 0, 0, 291, 292, 5, 12, 0, 0, 292, 293, 3, 30, 15, 0, 293, 29, 1, 0, 0, 0, 294, 299, 3, 32, 16, 0, 295, 296, 5, 34, 0, 0, 296, 298, 3, 32, 16, 0, 297, 295, 1, 0, 0, 0, 298, 301, 1, 0, 0, 0, 299, 297, 1, 0, 0, 0, 299, 300, 1, 0, 0, 0, 300, 31, 1, 0, 0, 0, 301, 299, 1, 0, 0, 0, 302, 303, 3, 58, 29, 0, 303, 304, 5, 32, 0, 0, 304, 306, 1, 0, 0, 0, 305, 302, 1, 0, 0, 0, 305, 306, 1, 0, 0, 0, 306, 307, 1, 0, 0, 0, 307, 308, 3, 10, 5, 0, 308, 33, 1, 0, 0, 0, 309, 310, 5, 6, 0, 0, 310, 315, 3, 36, 18, 0, 311, 312, 5, 34, 0, 0, 312, 314, 3, 36, 18, 0, 313, 311, 1, 0, 0, 0, 314, 317, 1, 0, 0, 0, 315, 313, 1, 0, 0, 0, 315, 316, 1, 0, 0, 0, 316, 319, 1, 0, 0, 0, 317, 315, 1, 0, 0, 0, 318, 320, 3, 42, 21, 0, 319, 318, 1, 0, 0, 0, 319, 320, 1, 0, 0, 0, 320, 35, 1, 0, 0, 0, 321, 322, 3, 38, 19, 0, 322, 323, 5, 24, 0, 0, 323, 325, 1, 0, 0, 0, 324, 321, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 326, 1, 0, 0, 0, 326, 327, 3, 40, 20, 0, 327, 37, 1, 0, 0, 0, 328, 329, 5, 76, 0, 0, 329, 39, 1, 0, 0, 0, 330, 331, 7, 2, 0, 0, 331, 41, 1, 0, 0, 0, 332, 335, 3, 44, 22, 0, 333, 335, 3, 46, 23, 0, 334, 332, 1, 0, 0, 0, 334, 333, 1, 0, 0, 0, 335, 43, 1, 0, 0, 0, 336, 337, 5, 75, 0, 0, 337, 342, 5, 76, 0, 0, 338, 339, 5, 34, 0, 0, 339, 341, 5, 76, 0, 0, 340, 338, 1, 0, 0, 0, 341, 344, 1, 0, 0, 0, 342, 340, 1, 0, 0, 0, 342, 343, 1, 0, 0, 0, 343, 45, 1, 0, 0, 0, 344, 342, 1, 0, 0, 0, 345, 346, 5, 65, 0, 0, 346, 347, 3, 44, 22, 0, 347, 348, 5, 66, 0, 0, 348, 47, 1, 0, 0, 0, 349, 350, 5, 19, 0, 0, 350, 355, 3, 36, 18, 0, 351, 352, 5, 34, 0, 0, 352, 354, 3, 36, 18, 0, 353, 351, 1, 0, 0, 0, 354, 357, 1, 0, 0, 0, 355, 353, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 359, 1, 0, 0, 0, 357, 355, 1, 0, 0, 0, 358, 360, 3, 54, 27, 0, 359, 358, 1, 0, 0, 0, 359, 360, 1, 0, 0, 0, 360, 363, 1, 0, 0, 0, 361, 362, 5, 29, 0, 0, 362, 364, 3, 30, 15, 0, 363, 361, 1, 0, 0, 0, 363, 364, 1, 0, 0, 0, 364, 49, 1, 0, 0, 0, 365, 366, 5, 4, 0, 0, 366, 367, 3, 30, 15, 0, 367, 51, 1, 0, 0, 0, 368, 370, 5, 15, 0, 0, 369, 371, 3, 54, 27, 0, 370, 369, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 371, 374, 1, 0, 0, 0, 372, 373, 5, 29, 0, 0, 373, 375, 3, 30, 15, 0, 374, 372, 1, 0, 0, 0, 374, 375, 1, 0, 0, 0, 375, 53, 1, 0, 0, 0, 376, 381, 3, 56, 28, 0, 377, 378, 5, 34, 0, 0, 378, 380, 3, 56, 28, 0, 379, 377, 1, 0, 0, 0, 380, 383, 1, 0, 0, 0, 381, 379, 1, 0, 0, 0, 381, 382, 1, 0, 0, 0, 382, 55, 1, 0, 0, 0, 383, 381, 1, 0, 0, 0, 384, 387, 3, 32, 16, 0, 385, 386, 5, 16, 0, 0, 386, 388, 3, 10, 5, 0, 387, 385, 1, 0, 0, 0, 387, 388, 1, 0, 0, 0, 388, 57, 1, 0, 0, 0, 389, 394, 3, 72, 36, 0, 390, 391, 5, 36, 0, 0, 391, 393, 3, 72, 36, 0, 392, 390, 1, 0, 0, 0, 393, 396, 1, 0, 0, 0, 394, 392, 1, 0, 0, 0, 394, 395, 1, 0, 0, 0, 395, 59, 1, 0, 0, 0, 396, 394, 1, 0, 0, 0, 397, 402, 3, 66, 33, 0, 398, 399, 5, 36, 0, 0, 399, 401, 3, 66, 33, 0, 400, 398, 1, 0, 0, 0, 401, 404, 1, 0, 0, 0, 402, 400, 1, 0, 0, 0, 402, 403, 1, 0, 0, 0, 403, 61, 1, 0, 0, 0, 404, 402, 1, 0, 0, 0, 405, 410, 3, 60, 30, 0, 406, 407, 5, 34, 0, 0, 407, 409, 3, 60, 30, 0, 408, 406, 1, 0, 0, 0, 409, 412, 1, 0, 0, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 63, 1, 0, 0, 0, 412, 410, 1, 0, 0, 0, 413, 414, 7, 3, 0, 0, 414, 65, 1, 0, 0, 0, 415, 419, 5, 80, 0, 0, 416, 417, 4, 33, 10, 0, 417, 419, 3, 70, 35, 0, 418, 415, 1, 0, 0, 0, 418, 416, 1, 0, 0, 0, 419, 67, 1, 0, 0, 0, 420, 463, 5, 45, 0, 0, 421, 422, 3, 104, 52, 0, 422, 423, 5, 67, 0, 0, 423, 463, 1, 0, 0, 0, 424, 463, 3, 102, 51, 0, 425, 463, 3, 104, 52, 0, 426, 463, 3, 98, 49, 0, 427, 463, 3, 70, 35, 0, 428, 463, 3, 106, 53, 0, 429, 430, 5, 65, 0, 0, 430, 435, 3, 100, 50, 0, 431, 432, 5, 34, 0, 0, 432, 434, 3, 100, 50, 0, 433, 431, 1, 0, 0, 0, 434, 437, 1, 0, 0, 0, 435, 433, 1, 0, 0, 0, 435, 436, 1, 0, 0, 0, 436, 438, 1, 0, 0, 0, 437, 435, 1, 0, 0, 0, 438, 439, 5, 66, 0, 0, 439, 463, 1, 0, 0, 0, 440, 441, 5, 65, 0, 0, 441, 446, 3, 98, 49, 0, 442, 443, 5, 34, 0, 0, 443, 445, 3, 98, 49, 0, 444, 442, 1, 0, 0, 0, 445, 448, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 449, 450, 5, 66, 0, 0, 450, 463, 1, 0, 0, 0, 451, 452, 5, 65, 0, 0, 452, 457, 3, 106, 53, 0, 453, 454, 5, 34, 0, 0, 454, 456, 3, 106, 53, 0, 455, 453, 1, 0, 0, 0, 456, 459, 1, 0, 0, 0, 457, 455, 1, 0, 0, 0, 457, 458, 1, 0, 0, 0, 458, 460, 1, 0, 0, 0, 459, 457, 1, 0, 0, 0, 460, 461, 5, 66, 0, 0, 461, 463, 1, 0, 0, 0, 462, 420, 1, 0, 0, 0, 462, 421, 1, 0, 0, 0, 462, 424, 1, 0, 0, 0, 462, 425, 1, 0, 0, 0, 462, 426, 1, 0, 0, 0, 462, 427, 1, 0, 0, 0, 462, 428, 1, 0, 0, 0, 462, 429, 1, 0, 0, 0, 462, 440, 1, 0, 0, 0, 462, 451, 1, 0, 0, 0, 463, 69, 1, 0, 0, 0, 464, 467, 5, 48, 0, 0, 465, 467, 5, 64, 0, 0, 466, 464, 1, 0, 0, 0, 466, 465, 1, 0, 0, 0, 467, 71, 1, 0, 0, 0, 468, 472, 3, 64, 32, 0, 469, 470, 4, 36, 11, 0, 470, 472, 3, 70, 35, 0, 471, 468, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 472, 73, 1, 0, 0, 0, 473, 474, 5, 9, 0, 0, 474, 475, 5, 27, 0, 0, 475, 75, 1, 0, 0, 0, 476, 477, 5, 14, 0, 0, 477, 482, 3, 78, 39, 0, 478, 479, 5, 34, 0, 0, 479, 481, 3, 78, 39, 0, 480, 478, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 480, 1, 0, 0, 0, 482, 483, 1, 0, 0, 0, 483, 77, 1, 0, 0, 0, 484, 482, 1, 0, 0, 0, 485, 487, 3, 10, 5, 0, 486, 488, 7, 4, 0, 0, 487, 486, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 491, 1, 0, 0, 0, 489, 490, 5, 46, 0, 0, 490, 492, 7, 5, 0, 0, 491, 489, 1, 0, 0, 0, 491, 492, 1, 0, 0, 0, 492, 79, 1, 0, 0, 0, 493, 494, 5, 8, 0, 0, 494, 495, 3, 62, 31, 0, 495, 81, 1, 0, 0, 0, 496, 497, 5, 2, 0, 0, 497, 498, 3, 62, 31, 0, 498, 83, 1, 0, 0, 0, 499, 500, 5, 11, 0, 0, 500, 505, 3, 86, 43, 0, 501, 502, 5, 34, 0, 0, 502, 504, 3, 86, 43, 0, 503, 501, 1, 0, 0, 0, 504, 507, 1, 0, 0, 0, 505, 503, 1, 0, 0, 0, 505, 506, 1, 0, 0, 0, 506, 85, 1, 0, 0, 0, 507, 505, 1, 0, 0, 0, 508, 509, 3, 60, 30, 0, 509, 510, 5, 84, 0, 0, 510, 511, 3, 60, 30, 0, 511, 87, 1, 0, 0, 0, 512, 513, 5, 1, 0, 0, 513, 514, 3, 20, 10, 0, 514, 516, 3, 106, 53, 0, 515, 517, 3, 94, 47, 0, 516, 515, 1, 0, 0, 0, 516, 517, 1, 0, 0, 0, 517, 89, 1, 0, 0, 0, 518, 519, 5, 7, 0, 0, 519, 520, 3, 20, 10, 0, 520, 521, 3, 106, 53, 0, 521, 91, 1, 0, 0, 0, 522, 523, 5, 10, 0, 0, 523, 524, 3, 58, 29, 0, 524, 93, 1, 0, 0, 0, 525, 530, 3, 96, 48, 0, 526, 527, 5, 34, 0, 0, 527, 529, 3, 96, 48, 0, 528, 526, 1, 0, 0, 0, 529, 532, 1, 0, 0, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 95, 1, 0, 0, 0, 532, 530, 1, 0, 0, 0, 533, 534, 3, 64, 32, 0, 534, 535, 5, 32, 0, 0, 535, 536, 3, 68, 34, 0, 536, 97, 1, 0, 0, 0, 537, 538, 7, 6, 0, 0, 538, 99, 1, 0, 0, 0, 539, 542, 3, 102, 51, 0, 540, 542, 3, 104, 52, 0, 541, 539, 1, 0, 0, 0, 541, 540, 1, 0, 0, 0, 542, 101, 1, 0, 0, 0, 543, 545, 7, 0, 0, 0, 544, 543, 1, 0, 0, 0, 544, 545, 1, 0, 0, 0, 545, 546, 1, 0, 0, 0, 546, 547, 5, 28, 0, 0, 547, 103, 1, 0, 0, 0, 548, 550, 7, 0, 0, 0, 549, 548, 1, 0, 0, 0, 549, 550, 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 5, 27, 0, 0, 552, 105, 1, 0, 0, 0, 553, 554, 5, 26, 0, 0, 554, 107, 1, 0, 0, 0, 555, 556, 7, 7, 0, 0, 556, 109, 1, 0, 0, 0, 557, 558, 5, 5, 0, 0, 558, 559, 3, 112, 56, 0, 559, 111, 1, 0, 0, 0, 560, 561, 5, 65, 0, 0, 561, 562, 3, 2, 1, 0, 562, 563, 5, 66, 0, 0, 563, 113, 1, 0, 0, 0, 564, 565, 5, 13, 0, 0, 565, 566, 5, 100, 0, 0, 566, 115, 1, 0, 0, 0, 567, 568, 5, 3, 0, 0, 568, 571, 5, 90, 0, 0, 569, 570, 5, 88, 0, 0, 570, 572, 3, 60, 30, 0, 571, 569, 1, 0, 0, 0, 571, 572, 1, 0, 0, 0, 572, 582, 1, 0, 0, 0, 573, 574, 5, 89, 0, 0, 574, 579, 3, 118, 59, 0, 575, 576, 5, 34, 0, 0, 576, 578, 3, 118, 59, 0, 577, 575, 1, 0, 0, 0, 578, 581, 1, 0, 0, 0, 579, 577, 1, 0, 0, 0, 579, 580, 1, 0, 0, 0, 580, 583, 1, 0, 0, 0, 581, 579, 1, 0, 0, 0, 582, 573, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 117, 1, 0, 0, 0, 584, 585, 3, 60, 30, 0, 585, 586, 5, 32, 0, 0, 586, 588, 1, 0, 0, 0, 587, 584, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 3, 60, 30, 0, 590, 119, 1, 0, 0, 0, 591, 592, 5, 18, 0, 0, 592, 593, 3, 36, 18, 0, 593, 594, 5, 88, 0, 0, 594, 595, 3, 62, 31, 0, 595, 121, 1, 0, 0, 0, 596, 597, 5, 17, 0, 0, 597, 600, 3, 54, 27, 0, 598, 599, 5, 29, 0, 0, 599, 601, 3, 30, 15, 0, 600, 598, 1, 0, 0, 0, 600, 601, 1, 0, 0, 0, 601, 123, 1, 0, 0, 0, 58, 135, 144, 162, 174, 183, 191, 197, 205, 207, 212, 219, 224, 235, 241, 249, 251, 262, 269, 280, 283, 299, 305, 315, 319, 324, 334, 342, 355, 359, 363, 370, 374, 381, 387, 394, 402, 410, 418, 435, 446, 457, 462, 466, 471, 482, 487, 491, 505, 516, 530, 541, 544, 549, 571, 579, 582, 587, 600] \ No newline at end of file +[4, 1, 128, 635, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 142, 8, 1, 10, 1, 12, 1, 145, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 153, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 173, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 185, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 192, 8, 5, 10, 5, 12, 5, 195, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 202, 8, 5, 1, 5, 1, 5, 1, 5, 3, 5, 207, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 215, 8, 5, 10, 5, 12, 5, 218, 9, 5, 1, 6, 1, 6, 3, 6, 222, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 229, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 234, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 245, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 251, 8, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 5, 9, 259, 8, 9, 10, 9, 12, 9, 262, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 3, 10, 272, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 277, 8, 10, 10, 10, 12, 10, 280, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, 11, 288, 8, 11, 10, 11, 12, 11, 291, 9, 11, 3, 11, 293, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 307, 8, 15, 10, 15, 12, 15, 310, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 315, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 323, 8, 17, 10, 17, 12, 17, 326, 9, 17, 1, 17, 3, 17, 329, 8, 17, 1, 18, 1, 18, 1, 18, 3, 18, 334, 8, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 3, 21, 344, 8, 21, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 350, 8, 22, 10, 22, 12, 22, 353, 9, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 363, 8, 24, 10, 24, 12, 24, 366, 9, 24, 1, 24, 3, 24, 369, 8, 24, 1, 24, 1, 24, 3, 24, 373, 8, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 3, 26, 380, 8, 26, 1, 26, 1, 26, 3, 26, 384, 8, 26, 1, 27, 1, 27, 1, 27, 5, 27, 389, 8, 27, 10, 27, 12, 27, 392, 9, 27, 1, 28, 1, 28, 1, 28, 3, 28, 397, 8, 28, 1, 29, 1, 29, 1, 29, 5, 29, 402, 8, 29, 10, 29, 12, 29, 405, 9, 29, 1, 30, 1, 30, 1, 30, 5, 30, 410, 8, 30, 10, 30, 12, 30, 413, 9, 30, 1, 31, 1, 31, 1, 31, 5, 31, 418, 8, 31, 10, 31, 12, 31, 421, 9, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 3, 33, 428, 8, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 443, 8, 34, 10, 34, 12, 34, 446, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 454, 8, 34, 10, 34, 12, 34, 457, 9, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 465, 8, 34, 10, 34, 12, 34, 468, 9, 34, 1, 34, 1, 34, 3, 34, 472, 8, 34, 1, 35, 1, 35, 3, 35, 476, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 481, 8, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 5, 38, 490, 8, 38, 10, 38, 12, 38, 493, 9, 38, 1, 39, 1, 39, 3, 39, 497, 8, 39, 1, 39, 1, 39, 3, 39, 501, 8, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 513, 8, 42, 10, 42, 12, 42, 516, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 3, 44, 526, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 5, 47, 538, 8, 47, 10, 47, 12, 47, 541, 9, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, 3, 50, 551, 8, 50, 1, 51, 3, 51, 554, 8, 51, 1, 51, 1, 51, 1, 52, 3, 52, 559, 8, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 581, 8, 58, 1, 58, 1, 58, 1, 58, 1, 58, 5, 58, 587, 8, 58, 10, 58, 12, 58, 590, 9, 58, 3, 58, 592, 8, 58, 1, 59, 1, 59, 1, 59, 3, 59, 597, 8, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 610, 8, 61, 1, 62, 3, 62, 613, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 3, 63, 622, 8, 63, 1, 64, 1, 64, 1, 64, 1, 64, 5, 64, 628, 8, 64, 10, 64, 12, 64, 631, 9, 64, 1, 65, 1, 65, 1, 65, 0, 4, 2, 10, 18, 20, 66, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 0, 9, 1, 0, 64, 65, 1, 0, 66, 68, 2, 0, 30, 30, 81, 81, 1, 0, 72, 73, 2, 0, 35, 35, 40, 40, 2, 0, 43, 43, 46, 46, 2, 0, 42, 42, 56, 56, 2, 0, 57, 57, 59, 63, 1, 0, 22, 24, 660, 0, 132, 1, 0, 0, 0, 2, 135, 1, 0, 0, 0, 4, 152, 1, 0, 0, 0, 6, 172, 1, 0, 0, 0, 8, 174, 1, 0, 0, 0, 10, 206, 1, 0, 0, 0, 12, 233, 1, 0, 0, 0, 14, 235, 1, 0, 0, 0, 16, 244, 1, 0, 0, 0, 18, 250, 1, 0, 0, 0, 20, 271, 1, 0, 0, 0, 22, 281, 1, 0, 0, 0, 24, 296, 1, 0, 0, 0, 26, 298, 1, 0, 0, 0, 28, 300, 1, 0, 0, 0, 30, 303, 1, 0, 0, 0, 32, 314, 1, 0, 0, 0, 34, 318, 1, 0, 0, 0, 36, 333, 1, 0, 0, 0, 38, 337, 1, 0, 0, 0, 40, 339, 1, 0, 0, 0, 42, 343, 1, 0, 0, 0, 44, 345, 1, 0, 0, 0, 46, 354, 1, 0, 0, 0, 48, 358, 1, 0, 0, 0, 50, 374, 1, 0, 0, 0, 52, 377, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 393, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 406, 1, 0, 0, 0, 62, 414, 1, 0, 0, 0, 64, 422, 1, 0, 0, 0, 66, 427, 1, 0, 0, 0, 68, 471, 1, 0, 0, 0, 70, 475, 1, 0, 0, 0, 72, 480, 1, 0, 0, 0, 74, 482, 1, 0, 0, 0, 76, 485, 1, 0, 0, 0, 78, 494, 1, 0, 0, 0, 80, 502, 1, 0, 0, 0, 82, 505, 1, 0, 0, 0, 84, 508, 1, 0, 0, 0, 86, 517, 1, 0, 0, 0, 88, 521, 1, 0, 0, 0, 90, 527, 1, 0, 0, 0, 92, 531, 1, 0, 0, 0, 94, 534, 1, 0, 0, 0, 96, 542, 1, 0, 0, 0, 98, 546, 1, 0, 0, 0, 100, 550, 1, 0, 0, 0, 102, 553, 1, 0, 0, 0, 104, 558, 1, 0, 0, 0, 106, 562, 1, 0, 0, 0, 108, 564, 1, 0, 0, 0, 110, 566, 1, 0, 0, 0, 112, 569, 1, 0, 0, 0, 114, 573, 1, 0, 0, 0, 116, 576, 1, 0, 0, 0, 118, 596, 1, 0, 0, 0, 120, 600, 1, 0, 0, 0, 122, 605, 1, 0, 0, 0, 124, 612, 1, 0, 0, 0, 126, 618, 1, 0, 0, 0, 128, 623, 1, 0, 0, 0, 130, 632, 1, 0, 0, 0, 132, 133, 3, 2, 1, 0, 133, 134, 5, 0, 0, 1, 134, 1, 1, 0, 0, 0, 135, 136, 6, 1, -1, 0, 136, 137, 3, 4, 2, 0, 137, 143, 1, 0, 0, 0, 138, 139, 10, 1, 0, 0, 139, 140, 5, 29, 0, 0, 140, 142, 3, 6, 3, 0, 141, 138, 1, 0, 0, 0, 142, 145, 1, 0, 0, 0, 143, 141, 1, 0, 0, 0, 143, 144, 1, 0, 0, 0, 144, 3, 1, 0, 0, 0, 145, 143, 1, 0, 0, 0, 146, 153, 3, 110, 55, 0, 147, 153, 3, 34, 17, 0, 148, 153, 3, 28, 14, 0, 149, 153, 3, 114, 57, 0, 150, 151, 4, 2, 1, 0, 151, 153, 3, 48, 24, 0, 152, 146, 1, 0, 0, 0, 152, 147, 1, 0, 0, 0, 152, 148, 1, 0, 0, 0, 152, 149, 1, 0, 0, 0, 152, 150, 1, 0, 0, 0, 153, 5, 1, 0, 0, 0, 154, 173, 3, 50, 25, 0, 155, 173, 3, 8, 4, 0, 156, 173, 3, 80, 40, 0, 157, 173, 3, 74, 37, 0, 158, 173, 3, 52, 26, 0, 159, 173, 3, 76, 38, 0, 160, 173, 3, 82, 41, 0, 161, 173, 3, 84, 42, 0, 162, 173, 3, 88, 44, 0, 163, 173, 3, 90, 45, 0, 164, 173, 3, 116, 58, 0, 165, 173, 3, 92, 46, 0, 166, 167, 4, 3, 2, 0, 167, 173, 3, 122, 61, 0, 168, 169, 4, 3, 3, 0, 169, 173, 3, 120, 60, 0, 170, 171, 4, 3, 4, 0, 171, 173, 3, 124, 62, 0, 172, 154, 1, 0, 0, 0, 172, 155, 1, 0, 0, 0, 172, 156, 1, 0, 0, 0, 172, 157, 1, 0, 0, 0, 172, 158, 1, 0, 0, 0, 172, 159, 1, 0, 0, 0, 172, 160, 1, 0, 0, 0, 172, 161, 1, 0, 0, 0, 172, 162, 1, 0, 0, 0, 172, 163, 1, 0, 0, 0, 172, 164, 1, 0, 0, 0, 172, 165, 1, 0, 0, 0, 172, 166, 1, 0, 0, 0, 172, 168, 1, 0, 0, 0, 172, 170, 1, 0, 0, 0, 173, 7, 1, 0, 0, 0, 174, 175, 5, 16, 0, 0, 175, 176, 3, 10, 5, 0, 176, 9, 1, 0, 0, 0, 177, 178, 6, 5, -1, 0, 178, 179, 5, 49, 0, 0, 179, 207, 3, 10, 5, 8, 180, 207, 3, 16, 8, 0, 181, 207, 3, 12, 6, 0, 182, 184, 3, 16, 8, 0, 183, 185, 5, 49, 0, 0, 184, 183, 1, 0, 0, 0, 184, 185, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 5, 44, 0, 0, 187, 188, 5, 48, 0, 0, 188, 193, 3, 16, 8, 0, 189, 190, 5, 39, 0, 0, 190, 192, 3, 16, 8, 0, 191, 189, 1, 0, 0, 0, 192, 195, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 193, 194, 1, 0, 0, 0, 194, 196, 1, 0, 0, 0, 195, 193, 1, 0, 0, 0, 196, 197, 5, 55, 0, 0, 197, 207, 1, 0, 0, 0, 198, 199, 3, 16, 8, 0, 199, 201, 5, 45, 0, 0, 200, 202, 5, 49, 0, 0, 201, 200, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202, 203, 1, 0, 0, 0, 203, 204, 5, 50, 0, 0, 204, 207, 1, 0, 0, 0, 205, 207, 3, 14, 7, 0, 206, 177, 1, 0, 0, 0, 206, 180, 1, 0, 0, 0, 206, 181, 1, 0, 0, 0, 206, 182, 1, 0, 0, 0, 206, 198, 1, 0, 0, 0, 206, 205, 1, 0, 0, 0, 207, 216, 1, 0, 0, 0, 208, 209, 10, 5, 0, 0, 209, 210, 5, 34, 0, 0, 210, 215, 3, 10, 5, 6, 211, 212, 10, 4, 0, 0, 212, 213, 5, 52, 0, 0, 213, 215, 3, 10, 5, 5, 214, 208, 1, 0, 0, 0, 214, 211, 1, 0, 0, 0, 215, 218, 1, 0, 0, 0, 216, 214, 1, 0, 0, 0, 216, 217, 1, 0, 0, 0, 217, 11, 1, 0, 0, 0, 218, 216, 1, 0, 0, 0, 219, 221, 3, 16, 8, 0, 220, 222, 5, 49, 0, 0, 221, 220, 1, 0, 0, 0, 221, 222, 1, 0, 0, 0, 222, 223, 1, 0, 0, 0, 223, 224, 5, 47, 0, 0, 224, 225, 3, 106, 53, 0, 225, 234, 1, 0, 0, 0, 226, 228, 3, 16, 8, 0, 227, 229, 5, 49, 0, 0, 228, 227, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 230, 1, 0, 0, 0, 230, 231, 5, 54, 0, 0, 231, 232, 3, 106, 53, 0, 232, 234, 1, 0, 0, 0, 233, 219, 1, 0, 0, 0, 233, 226, 1, 0, 0, 0, 234, 13, 1, 0, 0, 0, 235, 236, 3, 58, 29, 0, 236, 237, 5, 38, 0, 0, 237, 238, 3, 68, 34, 0, 238, 15, 1, 0, 0, 0, 239, 245, 3, 18, 9, 0, 240, 241, 3, 18, 9, 0, 241, 242, 3, 108, 54, 0, 242, 243, 3, 18, 9, 0, 243, 245, 1, 0, 0, 0, 244, 239, 1, 0, 0, 0, 244, 240, 1, 0, 0, 0, 245, 17, 1, 0, 0, 0, 246, 247, 6, 9, -1, 0, 247, 251, 3, 20, 10, 0, 248, 249, 7, 0, 0, 0, 249, 251, 3, 18, 9, 3, 250, 246, 1, 0, 0, 0, 250, 248, 1, 0, 0, 0, 251, 260, 1, 0, 0, 0, 252, 253, 10, 2, 0, 0, 253, 254, 7, 1, 0, 0, 254, 259, 3, 18, 9, 3, 255, 256, 10, 1, 0, 0, 256, 257, 7, 0, 0, 0, 257, 259, 3, 18, 9, 2, 258, 252, 1, 0, 0, 0, 258, 255, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 19, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, 263, 264, 6, 10, -1, 0, 264, 272, 3, 68, 34, 0, 265, 272, 3, 58, 29, 0, 266, 272, 3, 22, 11, 0, 267, 268, 5, 48, 0, 0, 268, 269, 3, 10, 5, 0, 269, 270, 5, 55, 0, 0, 270, 272, 1, 0, 0, 0, 271, 263, 1, 0, 0, 0, 271, 265, 1, 0, 0, 0, 271, 266, 1, 0, 0, 0, 271, 267, 1, 0, 0, 0, 272, 278, 1, 0, 0, 0, 273, 274, 10, 1, 0, 0, 274, 275, 5, 37, 0, 0, 275, 277, 3, 26, 13, 0, 276, 273, 1, 0, 0, 0, 277, 280, 1, 0, 0, 0, 278, 276, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 21, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 281, 282, 3, 24, 12, 0, 282, 292, 5, 48, 0, 0, 283, 293, 5, 66, 0, 0, 284, 289, 3, 10, 5, 0, 285, 286, 5, 39, 0, 0, 286, 288, 3, 10, 5, 0, 287, 285, 1, 0, 0, 0, 288, 291, 1, 0, 0, 0, 289, 287, 1, 0, 0, 0, 289, 290, 1, 0, 0, 0, 290, 293, 1, 0, 0, 0, 291, 289, 1, 0, 0, 0, 292, 283, 1, 0, 0, 0, 292, 284, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 5, 55, 0, 0, 295, 23, 1, 0, 0, 0, 296, 297, 3, 72, 36, 0, 297, 25, 1, 0, 0, 0, 298, 299, 3, 64, 32, 0, 299, 27, 1, 0, 0, 0, 300, 301, 5, 12, 0, 0, 301, 302, 3, 30, 15, 0, 302, 29, 1, 0, 0, 0, 303, 308, 3, 32, 16, 0, 304, 305, 5, 39, 0, 0, 305, 307, 3, 32, 16, 0, 306, 304, 1, 0, 0, 0, 307, 310, 1, 0, 0, 0, 308, 306, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 31, 1, 0, 0, 0, 310, 308, 1, 0, 0, 0, 311, 312, 3, 58, 29, 0, 312, 313, 5, 36, 0, 0, 313, 315, 1, 0, 0, 0, 314, 311, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 316, 1, 0, 0, 0, 316, 317, 3, 10, 5, 0, 317, 33, 1, 0, 0, 0, 318, 319, 5, 6, 0, 0, 319, 324, 3, 36, 18, 0, 320, 321, 5, 39, 0, 0, 321, 323, 3, 36, 18, 0, 322, 320, 1, 0, 0, 0, 323, 326, 1, 0, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 328, 1, 0, 0, 0, 326, 324, 1, 0, 0, 0, 327, 329, 3, 42, 21, 0, 328, 327, 1, 0, 0, 0, 328, 329, 1, 0, 0, 0, 329, 35, 1, 0, 0, 0, 330, 331, 3, 38, 19, 0, 331, 332, 5, 38, 0, 0, 332, 334, 1, 0, 0, 0, 333, 330, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 335, 1, 0, 0, 0, 335, 336, 3, 40, 20, 0, 336, 37, 1, 0, 0, 0, 337, 338, 5, 81, 0, 0, 338, 39, 1, 0, 0, 0, 339, 340, 7, 2, 0, 0, 340, 41, 1, 0, 0, 0, 341, 344, 3, 44, 22, 0, 342, 344, 3, 46, 23, 0, 343, 341, 1, 0, 0, 0, 343, 342, 1, 0, 0, 0, 344, 43, 1, 0, 0, 0, 345, 346, 5, 80, 0, 0, 346, 351, 5, 81, 0, 0, 347, 348, 5, 39, 0, 0, 348, 350, 5, 81, 0, 0, 349, 347, 1, 0, 0, 0, 350, 353, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 352, 1, 0, 0, 0, 352, 45, 1, 0, 0, 0, 353, 351, 1, 0, 0, 0, 354, 355, 5, 70, 0, 0, 355, 356, 3, 44, 22, 0, 356, 357, 5, 71, 0, 0, 357, 47, 1, 0, 0, 0, 358, 359, 5, 19, 0, 0, 359, 364, 3, 36, 18, 0, 360, 361, 5, 39, 0, 0, 361, 363, 3, 36, 18, 0, 362, 360, 1, 0, 0, 0, 363, 366, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 365, 1, 0, 0, 0, 365, 368, 1, 0, 0, 0, 366, 364, 1, 0, 0, 0, 367, 369, 3, 54, 27, 0, 368, 367, 1, 0, 0, 0, 368, 369, 1, 0, 0, 0, 369, 372, 1, 0, 0, 0, 370, 371, 5, 33, 0, 0, 371, 373, 3, 30, 15, 0, 372, 370, 1, 0, 0, 0, 372, 373, 1, 0, 0, 0, 373, 49, 1, 0, 0, 0, 374, 375, 5, 4, 0, 0, 375, 376, 3, 30, 15, 0, 376, 51, 1, 0, 0, 0, 377, 379, 5, 15, 0, 0, 378, 380, 3, 54, 27, 0, 379, 378, 1, 0, 0, 0, 379, 380, 1, 0, 0, 0, 380, 383, 1, 0, 0, 0, 381, 382, 5, 33, 0, 0, 382, 384, 3, 30, 15, 0, 383, 381, 1, 0, 0, 0, 383, 384, 1, 0, 0, 0, 384, 53, 1, 0, 0, 0, 385, 390, 3, 56, 28, 0, 386, 387, 5, 39, 0, 0, 387, 389, 3, 56, 28, 0, 388, 386, 1, 0, 0, 0, 389, 392, 1, 0, 0, 0, 390, 388, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 55, 1, 0, 0, 0, 392, 390, 1, 0, 0, 0, 393, 396, 3, 32, 16, 0, 394, 395, 5, 16, 0, 0, 395, 397, 3, 10, 5, 0, 396, 394, 1, 0, 0, 0, 396, 397, 1, 0, 0, 0, 397, 57, 1, 0, 0, 0, 398, 403, 3, 72, 36, 0, 399, 400, 5, 41, 0, 0, 400, 402, 3, 72, 36, 0, 401, 399, 1, 0, 0, 0, 402, 405, 1, 0, 0, 0, 403, 401, 1, 0, 0, 0, 403, 404, 1, 0, 0, 0, 404, 59, 1, 0, 0, 0, 405, 403, 1, 0, 0, 0, 406, 411, 3, 66, 33, 0, 407, 408, 5, 41, 0, 0, 408, 410, 3, 66, 33, 0, 409, 407, 1, 0, 0, 0, 410, 413, 1, 0, 0, 0, 411, 409, 1, 0, 0, 0, 411, 412, 1, 0, 0, 0, 412, 61, 1, 0, 0, 0, 413, 411, 1, 0, 0, 0, 414, 419, 3, 60, 30, 0, 415, 416, 5, 39, 0, 0, 416, 418, 3, 60, 30, 0, 417, 415, 1, 0, 0, 0, 418, 421, 1, 0, 0, 0, 419, 417, 1, 0, 0, 0, 419, 420, 1, 0, 0, 0, 420, 63, 1, 0, 0, 0, 421, 419, 1, 0, 0, 0, 422, 423, 7, 3, 0, 0, 423, 65, 1, 0, 0, 0, 424, 428, 5, 85, 0, 0, 425, 426, 4, 33, 10, 0, 426, 428, 3, 70, 35, 0, 427, 424, 1, 0, 0, 0, 427, 425, 1, 0, 0, 0, 428, 67, 1, 0, 0, 0, 429, 472, 5, 50, 0, 0, 430, 431, 3, 104, 52, 0, 431, 432, 5, 72, 0, 0, 432, 472, 1, 0, 0, 0, 433, 472, 3, 102, 51, 0, 434, 472, 3, 104, 52, 0, 435, 472, 3, 98, 49, 0, 436, 472, 3, 70, 35, 0, 437, 472, 3, 106, 53, 0, 438, 439, 5, 70, 0, 0, 439, 444, 3, 100, 50, 0, 440, 441, 5, 39, 0, 0, 441, 443, 3, 100, 50, 0, 442, 440, 1, 0, 0, 0, 443, 446, 1, 0, 0, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 447, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 447, 448, 5, 71, 0, 0, 448, 472, 1, 0, 0, 0, 449, 450, 5, 70, 0, 0, 450, 455, 3, 98, 49, 0, 451, 452, 5, 39, 0, 0, 452, 454, 3, 98, 49, 0, 453, 451, 1, 0, 0, 0, 454, 457, 1, 0, 0, 0, 455, 453, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 458, 1, 0, 0, 0, 457, 455, 1, 0, 0, 0, 458, 459, 5, 71, 0, 0, 459, 472, 1, 0, 0, 0, 460, 461, 5, 70, 0, 0, 461, 466, 3, 106, 53, 0, 462, 463, 5, 39, 0, 0, 463, 465, 3, 106, 53, 0, 464, 462, 1, 0, 0, 0, 465, 468, 1, 0, 0, 0, 466, 464, 1, 0, 0, 0, 466, 467, 1, 0, 0, 0, 467, 469, 1, 0, 0, 0, 468, 466, 1, 0, 0, 0, 469, 470, 5, 71, 0, 0, 470, 472, 1, 0, 0, 0, 471, 429, 1, 0, 0, 0, 471, 430, 1, 0, 0, 0, 471, 433, 1, 0, 0, 0, 471, 434, 1, 0, 0, 0, 471, 435, 1, 0, 0, 0, 471, 436, 1, 0, 0, 0, 471, 437, 1, 0, 0, 0, 471, 438, 1, 0, 0, 0, 471, 449, 1, 0, 0, 0, 471, 460, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 476, 5, 53, 0, 0, 474, 476, 5, 69, 0, 0, 475, 473, 1, 0, 0, 0, 475, 474, 1, 0, 0, 0, 476, 71, 1, 0, 0, 0, 477, 481, 3, 64, 32, 0, 478, 479, 4, 36, 11, 0, 479, 481, 3, 70, 35, 0, 480, 477, 1, 0, 0, 0, 480, 478, 1, 0, 0, 0, 481, 73, 1, 0, 0, 0, 482, 483, 5, 9, 0, 0, 483, 484, 5, 31, 0, 0, 484, 75, 1, 0, 0, 0, 485, 486, 5, 14, 0, 0, 486, 491, 3, 78, 39, 0, 487, 488, 5, 39, 0, 0, 488, 490, 3, 78, 39, 0, 489, 487, 1, 0, 0, 0, 490, 493, 1, 0, 0, 0, 491, 489, 1, 0, 0, 0, 491, 492, 1, 0, 0, 0, 492, 77, 1, 0, 0, 0, 493, 491, 1, 0, 0, 0, 494, 496, 3, 10, 5, 0, 495, 497, 7, 4, 0, 0, 496, 495, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 500, 1, 0, 0, 0, 498, 499, 5, 51, 0, 0, 499, 501, 7, 5, 0, 0, 500, 498, 1, 0, 0, 0, 500, 501, 1, 0, 0, 0, 501, 79, 1, 0, 0, 0, 502, 503, 5, 8, 0, 0, 503, 504, 3, 62, 31, 0, 504, 81, 1, 0, 0, 0, 505, 506, 5, 2, 0, 0, 506, 507, 3, 62, 31, 0, 507, 83, 1, 0, 0, 0, 508, 509, 5, 11, 0, 0, 509, 514, 3, 86, 43, 0, 510, 511, 5, 39, 0, 0, 511, 513, 3, 86, 43, 0, 512, 510, 1, 0, 0, 0, 513, 516, 1, 0, 0, 0, 514, 512, 1, 0, 0, 0, 514, 515, 1, 0, 0, 0, 515, 85, 1, 0, 0, 0, 516, 514, 1, 0, 0, 0, 517, 518, 3, 60, 30, 0, 518, 519, 5, 89, 0, 0, 519, 520, 3, 60, 30, 0, 520, 87, 1, 0, 0, 0, 521, 522, 5, 1, 0, 0, 522, 523, 3, 20, 10, 0, 523, 525, 3, 106, 53, 0, 524, 526, 3, 94, 47, 0, 525, 524, 1, 0, 0, 0, 525, 526, 1, 0, 0, 0, 526, 89, 1, 0, 0, 0, 527, 528, 5, 7, 0, 0, 528, 529, 3, 20, 10, 0, 529, 530, 3, 106, 53, 0, 530, 91, 1, 0, 0, 0, 531, 532, 5, 10, 0, 0, 532, 533, 3, 58, 29, 0, 533, 93, 1, 0, 0, 0, 534, 539, 3, 96, 48, 0, 535, 536, 5, 39, 0, 0, 536, 538, 3, 96, 48, 0, 537, 535, 1, 0, 0, 0, 538, 541, 1, 0, 0, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 95, 1, 0, 0, 0, 541, 539, 1, 0, 0, 0, 542, 543, 3, 64, 32, 0, 543, 544, 5, 36, 0, 0, 544, 545, 3, 68, 34, 0, 545, 97, 1, 0, 0, 0, 546, 547, 7, 6, 0, 0, 547, 99, 1, 0, 0, 0, 548, 551, 3, 102, 51, 0, 549, 551, 3, 104, 52, 0, 550, 548, 1, 0, 0, 0, 550, 549, 1, 0, 0, 0, 551, 101, 1, 0, 0, 0, 552, 554, 7, 0, 0, 0, 553, 552, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 555, 1, 0, 0, 0, 555, 556, 5, 32, 0, 0, 556, 103, 1, 0, 0, 0, 557, 559, 7, 0, 0, 0, 558, 557, 1, 0, 0, 0, 558, 559, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 561, 5, 31, 0, 0, 561, 105, 1, 0, 0, 0, 562, 563, 5, 30, 0, 0, 563, 107, 1, 0, 0, 0, 564, 565, 7, 7, 0, 0, 565, 109, 1, 0, 0, 0, 566, 567, 5, 5, 0, 0, 567, 568, 3, 112, 56, 0, 568, 111, 1, 0, 0, 0, 569, 570, 5, 70, 0, 0, 570, 571, 3, 2, 1, 0, 571, 572, 5, 71, 0, 0, 572, 113, 1, 0, 0, 0, 573, 574, 5, 13, 0, 0, 574, 575, 5, 105, 0, 0, 575, 115, 1, 0, 0, 0, 576, 577, 5, 3, 0, 0, 577, 580, 5, 95, 0, 0, 578, 579, 5, 93, 0, 0, 579, 581, 3, 60, 30, 0, 580, 578, 1, 0, 0, 0, 580, 581, 1, 0, 0, 0, 581, 591, 1, 0, 0, 0, 582, 583, 5, 94, 0, 0, 583, 588, 3, 118, 59, 0, 584, 585, 5, 39, 0, 0, 585, 587, 3, 118, 59, 0, 586, 584, 1, 0, 0, 0, 587, 590, 1, 0, 0, 0, 588, 586, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 592, 1, 0, 0, 0, 590, 588, 1, 0, 0, 0, 591, 582, 1, 0, 0, 0, 591, 592, 1, 0, 0, 0, 592, 117, 1, 0, 0, 0, 593, 594, 3, 60, 30, 0, 594, 595, 5, 36, 0, 0, 595, 597, 1, 0, 0, 0, 596, 593, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 3, 60, 30, 0, 599, 119, 1, 0, 0, 0, 600, 601, 5, 18, 0, 0, 601, 602, 3, 36, 18, 0, 602, 603, 5, 93, 0, 0, 603, 604, 3, 62, 31, 0, 604, 121, 1, 0, 0, 0, 605, 606, 5, 17, 0, 0, 606, 609, 3, 54, 27, 0, 607, 608, 5, 33, 0, 0, 608, 610, 3, 30, 15, 0, 609, 607, 1, 0, 0, 0, 609, 610, 1, 0, 0, 0, 610, 123, 1, 0, 0, 0, 611, 613, 7, 8, 0, 0, 612, 611, 1, 0, 0, 0, 612, 613, 1, 0, 0, 0, 613, 614, 1, 0, 0, 0, 614, 615, 5, 20, 0, 0, 615, 616, 3, 126, 63, 0, 616, 617, 3, 128, 64, 0, 617, 125, 1, 0, 0, 0, 618, 621, 3, 64, 32, 0, 619, 620, 5, 89, 0, 0, 620, 622, 3, 64, 32, 0, 621, 619, 1, 0, 0, 0, 621, 622, 1, 0, 0, 0, 622, 127, 1, 0, 0, 0, 623, 624, 5, 93, 0, 0, 624, 629, 3, 130, 65, 0, 625, 626, 5, 39, 0, 0, 626, 628, 3, 130, 65, 0, 627, 625, 1, 0, 0, 0, 628, 631, 1, 0, 0, 0, 629, 627, 1, 0, 0, 0, 629, 630, 1, 0, 0, 0, 630, 129, 1, 0, 0, 0, 631, 629, 1, 0, 0, 0, 632, 633, 3, 16, 8, 0, 633, 131, 1, 0, 0, 0, 61, 143, 152, 172, 184, 193, 201, 206, 214, 216, 221, 228, 233, 244, 250, 258, 260, 271, 278, 289, 292, 308, 314, 324, 328, 333, 343, 351, 364, 368, 372, 379, 383, 390, 396, 403, 411, 419, 427, 444, 455, 466, 471, 475, 480, 491, 496, 500, 514, 525, 539, 550, 553, 558, 580, 588, 591, 596, 609, 612, 621, 629] \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.tokens b/packages/kbn-esql-ast/src/antlr/esql_parser.tokens index 3dd1a2c75403..b1a16987dd8c 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.tokens +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.tokens @@ -17,106 +17,115 @@ WHERE=16 DEV_INLINESTATS=17 DEV_LOOKUP=18 DEV_METRICS=19 -UNKNOWN_CMD=20 -LINE_COMMENT=21 -MULTILINE_COMMENT=22 -WS=23 -COLON=24 -PIPE=25 -QUOTED_STRING=26 -INTEGER_LITERAL=27 -DECIMAL_LITERAL=28 -BY=29 -AND=30 -ASC=31 -ASSIGN=32 -CAST_OP=33 -COMMA=34 -DESC=35 -DOT=36 -FALSE=37 -FIRST=38 -IN=39 -IS=40 -LAST=41 -LIKE=42 -LP=43 -NOT=44 -NULL=45 -NULLS=46 -OR=47 -PARAM=48 -RLIKE=49 -RP=50 -TRUE=51 -EQ=52 -CIEQ=53 -NEQ=54 -LT=55 -LTE=56 -GT=57 -GTE=58 -PLUS=59 -MINUS=60 -ASTERISK=61 -SLASH=62 -PERCENT=63 -NAMED_OR_POSITIONAL_PARAM=64 -OPENING_BRACKET=65 -CLOSING_BRACKET=66 -UNQUOTED_IDENTIFIER=67 -QUOTED_IDENTIFIER=68 -EXPR_LINE_COMMENT=69 -EXPR_MULTILINE_COMMENT=70 -EXPR_WS=71 -EXPLAIN_WS=72 -EXPLAIN_LINE_COMMENT=73 -EXPLAIN_MULTILINE_COMMENT=74 -METADATA=75 -UNQUOTED_SOURCE=76 -FROM_LINE_COMMENT=77 -FROM_MULTILINE_COMMENT=78 -FROM_WS=79 -ID_PATTERN=80 -PROJECT_LINE_COMMENT=81 -PROJECT_MULTILINE_COMMENT=82 -PROJECT_WS=83 -AS=84 -RENAME_LINE_COMMENT=85 -RENAME_MULTILINE_COMMENT=86 -RENAME_WS=87 -ON=88 -WITH=89 -ENRICH_POLICY_NAME=90 -ENRICH_LINE_COMMENT=91 -ENRICH_MULTILINE_COMMENT=92 -ENRICH_WS=93 -ENRICH_FIELD_LINE_COMMENT=94 -ENRICH_FIELD_MULTILINE_COMMENT=95 -ENRICH_FIELD_WS=96 -MVEXPAND_LINE_COMMENT=97 -MVEXPAND_MULTILINE_COMMENT=98 -MVEXPAND_WS=99 -INFO=100 -SHOW_LINE_COMMENT=101 -SHOW_MULTILINE_COMMENT=102 -SHOW_WS=103 -SETTING=104 -SETTING_LINE_COMMENT=105 -SETTTING_MULTILINE_COMMENT=106 -SETTING_WS=107 -LOOKUP_LINE_COMMENT=108 -LOOKUP_MULTILINE_COMMENT=109 -LOOKUP_WS=110 -LOOKUP_FIELD_LINE_COMMENT=111 -LOOKUP_FIELD_MULTILINE_COMMENT=112 -LOOKUP_FIELD_WS=113 -METRICS_LINE_COMMENT=114 -METRICS_MULTILINE_COMMENT=115 -METRICS_WS=116 -CLOSING_METRICS_LINE_COMMENT=117 -CLOSING_METRICS_MULTILINE_COMMENT=118 -CLOSING_METRICS_WS=119 +DEV_JOIN=20 +DEV_JOIN_FULL=21 +DEV_JOIN_LEFT=22 +DEV_JOIN_RIGHT=23 +DEV_JOIN_LOOKUP=24 +UNKNOWN_CMD=25 +LINE_COMMENT=26 +MULTILINE_COMMENT=27 +WS=28 +PIPE=29 +QUOTED_STRING=30 +INTEGER_LITERAL=31 +DECIMAL_LITERAL=32 +BY=33 +AND=34 +ASC=35 +ASSIGN=36 +CAST_OP=37 +COLON=38 +COMMA=39 +DESC=40 +DOT=41 +FALSE=42 +FIRST=43 +IN=44 +IS=45 +LAST=46 +LIKE=47 +LP=48 +NOT=49 +NULL=50 +NULLS=51 +OR=52 +PARAM=53 +RLIKE=54 +RP=55 +TRUE=56 +EQ=57 +CIEQ=58 +NEQ=59 +LT=60 +LTE=61 +GT=62 +GTE=63 +PLUS=64 +MINUS=65 +ASTERISK=66 +SLASH=67 +PERCENT=68 +NAMED_OR_POSITIONAL_PARAM=69 +OPENING_BRACKET=70 +CLOSING_BRACKET=71 +UNQUOTED_IDENTIFIER=72 +QUOTED_IDENTIFIER=73 +EXPR_LINE_COMMENT=74 +EXPR_MULTILINE_COMMENT=75 +EXPR_WS=76 +EXPLAIN_WS=77 +EXPLAIN_LINE_COMMENT=78 +EXPLAIN_MULTILINE_COMMENT=79 +METADATA=80 +UNQUOTED_SOURCE=81 +FROM_LINE_COMMENT=82 +FROM_MULTILINE_COMMENT=83 +FROM_WS=84 +ID_PATTERN=85 +PROJECT_LINE_COMMENT=86 +PROJECT_MULTILINE_COMMENT=87 +PROJECT_WS=88 +AS=89 +RENAME_LINE_COMMENT=90 +RENAME_MULTILINE_COMMENT=91 +RENAME_WS=92 +ON=93 +WITH=94 +ENRICH_POLICY_NAME=95 +ENRICH_LINE_COMMENT=96 +ENRICH_MULTILINE_COMMENT=97 +ENRICH_WS=98 +ENRICH_FIELD_LINE_COMMENT=99 +ENRICH_FIELD_MULTILINE_COMMENT=100 +ENRICH_FIELD_WS=101 +MVEXPAND_LINE_COMMENT=102 +MVEXPAND_MULTILINE_COMMENT=103 +MVEXPAND_WS=104 +INFO=105 +SHOW_LINE_COMMENT=106 +SHOW_MULTILINE_COMMENT=107 +SHOW_WS=108 +SETTING=109 +SETTING_LINE_COMMENT=110 +SETTTING_MULTILINE_COMMENT=111 +SETTING_WS=112 +LOOKUP_LINE_COMMENT=113 +LOOKUP_MULTILINE_COMMENT=114 +LOOKUP_WS=115 +LOOKUP_FIELD_LINE_COMMENT=116 +LOOKUP_FIELD_MULTILINE_COMMENT=117 +LOOKUP_FIELD_WS=118 +USING=119 +JOIN_LINE_COMMENT=120 +JOIN_MULTILINE_COMMENT=121 +JOIN_WS=122 +METRICS_LINE_COMMENT=123 +METRICS_MULTILINE_COMMENT=124 +METRICS_WS=125 +CLOSING_METRICS_LINE_COMMENT=126 +CLOSING_METRICS_MULTILINE_COMMENT=127 +CLOSING_METRICS_WS=128 'dissect'=1 'drop'=2 'enrich'=3 @@ -133,46 +142,47 @@ CLOSING_METRICS_WS=119 'sort'=14 'stats'=15 'where'=16 -':'=24 -'|'=25 -'by'=29 -'and'=30 -'asc'=31 -'='=32 -'::'=33 -','=34 -'desc'=35 -'.'=36 -'false'=37 -'first'=38 -'in'=39 -'is'=40 -'last'=41 -'like'=42 -'('=43 -'not'=44 -'null'=45 -'nulls'=46 -'or'=47 -'?'=48 -'rlike'=49 -')'=50 -'true'=51 -'=='=52 -'=~'=53 -'!='=54 -'<'=55 -'<='=56 -'>'=57 -'>='=58 -'+'=59 -'-'=60 -'*'=61 -'/'=62 -'%'=63 -']'=66 -'metadata'=75 -'as'=84 -'on'=88 -'with'=89 -'info'=100 +'|'=29 +'by'=33 +'and'=34 +'asc'=35 +'='=36 +'::'=37 +':'=38 +','=39 +'desc'=40 +'.'=41 +'false'=42 +'first'=43 +'in'=44 +'is'=45 +'last'=46 +'like'=47 +'('=48 +'not'=49 +'null'=50 +'nulls'=51 +'or'=52 +'?'=53 +'rlike'=54 +')'=55 +'true'=56 +'=='=57 +'=~'=58 +'!='=59 +'<'=60 +'<='=61 +'>'=62 +'>='=63 +'+'=64 +'-'=65 +'*'=66 +'/'=67 +'%'=68 +']'=71 +'metadata'=80 +'as'=89 +'on'=93 +'with'=94 +'info'=105 +'USING'=119 diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.ts b/packages/kbn-esql-ast/src/antlr/esql_parser.ts index 4dc0c5c628e3..ec261299493a 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.ts @@ -47,106 +47,115 @@ export default class esql_parser extends parser_config { public static readonly DEV_INLINESTATS = 17; public static readonly DEV_LOOKUP = 18; public static readonly DEV_METRICS = 19; - public static readonly UNKNOWN_CMD = 20; - public static readonly LINE_COMMENT = 21; - public static readonly MULTILINE_COMMENT = 22; - public static readonly WS = 23; - public static readonly COLON = 24; - public static readonly PIPE = 25; - public static readonly QUOTED_STRING = 26; - public static readonly INTEGER_LITERAL = 27; - public static readonly DECIMAL_LITERAL = 28; - public static readonly BY = 29; - public static readonly AND = 30; - public static readonly ASC = 31; - public static readonly ASSIGN = 32; - public static readonly CAST_OP = 33; - public static readonly COMMA = 34; - public static readonly DESC = 35; - public static readonly DOT = 36; - public static readonly FALSE = 37; - public static readonly FIRST = 38; - public static readonly IN = 39; - public static readonly IS = 40; - public static readonly LAST = 41; - public static readonly LIKE = 42; - public static readonly LP = 43; - public static readonly NOT = 44; - public static readonly NULL = 45; - public static readonly NULLS = 46; - public static readonly OR = 47; - public static readonly PARAM = 48; - public static readonly RLIKE = 49; - public static readonly RP = 50; - public static readonly TRUE = 51; - public static readonly EQ = 52; - public static readonly CIEQ = 53; - public static readonly NEQ = 54; - public static readonly LT = 55; - public static readonly LTE = 56; - public static readonly GT = 57; - public static readonly GTE = 58; - public static readonly PLUS = 59; - public static readonly MINUS = 60; - public static readonly ASTERISK = 61; - public static readonly SLASH = 62; - public static readonly PERCENT = 63; - public static readonly NAMED_OR_POSITIONAL_PARAM = 64; - public static readonly OPENING_BRACKET = 65; - public static readonly CLOSING_BRACKET = 66; - public static readonly UNQUOTED_IDENTIFIER = 67; - public static readonly QUOTED_IDENTIFIER = 68; - public static readonly EXPR_LINE_COMMENT = 69; - public static readonly EXPR_MULTILINE_COMMENT = 70; - public static readonly EXPR_WS = 71; - public static readonly EXPLAIN_WS = 72; - public static readonly EXPLAIN_LINE_COMMENT = 73; - public static readonly EXPLAIN_MULTILINE_COMMENT = 74; - public static readonly METADATA = 75; - public static readonly UNQUOTED_SOURCE = 76; - public static readonly FROM_LINE_COMMENT = 77; - public static readonly FROM_MULTILINE_COMMENT = 78; - public static readonly FROM_WS = 79; - public static readonly ID_PATTERN = 80; - public static readonly PROJECT_LINE_COMMENT = 81; - public static readonly PROJECT_MULTILINE_COMMENT = 82; - public static readonly PROJECT_WS = 83; - public static readonly AS = 84; - public static readonly RENAME_LINE_COMMENT = 85; - public static readonly RENAME_MULTILINE_COMMENT = 86; - public static readonly RENAME_WS = 87; - public static readonly ON = 88; - public static readonly WITH = 89; - public static readonly ENRICH_POLICY_NAME = 90; - public static readonly ENRICH_LINE_COMMENT = 91; - public static readonly ENRICH_MULTILINE_COMMENT = 92; - public static readonly ENRICH_WS = 93; - public static readonly ENRICH_FIELD_LINE_COMMENT = 94; - public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 95; - public static readonly ENRICH_FIELD_WS = 96; - public static readonly MVEXPAND_LINE_COMMENT = 97; - public static readonly MVEXPAND_MULTILINE_COMMENT = 98; - public static readonly MVEXPAND_WS = 99; - public static readonly INFO = 100; - public static readonly SHOW_LINE_COMMENT = 101; - public static readonly SHOW_MULTILINE_COMMENT = 102; - public static readonly SHOW_WS = 103; - public static readonly SETTING = 104; - public static readonly SETTING_LINE_COMMENT = 105; - public static readonly SETTTING_MULTILINE_COMMENT = 106; - public static readonly SETTING_WS = 107; - public static readonly LOOKUP_LINE_COMMENT = 108; - public static readonly LOOKUP_MULTILINE_COMMENT = 109; - public static readonly LOOKUP_WS = 110; - public static readonly LOOKUP_FIELD_LINE_COMMENT = 111; - public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 112; - public static readonly LOOKUP_FIELD_WS = 113; - public static readonly METRICS_LINE_COMMENT = 114; - public static readonly METRICS_MULTILINE_COMMENT = 115; - public static readonly METRICS_WS = 116; - public static readonly CLOSING_METRICS_LINE_COMMENT = 117; - public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 118; - public static readonly CLOSING_METRICS_WS = 119; + public static readonly DEV_JOIN = 20; + public static readonly DEV_JOIN_FULL = 21; + public static readonly DEV_JOIN_LEFT = 22; + public static readonly DEV_JOIN_RIGHT = 23; + public static readonly DEV_JOIN_LOOKUP = 24; + public static readonly UNKNOWN_CMD = 25; + public static readonly LINE_COMMENT = 26; + public static readonly MULTILINE_COMMENT = 27; + public static readonly WS = 28; + public static readonly PIPE = 29; + public static readonly QUOTED_STRING = 30; + public static readonly INTEGER_LITERAL = 31; + public static readonly DECIMAL_LITERAL = 32; + public static readonly BY = 33; + public static readonly AND = 34; + public static readonly ASC = 35; + public static readonly ASSIGN = 36; + public static readonly CAST_OP = 37; + public static readonly COLON = 38; + public static readonly COMMA = 39; + public static readonly DESC = 40; + public static readonly DOT = 41; + public static readonly FALSE = 42; + public static readonly FIRST = 43; + public static readonly IN = 44; + public static readonly IS = 45; + public static readonly LAST = 46; + public static readonly LIKE = 47; + public static readonly LP = 48; + public static readonly NOT = 49; + public static readonly NULL = 50; + public static readonly NULLS = 51; + public static readonly OR = 52; + public static readonly PARAM = 53; + public static readonly RLIKE = 54; + public static readonly RP = 55; + public static readonly TRUE = 56; + public static readonly EQ = 57; + public static readonly CIEQ = 58; + public static readonly NEQ = 59; + public static readonly LT = 60; + public static readonly LTE = 61; + public static readonly GT = 62; + public static readonly GTE = 63; + public static readonly PLUS = 64; + public static readonly MINUS = 65; + public static readonly ASTERISK = 66; + public static readonly SLASH = 67; + public static readonly PERCENT = 68; + public static readonly NAMED_OR_POSITIONAL_PARAM = 69; + public static readonly OPENING_BRACKET = 70; + public static readonly CLOSING_BRACKET = 71; + public static readonly UNQUOTED_IDENTIFIER = 72; + public static readonly QUOTED_IDENTIFIER = 73; + public static readonly EXPR_LINE_COMMENT = 74; + public static readonly EXPR_MULTILINE_COMMENT = 75; + public static readonly EXPR_WS = 76; + public static readonly EXPLAIN_WS = 77; + public static readonly EXPLAIN_LINE_COMMENT = 78; + public static readonly EXPLAIN_MULTILINE_COMMENT = 79; + public static readonly METADATA = 80; + public static readonly UNQUOTED_SOURCE = 81; + public static readonly FROM_LINE_COMMENT = 82; + public static readonly FROM_MULTILINE_COMMENT = 83; + public static readonly FROM_WS = 84; + public static readonly ID_PATTERN = 85; + public static readonly PROJECT_LINE_COMMENT = 86; + public static readonly PROJECT_MULTILINE_COMMENT = 87; + public static readonly PROJECT_WS = 88; + public static readonly AS = 89; + public static readonly RENAME_LINE_COMMENT = 90; + public static readonly RENAME_MULTILINE_COMMENT = 91; + public static readonly RENAME_WS = 92; + public static readonly ON = 93; + public static readonly WITH = 94; + public static readonly ENRICH_POLICY_NAME = 95; + public static readonly ENRICH_LINE_COMMENT = 96; + public static readonly ENRICH_MULTILINE_COMMENT = 97; + public static readonly ENRICH_WS = 98; + public static readonly ENRICH_FIELD_LINE_COMMENT = 99; + public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 100; + public static readonly ENRICH_FIELD_WS = 101; + public static readonly MVEXPAND_LINE_COMMENT = 102; + public static readonly MVEXPAND_MULTILINE_COMMENT = 103; + public static readonly MVEXPAND_WS = 104; + public static readonly INFO = 105; + public static readonly SHOW_LINE_COMMENT = 106; + public static readonly SHOW_MULTILINE_COMMENT = 107; + public static readonly SHOW_WS = 108; + public static readonly SETTING = 109; + public static readonly SETTING_LINE_COMMENT = 110; + public static readonly SETTTING_MULTILINE_COMMENT = 111; + public static readonly SETTING_WS = 112; + public static readonly LOOKUP_LINE_COMMENT = 113; + public static readonly LOOKUP_MULTILINE_COMMENT = 114; + public static readonly LOOKUP_WS = 115; + public static readonly LOOKUP_FIELD_LINE_COMMENT = 116; + public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 117; + public static readonly LOOKUP_FIELD_WS = 118; + public static readonly USING = 119; + public static readonly JOIN_LINE_COMMENT = 120; + public static readonly JOIN_MULTILINE_COMMENT = 121; + public static readonly JOIN_WS = 122; + public static readonly METRICS_LINE_COMMENT = 123; + public static readonly METRICS_MULTILINE_COMMENT = 124; + public static readonly METRICS_WS = 125; + public static readonly CLOSING_METRICS_LINE_COMMENT = 126; + public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 127; + public static readonly CLOSING_METRICS_WS = 128; public static override readonly EOF = Token.EOF; public static readonly RULE_singleStatement = 0; public static readonly RULE_query = 1; @@ -210,6 +219,10 @@ export default class esql_parser extends parser_config { public static readonly RULE_enrichWithClause = 59; public static readonly RULE_lookupCommand = 60; public static readonly RULE_inlinestatsCommand = 61; + public static readonly RULE_joinCommand = 62; + public static readonly RULE_joinTarget = 63; + public static readonly RULE_joinCondition = 64; + public static readonly RULE_joinPredicate = 65; public static readonly literalNames: (string | null)[] = [ null, "'dissect'", "'drop'", "'enrich'", "'eval'", "'explain'", @@ -223,32 +236,35 @@ export default class esql_parser extends parser_config { null, null, null, null, null, null, - "':'", "'|'", + null, null, + null, null, + null, "'|'", null, null, null, "'by'", "'and'", "'asc'", "'='", "'::'", - "','", "'desc'", - "'.'", "'false'", - "'first'", "'in'", - "'is'", "'last'", - "'like'", "'('", - "'not'", "'null'", - "'nulls'", "'or'", - "'?'", "'rlike'", - "')'", "'true'", - "'=='", "'=~'", - "'!='", "'<'", - "'<='", "'>'", - "'>='", "'+'", - "'-'", "'*'", - "'/'", "'%'", + "':'", "','", + "'desc'", "'.'", + "'false'", "'first'", + "'in'", "'is'", + "'last'", "'like'", + "'('", "'not'", + "'null'", "'nulls'", + "'or'", "'?'", + "'rlike'", "')'", + "'true'", "'=='", + "'=~'", "'!='", + "'<'", "'<='", + "'>'", "'>='", + "'+'", "'-'", + "'*'", "'/'", + "'%'", null, + null, "']'", null, null, - "']'", null, null, null, null, null, null, null, - null, "'metadata'", + "'metadata'", null, null, null, null, null, null, @@ -261,7 +277,14 @@ export default class esql_parser extends parser_config { null, null, null, null, null, null, - "'info'" ]; + "'info'", null, + null, null, + null, null, + null, null, + null, null, + null, null, + null, null, + "'USING'" ]; public static readonly symbolicNames: (string | null)[] = [ null, "DISSECT", "DROP", "ENRICH", "EVAL", "EXPLAIN", @@ -274,30 +297,36 @@ export default class esql_parser extends parser_config { "DEV_INLINESTATS", "DEV_LOOKUP", "DEV_METRICS", + "DEV_JOIN", + "DEV_JOIN_FULL", + "DEV_JOIN_LEFT", + "DEV_JOIN_RIGHT", + "DEV_JOIN_LOOKUP", "UNKNOWN_CMD", "LINE_COMMENT", "MULTILINE_COMMENT", - "WS", "COLON", - "PIPE", "QUOTED_STRING", + "WS", "PIPE", + "QUOTED_STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "CAST_OP", - "COMMA", "DESC", - "DOT", "FALSE", - "FIRST", "IN", - "IS", "LAST", - "LIKE", "LP", - "NOT", "NULL", - "NULLS", "OR", - "PARAM", "RLIKE", - "RP", "TRUE", - "EQ", "CIEQ", - "NEQ", "LT", - "LTE", "GT", - "GTE", "PLUS", - "MINUS", "ASTERISK", + "COLON", "COMMA", + "DESC", "DOT", + "FALSE", "FIRST", + "IN", "IS", + "LAST", "LIKE", + "LP", "NOT", + "NULL", "NULLS", + "OR", "PARAM", + "RLIKE", "RP", + "TRUE", "EQ", + "CIEQ", "NEQ", + "LT", "LTE", + "GT", "GTE", + "PLUS", "MINUS", + "ASTERISK", "SLASH", "PERCENT", "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", @@ -346,6 +375,9 @@ export default class esql_parser extends parser_config { "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", + "USING", "JOIN_LINE_COMMENT", + "JOIN_MULTILINE_COMMENT", + "JOIN_WS", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", "METRICS_WS", @@ -366,7 +398,8 @@ export default class esql_parser extends parser_config { "renameCommand", "renameClause", "dissectCommand", "grokCommand", "mvExpandCommand", "commandOptions", "commandOption", "booleanValue", "numericValue", "decimalValue", "integerValue", "string", "comparisonOperator", "explainCommand", "subqueryExpression", - "showCommand", "enrichCommand", "enrichWithClause", "lookupCommand", "inlinestatsCommand", + "showCommand", "enrichCommand", "enrichWithClause", "lookupCommand", "inlinestatsCommand", + "joinCommand", "joinTarget", "joinCondition", "joinPredicate", ]; public get grammarFileName(): string { return "esql_parser.g4"; } public get literalNames(): (string | null)[] { return esql_parser.literalNames; } @@ -389,9 +422,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 124; + this.state = 132; this.query(0); - this.state = 125; + this.state = 133; this.match(esql_parser.EOF); } } @@ -433,11 +466,11 @@ export default class esql_parser extends parser_config { this._ctx = localctx; _prevctx = localctx; - this.state = 128; + this.state = 136; this.sourceCommand(); } this._ctx.stop = this._input.LT(-1); - this.state = 135; + this.state = 143; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 0, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -450,18 +483,18 @@ export default class esql_parser extends parser_config { { localctx = new CompositeQueryContext(this, new QueryContext(this, _parentctx, _parentState)); this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_query); - this.state = 130; + this.state = 138; if (!(this.precpred(this._ctx, 1))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 1)"); } - this.state = 131; + this.state = 139; this.match(esql_parser.PIPE); - this.state = 132; + this.state = 140; this.processingCommand(); } } } - this.state = 137; + this.state = 145; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 0, this._ctx); } @@ -486,45 +519,45 @@ export default class esql_parser extends parser_config { let localctx: SourceCommandContext = new SourceCommandContext(this, this._ctx, this.state); this.enterRule(localctx, 4, esql_parser.RULE_sourceCommand); try { - this.state = 144; + this.state = 152; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 1, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 138; + this.state = 146; this.explainCommand(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 139; + this.state = 147; this.fromCommand(); } break; case 3: this.enterOuterAlt(localctx, 3); { - this.state = 140; + this.state = 148; this.rowCommand(); } break; case 4: this.enterOuterAlt(localctx, 4); { - this.state = 141; + this.state = 149; this.showCommand(); } break; case 5: this.enterOuterAlt(localctx, 5); { - this.state = 142; + this.state = 150; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 143; + this.state = 151; this.metricsCommand(); } break; @@ -549,115 +582,126 @@ export default class esql_parser extends parser_config { let localctx: ProcessingCommandContext = new ProcessingCommandContext(this, this._ctx, this.state); this.enterRule(localctx, 6, esql_parser.RULE_processingCommand); try { - this.state = 162; + this.state = 172; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 2, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 146; + this.state = 154; this.evalCommand(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 147; + this.state = 155; this.whereCommand(); } break; case 3: this.enterOuterAlt(localctx, 3); { - this.state = 148; + this.state = 156; this.keepCommand(); } break; case 4: this.enterOuterAlt(localctx, 4); { - this.state = 149; + this.state = 157; this.limitCommand(); } break; case 5: this.enterOuterAlt(localctx, 5); { - this.state = 150; + this.state = 158; this.statsCommand(); } break; case 6: this.enterOuterAlt(localctx, 6); { - this.state = 151; + this.state = 159; this.sortCommand(); } break; case 7: this.enterOuterAlt(localctx, 7); { - this.state = 152; + this.state = 160; this.dropCommand(); } break; case 8: this.enterOuterAlt(localctx, 8); { - this.state = 153; + this.state = 161; this.renameCommand(); } break; case 9: this.enterOuterAlt(localctx, 9); { - this.state = 154; + this.state = 162; this.dissectCommand(); } break; case 10: this.enterOuterAlt(localctx, 10); { - this.state = 155; + this.state = 163; this.grokCommand(); } break; case 11: this.enterOuterAlt(localctx, 11); { - this.state = 156; + this.state = 164; this.enrichCommand(); } break; case 12: this.enterOuterAlt(localctx, 12); { - this.state = 157; + this.state = 165; this.mvExpandCommand(); } break; case 13: this.enterOuterAlt(localctx, 13); { - this.state = 158; + this.state = 166; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 159; + this.state = 167; this.inlinestatsCommand(); } break; case 14: this.enterOuterAlt(localctx, 14); { - this.state = 160; + this.state = 168; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 161; + this.state = 169; this.lookupCommand(); } break; + case 15: + this.enterOuterAlt(localctx, 15); + { + this.state = 170; + if (!(this.isDevVersion())) { + throw this.createFailedPredicateException("this.isDevVersion()"); + } + this.state = 171; + this.joinCommand(); + } + break; } } catch (re) { @@ -681,9 +725,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 164; + this.state = 174; this.match(esql_parser.WHERE); - this.state = 165; + this.state = 175; this.booleanExpression(0); } } @@ -721,7 +765,7 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 197; + this.state = 206; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 6, this._ctx) ) { case 1: @@ -730,9 +774,9 @@ export default class esql_parser extends parser_config { this._ctx = localctx; _prevctx = localctx; - this.state = 168; + this.state = 178; this.match(esql_parser.NOT); - this.state = 169; + this.state = 179; this.booleanExpression(8); } break; @@ -741,7 +785,7 @@ export default class esql_parser extends parser_config { localctx = new BooleanDefaultContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 170; + this.state = 180; this.valueExpression(); } break; @@ -750,7 +794,7 @@ export default class esql_parser extends parser_config { localctx = new RegexExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 171; + this.state = 181; this.regexBooleanExpression(); } break; @@ -759,41 +803,41 @@ export default class esql_parser extends parser_config { localctx = new LogicalInContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 172; + this.state = 182; this.valueExpression(); - this.state = 174; + this.state = 184; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===44) { + if (_la===49) { { - this.state = 173; + this.state = 183; this.match(esql_parser.NOT); } } - this.state = 176; + this.state = 186; this.match(esql_parser.IN); - this.state = 177; + this.state = 187; this.match(esql_parser.LP); - this.state = 178; + this.state = 188; this.valueExpression(); - this.state = 183; + this.state = 193; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===34) { + while (_la===39) { { { - this.state = 179; + this.state = 189; this.match(esql_parser.COMMA); - this.state = 180; + this.state = 190; this.valueExpression(); } } - this.state = 185; + this.state = 195; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 186; + this.state = 196; this.match(esql_parser.RP); } break; @@ -802,21 +846,21 @@ export default class esql_parser extends parser_config { localctx = new IsNullContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 188; + this.state = 198; this.valueExpression(); - this.state = 189; + this.state = 199; this.match(esql_parser.IS); - this.state = 191; + this.state = 201; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===44) { + if (_la===49) { { - this.state = 190; + this.state = 200; this.match(esql_parser.NOT); } } - this.state = 193; + this.state = 203; this.match(esql_parser.NULL); } break; @@ -825,17 +869,13 @@ export default class esql_parser extends parser_config { localctx = new MatchExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 195; - if (!(this.isDevVersion())) { - throw this.createFailedPredicateException("this.isDevVersion()"); - } - this.state = 196; + this.state = 205; this.matchBooleanExpression(); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 207; + this.state = 216; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 8, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -845,7 +885,7 @@ export default class esql_parser extends parser_config { } _prevctx = localctx; { - this.state = 205; + this.state = 214; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 7, this._ctx) ) { case 1: @@ -853,13 +893,13 @@ export default class esql_parser extends parser_config { localctx = new LogicalBinaryContext(this, new BooleanExpressionContext(this, _parentctx, _parentState)); (localctx as LogicalBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_booleanExpression); - this.state = 199; + this.state = 208; if (!(this.precpred(this._ctx, 5))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 5)"); } - this.state = 200; + this.state = 209; (localctx as LogicalBinaryContext)._operator = this.match(esql_parser.AND); - this.state = 201; + this.state = 210; (localctx as LogicalBinaryContext)._right = this.booleanExpression(6); } break; @@ -868,20 +908,20 @@ export default class esql_parser extends parser_config { localctx = new LogicalBinaryContext(this, new BooleanExpressionContext(this, _parentctx, _parentState)); (localctx as LogicalBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_booleanExpression); - this.state = 202; + this.state = 211; if (!(this.precpred(this._ctx, 4))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 4)"); } - this.state = 203; + this.state = 212; (localctx as LogicalBinaryContext)._operator = this.match(esql_parser.OR); - this.state = 204; + this.state = 213; (localctx as LogicalBinaryContext)._right = this.booleanExpression(5); } break; } } } - this.state = 209; + this.state = 218; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 8, this._ctx); } @@ -907,48 +947,48 @@ export default class esql_parser extends parser_config { this.enterRule(localctx, 12, esql_parser.RULE_regexBooleanExpression); let _la: number; try { - this.state = 224; + this.state = 233; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 11, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 210; + this.state = 219; this.valueExpression(); - this.state = 212; + this.state = 221; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===44) { + if (_la===49) { { - this.state = 211; + this.state = 220; this.match(esql_parser.NOT); } } - this.state = 214; + this.state = 223; localctx._kind = this.match(esql_parser.LIKE); - this.state = 215; + this.state = 224; localctx._pattern = this.string_(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 217; + this.state = 226; this.valueExpression(); - this.state = 219; + this.state = 228; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===44) { + if (_la===49) { { - this.state = 218; + this.state = 227; this.match(esql_parser.NOT); } } - this.state = 221; + this.state = 230; localctx._kind = this.match(esql_parser.RLIKE); - this.state = 222; + this.state = 231; localctx._pattern = this.string_(); } break; @@ -975,11 +1015,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 226; + this.state = 235; localctx._fieldExp = this.qualifiedName(); - this.state = 227; + this.state = 236; this.match(esql_parser.COLON); - this.state = 228; + this.state = 237; localctx._queryString = this.constant(); } } @@ -1002,14 +1042,14 @@ export default class esql_parser extends parser_config { let localctx: ValueExpressionContext = new ValueExpressionContext(this, this._ctx, this.state); this.enterRule(localctx, 16, esql_parser.RULE_valueExpression); try { - this.state = 235; + this.state = 244; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 12, this._ctx) ) { case 1: localctx = new ValueExpressionDefaultContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 230; + this.state = 239; this.operatorExpression(0); } break; @@ -1017,11 +1057,11 @@ export default class esql_parser extends parser_config { localctx = new ComparisonContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 231; + this.state = 240; (localctx as ComparisonContext)._left = this.operatorExpression(0); - this.state = 232; + this.state = 241; this.comparisonOperator(); - this.state = 233; + this.state = 242; (localctx as ComparisonContext)._right = this.operatorExpression(0); } break; @@ -1061,7 +1101,7 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 241; + this.state = 250; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 13, this._ctx) ) { case 1: @@ -1070,7 +1110,7 @@ export default class esql_parser extends parser_config { this._ctx = localctx; _prevctx = localctx; - this.state = 238; + this.state = 247; this.primaryExpression(0); } break; @@ -1079,23 +1119,23 @@ export default class esql_parser extends parser_config { localctx = new ArithmeticUnaryContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 239; + this.state = 248; (localctx as ArithmeticUnaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===59 || _la===60)) { + if(!(_la===64 || _la===65)) { (localctx as ArithmeticUnaryContext)._operator = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 240; + this.state = 249; this.operatorExpression(3); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 251; + this.state = 260; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 15, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -1105,7 +1145,7 @@ export default class esql_parser extends parser_config { } _prevctx = localctx; { - this.state = 249; + this.state = 258; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 14, this._ctx) ) { case 1: @@ -1113,21 +1153,21 @@ export default class esql_parser extends parser_config { localctx = new ArithmeticBinaryContext(this, new OperatorExpressionContext(this, _parentctx, _parentState)); (localctx as ArithmeticBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_operatorExpression); - this.state = 243; + this.state = 252; if (!(this.precpred(this._ctx, 2))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 2)"); } - this.state = 244; + this.state = 253; (localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(((((_la - 61)) & ~0x1F) === 0 && ((1 << (_la - 61)) & 7) !== 0))) { + if(!(((((_la - 66)) & ~0x1F) === 0 && ((1 << (_la - 66)) & 7) !== 0))) { (localctx as ArithmeticBinaryContext)._operator = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 245; + this.state = 254; (localctx as ArithmeticBinaryContext)._right = this.operatorExpression(3); } break; @@ -1136,28 +1176,28 @@ export default class esql_parser extends parser_config { localctx = new ArithmeticBinaryContext(this, new OperatorExpressionContext(this, _parentctx, _parentState)); (localctx as ArithmeticBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_operatorExpression); - this.state = 246; + this.state = 255; if (!(this.precpred(this._ctx, 1))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 1)"); } - this.state = 247; + this.state = 256; (localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===59 || _la===60)) { + if(!(_la===64 || _la===65)) { (localctx as ArithmeticBinaryContext)._operator = this._errHandler.recoverInline(this); } else { this._errHandler.reportMatch(this); this.consume(); } - this.state = 248; + this.state = 257; (localctx as ArithmeticBinaryContext)._right = this.operatorExpression(2); } break; } } } - this.state = 253; + this.state = 262; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 15, this._ctx); } @@ -1196,7 +1236,7 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 262; + this.state = 271; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 16, this._ctx) ) { case 1: @@ -1205,7 +1245,7 @@ export default class esql_parser extends parser_config { this._ctx = localctx; _prevctx = localctx; - this.state = 255; + this.state = 264; this.constant(); } break; @@ -1214,7 +1254,7 @@ export default class esql_parser extends parser_config { localctx = new DereferenceContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 256; + this.state = 265; this.qualifiedName(); } break; @@ -1223,7 +1263,7 @@ export default class esql_parser extends parser_config { localctx = new FunctionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 257; + this.state = 266; this.functionExpression(); } break; @@ -1232,17 +1272,17 @@ export default class esql_parser extends parser_config { localctx = new ParenthesizedExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 258; + this.state = 267; this.match(esql_parser.LP); - this.state = 259; + this.state = 268; this.booleanExpression(0); - this.state = 260; + this.state = 269; this.match(esql_parser.RP); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 269; + this.state = 278; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 17, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -1255,18 +1295,18 @@ export default class esql_parser extends parser_config { { localctx = new InlineCastContext(this, new PrimaryExpressionContext(this, _parentctx, _parentState)); this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_primaryExpression); - this.state = 264; + this.state = 273; if (!(this.precpred(this._ctx, 1))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 1)"); } - this.state = 265; + this.state = 274; this.match(esql_parser.CAST_OP); - this.state = 266; + this.state = 275; this.dataType(); } } } - this.state = 271; + this.state = 280; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 17, this._ctx); } @@ -1294,37 +1334,37 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 272; + this.state = 281; this.functionName(); - this.state = 273; + this.state = 282; this.match(esql_parser.LP); - this.state = 283; + this.state = 292; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 19, this._ctx) ) { case 1: { - this.state = 274; + this.state = 283; this.match(esql_parser.ASTERISK); } break; case 2: { { - this.state = 275; + this.state = 284; this.booleanExpression(0); - this.state = 280; + this.state = 289; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===34) { + while (_la===39) { { { - this.state = 276; + this.state = 285; this.match(esql_parser.COMMA); - this.state = 277; + this.state = 286; this.booleanExpression(0); } } - this.state = 282; + this.state = 291; this._errHandler.sync(this); _la = this._input.LA(1); } @@ -1332,7 +1372,7 @@ export default class esql_parser extends parser_config { } break; } - this.state = 285; + this.state = 294; this.match(esql_parser.RP); } } @@ -1357,7 +1397,7 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 287; + this.state = 296; this.identifierOrParameter(); } } @@ -1383,7 +1423,7 @@ export default class esql_parser extends parser_config { localctx = new ToDataTypeContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 289; + this.state = 298; this.identifier(); } } @@ -1408,9 +1448,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 291; + this.state = 300; this.match(esql_parser.ROW); - this.state = 292; + this.state = 301; this.fields(); } } @@ -1436,23 +1476,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 294; + this.state = 303; this.field(); - this.state = 299; + this.state = 308; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 295; + this.state = 304; this.match(esql_parser.COMMA); - this.state = 296; + this.state = 305; this.field(); } } } - this.state = 301; + this.state = 310; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); } @@ -1479,19 +1519,19 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 305; + this.state = 314; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 21, this._ctx) ) { case 1: { - this.state = 302; + this.state = 311; this.qualifiedName(); - this.state = 303; + this.state = 312; this.match(esql_parser.ASSIGN); } break; } - this.state = 307; + this.state = 316; this.booleanExpression(0); } } @@ -1517,34 +1557,34 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 309; + this.state = 318; this.match(esql_parser.FROM); - this.state = 310; + this.state = 319; this.indexPattern(); - this.state = 315; + this.state = 324; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 311; + this.state = 320; this.match(esql_parser.COMMA); - this.state = 312; + this.state = 321; this.indexPattern(); } } } - this.state = 317; + this.state = 326; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); } - this.state = 319; + this.state = 328; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 23, this._ctx) ) { case 1: { - this.state = 318; + this.state = 327; this.metadata(); } break; @@ -1572,19 +1612,19 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 324; + this.state = 333; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 24, this._ctx) ) { case 1: { - this.state = 321; + this.state = 330; this.clusterString(); - this.state = 322; + this.state = 331; this.match(esql_parser.COLON); } break; } - this.state = 326; + this.state = 335; this.indexString(); } } @@ -1609,7 +1649,7 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 328; + this.state = 337; this.match(esql_parser.UNQUOTED_SOURCE); } } @@ -1635,9 +1675,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 330; + this.state = 339; _la = this._input.LA(1); - if(!(_la===26 || _la===76)) { + if(!(_la===30 || _la===81)) { this._errHandler.recoverInline(this); } else { @@ -1665,20 +1705,20 @@ export default class esql_parser extends parser_config { let localctx: MetadataContext = new MetadataContext(this, this._ctx, this.state); this.enterRule(localctx, 42, esql_parser.RULE_metadata); try { - this.state = 334; + this.state = 343; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 75: + case 80: this.enterOuterAlt(localctx, 1); { - this.state = 332; + this.state = 341; this.metadataOption(); } break; - case 65: + case 70: this.enterOuterAlt(localctx, 2); { - this.state = 333; + this.state = 342; this.deprecated_metadata(); } break; @@ -1708,25 +1748,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 336; + this.state = 345; this.match(esql_parser.METADATA); - this.state = 337; + this.state = 346; this.match(esql_parser.UNQUOTED_SOURCE); - this.state = 342; + this.state = 351; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 26, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 338; + this.state = 347; this.match(esql_parser.COMMA); - this.state = 339; + this.state = 348; this.match(esql_parser.UNQUOTED_SOURCE); } } } - this.state = 344; + this.state = 353; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 26, this._ctx); } @@ -1753,11 +1793,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 345; + this.state = 354; this.match(esql_parser.OPENING_BRACKET); - this.state = 346; + this.state = 355; this.metadataOption(); - this.state = 347; + this.state = 356; this.match(esql_parser.CLOSING_BRACKET); } } @@ -1783,46 +1823,46 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 349; + this.state = 358; this.match(esql_parser.DEV_METRICS); - this.state = 350; + this.state = 359; this.indexPattern(); - this.state = 355; + this.state = 364; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 351; + this.state = 360; this.match(esql_parser.COMMA); - this.state = 352; + this.state = 361; this.indexPattern(); } } } - this.state = 357; + this.state = 366; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 27, this._ctx); } - this.state = 359; + this.state = 368; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 28, this._ctx) ) { case 1: { - this.state = 358; + this.state = 367; localctx._aggregates = this.aggFields(); } break; } - this.state = 363; + this.state = 372; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 29, this._ctx) ) { case 1: { - this.state = 361; + this.state = 370; this.match(esql_parser.BY); - this.state = 362; + this.state = 371; localctx._grouping = this.fields(); } break; @@ -1850,9 +1890,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 365; + this.state = 374; this.match(esql_parser.EVAL); - this.state = 366; + this.state = 375; this.fields(); } } @@ -1877,26 +1917,26 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 368; + this.state = 377; this.match(esql_parser.STATS); - this.state = 370; + this.state = 379; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 30, this._ctx) ) { case 1: { - this.state = 369; + this.state = 378; localctx._stats = this.aggFields(); } break; } - this.state = 374; + this.state = 383; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 31, this._ctx) ) { case 1: { - this.state = 372; + this.state = 381; this.match(esql_parser.BY); - this.state = 373; + this.state = 382; localctx._grouping = this.fields(); } break; @@ -1925,23 +1965,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 376; + this.state = 385; this.aggField(); - this.state = 381; + this.state = 390; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 377; + this.state = 386; this.match(esql_parser.COMMA); - this.state = 378; + this.state = 387; this.aggField(); } } } - this.state = 383; + this.state = 392; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); } @@ -1968,16 +2008,16 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 384; + this.state = 393; this.field(); - this.state = 387; + this.state = 396; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 33, this._ctx) ) { case 1: { - this.state = 385; + this.state = 394; this.match(esql_parser.WHERE); - this.state = 386; + this.state = 395; this.booleanExpression(0); } break; @@ -2006,23 +2046,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 389; + this.state = 398; this.identifierOrParameter(); - this.state = 394; + this.state = 403; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 390; + this.state = 399; this.match(esql_parser.DOT); - this.state = 391; + this.state = 400; this.identifierOrParameter(); } } } - this.state = 396; + this.state = 405; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); } @@ -2050,23 +2090,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 397; + this.state = 406; this.identifierPattern(); - this.state = 402; + this.state = 411; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 35, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 398; + this.state = 407; this.match(esql_parser.DOT); - this.state = 399; + this.state = 408; this.identifierPattern(); } } } - this.state = 404; + this.state = 413; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 35, this._ctx); } @@ -2094,23 +2134,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 405; + this.state = 414; this.qualifiedNamePattern(); - this.state = 410; + this.state = 419; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 36, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 406; + this.state = 415; this.match(esql_parser.COMMA); - this.state = 407; + this.state = 416; this.qualifiedNamePattern(); } } } - this.state = 412; + this.state = 421; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 36, this._ctx); } @@ -2138,9 +2178,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 413; + this.state = 422; _la = this._input.LA(1); - if(!(_la===67 || _la===68)) { + if(!(_la===72 || _la===73)) { this._errHandler.recoverInline(this); } else { @@ -2168,24 +2208,24 @@ export default class esql_parser extends parser_config { let localctx: IdentifierPatternContext = new IdentifierPatternContext(this, this._ctx, this.state); this.enterRule(localctx, 66, esql_parser.RULE_identifierPattern); try { - this.state = 418; + this.state = 427; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 37, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 415; + this.state = 424; this.match(esql_parser.ID_PATTERN); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 416; + this.state = 425; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 417; + this.state = 426; this.parameter(); } break; @@ -2211,14 +2251,14 @@ export default class esql_parser extends parser_config { this.enterRule(localctx, 68, esql_parser.RULE_constant); let _la: number; try { - this.state = 462; + this.state = 471; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 41, this._ctx) ) { case 1: localctx = new NullLiteralContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 420; + this.state = 429; this.match(esql_parser.NULL); } break; @@ -2226,9 +2266,9 @@ export default class esql_parser extends parser_config { localctx = new QualifiedIntegerLiteralContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 421; + this.state = 430; this.integerValue(); - this.state = 422; + this.state = 431; this.match(esql_parser.UNQUOTED_IDENTIFIER); } break; @@ -2236,7 +2276,7 @@ export default class esql_parser extends parser_config { localctx = new DecimalLiteralContext(this, localctx); this.enterOuterAlt(localctx, 3); { - this.state = 424; + this.state = 433; this.decimalValue(); } break; @@ -2244,7 +2284,7 @@ export default class esql_parser extends parser_config { localctx = new IntegerLiteralContext(this, localctx); this.enterOuterAlt(localctx, 4); { - this.state = 425; + this.state = 434; this.integerValue(); } break; @@ -2252,7 +2292,7 @@ export default class esql_parser extends parser_config { localctx = new BooleanLiteralContext(this, localctx); this.enterOuterAlt(localctx, 5); { - this.state = 426; + this.state = 435; this.booleanValue(); } break; @@ -2260,7 +2300,7 @@ export default class esql_parser extends parser_config { localctx = new InputParameterContext(this, localctx); this.enterOuterAlt(localctx, 6); { - this.state = 427; + this.state = 436; this.parameter(); } break; @@ -2268,7 +2308,7 @@ export default class esql_parser extends parser_config { localctx = new StringLiteralContext(this, localctx); this.enterOuterAlt(localctx, 7); { - this.state = 428; + this.state = 437; this.string_(); } break; @@ -2276,27 +2316,27 @@ export default class esql_parser extends parser_config { localctx = new NumericArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 8); { - this.state = 429; + this.state = 438; this.match(esql_parser.OPENING_BRACKET); - this.state = 430; + this.state = 439; this.numericValue(); - this.state = 435; + this.state = 444; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===34) { + while (_la===39) { { { - this.state = 431; + this.state = 440; this.match(esql_parser.COMMA); - this.state = 432; + this.state = 441; this.numericValue(); } } - this.state = 437; + this.state = 446; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 438; + this.state = 447; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2304,27 +2344,27 @@ export default class esql_parser extends parser_config { localctx = new BooleanArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 9); { - this.state = 440; + this.state = 449; this.match(esql_parser.OPENING_BRACKET); - this.state = 441; + this.state = 450; this.booleanValue(); - this.state = 446; + this.state = 455; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===34) { + while (_la===39) { { { - this.state = 442; + this.state = 451; this.match(esql_parser.COMMA); - this.state = 443; + this.state = 452; this.booleanValue(); } } - this.state = 448; + this.state = 457; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 449; + this.state = 458; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2332,27 +2372,27 @@ export default class esql_parser extends parser_config { localctx = new StringArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 10); { - this.state = 451; + this.state = 460; this.match(esql_parser.OPENING_BRACKET); - this.state = 452; + this.state = 461; this.string_(); - this.state = 457; + this.state = 466; this._errHandler.sync(this); _la = this._input.LA(1); - while (_la===34) { + while (_la===39) { { { - this.state = 453; + this.state = 462; this.match(esql_parser.COMMA); - this.state = 454; + this.state = 463; this.string_(); } } - this.state = 459; + this.state = 468; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 460; + this.state = 469; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2377,22 +2417,22 @@ export default class esql_parser extends parser_config { let localctx: ParameterContext = new ParameterContext(this, this._ctx, this.state); this.enterRule(localctx, 70, esql_parser.RULE_parameter); try { - this.state = 466; + this.state = 475; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 48: + case 53: localctx = new InputParamContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 464; + this.state = 473; this.match(esql_parser.PARAM); } break; - case 64: + case 69: localctx = new InputNamedOrPositionalParamContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 465; + this.state = 474; this.match(esql_parser.NAMED_OR_POSITIONAL_PARAM); } break; @@ -2419,24 +2459,24 @@ export default class esql_parser extends parser_config { let localctx: IdentifierOrParameterContext = new IdentifierOrParameterContext(this, this._ctx, this.state); this.enterRule(localctx, 72, esql_parser.RULE_identifierOrParameter); try { - this.state = 471; + this.state = 480; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 43, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 468; + this.state = 477; this.identifier(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 469; + this.state = 478; if (!(this.isDevVersion())) { throw this.createFailedPredicateException("this.isDevVersion()"); } - this.state = 470; + this.state = 479; this.parameter(); } break; @@ -2463,9 +2503,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 473; + this.state = 482; this.match(esql_parser.LIMIT); - this.state = 474; + this.state = 483; this.match(esql_parser.INTEGER_LITERAL); } } @@ -2491,25 +2531,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 476; + this.state = 485; this.match(esql_parser.SORT); - this.state = 477; + this.state = 486; this.orderExpression(); - this.state = 482; + this.state = 491; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 44, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 478; + this.state = 487; this.match(esql_parser.COMMA); - this.state = 479; + this.state = 488; this.orderExpression(); } } } - this.state = 484; + this.state = 493; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 44, this._ctx); } @@ -2537,17 +2577,17 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 485; + this.state = 494; this.booleanExpression(0); - this.state = 487; + this.state = 496; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 45, this._ctx) ) { case 1: { - this.state = 486; + this.state = 495; localctx._ordering = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===31 || _la===35)) { + if(!(_la===35 || _la===40)) { localctx._ordering = this._errHandler.recoverInline(this); } else { @@ -2557,17 +2597,17 @@ export default class esql_parser extends parser_config { } break; } - this.state = 491; + this.state = 500; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 46, this._ctx) ) { case 1: { - this.state = 489; + this.state = 498; this.match(esql_parser.NULLS); - this.state = 490; + this.state = 499; localctx._nullOrdering = this._input.LT(1); _la = this._input.LA(1); - if(!(_la===38 || _la===41)) { + if(!(_la===43 || _la===46)) { localctx._nullOrdering = this._errHandler.recoverInline(this); } else { @@ -2600,9 +2640,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 493; + this.state = 502; this.match(esql_parser.KEEP); - this.state = 494; + this.state = 503; this.qualifiedNamePatterns(); } } @@ -2627,9 +2667,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 496; + this.state = 505; this.match(esql_parser.DROP); - this.state = 497; + this.state = 506; this.qualifiedNamePatterns(); } } @@ -2655,25 +2695,25 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 499; + this.state = 508; this.match(esql_parser.RENAME); - this.state = 500; + this.state = 509; this.renameClause(); - this.state = 505; + this.state = 514; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 47, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 501; + this.state = 510; this.match(esql_parser.COMMA); - this.state = 502; + this.state = 511; this.renameClause(); } } } - this.state = 507; + this.state = 516; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 47, this._ctx); } @@ -2700,11 +2740,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 508; + this.state = 517; localctx._oldName = this.qualifiedNamePattern(); - this.state = 509; + this.state = 518; this.match(esql_parser.AS); - this.state = 510; + this.state = 519; localctx._newName = this.qualifiedNamePattern(); } } @@ -2729,18 +2769,18 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 512; + this.state = 521; this.match(esql_parser.DISSECT); - this.state = 513; + this.state = 522; this.primaryExpression(0); - this.state = 514; + this.state = 523; this.string_(); - this.state = 516; + this.state = 525; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 48, this._ctx) ) { case 1: { - this.state = 515; + this.state = 524; this.commandOptions(); } break; @@ -2768,11 +2808,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 518; + this.state = 527; this.match(esql_parser.GROK); - this.state = 519; + this.state = 528; this.primaryExpression(0); - this.state = 520; + this.state = 529; this.string_(); } } @@ -2797,9 +2837,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 522; + this.state = 531; this.match(esql_parser.MV_EXPAND); - this.state = 523; + this.state = 532; this.qualifiedName(); } } @@ -2825,23 +2865,23 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 525; + this.state = 534; this.commandOption(); - this.state = 530; + this.state = 539; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 49, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 526; + this.state = 535; this.match(esql_parser.COMMA); - this.state = 527; + this.state = 536; this.commandOption(); } } } - this.state = 532; + this.state = 541; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 49, this._ctx); } @@ -2868,11 +2908,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 533; + this.state = 542; this.identifier(); - this.state = 534; + this.state = 543; this.match(esql_parser.ASSIGN); - this.state = 535; + this.state = 544; this.constant(); } } @@ -2898,9 +2938,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 537; + this.state = 546; _la = this._input.LA(1); - if(!(_la===37 || _la===51)) { + if(!(_la===42 || _la===56)) { this._errHandler.recoverInline(this); } else { @@ -2928,20 +2968,20 @@ export default class esql_parser extends parser_config { let localctx: NumericValueContext = new NumericValueContext(this, this._ctx, this.state); this.enterRule(localctx, 100, esql_parser.RULE_numericValue); try { - this.state = 541; + this.state = 550; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 50, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 539; + this.state = 548; this.decimalValue(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 540; + this.state = 549; this.integerValue(); } break; @@ -2969,14 +3009,14 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 544; + this.state = 553; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===59 || _la===60) { + if (_la===64 || _la===65) { { - this.state = 543; + this.state = 552; _la = this._input.LA(1); - if(!(_la===59 || _la===60)) { + if(!(_la===64 || _la===65)) { this._errHandler.recoverInline(this); } else { @@ -2986,7 +3026,7 @@ export default class esql_parser extends parser_config { } } - this.state = 546; + this.state = 555; this.match(esql_parser.DECIMAL_LITERAL); } } @@ -3012,14 +3052,14 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 549; + this.state = 558; this._errHandler.sync(this); _la = this._input.LA(1); - if (_la===59 || _la===60) { + if (_la===64 || _la===65) { { - this.state = 548; + this.state = 557; _la = this._input.LA(1); - if(!(_la===59 || _la===60)) { + if(!(_la===64 || _la===65)) { this._errHandler.recoverInline(this); } else { @@ -3029,7 +3069,7 @@ export default class esql_parser extends parser_config { } } - this.state = 551; + this.state = 560; this.match(esql_parser.INTEGER_LITERAL); } } @@ -3054,7 +3094,7 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 553; + this.state = 562; this.match(esql_parser.QUOTED_STRING); } } @@ -3080,9 +3120,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 555; + this.state = 564; _la = this._input.LA(1); - if(!(((((_la - 52)) & ~0x1F) === 0 && ((1 << (_la - 52)) & 125) !== 0))) { + if(!(((((_la - 57)) & ~0x1F) === 0 && ((1 << (_la - 57)) & 125) !== 0))) { this._errHandler.recoverInline(this); } else { @@ -3112,9 +3152,9 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 557; + this.state = 566; this.match(esql_parser.EXPLAIN); - this.state = 558; + this.state = 567; this.subqueryExpression(); } } @@ -3139,11 +3179,11 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 560; + this.state = 569; this.match(esql_parser.OPENING_BRACKET); - this.state = 561; + this.state = 570; this.query(0); - this.state = 562; + this.state = 571; this.match(esql_parser.CLOSING_BRACKET); } } @@ -3169,9 +3209,9 @@ export default class esql_parser extends parser_config { localctx = new ShowInfoContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 564; + this.state = 573; this.match(esql_parser.SHOW); - this.state = 565; + this.state = 574; this.match(esql_parser.INFO); } } @@ -3197,46 +3237,46 @@ export default class esql_parser extends parser_config { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 567; + this.state = 576; this.match(esql_parser.ENRICH); - this.state = 568; + this.state = 577; localctx._policyName = this.match(esql_parser.ENRICH_POLICY_NAME); - this.state = 571; + this.state = 580; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 53, this._ctx) ) { case 1: { - this.state = 569; + this.state = 578; this.match(esql_parser.ON); - this.state = 570; + this.state = 579; localctx._matchField = this.qualifiedNamePattern(); } break; } - this.state = 582; + this.state = 591; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 55, this._ctx) ) { case 1: { - this.state = 573; + this.state = 582; this.match(esql_parser.WITH); - this.state = 574; + this.state = 583; this.enrichWithClause(); - this.state = 579; + this.state = 588; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 54, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 575; + this.state = 584; this.match(esql_parser.COMMA); - this.state = 576; + this.state = 585; this.enrichWithClause(); } } } - this.state = 581; + this.state = 590; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 54, this._ctx); } @@ -3266,19 +3306,19 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 587; + this.state = 596; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 56, this._ctx) ) { case 1: { - this.state = 584; + this.state = 593; localctx._newName = this.qualifiedNamePattern(); - this.state = 585; + this.state = 594; this.match(esql_parser.ASSIGN); } break; } - this.state = 589; + this.state = 598; localctx._enrichField = this.qualifiedNamePattern(); } } @@ -3303,13 +3343,13 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 591; + this.state = 600; this.match(esql_parser.DEV_LOOKUP); - this.state = 592; + this.state = 601; localctx._tableName = this.indexPattern(); - this.state = 593; + this.state = 602; this.match(esql_parser.ON); - this.state = 594; + this.state = 603; localctx._matchFields = this.qualifiedNamePatterns(); } } @@ -3334,18 +3374,18 @@ export default class esql_parser extends parser_config { try { this.enterOuterAlt(localctx, 1); { - this.state = 596; + this.state = 605; this.match(esql_parser.DEV_INLINESTATS); - this.state = 597; + this.state = 606; localctx._stats = this.aggFields(); - this.state = 600; + this.state = 609; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 57, this._ctx) ) { case 1: { - this.state = 598; + this.state = 607; this.match(esql_parser.BY); - this.state = 599; + this.state = 608; localctx._grouping = this.fields(); } break; @@ -3366,6 +3406,163 @@ export default class esql_parser extends parser_config { } return localctx; } + // @RuleVersion(0) + public joinCommand(): JoinCommandContext { + let localctx: JoinCommandContext = new JoinCommandContext(this, this._ctx, this.state); + this.enterRule(localctx, 124, esql_parser.RULE_joinCommand); + let _la: number; + try { + this.enterOuterAlt(localctx, 1); + { + this.state = 612; + this._errHandler.sync(this); + _la = this._input.LA(1); + if ((((_la) & ~0x1F) === 0 && ((1 << _la) & 29360128) !== 0)) { + { + this.state = 611; + localctx._type_ = this._input.LT(1); + _la = this._input.LA(1); + if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 29360128) !== 0))) { + localctx._type_ = this._errHandler.recoverInline(this); + } + else { + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + + this.state = 614; + this.match(esql_parser.DEV_JOIN); + this.state = 615; + this.joinTarget(); + this.state = 616; + this.joinCondition(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) + public joinTarget(): JoinTargetContext { + let localctx: JoinTargetContext = new JoinTargetContext(this, this._ctx, this.state); + this.enterRule(localctx, 126, esql_parser.RULE_joinTarget); + let _la: number; + try { + this.enterOuterAlt(localctx, 1); + { + this.state = 618; + localctx._index = this.identifier(); + this.state = 621; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la===89) { + { + this.state = 619; + this.match(esql_parser.AS); + this.state = 620; + localctx._alias = this.identifier(); + } + } + + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) + public joinCondition(): JoinConditionContext { + let localctx: JoinConditionContext = new JoinConditionContext(this, this._ctx, this.state); + this.enterRule(localctx, 128, esql_parser.RULE_joinCondition); + try { + let _alt: number; + this.enterOuterAlt(localctx, 1); + { + this.state = 623; + this.match(esql_parser.ON); + this.state = 624; + this.joinPredicate(); + this.state = 629; + this._errHandler.sync(this); + _alt = this._interp.adaptivePredict(this._input, 60, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 625; + this.match(esql_parser.COMMA); + this.state = 626; + this.joinPredicate(); + } + } + } + this.state = 631; + this._errHandler.sync(this); + _alt = this._interp.adaptivePredict(this._input, 60, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) + public joinPredicate(): JoinPredicateContext { + let localctx: JoinPredicateContext = new JoinPredicateContext(this, this._ctx, this.state); + this.enterRule(localctx, 130, esql_parser.RULE_joinPredicate); + try { + this.enterOuterAlt(localctx, 1); + { + this.state = 632; + this.valueExpression(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } public sempred(localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { switch (ruleIndex) { @@ -3408,13 +3605,13 @@ export default class esql_parser extends parser_config { return this.isDevVersion(); case 3: return this.isDevVersion(); + case 4: + return this.isDevVersion(); } return true; } private booleanExpression_sempred(localctx: BooleanExpressionContext, predIndex: number): boolean { switch (predIndex) { - case 4: - return this.isDevVersion(); case 5: return this.precpred(this._ctx, 5); case 6: @@ -3453,7 +3650,7 @@ export default class esql_parser extends parser_config { return true; } - public static readonly _serializedATN: number[] = [4,1,119,603,2,0,7,0, + public static readonly _serializedATN: number[] = [4,1,128,635,2,0,7,0, 2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9, 2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2, 17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24, @@ -3462,194 +3659,204 @@ export default class esql_parser extends parser_config { 2,39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7,45,2, 46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7,52,2,53, 7,53,2,54,7,54,2,55,7,55,2,56,7,56,2,57,7,57,2,58,7,58,2,59,7,59,2,60,7, - 60,2,61,7,61,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,5,1,134,8,1,10,1,12,1, - 137,9,1,1,2,1,2,1,2,1,2,1,2,1,2,3,2,145,8,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3, - 1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,3,3,163,8,3,1,4,1,4,1,4,1,5,1,5,1,5, - 1,5,1,5,1,5,1,5,3,5,175,8,5,1,5,1,5,1,5,1,5,1,5,5,5,182,8,5,10,5,12,5,185, - 9,5,1,5,1,5,1,5,1,5,1,5,3,5,192,8,5,1,5,1,5,1,5,1,5,3,5,198,8,5,1,5,1,5, - 1,5,1,5,1,5,1,5,5,5,206,8,5,10,5,12,5,209,9,5,1,6,1,6,3,6,213,8,6,1,6,1, - 6,1,6,1,6,1,6,3,6,220,8,6,1,6,1,6,1,6,3,6,225,8,6,1,7,1,7,1,7,1,7,1,8,1, - 8,1,8,1,8,1,8,3,8,236,8,8,1,9,1,9,1,9,1,9,3,9,242,8,9,1,9,1,9,1,9,1,9,1, - 9,1,9,5,9,250,8,9,10,9,12,9,253,9,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10, - 1,10,3,10,263,8,10,1,10,1,10,1,10,5,10,268,8,10,10,10,12,10,271,9,10,1, - 11,1,11,1,11,1,11,1,11,1,11,5,11,279,8,11,10,11,12,11,282,9,11,3,11,284, - 8,11,1,11,1,11,1,12,1,12,1,13,1,13,1,14,1,14,1,14,1,15,1,15,1,15,5,15,298, - 8,15,10,15,12,15,301,9,15,1,16,1,16,1,16,3,16,306,8,16,1,16,1,16,1,17,1, - 17,1,17,1,17,5,17,314,8,17,10,17,12,17,317,9,17,1,17,3,17,320,8,17,1,18, - 1,18,1,18,3,18,325,8,18,1,18,1,18,1,19,1,19,1,20,1,20,1,21,1,21,3,21,335, - 8,21,1,22,1,22,1,22,1,22,5,22,341,8,22,10,22,12,22,344,9,22,1,23,1,23,1, - 23,1,23,1,24,1,24,1,24,1,24,5,24,354,8,24,10,24,12,24,357,9,24,1,24,3,24, - 360,8,24,1,24,1,24,3,24,364,8,24,1,25,1,25,1,25,1,26,1,26,3,26,371,8,26, - 1,26,1,26,3,26,375,8,26,1,27,1,27,1,27,5,27,380,8,27,10,27,12,27,383,9, - 27,1,28,1,28,1,28,3,28,388,8,28,1,29,1,29,1,29,5,29,393,8,29,10,29,12,29, - 396,9,29,1,30,1,30,1,30,5,30,401,8,30,10,30,12,30,404,9,30,1,31,1,31,1, - 31,5,31,409,8,31,10,31,12,31,412,9,31,1,32,1,32,1,33,1,33,1,33,3,33,419, - 8,33,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,5, - 34,434,8,34,10,34,12,34,437,9,34,1,34,1,34,1,34,1,34,1,34,1,34,5,34,445, - 8,34,10,34,12,34,448,9,34,1,34,1,34,1,34,1,34,1,34,1,34,5,34,456,8,34,10, - 34,12,34,459,9,34,1,34,1,34,3,34,463,8,34,1,35,1,35,3,35,467,8,35,1,36, - 1,36,1,36,3,36,472,8,36,1,37,1,37,1,37,1,38,1,38,1,38,1,38,5,38,481,8,38, - 10,38,12,38,484,9,38,1,39,1,39,3,39,488,8,39,1,39,1,39,3,39,492,8,39,1, - 40,1,40,1,40,1,41,1,41,1,41,1,42,1,42,1,42,1,42,5,42,504,8,42,10,42,12, - 42,507,9,42,1,43,1,43,1,43,1,43,1,44,1,44,1,44,1,44,3,44,517,8,44,1,45, - 1,45,1,45,1,45,1,46,1,46,1,46,1,47,1,47,1,47,5,47,529,8,47,10,47,12,47, - 532,9,47,1,48,1,48,1,48,1,48,1,49,1,49,1,50,1,50,3,50,542,8,50,1,51,3,51, - 545,8,51,1,51,1,51,1,52,3,52,550,8,52,1,52,1,52,1,53,1,53,1,54,1,54,1,55, - 1,55,1,55,1,56,1,56,1,56,1,56,1,57,1,57,1,57,1,58,1,58,1,58,1,58,3,58,572, - 8,58,1,58,1,58,1,58,1,58,5,58,578,8,58,10,58,12,58,581,9,58,3,58,583,8, - 58,1,59,1,59,1,59,3,59,588,8,59,1,59,1,59,1,60,1,60,1,60,1,60,1,60,1,61, - 1,61,1,61,1,61,3,61,601,8,61,1,61,0,4,2,10,18,20,62,0,2,4,6,8,10,12,14, - 16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62, - 64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108, - 110,112,114,116,118,120,122,0,8,1,0,59,60,1,0,61,63,2,0,26,26,76,76,1,0, - 67,68,2,0,31,31,35,35,2,0,38,38,41,41,2,0,37,37,51,51,2,0,52,52,54,58,628, - 0,124,1,0,0,0,2,127,1,0,0,0,4,144,1,0,0,0,6,162,1,0,0,0,8,164,1,0,0,0,10, - 197,1,0,0,0,12,224,1,0,0,0,14,226,1,0,0,0,16,235,1,0,0,0,18,241,1,0,0,0, - 20,262,1,0,0,0,22,272,1,0,0,0,24,287,1,0,0,0,26,289,1,0,0,0,28,291,1,0, - 0,0,30,294,1,0,0,0,32,305,1,0,0,0,34,309,1,0,0,0,36,324,1,0,0,0,38,328, - 1,0,0,0,40,330,1,0,0,0,42,334,1,0,0,0,44,336,1,0,0,0,46,345,1,0,0,0,48, - 349,1,0,0,0,50,365,1,0,0,0,52,368,1,0,0,0,54,376,1,0,0,0,56,384,1,0,0,0, - 58,389,1,0,0,0,60,397,1,0,0,0,62,405,1,0,0,0,64,413,1,0,0,0,66,418,1,0, - 0,0,68,462,1,0,0,0,70,466,1,0,0,0,72,471,1,0,0,0,74,473,1,0,0,0,76,476, - 1,0,0,0,78,485,1,0,0,0,80,493,1,0,0,0,82,496,1,0,0,0,84,499,1,0,0,0,86, - 508,1,0,0,0,88,512,1,0,0,0,90,518,1,0,0,0,92,522,1,0,0,0,94,525,1,0,0,0, - 96,533,1,0,0,0,98,537,1,0,0,0,100,541,1,0,0,0,102,544,1,0,0,0,104,549,1, - 0,0,0,106,553,1,0,0,0,108,555,1,0,0,0,110,557,1,0,0,0,112,560,1,0,0,0,114, - 564,1,0,0,0,116,567,1,0,0,0,118,587,1,0,0,0,120,591,1,0,0,0,122,596,1,0, - 0,0,124,125,3,2,1,0,125,126,5,0,0,1,126,1,1,0,0,0,127,128,6,1,-1,0,128, - 129,3,4,2,0,129,135,1,0,0,0,130,131,10,1,0,0,131,132,5,25,0,0,132,134,3, - 6,3,0,133,130,1,0,0,0,134,137,1,0,0,0,135,133,1,0,0,0,135,136,1,0,0,0,136, - 3,1,0,0,0,137,135,1,0,0,0,138,145,3,110,55,0,139,145,3,34,17,0,140,145, - 3,28,14,0,141,145,3,114,57,0,142,143,4,2,1,0,143,145,3,48,24,0,144,138, - 1,0,0,0,144,139,1,0,0,0,144,140,1,0,0,0,144,141,1,0,0,0,144,142,1,0,0,0, - 145,5,1,0,0,0,146,163,3,50,25,0,147,163,3,8,4,0,148,163,3,80,40,0,149,163, - 3,74,37,0,150,163,3,52,26,0,151,163,3,76,38,0,152,163,3,82,41,0,153,163, - 3,84,42,0,154,163,3,88,44,0,155,163,3,90,45,0,156,163,3,116,58,0,157,163, - 3,92,46,0,158,159,4,3,2,0,159,163,3,122,61,0,160,161,4,3,3,0,161,163,3, - 120,60,0,162,146,1,0,0,0,162,147,1,0,0,0,162,148,1,0,0,0,162,149,1,0,0, - 0,162,150,1,0,0,0,162,151,1,0,0,0,162,152,1,0,0,0,162,153,1,0,0,0,162,154, - 1,0,0,0,162,155,1,0,0,0,162,156,1,0,0,0,162,157,1,0,0,0,162,158,1,0,0,0, - 162,160,1,0,0,0,163,7,1,0,0,0,164,165,5,16,0,0,165,166,3,10,5,0,166,9,1, - 0,0,0,167,168,6,5,-1,0,168,169,5,44,0,0,169,198,3,10,5,8,170,198,3,16,8, - 0,171,198,3,12,6,0,172,174,3,16,8,0,173,175,5,44,0,0,174,173,1,0,0,0,174, - 175,1,0,0,0,175,176,1,0,0,0,176,177,5,39,0,0,177,178,5,43,0,0,178,183,3, - 16,8,0,179,180,5,34,0,0,180,182,3,16,8,0,181,179,1,0,0,0,182,185,1,0,0, - 0,183,181,1,0,0,0,183,184,1,0,0,0,184,186,1,0,0,0,185,183,1,0,0,0,186,187, - 5,50,0,0,187,198,1,0,0,0,188,189,3,16,8,0,189,191,5,40,0,0,190,192,5,44, - 0,0,191,190,1,0,0,0,191,192,1,0,0,0,192,193,1,0,0,0,193,194,5,45,0,0,194, - 198,1,0,0,0,195,196,4,5,4,0,196,198,3,14,7,0,197,167,1,0,0,0,197,170,1, - 0,0,0,197,171,1,0,0,0,197,172,1,0,0,0,197,188,1,0,0,0,197,195,1,0,0,0,198, - 207,1,0,0,0,199,200,10,5,0,0,200,201,5,30,0,0,201,206,3,10,5,6,202,203, - 10,4,0,0,203,204,5,47,0,0,204,206,3,10,5,5,205,199,1,0,0,0,205,202,1,0, - 0,0,206,209,1,0,0,0,207,205,1,0,0,0,207,208,1,0,0,0,208,11,1,0,0,0,209, - 207,1,0,0,0,210,212,3,16,8,0,211,213,5,44,0,0,212,211,1,0,0,0,212,213,1, - 0,0,0,213,214,1,0,0,0,214,215,5,42,0,0,215,216,3,106,53,0,216,225,1,0,0, - 0,217,219,3,16,8,0,218,220,5,44,0,0,219,218,1,0,0,0,219,220,1,0,0,0,220, - 221,1,0,0,0,221,222,5,49,0,0,222,223,3,106,53,0,223,225,1,0,0,0,224,210, - 1,0,0,0,224,217,1,0,0,0,225,13,1,0,0,0,226,227,3,58,29,0,227,228,5,24,0, - 0,228,229,3,68,34,0,229,15,1,0,0,0,230,236,3,18,9,0,231,232,3,18,9,0,232, - 233,3,108,54,0,233,234,3,18,9,0,234,236,1,0,0,0,235,230,1,0,0,0,235,231, - 1,0,0,0,236,17,1,0,0,0,237,238,6,9,-1,0,238,242,3,20,10,0,239,240,7,0,0, - 0,240,242,3,18,9,3,241,237,1,0,0,0,241,239,1,0,0,0,242,251,1,0,0,0,243, - 244,10,2,0,0,244,245,7,1,0,0,245,250,3,18,9,3,246,247,10,1,0,0,247,248, - 7,0,0,0,248,250,3,18,9,2,249,243,1,0,0,0,249,246,1,0,0,0,250,253,1,0,0, - 0,251,249,1,0,0,0,251,252,1,0,0,0,252,19,1,0,0,0,253,251,1,0,0,0,254,255, - 6,10,-1,0,255,263,3,68,34,0,256,263,3,58,29,0,257,263,3,22,11,0,258,259, - 5,43,0,0,259,260,3,10,5,0,260,261,5,50,0,0,261,263,1,0,0,0,262,254,1,0, - 0,0,262,256,1,0,0,0,262,257,1,0,0,0,262,258,1,0,0,0,263,269,1,0,0,0,264, - 265,10,1,0,0,265,266,5,33,0,0,266,268,3,26,13,0,267,264,1,0,0,0,268,271, - 1,0,0,0,269,267,1,0,0,0,269,270,1,0,0,0,270,21,1,0,0,0,271,269,1,0,0,0, - 272,273,3,24,12,0,273,283,5,43,0,0,274,284,5,61,0,0,275,280,3,10,5,0,276, - 277,5,34,0,0,277,279,3,10,5,0,278,276,1,0,0,0,279,282,1,0,0,0,280,278,1, - 0,0,0,280,281,1,0,0,0,281,284,1,0,0,0,282,280,1,0,0,0,283,274,1,0,0,0,283, - 275,1,0,0,0,283,284,1,0,0,0,284,285,1,0,0,0,285,286,5,50,0,0,286,23,1,0, - 0,0,287,288,3,72,36,0,288,25,1,0,0,0,289,290,3,64,32,0,290,27,1,0,0,0,291, - 292,5,12,0,0,292,293,3,30,15,0,293,29,1,0,0,0,294,299,3,32,16,0,295,296, - 5,34,0,0,296,298,3,32,16,0,297,295,1,0,0,0,298,301,1,0,0,0,299,297,1,0, - 0,0,299,300,1,0,0,0,300,31,1,0,0,0,301,299,1,0,0,0,302,303,3,58,29,0,303, - 304,5,32,0,0,304,306,1,0,0,0,305,302,1,0,0,0,305,306,1,0,0,0,306,307,1, - 0,0,0,307,308,3,10,5,0,308,33,1,0,0,0,309,310,5,6,0,0,310,315,3,36,18,0, - 311,312,5,34,0,0,312,314,3,36,18,0,313,311,1,0,0,0,314,317,1,0,0,0,315, - 313,1,0,0,0,315,316,1,0,0,0,316,319,1,0,0,0,317,315,1,0,0,0,318,320,3,42, - 21,0,319,318,1,0,0,0,319,320,1,0,0,0,320,35,1,0,0,0,321,322,3,38,19,0,322, - 323,5,24,0,0,323,325,1,0,0,0,324,321,1,0,0,0,324,325,1,0,0,0,325,326,1, - 0,0,0,326,327,3,40,20,0,327,37,1,0,0,0,328,329,5,76,0,0,329,39,1,0,0,0, - 330,331,7,2,0,0,331,41,1,0,0,0,332,335,3,44,22,0,333,335,3,46,23,0,334, - 332,1,0,0,0,334,333,1,0,0,0,335,43,1,0,0,0,336,337,5,75,0,0,337,342,5,76, - 0,0,338,339,5,34,0,0,339,341,5,76,0,0,340,338,1,0,0,0,341,344,1,0,0,0,342, - 340,1,0,0,0,342,343,1,0,0,0,343,45,1,0,0,0,344,342,1,0,0,0,345,346,5,65, - 0,0,346,347,3,44,22,0,347,348,5,66,0,0,348,47,1,0,0,0,349,350,5,19,0,0, - 350,355,3,36,18,0,351,352,5,34,0,0,352,354,3,36,18,0,353,351,1,0,0,0,354, - 357,1,0,0,0,355,353,1,0,0,0,355,356,1,0,0,0,356,359,1,0,0,0,357,355,1,0, - 0,0,358,360,3,54,27,0,359,358,1,0,0,0,359,360,1,0,0,0,360,363,1,0,0,0,361, - 362,5,29,0,0,362,364,3,30,15,0,363,361,1,0,0,0,363,364,1,0,0,0,364,49,1, - 0,0,0,365,366,5,4,0,0,366,367,3,30,15,0,367,51,1,0,0,0,368,370,5,15,0,0, - 369,371,3,54,27,0,370,369,1,0,0,0,370,371,1,0,0,0,371,374,1,0,0,0,372,373, - 5,29,0,0,373,375,3,30,15,0,374,372,1,0,0,0,374,375,1,0,0,0,375,53,1,0,0, - 0,376,381,3,56,28,0,377,378,5,34,0,0,378,380,3,56,28,0,379,377,1,0,0,0, - 380,383,1,0,0,0,381,379,1,0,0,0,381,382,1,0,0,0,382,55,1,0,0,0,383,381, - 1,0,0,0,384,387,3,32,16,0,385,386,5,16,0,0,386,388,3,10,5,0,387,385,1,0, - 0,0,387,388,1,0,0,0,388,57,1,0,0,0,389,394,3,72,36,0,390,391,5,36,0,0,391, - 393,3,72,36,0,392,390,1,0,0,0,393,396,1,0,0,0,394,392,1,0,0,0,394,395,1, - 0,0,0,395,59,1,0,0,0,396,394,1,0,0,0,397,402,3,66,33,0,398,399,5,36,0,0, - 399,401,3,66,33,0,400,398,1,0,0,0,401,404,1,0,0,0,402,400,1,0,0,0,402,403, - 1,0,0,0,403,61,1,0,0,0,404,402,1,0,0,0,405,410,3,60,30,0,406,407,5,34,0, - 0,407,409,3,60,30,0,408,406,1,0,0,0,409,412,1,0,0,0,410,408,1,0,0,0,410, - 411,1,0,0,0,411,63,1,0,0,0,412,410,1,0,0,0,413,414,7,3,0,0,414,65,1,0,0, - 0,415,419,5,80,0,0,416,417,4,33,10,0,417,419,3,70,35,0,418,415,1,0,0,0, - 418,416,1,0,0,0,419,67,1,0,0,0,420,463,5,45,0,0,421,422,3,104,52,0,422, - 423,5,67,0,0,423,463,1,0,0,0,424,463,3,102,51,0,425,463,3,104,52,0,426, - 463,3,98,49,0,427,463,3,70,35,0,428,463,3,106,53,0,429,430,5,65,0,0,430, - 435,3,100,50,0,431,432,5,34,0,0,432,434,3,100,50,0,433,431,1,0,0,0,434, - 437,1,0,0,0,435,433,1,0,0,0,435,436,1,0,0,0,436,438,1,0,0,0,437,435,1,0, - 0,0,438,439,5,66,0,0,439,463,1,0,0,0,440,441,5,65,0,0,441,446,3,98,49,0, - 442,443,5,34,0,0,443,445,3,98,49,0,444,442,1,0,0,0,445,448,1,0,0,0,446, - 444,1,0,0,0,446,447,1,0,0,0,447,449,1,0,0,0,448,446,1,0,0,0,449,450,5,66, - 0,0,450,463,1,0,0,0,451,452,5,65,0,0,452,457,3,106,53,0,453,454,5,34,0, - 0,454,456,3,106,53,0,455,453,1,0,0,0,456,459,1,0,0,0,457,455,1,0,0,0,457, - 458,1,0,0,0,458,460,1,0,0,0,459,457,1,0,0,0,460,461,5,66,0,0,461,463,1, - 0,0,0,462,420,1,0,0,0,462,421,1,0,0,0,462,424,1,0,0,0,462,425,1,0,0,0,462, - 426,1,0,0,0,462,427,1,0,0,0,462,428,1,0,0,0,462,429,1,0,0,0,462,440,1,0, - 0,0,462,451,1,0,0,0,463,69,1,0,0,0,464,467,5,48,0,0,465,467,5,64,0,0,466, - 464,1,0,0,0,466,465,1,0,0,0,467,71,1,0,0,0,468,472,3,64,32,0,469,470,4, - 36,11,0,470,472,3,70,35,0,471,468,1,0,0,0,471,469,1,0,0,0,472,73,1,0,0, - 0,473,474,5,9,0,0,474,475,5,27,0,0,475,75,1,0,0,0,476,477,5,14,0,0,477, - 482,3,78,39,0,478,479,5,34,0,0,479,481,3,78,39,0,480,478,1,0,0,0,481,484, - 1,0,0,0,482,480,1,0,0,0,482,483,1,0,0,0,483,77,1,0,0,0,484,482,1,0,0,0, - 485,487,3,10,5,0,486,488,7,4,0,0,487,486,1,0,0,0,487,488,1,0,0,0,488,491, - 1,0,0,0,489,490,5,46,0,0,490,492,7,5,0,0,491,489,1,0,0,0,491,492,1,0,0, - 0,492,79,1,0,0,0,493,494,5,8,0,0,494,495,3,62,31,0,495,81,1,0,0,0,496,497, - 5,2,0,0,497,498,3,62,31,0,498,83,1,0,0,0,499,500,5,11,0,0,500,505,3,86, - 43,0,501,502,5,34,0,0,502,504,3,86,43,0,503,501,1,0,0,0,504,507,1,0,0,0, - 505,503,1,0,0,0,505,506,1,0,0,0,506,85,1,0,0,0,507,505,1,0,0,0,508,509, - 3,60,30,0,509,510,5,84,0,0,510,511,3,60,30,0,511,87,1,0,0,0,512,513,5,1, - 0,0,513,514,3,20,10,0,514,516,3,106,53,0,515,517,3,94,47,0,516,515,1,0, - 0,0,516,517,1,0,0,0,517,89,1,0,0,0,518,519,5,7,0,0,519,520,3,20,10,0,520, - 521,3,106,53,0,521,91,1,0,0,0,522,523,5,10,0,0,523,524,3,58,29,0,524,93, - 1,0,0,0,525,530,3,96,48,0,526,527,5,34,0,0,527,529,3,96,48,0,528,526,1, - 0,0,0,529,532,1,0,0,0,530,528,1,0,0,0,530,531,1,0,0,0,531,95,1,0,0,0,532, - 530,1,0,0,0,533,534,3,64,32,0,534,535,5,32,0,0,535,536,3,68,34,0,536,97, - 1,0,0,0,537,538,7,6,0,0,538,99,1,0,0,0,539,542,3,102,51,0,540,542,3,104, - 52,0,541,539,1,0,0,0,541,540,1,0,0,0,542,101,1,0,0,0,543,545,7,0,0,0,544, - 543,1,0,0,0,544,545,1,0,0,0,545,546,1,0,0,0,546,547,5,28,0,0,547,103,1, - 0,0,0,548,550,7,0,0,0,549,548,1,0,0,0,549,550,1,0,0,0,550,551,1,0,0,0,551, - 552,5,27,0,0,552,105,1,0,0,0,553,554,5,26,0,0,554,107,1,0,0,0,555,556,7, - 7,0,0,556,109,1,0,0,0,557,558,5,5,0,0,558,559,3,112,56,0,559,111,1,0,0, - 0,560,561,5,65,0,0,561,562,3,2,1,0,562,563,5,66,0,0,563,113,1,0,0,0,564, - 565,5,13,0,0,565,566,5,100,0,0,566,115,1,0,0,0,567,568,5,3,0,0,568,571, - 5,90,0,0,569,570,5,88,0,0,570,572,3,60,30,0,571,569,1,0,0,0,571,572,1,0, - 0,0,572,582,1,0,0,0,573,574,5,89,0,0,574,579,3,118,59,0,575,576,5,34,0, - 0,576,578,3,118,59,0,577,575,1,0,0,0,578,581,1,0,0,0,579,577,1,0,0,0,579, - 580,1,0,0,0,580,583,1,0,0,0,581,579,1,0,0,0,582,573,1,0,0,0,582,583,1,0, - 0,0,583,117,1,0,0,0,584,585,3,60,30,0,585,586,5,32,0,0,586,588,1,0,0,0, - 587,584,1,0,0,0,587,588,1,0,0,0,588,589,1,0,0,0,589,590,3,60,30,0,590,119, - 1,0,0,0,591,592,5,18,0,0,592,593,3,36,18,0,593,594,5,88,0,0,594,595,3,62, - 31,0,595,121,1,0,0,0,596,597,5,17,0,0,597,600,3,54,27,0,598,599,5,29,0, - 0,599,601,3,30,15,0,600,598,1,0,0,0,600,601,1,0,0,0,601,123,1,0,0,0,58, - 135,144,162,174,183,191,197,205,207,212,219,224,235,241,249,251,262,269, - 280,283,299,305,315,319,324,334,342,355,359,363,370,374,381,387,394,402, - 410,418,435,446,457,462,466,471,482,487,491,505,516,530,541,544,549,571, - 579,582,587,600]; + 60,2,61,7,61,2,62,7,62,2,63,7,63,2,64,7,64,2,65,7,65,1,0,1,0,1,0,1,1,1, + 1,1,1,1,1,1,1,1,1,5,1,142,8,1,10,1,12,1,145,9,1,1,2,1,2,1,2,1,2,1,2,1,2, + 3,2,153,8,2,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3, + 1,3,1,3,1,3,3,3,173,8,3,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,3,5,185, + 8,5,1,5,1,5,1,5,1,5,1,5,5,5,192,8,5,10,5,12,5,195,9,5,1,5,1,5,1,5,1,5,1, + 5,3,5,202,8,5,1,5,1,5,1,5,3,5,207,8,5,1,5,1,5,1,5,1,5,1,5,1,5,5,5,215,8, + 5,10,5,12,5,218,9,5,1,6,1,6,3,6,222,8,6,1,6,1,6,1,6,1,6,1,6,3,6,229,8,6, + 1,6,1,6,1,6,3,6,234,8,6,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,3,8,245,8,8, + 1,9,1,9,1,9,1,9,3,9,251,8,9,1,9,1,9,1,9,1,9,1,9,1,9,5,9,259,8,9,10,9,12, + 9,262,9,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,3,10,272,8,10,1,10,1, + 10,1,10,5,10,277,8,10,10,10,12,10,280,9,10,1,11,1,11,1,11,1,11,1,11,1,11, + 5,11,288,8,11,10,11,12,11,291,9,11,3,11,293,8,11,1,11,1,11,1,12,1,12,1, + 13,1,13,1,14,1,14,1,14,1,15,1,15,1,15,5,15,307,8,15,10,15,12,15,310,9,15, + 1,16,1,16,1,16,3,16,315,8,16,1,16,1,16,1,17,1,17,1,17,1,17,5,17,323,8,17, + 10,17,12,17,326,9,17,1,17,3,17,329,8,17,1,18,1,18,1,18,3,18,334,8,18,1, + 18,1,18,1,19,1,19,1,20,1,20,1,21,1,21,3,21,344,8,21,1,22,1,22,1,22,1,22, + 5,22,350,8,22,10,22,12,22,353,9,22,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1, + 24,5,24,363,8,24,10,24,12,24,366,9,24,1,24,3,24,369,8,24,1,24,1,24,3,24, + 373,8,24,1,25,1,25,1,25,1,26,1,26,3,26,380,8,26,1,26,1,26,3,26,384,8,26, + 1,27,1,27,1,27,5,27,389,8,27,10,27,12,27,392,9,27,1,28,1,28,1,28,3,28,397, + 8,28,1,29,1,29,1,29,5,29,402,8,29,10,29,12,29,405,9,29,1,30,1,30,1,30,5, + 30,410,8,30,10,30,12,30,413,9,30,1,31,1,31,1,31,5,31,418,8,31,10,31,12, + 31,421,9,31,1,32,1,32,1,33,1,33,1,33,3,33,428,8,33,1,34,1,34,1,34,1,34, + 1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,1,34,5,34,443,8,34,10,34,12,34, + 446,9,34,1,34,1,34,1,34,1,34,1,34,1,34,5,34,454,8,34,10,34,12,34,457,9, + 34,1,34,1,34,1,34,1,34,1,34,1,34,5,34,465,8,34,10,34,12,34,468,9,34,1,34, + 1,34,3,34,472,8,34,1,35,1,35,3,35,476,8,35,1,36,1,36,1,36,3,36,481,8,36, + 1,37,1,37,1,37,1,38,1,38,1,38,1,38,5,38,490,8,38,10,38,12,38,493,9,38,1, + 39,1,39,3,39,497,8,39,1,39,1,39,3,39,501,8,39,1,40,1,40,1,40,1,41,1,41, + 1,41,1,42,1,42,1,42,1,42,5,42,513,8,42,10,42,12,42,516,9,42,1,43,1,43,1, + 43,1,43,1,44,1,44,1,44,1,44,3,44,526,8,44,1,45,1,45,1,45,1,45,1,46,1,46, + 1,46,1,47,1,47,1,47,5,47,538,8,47,10,47,12,47,541,9,47,1,48,1,48,1,48,1, + 48,1,49,1,49,1,50,1,50,3,50,551,8,50,1,51,3,51,554,8,51,1,51,1,51,1,52, + 3,52,559,8,52,1,52,1,52,1,53,1,53,1,54,1,54,1,55,1,55,1,55,1,56,1,56,1, + 56,1,56,1,57,1,57,1,57,1,58,1,58,1,58,1,58,3,58,581,8,58,1,58,1,58,1,58, + 1,58,5,58,587,8,58,10,58,12,58,590,9,58,3,58,592,8,58,1,59,1,59,1,59,3, + 59,597,8,59,1,59,1,59,1,60,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,61,3,61, + 610,8,61,1,62,3,62,613,8,62,1,62,1,62,1,62,1,62,1,63,1,63,1,63,3,63,622, + 8,63,1,64,1,64,1,64,1,64,5,64,628,8,64,10,64,12,64,631,9,64,1,65,1,65,1, + 65,0,4,2,10,18,20,66,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36, + 38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84, + 86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124, + 126,128,130,0,9,1,0,64,65,1,0,66,68,2,0,30,30,81,81,1,0,72,73,2,0,35,35, + 40,40,2,0,43,43,46,46,2,0,42,42,56,56,2,0,57,57,59,63,1,0,22,24,660,0,132, + 1,0,0,0,2,135,1,0,0,0,4,152,1,0,0,0,6,172,1,0,0,0,8,174,1,0,0,0,10,206, + 1,0,0,0,12,233,1,0,0,0,14,235,1,0,0,0,16,244,1,0,0,0,18,250,1,0,0,0,20, + 271,1,0,0,0,22,281,1,0,0,0,24,296,1,0,0,0,26,298,1,0,0,0,28,300,1,0,0,0, + 30,303,1,0,0,0,32,314,1,0,0,0,34,318,1,0,0,0,36,333,1,0,0,0,38,337,1,0, + 0,0,40,339,1,0,0,0,42,343,1,0,0,0,44,345,1,0,0,0,46,354,1,0,0,0,48,358, + 1,0,0,0,50,374,1,0,0,0,52,377,1,0,0,0,54,385,1,0,0,0,56,393,1,0,0,0,58, + 398,1,0,0,0,60,406,1,0,0,0,62,414,1,0,0,0,64,422,1,0,0,0,66,427,1,0,0,0, + 68,471,1,0,0,0,70,475,1,0,0,0,72,480,1,0,0,0,74,482,1,0,0,0,76,485,1,0, + 0,0,78,494,1,0,0,0,80,502,1,0,0,0,82,505,1,0,0,0,84,508,1,0,0,0,86,517, + 1,0,0,0,88,521,1,0,0,0,90,527,1,0,0,0,92,531,1,0,0,0,94,534,1,0,0,0,96, + 542,1,0,0,0,98,546,1,0,0,0,100,550,1,0,0,0,102,553,1,0,0,0,104,558,1,0, + 0,0,106,562,1,0,0,0,108,564,1,0,0,0,110,566,1,0,0,0,112,569,1,0,0,0,114, + 573,1,0,0,0,116,576,1,0,0,0,118,596,1,0,0,0,120,600,1,0,0,0,122,605,1,0, + 0,0,124,612,1,0,0,0,126,618,1,0,0,0,128,623,1,0,0,0,130,632,1,0,0,0,132, + 133,3,2,1,0,133,134,5,0,0,1,134,1,1,0,0,0,135,136,6,1,-1,0,136,137,3,4, + 2,0,137,143,1,0,0,0,138,139,10,1,0,0,139,140,5,29,0,0,140,142,3,6,3,0,141, + 138,1,0,0,0,142,145,1,0,0,0,143,141,1,0,0,0,143,144,1,0,0,0,144,3,1,0,0, + 0,145,143,1,0,0,0,146,153,3,110,55,0,147,153,3,34,17,0,148,153,3,28,14, + 0,149,153,3,114,57,0,150,151,4,2,1,0,151,153,3,48,24,0,152,146,1,0,0,0, + 152,147,1,0,0,0,152,148,1,0,0,0,152,149,1,0,0,0,152,150,1,0,0,0,153,5,1, + 0,0,0,154,173,3,50,25,0,155,173,3,8,4,0,156,173,3,80,40,0,157,173,3,74, + 37,0,158,173,3,52,26,0,159,173,3,76,38,0,160,173,3,82,41,0,161,173,3,84, + 42,0,162,173,3,88,44,0,163,173,3,90,45,0,164,173,3,116,58,0,165,173,3,92, + 46,0,166,167,4,3,2,0,167,173,3,122,61,0,168,169,4,3,3,0,169,173,3,120,60, + 0,170,171,4,3,4,0,171,173,3,124,62,0,172,154,1,0,0,0,172,155,1,0,0,0,172, + 156,1,0,0,0,172,157,1,0,0,0,172,158,1,0,0,0,172,159,1,0,0,0,172,160,1,0, + 0,0,172,161,1,0,0,0,172,162,1,0,0,0,172,163,1,0,0,0,172,164,1,0,0,0,172, + 165,1,0,0,0,172,166,1,0,0,0,172,168,1,0,0,0,172,170,1,0,0,0,173,7,1,0,0, + 0,174,175,5,16,0,0,175,176,3,10,5,0,176,9,1,0,0,0,177,178,6,5,-1,0,178, + 179,5,49,0,0,179,207,3,10,5,8,180,207,3,16,8,0,181,207,3,12,6,0,182,184, + 3,16,8,0,183,185,5,49,0,0,184,183,1,0,0,0,184,185,1,0,0,0,185,186,1,0,0, + 0,186,187,5,44,0,0,187,188,5,48,0,0,188,193,3,16,8,0,189,190,5,39,0,0,190, + 192,3,16,8,0,191,189,1,0,0,0,192,195,1,0,0,0,193,191,1,0,0,0,193,194,1, + 0,0,0,194,196,1,0,0,0,195,193,1,0,0,0,196,197,5,55,0,0,197,207,1,0,0,0, + 198,199,3,16,8,0,199,201,5,45,0,0,200,202,5,49,0,0,201,200,1,0,0,0,201, + 202,1,0,0,0,202,203,1,0,0,0,203,204,5,50,0,0,204,207,1,0,0,0,205,207,3, + 14,7,0,206,177,1,0,0,0,206,180,1,0,0,0,206,181,1,0,0,0,206,182,1,0,0,0, + 206,198,1,0,0,0,206,205,1,0,0,0,207,216,1,0,0,0,208,209,10,5,0,0,209,210, + 5,34,0,0,210,215,3,10,5,6,211,212,10,4,0,0,212,213,5,52,0,0,213,215,3,10, + 5,5,214,208,1,0,0,0,214,211,1,0,0,0,215,218,1,0,0,0,216,214,1,0,0,0,216, + 217,1,0,0,0,217,11,1,0,0,0,218,216,1,0,0,0,219,221,3,16,8,0,220,222,5,49, + 0,0,221,220,1,0,0,0,221,222,1,0,0,0,222,223,1,0,0,0,223,224,5,47,0,0,224, + 225,3,106,53,0,225,234,1,0,0,0,226,228,3,16,8,0,227,229,5,49,0,0,228,227, + 1,0,0,0,228,229,1,0,0,0,229,230,1,0,0,0,230,231,5,54,0,0,231,232,3,106, + 53,0,232,234,1,0,0,0,233,219,1,0,0,0,233,226,1,0,0,0,234,13,1,0,0,0,235, + 236,3,58,29,0,236,237,5,38,0,0,237,238,3,68,34,0,238,15,1,0,0,0,239,245, + 3,18,9,0,240,241,3,18,9,0,241,242,3,108,54,0,242,243,3,18,9,0,243,245,1, + 0,0,0,244,239,1,0,0,0,244,240,1,0,0,0,245,17,1,0,0,0,246,247,6,9,-1,0,247, + 251,3,20,10,0,248,249,7,0,0,0,249,251,3,18,9,3,250,246,1,0,0,0,250,248, + 1,0,0,0,251,260,1,0,0,0,252,253,10,2,0,0,253,254,7,1,0,0,254,259,3,18,9, + 3,255,256,10,1,0,0,256,257,7,0,0,0,257,259,3,18,9,2,258,252,1,0,0,0,258, + 255,1,0,0,0,259,262,1,0,0,0,260,258,1,0,0,0,260,261,1,0,0,0,261,19,1,0, + 0,0,262,260,1,0,0,0,263,264,6,10,-1,0,264,272,3,68,34,0,265,272,3,58,29, + 0,266,272,3,22,11,0,267,268,5,48,0,0,268,269,3,10,5,0,269,270,5,55,0,0, + 270,272,1,0,0,0,271,263,1,0,0,0,271,265,1,0,0,0,271,266,1,0,0,0,271,267, + 1,0,0,0,272,278,1,0,0,0,273,274,10,1,0,0,274,275,5,37,0,0,275,277,3,26, + 13,0,276,273,1,0,0,0,277,280,1,0,0,0,278,276,1,0,0,0,278,279,1,0,0,0,279, + 21,1,0,0,0,280,278,1,0,0,0,281,282,3,24,12,0,282,292,5,48,0,0,283,293,5, + 66,0,0,284,289,3,10,5,0,285,286,5,39,0,0,286,288,3,10,5,0,287,285,1,0,0, + 0,288,291,1,0,0,0,289,287,1,0,0,0,289,290,1,0,0,0,290,293,1,0,0,0,291,289, + 1,0,0,0,292,283,1,0,0,0,292,284,1,0,0,0,292,293,1,0,0,0,293,294,1,0,0,0, + 294,295,5,55,0,0,295,23,1,0,0,0,296,297,3,72,36,0,297,25,1,0,0,0,298,299, + 3,64,32,0,299,27,1,0,0,0,300,301,5,12,0,0,301,302,3,30,15,0,302,29,1,0, + 0,0,303,308,3,32,16,0,304,305,5,39,0,0,305,307,3,32,16,0,306,304,1,0,0, + 0,307,310,1,0,0,0,308,306,1,0,0,0,308,309,1,0,0,0,309,31,1,0,0,0,310,308, + 1,0,0,0,311,312,3,58,29,0,312,313,5,36,0,0,313,315,1,0,0,0,314,311,1,0, + 0,0,314,315,1,0,0,0,315,316,1,0,0,0,316,317,3,10,5,0,317,33,1,0,0,0,318, + 319,5,6,0,0,319,324,3,36,18,0,320,321,5,39,0,0,321,323,3,36,18,0,322,320, + 1,0,0,0,323,326,1,0,0,0,324,322,1,0,0,0,324,325,1,0,0,0,325,328,1,0,0,0, + 326,324,1,0,0,0,327,329,3,42,21,0,328,327,1,0,0,0,328,329,1,0,0,0,329,35, + 1,0,0,0,330,331,3,38,19,0,331,332,5,38,0,0,332,334,1,0,0,0,333,330,1,0, + 0,0,333,334,1,0,0,0,334,335,1,0,0,0,335,336,3,40,20,0,336,37,1,0,0,0,337, + 338,5,81,0,0,338,39,1,0,0,0,339,340,7,2,0,0,340,41,1,0,0,0,341,344,3,44, + 22,0,342,344,3,46,23,0,343,341,1,0,0,0,343,342,1,0,0,0,344,43,1,0,0,0,345, + 346,5,80,0,0,346,351,5,81,0,0,347,348,5,39,0,0,348,350,5,81,0,0,349,347, + 1,0,0,0,350,353,1,0,0,0,351,349,1,0,0,0,351,352,1,0,0,0,352,45,1,0,0,0, + 353,351,1,0,0,0,354,355,5,70,0,0,355,356,3,44,22,0,356,357,5,71,0,0,357, + 47,1,0,0,0,358,359,5,19,0,0,359,364,3,36,18,0,360,361,5,39,0,0,361,363, + 3,36,18,0,362,360,1,0,0,0,363,366,1,0,0,0,364,362,1,0,0,0,364,365,1,0,0, + 0,365,368,1,0,0,0,366,364,1,0,0,0,367,369,3,54,27,0,368,367,1,0,0,0,368, + 369,1,0,0,0,369,372,1,0,0,0,370,371,5,33,0,0,371,373,3,30,15,0,372,370, + 1,0,0,0,372,373,1,0,0,0,373,49,1,0,0,0,374,375,5,4,0,0,375,376,3,30,15, + 0,376,51,1,0,0,0,377,379,5,15,0,0,378,380,3,54,27,0,379,378,1,0,0,0,379, + 380,1,0,0,0,380,383,1,0,0,0,381,382,5,33,0,0,382,384,3,30,15,0,383,381, + 1,0,0,0,383,384,1,0,0,0,384,53,1,0,0,0,385,390,3,56,28,0,386,387,5,39,0, + 0,387,389,3,56,28,0,388,386,1,0,0,0,389,392,1,0,0,0,390,388,1,0,0,0,390, + 391,1,0,0,0,391,55,1,0,0,0,392,390,1,0,0,0,393,396,3,32,16,0,394,395,5, + 16,0,0,395,397,3,10,5,0,396,394,1,0,0,0,396,397,1,0,0,0,397,57,1,0,0,0, + 398,403,3,72,36,0,399,400,5,41,0,0,400,402,3,72,36,0,401,399,1,0,0,0,402, + 405,1,0,0,0,403,401,1,0,0,0,403,404,1,0,0,0,404,59,1,0,0,0,405,403,1,0, + 0,0,406,411,3,66,33,0,407,408,5,41,0,0,408,410,3,66,33,0,409,407,1,0,0, + 0,410,413,1,0,0,0,411,409,1,0,0,0,411,412,1,0,0,0,412,61,1,0,0,0,413,411, + 1,0,0,0,414,419,3,60,30,0,415,416,5,39,0,0,416,418,3,60,30,0,417,415,1, + 0,0,0,418,421,1,0,0,0,419,417,1,0,0,0,419,420,1,0,0,0,420,63,1,0,0,0,421, + 419,1,0,0,0,422,423,7,3,0,0,423,65,1,0,0,0,424,428,5,85,0,0,425,426,4,33, + 10,0,426,428,3,70,35,0,427,424,1,0,0,0,427,425,1,0,0,0,428,67,1,0,0,0,429, + 472,5,50,0,0,430,431,3,104,52,0,431,432,5,72,0,0,432,472,1,0,0,0,433,472, + 3,102,51,0,434,472,3,104,52,0,435,472,3,98,49,0,436,472,3,70,35,0,437,472, + 3,106,53,0,438,439,5,70,0,0,439,444,3,100,50,0,440,441,5,39,0,0,441,443, + 3,100,50,0,442,440,1,0,0,0,443,446,1,0,0,0,444,442,1,0,0,0,444,445,1,0, + 0,0,445,447,1,0,0,0,446,444,1,0,0,0,447,448,5,71,0,0,448,472,1,0,0,0,449, + 450,5,70,0,0,450,455,3,98,49,0,451,452,5,39,0,0,452,454,3,98,49,0,453,451, + 1,0,0,0,454,457,1,0,0,0,455,453,1,0,0,0,455,456,1,0,0,0,456,458,1,0,0,0, + 457,455,1,0,0,0,458,459,5,71,0,0,459,472,1,0,0,0,460,461,5,70,0,0,461,466, + 3,106,53,0,462,463,5,39,0,0,463,465,3,106,53,0,464,462,1,0,0,0,465,468, + 1,0,0,0,466,464,1,0,0,0,466,467,1,0,0,0,467,469,1,0,0,0,468,466,1,0,0,0, + 469,470,5,71,0,0,470,472,1,0,0,0,471,429,1,0,0,0,471,430,1,0,0,0,471,433, + 1,0,0,0,471,434,1,0,0,0,471,435,1,0,0,0,471,436,1,0,0,0,471,437,1,0,0,0, + 471,438,1,0,0,0,471,449,1,0,0,0,471,460,1,0,0,0,472,69,1,0,0,0,473,476, + 5,53,0,0,474,476,5,69,0,0,475,473,1,0,0,0,475,474,1,0,0,0,476,71,1,0,0, + 0,477,481,3,64,32,0,478,479,4,36,11,0,479,481,3,70,35,0,480,477,1,0,0,0, + 480,478,1,0,0,0,481,73,1,0,0,0,482,483,5,9,0,0,483,484,5,31,0,0,484,75, + 1,0,0,0,485,486,5,14,0,0,486,491,3,78,39,0,487,488,5,39,0,0,488,490,3,78, + 39,0,489,487,1,0,0,0,490,493,1,0,0,0,491,489,1,0,0,0,491,492,1,0,0,0,492, + 77,1,0,0,0,493,491,1,0,0,0,494,496,3,10,5,0,495,497,7,4,0,0,496,495,1,0, + 0,0,496,497,1,0,0,0,497,500,1,0,0,0,498,499,5,51,0,0,499,501,7,5,0,0,500, + 498,1,0,0,0,500,501,1,0,0,0,501,79,1,0,0,0,502,503,5,8,0,0,503,504,3,62, + 31,0,504,81,1,0,0,0,505,506,5,2,0,0,506,507,3,62,31,0,507,83,1,0,0,0,508, + 509,5,11,0,0,509,514,3,86,43,0,510,511,5,39,0,0,511,513,3,86,43,0,512,510, + 1,0,0,0,513,516,1,0,0,0,514,512,1,0,0,0,514,515,1,0,0,0,515,85,1,0,0,0, + 516,514,1,0,0,0,517,518,3,60,30,0,518,519,5,89,0,0,519,520,3,60,30,0,520, + 87,1,0,0,0,521,522,5,1,0,0,522,523,3,20,10,0,523,525,3,106,53,0,524,526, + 3,94,47,0,525,524,1,0,0,0,525,526,1,0,0,0,526,89,1,0,0,0,527,528,5,7,0, + 0,528,529,3,20,10,0,529,530,3,106,53,0,530,91,1,0,0,0,531,532,5,10,0,0, + 532,533,3,58,29,0,533,93,1,0,0,0,534,539,3,96,48,0,535,536,5,39,0,0,536, + 538,3,96,48,0,537,535,1,0,0,0,538,541,1,0,0,0,539,537,1,0,0,0,539,540,1, + 0,0,0,540,95,1,0,0,0,541,539,1,0,0,0,542,543,3,64,32,0,543,544,5,36,0,0, + 544,545,3,68,34,0,545,97,1,0,0,0,546,547,7,6,0,0,547,99,1,0,0,0,548,551, + 3,102,51,0,549,551,3,104,52,0,550,548,1,0,0,0,550,549,1,0,0,0,551,101,1, + 0,0,0,552,554,7,0,0,0,553,552,1,0,0,0,553,554,1,0,0,0,554,555,1,0,0,0,555, + 556,5,32,0,0,556,103,1,0,0,0,557,559,7,0,0,0,558,557,1,0,0,0,558,559,1, + 0,0,0,559,560,1,0,0,0,560,561,5,31,0,0,561,105,1,0,0,0,562,563,5,30,0,0, + 563,107,1,0,0,0,564,565,7,7,0,0,565,109,1,0,0,0,566,567,5,5,0,0,567,568, + 3,112,56,0,568,111,1,0,0,0,569,570,5,70,0,0,570,571,3,2,1,0,571,572,5,71, + 0,0,572,113,1,0,0,0,573,574,5,13,0,0,574,575,5,105,0,0,575,115,1,0,0,0, + 576,577,5,3,0,0,577,580,5,95,0,0,578,579,5,93,0,0,579,581,3,60,30,0,580, + 578,1,0,0,0,580,581,1,0,0,0,581,591,1,0,0,0,582,583,5,94,0,0,583,588,3, + 118,59,0,584,585,5,39,0,0,585,587,3,118,59,0,586,584,1,0,0,0,587,590,1, + 0,0,0,588,586,1,0,0,0,588,589,1,0,0,0,589,592,1,0,0,0,590,588,1,0,0,0,591, + 582,1,0,0,0,591,592,1,0,0,0,592,117,1,0,0,0,593,594,3,60,30,0,594,595,5, + 36,0,0,595,597,1,0,0,0,596,593,1,0,0,0,596,597,1,0,0,0,597,598,1,0,0,0, + 598,599,3,60,30,0,599,119,1,0,0,0,600,601,5,18,0,0,601,602,3,36,18,0,602, + 603,5,93,0,0,603,604,3,62,31,0,604,121,1,0,0,0,605,606,5,17,0,0,606,609, + 3,54,27,0,607,608,5,33,0,0,608,610,3,30,15,0,609,607,1,0,0,0,609,610,1, + 0,0,0,610,123,1,0,0,0,611,613,7,8,0,0,612,611,1,0,0,0,612,613,1,0,0,0,613, + 614,1,0,0,0,614,615,5,20,0,0,615,616,3,126,63,0,616,617,3,128,64,0,617, + 125,1,0,0,0,618,621,3,64,32,0,619,620,5,89,0,0,620,622,3,64,32,0,621,619, + 1,0,0,0,621,622,1,0,0,0,622,127,1,0,0,0,623,624,5,93,0,0,624,629,3,130, + 65,0,625,626,5,39,0,0,626,628,3,130,65,0,627,625,1,0,0,0,628,631,1,0,0, + 0,629,627,1,0,0,0,629,630,1,0,0,0,630,129,1,0,0,0,631,629,1,0,0,0,632,633, + 3,16,8,0,633,131,1,0,0,0,61,143,152,172,184,193,201,206,214,216,221,228, + 233,244,250,258,260,271,278,289,292,308,314,324,328,333,343,351,364,368, + 372,379,383,390,396,403,411,419,427,444,455,466,471,475,480,491,496,500, + 514,525,539,550,553,558,580,588,591,596,609,612,621,629]; private static __ATN: ATN; public static get _ATN(): ATN { @@ -3833,6 +4040,9 @@ export class ProcessingCommandContext extends ParserRuleContext { public lookupCommand(): LookupCommandContext { return this.getTypedRuleContext(LookupCommandContext, 0) as LookupCommandContext; } + public joinCommand(): JoinCommandContext { + return this.getTypedRuleContext(JoinCommandContext, 0) as JoinCommandContext; + } public get ruleIndex(): number { return esql_parser.RULE_processingCommand; } @@ -6278,3 +6488,135 @@ export class InlinestatsCommandContext extends ParserRuleContext { } } } + + +export class JoinCommandContext extends ParserRuleContext { + public _type_!: Token; + constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public DEV_JOIN(): TerminalNode { + return this.getToken(esql_parser.DEV_JOIN, 0); + } + public joinTarget(): JoinTargetContext { + return this.getTypedRuleContext(JoinTargetContext, 0) as JoinTargetContext; + } + public joinCondition(): JoinConditionContext { + return this.getTypedRuleContext(JoinConditionContext, 0) as JoinConditionContext; + } + public DEV_JOIN_LOOKUP(): TerminalNode { + return this.getToken(esql_parser.DEV_JOIN_LOOKUP, 0); + } + public DEV_JOIN_LEFT(): TerminalNode { + return this.getToken(esql_parser.DEV_JOIN_LEFT, 0); + } + public DEV_JOIN_RIGHT(): TerminalNode { + return this.getToken(esql_parser.DEV_JOIN_RIGHT, 0); + } + public get ruleIndex(): number { + return esql_parser.RULE_joinCommand; + } + public enterRule(listener: esql_parserListener): void { + if(listener.enterJoinCommand) { + listener.enterJoinCommand(this); + } + } + public exitRule(listener: esql_parserListener): void { + if(listener.exitJoinCommand) { + listener.exitJoinCommand(this); + } + } +} + + +export class JoinTargetContext extends ParserRuleContext { + public _index!: IdentifierContext; + public _alias!: IdentifierContext; + constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public identifier_list(): IdentifierContext[] { + return this.getTypedRuleContexts(IdentifierContext) as IdentifierContext[]; + } + public identifier(i: number): IdentifierContext { + return this.getTypedRuleContext(IdentifierContext, i) as IdentifierContext; + } + public AS(): TerminalNode { + return this.getToken(esql_parser.AS, 0); + } + public get ruleIndex(): number { + return esql_parser.RULE_joinTarget; + } + public enterRule(listener: esql_parserListener): void { + if(listener.enterJoinTarget) { + listener.enterJoinTarget(this); + } + } + public exitRule(listener: esql_parserListener): void { + if(listener.exitJoinTarget) { + listener.exitJoinTarget(this); + } + } +} + + +export class JoinConditionContext extends ParserRuleContext { + constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public ON(): TerminalNode { + return this.getToken(esql_parser.ON, 0); + } + public joinPredicate_list(): JoinPredicateContext[] { + return this.getTypedRuleContexts(JoinPredicateContext) as JoinPredicateContext[]; + } + public joinPredicate(i: number): JoinPredicateContext { + return this.getTypedRuleContext(JoinPredicateContext, i) as JoinPredicateContext; + } + public COMMA_list(): TerminalNode[] { + return this.getTokens(esql_parser.COMMA); + } + public COMMA(i: number): TerminalNode { + return this.getToken(esql_parser.COMMA, i); + } + public get ruleIndex(): number { + return esql_parser.RULE_joinCondition; + } + public enterRule(listener: esql_parserListener): void { + if(listener.enterJoinCondition) { + listener.enterJoinCondition(this); + } + } + public exitRule(listener: esql_parserListener): void { + if(listener.exitJoinCondition) { + listener.exitJoinCondition(this); + } + } +} + + +export class JoinPredicateContext extends ParserRuleContext { + constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public valueExpression(): ValueExpressionContext { + return this.getTypedRuleContext(ValueExpressionContext, 0) as ValueExpressionContext; + } + public get ruleIndex(): number { + return esql_parser.RULE_joinPredicate; + } + public enterRule(listener: esql_parserListener): void { + if(listener.enterJoinPredicate) { + listener.enterJoinPredicate(this); + } + } + public exitRule(listener: esql_parserListener): void { + if(listener.exitJoinPredicate) { + listener.exitJoinPredicate(this); + } + } +} diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts b/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts index 576418862be0..d206b099dd58 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts @@ -98,6 +98,10 @@ import { EnrichCommandContext } from "./esql_parser.js"; import { EnrichWithClauseContext } from "./esql_parser.js"; import { LookupCommandContext } from "./esql_parser.js"; import { InlinestatsCommandContext } from "./esql_parser.js"; +import { JoinCommandContext } from "./esql_parser.js"; +import { JoinTargetContext } from "./esql_parser.js"; +import { JoinConditionContext } from "./esql_parser.js"; +import { JoinPredicateContext } from "./esql_parser.js"; /** @@ -1031,5 +1035,45 @@ export default class esql_parserListener extends ParseTreeListener { * @param ctx the parse tree */ exitInlinestatsCommand?: (ctx: InlinestatsCommandContext) => void; + /** + * Enter a parse tree produced by `esql_parser.joinCommand`. + * @param ctx the parse tree + */ + enterJoinCommand?: (ctx: JoinCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.joinCommand`. + * @param ctx the parse tree + */ + exitJoinCommand?: (ctx: JoinCommandContext) => void; + /** + * Enter a parse tree produced by `esql_parser.joinTarget`. + * @param ctx the parse tree + */ + enterJoinTarget?: (ctx: JoinTargetContext) => void; + /** + * Exit a parse tree produced by `esql_parser.joinTarget`. + * @param ctx the parse tree + */ + exitJoinTarget?: (ctx: JoinTargetContext) => void; + /** + * Enter a parse tree produced by `esql_parser.joinCondition`. + * @param ctx the parse tree + */ + enterJoinCondition?: (ctx: JoinConditionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.joinCondition`. + * @param ctx the parse tree + */ + exitJoinCondition?: (ctx: JoinConditionContext) => void; + /** + * Enter a parse tree produced by `esql_parser.joinPredicate`. + * @param ctx the parse tree + */ + enterJoinPredicate?: (ctx: JoinPredicateContext) => void; + /** + * Exit a parse tree produced by `esql_parser.joinPredicate`. + * @param ctx the parse tree + */ + exitJoinPredicate?: (ctx: JoinPredicateContext) => void; } diff --git a/packages/kbn-esql-editor/kibana.jsonc b/packages/kbn-esql-editor/kibana.jsonc index 005fdb2e6e35..369a2c5b0821 100644 --- a/packages/kbn-esql-editor/kibana.jsonc +++ b/packages/kbn-esql-editor/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/esql-editor", - "owner": "@elastic/kibana-esql", + "owner": [ + "@elastic/kibana-esql" + ], + "group": "platform", + "visibility": "private" } \ No newline at end of file diff --git a/packages/kbn-esql-utils/kibana.jsonc b/packages/kbn-esql-utils/kibana.jsonc index 959a5d947b2b..32404726c90f 100644 --- a/packages/kbn-esql-utils/kibana.jsonc +++ b/packages/kbn-esql-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/esql-utils", - "owner": "@elastic/kibana-esql" -} + "owner": [ + "@elastic/kibana-esql" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-esql-validation-autocomplete/kibana.jsonc b/packages/kbn-esql-validation-autocomplete/kibana.jsonc index b7c1d12d48cd..b0aa37e71937 100644 --- a/packages/kbn-esql-validation-autocomplete/kibana.jsonc +++ b/packages/kbn-esql-validation-autocomplete/kibana.jsonc @@ -1,5 +1,9 @@ { - "type": "shared-common", - "id": "@kbn/esql-validation-autocomplete", - "owner": "@elastic/kibana-esql" - } \ No newline at end of file + "type": "shared-common", + "id": "@kbn/esql-validation-autocomplete", + "owner": [ + "@elastic/kibana-esql" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts index 8462f9e2a050..3a810cac3ad7 100644 --- a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts +++ b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts @@ -218,7 +218,7 @@ const functionEnrichments: Record> ], }, mv_sort: { - signatures: new Array(9).fill({ + signatures: new Array(10).fill({ params: [{}, { acceptedValues: ['asc', 'desc'] }], }), }, diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts index 829c12f7dabb..5234e93c159e 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts @@ -150,6 +150,7 @@ describe('autocomplete.suggest', () => { ...getFieldNamesByType([ ...ESQL_COMMON_NUMERIC_TYPES, 'date', + 'date_nanos', 'boolean', 'ip', 'version', @@ -158,7 +159,16 @@ describe('autocomplete.suggest', () => { ]), ...getFunctionSignaturesByReturnType( 'stats', - [...ESQL_COMMON_NUMERIC_TYPES, 'date', 'boolean', 'ip', 'version', 'text', 'keyword'], + [ + ...ESQL_COMMON_NUMERIC_TYPES, + 'date', + 'boolean', + 'ip', + 'version', + 'text', + 'keyword', + 'date_nanos', + ], { scalar: true, } diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.eval.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.eval.test.ts index aae715ee6674..e81e74427b72 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.eval.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.suggest.eval.test.ts @@ -370,8 +370,12 @@ describe('autocomplete.suggest', () => { // // Test suggestions for each possible param, within each signature variation, for each function for (const fn of scalarFunctionDefinitions) { // skip this fn for the moment as it's quite hard to test - // Add match in the text when the autocomplete is ready https://github.com/elastic/kibana/issues/196995 - if (!['bucket', 'date_extract', 'date_diff', 'case', 'match', 'qstr'].includes(fn.name)) { + // Add match in the test when the autocomplete is ready https://github.com/elastic/kibana/issues/196995 + if ( + !['bucket', 'date_extract', 'date_diff', 'case', 'match', 'qstr', 'date_trunc'].includes( + fn.name + ) + ) { test(`${fn.name}`, async () => { const testedCases = new Set(); @@ -539,9 +543,9 @@ describe('autocomplete.suggest', () => { 'from a | eval var0=date_trunc(/)', [ ...getLiteralsByType('time_literal').map((t) => `${t}, `), - ...getFunctionSignaturesByReturnType('eval', 'time_duration', { scalar: true }).map( - (t) => `${t.text},` - ), + ...getFunctionSignaturesByReturnType('eval', ['time_duration', 'date_period'], { + scalar: true, + }).map((t) => `${t.text},`), ], { triggerCharacter: '(' } ); diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/generated/aggregation_functions.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/generated/aggregation_functions.ts index 6a429f2288f9..4bfdbb6a1639 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/generated/aggregation_functions.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/generated/aggregation_functions.ts @@ -216,7 +216,7 @@ const countDefinition: FunctionDefinition = { validate: undefined, examples: [ 'FROM employees\n| STATS COUNT(height)', - 'FROM employees \n| STATS count = COUNT(*) BY languages \n| SORT languages DESC', + 'FROM employees\n| STATS count = COUNT(*) BY languages\n| SORT languages DESC', 'ROW words="foo;bar;baz;qux;quux;foo"\n| STATS word_count = COUNT(SPLIT(words, ";"))', 'ROW n=1\n| WHERE n < 0\n| STATS COUNT(n)', 'ROW n=1\n| STATS COUNT(n > 0 OR NULL), COUNT(n < 0 OR NULL)', @@ -343,6 +343,61 @@ const countDistinctDefinition: FunctionDefinition = { ], returnType: 'long', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + { + name: 'precision', + type: 'integer', + optional: true, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + { + name: 'precision', + type: 'long', + optional: true, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + { + name: 'precision', + type: 'unsigned_long', + optional: true, + }, + ], + returnType: 'long', + }, { params: [ { @@ -769,6 +824,16 @@ const maxDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -984,6 +1049,16 @@ const minDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -1266,6 +1341,56 @@ const stCentroidAggDefinition: FunctionDefinition = { examples: ['FROM airports\n| STATS centroid=ST_CENTROID_AGG(location)'], }; +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const stdDevDefinition: FunctionDefinition = { + type: 'agg', + name: 'std_dev', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.std_dev', { + defaultMessage: 'The standard deviation of a numeric field.', + }), + preview: false, + alias: undefined, + signatures: [ + { + params: [ + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics'], + supportedOptions: undefined, + validate: undefined, + examples: [ + 'FROM employees\n| STATS STD_DEV(height)', + 'FROM employees\n| STATS stddev_salary_change = STD_DEV(MV_MAX(salary_change))', + ], +}; + // Do not edit this manually... generated by scripts/generate_function_definitions.ts const sumDefinition: FunctionDefinition = { type: 'agg', @@ -1544,6 +1669,16 @@ const valuesDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -1786,6 +1921,7 @@ export const aggregationFunctionDefinitions = [ minDefinition, percentileDefinition, stCentroidAggDefinition, + stdDevDefinition, sumDefinition, topDefinition, valuesDefinition, diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts index fb98d7cb4b21..d45271b18991 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts @@ -892,6 +892,22 @@ const coalesceDefinition: FunctionDefinition = { returnType: 'date', minParams: 1, }, + { + params: [ + { + name: 'first', + type: 'date_nanos', + optional: false, + }, + { + name: 'rest', + type: 'date_nanos', + optional: true, + }, + ], + returnType: 'date_nanos', + minParams: 1, + }, { params: [ { @@ -1624,6 +1640,21 @@ const dateTruncDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'interval', + type: 'date_period', + optional: false, + }, + { + name: 'date', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -1639,6 +1670,21 @@ const dateTruncDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'interval', + type: 'time_duration', + optional: false, + }, + { + name: 'date', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, ], supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], @@ -1954,6 +2000,22 @@ const greatestDefinition: FunctionDefinition = { returnType: 'date', minParams: 1, }, + { + params: [ + { + name: 'first', + type: 'date_nanos', + optional: false, + }, + { + name: 'rest', + type: 'date_nanos', + optional: true, + }, + ], + returnType: 'date_nanos', + minParams: 1, + }, { params: [ { @@ -2468,6 +2530,22 @@ const leastDefinition: FunctionDefinition = { returnType: 'date', minParams: 1, }, + { + params: [ + { + name: 'first', + type: 'date_nanos', + optional: false, + }, + { + name: 'rest', + type: 'date_nanos', + optional: true, + }, + ], + returnType: 'date_nanos', + minParams: 1, + }, { params: [ { @@ -3402,7 +3480,7 @@ const matchDefinition: FunctionDefinition = { supportedOptions: [], validate: undefined, examples: [ - 'from books \n| where match(author, "Faulkner")\n| keep book_no, author \n| sort book_no \n| limit 5;', + 'FROM books \n| WHERE MATCH(author, "Faulkner")\n| KEEP book_no, author \n| SORT book_no \n| LIMIT 5;', ], }; @@ -3808,6 +3886,16 @@ const mvCountDefinition: FunctionDefinition = { ], returnType: 'integer', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'integer', + }, { params: [ { @@ -3965,6 +4053,16 @@ const mvDedupeDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -4113,6 +4211,16 @@ const mvFirstDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -4271,6 +4379,16 @@ const mvLastDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -4409,6 +4527,16 @@ const mvMaxDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -4654,6 +4782,16 @@ const mvMinDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -5028,6 +5166,26 @@ const mvSliceDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -5260,6 +5418,22 @@ const mvSortDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + { + name: 'order', + type: 'keyword', + optional: true, + acceptedValues: ['asc', 'desc'], + }, + ], + returnType: 'date_nanos', + }, { params: [ { @@ -6017,7 +6191,7 @@ const qstrDefinition: FunctionDefinition = { supportedOptions: [], validate: undefined, examples: [ - 'from books \n| where qstr("author: Faulkner")\n| keep book_no, author \n| sort book_no \n| limit 5;', + 'FROM books \n| WHERE QSTR("author: Faulkner")\n| KEEP book_no, author \n| SORT book_no \n| LIMIT 5;', ], }; @@ -8030,7 +8204,78 @@ const toDateNanosDefinition: FunctionDefinition = { }), preview: true, alias: undefined, - signatures: [], + signatures: [ + { + params: [ + { + name: 'field', + type: 'date', + optional: false, + }, + ], + returnType: 'date_nanos', + }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date_nanos', + }, + { + params: [ + { + name: 'field', + type: 'double', + optional: false, + }, + ], + returnType: 'date_nanos', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + }, + ], + returnType: 'date_nanos', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + }, + ], + returnType: 'date_nanos', + }, + { + params: [ + { + name: 'field', + type: 'text', + optional: false, + }, + ], + returnType: 'date_nanos', + }, + { + params: [ + { + name: 'field', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'date_nanos', + }, + ], supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, @@ -8107,6 +8352,16 @@ const toDatetimeDefinition: FunctionDefinition = { ], returnType: 'date', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'date', + }, { params: [ { @@ -8680,6 +8935,16 @@ const toLongDefinition: FunctionDefinition = { ], returnType: 'long', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'long', + }, { params: [ { @@ -8893,6 +9158,16 @@ const toStringDefinition: FunctionDefinition = { ], returnType: 'keyword', }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'keyword', + }, { params: [ { diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index fee9f90f38c9..2e6b4a085656 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -2203,7 +2203,7 @@ "warning": [] }, { - "query": "ROW a=1::LONG | LOOKUP t ON a", + "query": "ROW a=1::LONG | LOOKUP JOIN t ON a", "error": [], "warning": [] }, diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index 68d8ebb233f5..442a2299d8ab 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -429,7 +429,6 @@ describe('validation logic', () => { [], ['Invalid option ["bogus"] for mv_sort. Supported options: ["asc", "desc"].'] ); - testErrorsAndWarnings(`row var = mv_sort(["a", "b"], "ASC")`, []); testErrorsAndWarnings(`row var = mv_sort(["a", "b"], "DESC")`, []); @@ -507,7 +506,7 @@ describe('validation logic', () => { }); describe('lookup', () => { - testErrorsAndWarnings('ROW a=1::LONG | LOOKUP t ON a', []); + testErrorsAndWarnings('ROW a=1::LONG | LOOKUP JOIN t ON a', []); }); describe('keep', () => { diff --git a/packages/kbn-event-annotation-common/kibana.jsonc b/packages/kbn-event-annotation-common/kibana.jsonc index cdf2a346730e..161e6bb8f22b 100644 --- a/packages/kbn-event-annotation-common/kibana.jsonc +++ b/packages/kbn-event-annotation-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/event-annotation-common", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-event-annotation-components/kibana.jsonc b/packages/kbn-event-annotation-components/kibana.jsonc index af30ffe6d327..7454473a9483 100644 --- a/packages/kbn-event-annotation-components/kibana.jsonc +++ b/packages/kbn-event-annotation-components/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/event-annotation-components", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-expandable-flyout/kibana.jsonc b/packages/kbn-expandable-flyout/kibana.jsonc index ae15fc604a1d..5a8bf3183968 100644 --- a/packages/kbn-expandable-flyout/kibana.jsonc +++ b/packages/kbn-expandable-flyout/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/expandable-flyout", - "owner": "@elastic/security-threat-hunting-investigations" -} + "owner": [ + "@elastic/security-threat-hunting-investigations" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-expandable-flyout/src/hooks/use_initialize_from_local_storage.test.ts b/packages/kbn-expandable-flyout/src/hooks/use_initialize_from_local_storage.test.ts index 26b3daf8161d..cc3623f9238c 100644 --- a/packages/kbn-expandable-flyout/src/hooks/use_initialize_from_local_storage.test.ts +++ b/packages/kbn-expandable-flyout/src/hooks/use_initialize_from_local_storage.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useInitializeFromLocalStorage } from './use_initialize_from_local_storage'; import { localStorageMock } from '../../__mocks__'; import { diff --git a/packages/kbn-expandable-flyout/src/hooks/use_sections.test.tsx b/packages/kbn-expandable-flyout/src/hooks/use_sections.test.tsx index 4526f128affd..8dc4aacaefbc 100644 --- a/packages/kbn-expandable-flyout/src/hooks/use_sections.test.tsx +++ b/packages/kbn-expandable-flyout/src/hooks/use_sections.test.tsx @@ -8,8 +8,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import type { RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook, RenderHookResult } from '@testing-library/react'; import type { UseSectionsParams, UseSectionsResult } from './use_sections'; import { useSections } from './use_sections'; import { useExpandableFlyoutState } from '../..'; @@ -17,7 +16,7 @@ import { useExpandableFlyoutState } from '../..'; jest.mock('../..'); describe('useSections', () => { - let hookResult: RenderHookResult; + let hookResult: RenderHookResult; it('should return undefined for all values if no registeredPanels', () => { (useExpandableFlyoutState as jest.Mock).mockReturnValue({ diff --git a/packages/kbn-expandable-flyout/src/hooks/use_window_width.test.ts b/packages/kbn-expandable-flyout/src/hooks/use_window_width.test.ts index 72ab9148743d..696191e7fbdf 100644 --- a/packages/kbn-expandable-flyout/src/hooks/use_window_width.test.ts +++ b/packages/kbn-expandable-flyout/src/hooks/use_window_width.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { FULL_WIDTH_PADDING, MAX_RESOLUTION_BREAKPOINT, diff --git a/packages/kbn-expect/kibana.jsonc b/packages/kbn-expect/kibana.jsonc index 6f2732af83e6..8b32dc829a86 100644 --- a/packages/kbn-expect/kibana.jsonc +++ b/packages/kbn-expect/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "shared-common", "id": "@kbn/expect", - "devOnly": true, - "owner": ["@elastic/kibana-operations", "@elastic/appex-qa"], -} + "owner": [ + "@elastic/kibana-operations", + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-field-types/kibana.jsonc b/packages/kbn-field-types/kibana.jsonc index e19eebb3a3a9..ee1d33c31da0 100644 --- a/packages/kbn-field-types/kibana.jsonc +++ b/packages/kbn-field-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/field-types", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-field-utils/kibana.jsonc b/packages/kbn-field-utils/kibana.jsonc index 891f5f962f9e..16efdbb4ffd4 100644 --- a/packages/kbn-field-utils/kibana.jsonc +++ b/packages/kbn-field-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/field-utils", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-flot-charts/kibana.jsonc b/packages/kbn-flot-charts/kibana.jsonc index f2ebe8d27999..a027dbd460ab 100644 --- a/packages/kbn-flot-charts/kibana.jsonc +++ b/packages/kbn-flot-charts/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/flot-charts", - "owner": ["@elastic/kibana-presentation", "@elastic/stack-monitoring"] -} + "owner": [ + "@elastic/kibana-presentation", + "@elastic/stack-monitoring" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-ftr-common-functional-services/kibana.jsonc b/packages/kbn-ftr-common-functional-services/kibana.jsonc index c82496f10987..cd87612fe981 100644 --- a/packages/kbn-ftr-common-functional-services/kibana.jsonc +++ b/packages/kbn-ftr-common-functional-services/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "test-helper", "id": "@kbn/ftr-common-functional-services", - "owner": ["@elastic/kibana-operations", "@elastic/appex-qa"], + "owner": [ + "@elastic/kibana-operations", + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "shared", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-ftr-common-functional-ui-services/kibana.jsonc b/packages/kbn-ftr-common-functional-ui-services/kibana.jsonc index a92a8cd4c4a9..16b19f3a7e20 100644 --- a/packages/kbn-ftr-common-functional-ui-services/kibana.jsonc +++ b/packages/kbn-ftr-common-functional-ui-services/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "test-helper", "id": "@kbn/ftr-common-functional-ui-services", - "owner": "@elastic/appex-qa", + "owner": [ + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "shared", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-ftr-screenshot-filename/kibana.jsonc b/packages/kbn-ftr-screenshot-filename/kibana.jsonc index c4d6004ed83d..b1534ddf0bc1 100644 --- a/packages/kbn-ftr-screenshot-filename/kibana.jsonc +++ b/packages/kbn-ftr-screenshot-filename/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "shared-common", "id": "@kbn/ftr-screenshot-filename", - "owner": ["@elastic/kibana-operations", "@elastic/appex-qa"], + "owner": [ + "@elastic/kibana-operations", + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "private", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-generate-csv/kibana.jsonc b/packages/kbn-generate-csv/kibana.jsonc index d29b33047007..bcd72c4e8499 100644 --- a/packages/kbn-generate-csv/kibana.jsonc +++ b/packages/kbn-generate-csv/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/generate-csv", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-generate-csv/src/generate_csv.test.ts b/packages/kbn-generate-csv/src/generate_csv.test.ts index f39cf51352a5..e2999b63088d 100644 --- a/packages/kbn-generate-csv/src/generate_csv.test.ts +++ b/packages/kbn-generate-csv/src/generate_csv.test.ts @@ -47,7 +47,6 @@ const getMockConfig = (opts: Partial = {}): CsvConfigType => ({ maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: false, maxConcurrentShardRequests: 5, ...opts, }); diff --git a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts index 9ae0b2b711c1..d2ee8e834543 100644 --- a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts +++ b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts @@ -98,7 +98,6 @@ describe('CsvESQLGenerator', () => { maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: false, maxConcurrentShardRequests: 5, }; @@ -569,7 +568,6 @@ describe('CsvESQLGenerator', () => { maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: false, maxConcurrentShardRequests: 5, }; mockSearchResponse({ diff --git a/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts b/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts index 05a321aa1a25..f1c73680a8b9 100644 --- a/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts +++ b/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts @@ -39,7 +39,6 @@ describe('getExportSettings', () => { scroll: { size: 500, duration: '30s', strategy: 'pit' }, useByteOrderMarkEncoding: false, maxConcurrentShardRequests: 5, - enablePanelActionDownload: false, }; taskInstanceFields = { startedAt: null, retryAt: null }; diff --git a/packages/kbn-get-repo-files/kibana.jsonc b/packages/kbn-get-repo-files/kibana.jsonc index 9bf339cc5c32..9268d6b7a70c 100644 --- a/packages/kbn-get-repo-files/kibana.jsonc +++ b/packages/kbn-get-repo-files/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/get-repo-files", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-grid-layout/grid/grid_layout.tsx b/packages/kbn-grid-layout/grid/grid_layout.tsx index c3f952150310..fc67c5b13460 100644 --- a/packages/kbn-grid-layout/grid/grid_layout.tsx +++ b/packages/kbn-grid-layout/grid/grid_layout.tsx @@ -8,109 +8,139 @@ */ import { cloneDeep } from 'lodash'; -import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { combineLatest, distinctUntilChanged, filter, map, pairwise, skip } from 'rxjs'; import { GridHeightSmoother } from './grid_height_smoother'; import { GridRow } from './grid_row'; -import { GridLayoutApi, GridLayoutData, GridSettings } from './types'; -import { useGridLayoutApi } from './use_grid_layout_api'; +import { GridLayoutData, GridSettings } from './types'; import { useGridLayoutEvents } from './use_grid_layout_events'; import { useGridLayoutState } from './use_grid_layout_state'; import { isLayoutEqual } from './utils/equality_checks'; +import { compactGridRow } from './utils/resolve_grid_row'; interface GridLayoutProps { - getCreationOptions: () => { initialLayout: GridLayoutData; gridSettings: GridSettings }; + layout: GridLayoutData; + gridSettings: GridSettings; renderPanelContents: (panelId: string) => React.ReactNode; onLayoutChange: (newLayout: GridLayoutData) => void; } -export const GridLayout = forwardRef( - ({ getCreationOptions, renderPanelContents, onLayoutChange }, ref) => { - const { gridLayoutStateManager, setDimensionsRef } = useGridLayoutState({ - getCreationOptions, - }); - useGridLayoutEvents({ gridLayoutStateManager }); - - const gridLayoutApi = useGridLayoutApi({ gridLayoutStateManager }); - useImperativeHandle(ref, () => gridLayoutApi, [gridLayoutApi]); +export const GridLayout = ({ + layout, + gridSettings, + renderPanelContents, + onLayoutChange, +}: GridLayoutProps) => { + const { gridLayoutStateManager, setDimensionsRef } = useGridLayoutState({ + layout, + gridSettings, + }); + useGridLayoutEvents({ gridLayoutStateManager }); - const [rowCount, setRowCount] = useState( - gridLayoutStateManager.gridLayout$.getValue().length - ); + const [rowCount, setRowCount] = useState( + gridLayoutStateManager.gridLayout$.getValue().length + ); - useEffect(() => { + /** + * Update the `gridLayout$` behaviour subject in response to the `layout` prop changing + */ + useEffect(() => { + if (!isLayoutEqual(layout, gridLayoutStateManager.gridLayout$.getValue())) { + const newLayout = cloneDeep(layout); /** - * The only thing that should cause the entire layout to re-render is adding a new row; - * this subscription ensures this by updating the `rowCount` state when it changes. + * the layout sent in as a prop is not guaranteed to be valid (i.e it may have floating panels) - + * so, we need to loop through each row and ensure it is compacted */ - const rowCountSubscription = gridLayoutStateManager.gridLayout$ - .pipe( - skip(1), // we initialized `rowCount` above, so skip the initial emit - map((newLayout) => newLayout.length), - distinctUntilChanged() - ) - .subscribe((newRowCount) => { - setRowCount(newRowCount); - }); + newLayout.forEach((row, rowIndex) => { + newLayout[rowIndex] = compactGridRow(row); + }); + gridLayoutStateManager.gridLayout$.next(newLayout); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [layout]); - const onLayoutChangeSubscription = combineLatest([ - gridLayoutStateManager.gridLayout$, - gridLayoutStateManager.interactionEvent$, - ]) - .pipe( - // if an interaction event is happening, then ignore any "draft" layout changes - filter(([_, event]) => !Boolean(event)), - // once no interaction event, create pairs of "old" and "new" layouts for comparison - map(([layout]) => layout), - pairwise() - ) - .subscribe(([layoutBefore, layoutAfter]) => { - if (!isLayoutEqual(layoutBefore, layoutAfter)) { - onLayoutChange(layoutAfter); - } - }); + /** + * Set up subscriptions + */ + useEffect(() => { + /** + * The only thing that should cause the entire layout to re-render is adding a new row; + * this subscription ensures this by updating the `rowCount` state when it changes. + */ + const rowCountSubscription = gridLayoutStateManager.gridLayout$ + .pipe( + skip(1), // we initialized `rowCount` above, so skip the initial emit + map((newLayout) => newLayout.length), + distinctUntilChanged() + ) + .subscribe((newRowCount) => { + setRowCount(newRowCount); + }); + + const onLayoutChangeSubscription = combineLatest([ + gridLayoutStateManager.gridLayout$, + gridLayoutStateManager.interactionEvent$, + ]) + .pipe( + // if an interaction event is happening, then ignore any "draft" layout changes + filter(([_, event]) => !Boolean(event)), + // once no interaction event, create pairs of "old" and "new" layouts for comparison + map(([newLayout]) => newLayout), + pairwise() + ) + .subscribe(([layoutBefore, layoutAfter]) => { + if (!isLayoutEqual(layoutBefore, layoutAfter)) { + onLayoutChange(layoutAfter); + } + }); - return () => { - rowCountSubscription.unsubscribe(); - onLayoutChangeSubscription.unsubscribe(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + return () => { + rowCountSubscription.unsubscribe(); + onLayoutChangeSubscription.unsubscribe(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + /** + * Memoize row children components to prevent unnecessary re-renders + */ + const children = useMemo(() => { + return Array.from({ length: rowCount }, (_, rowIndex) => { + return ( + { + const newLayout = cloneDeep(gridLayoutStateManager.gridLayout$.value); + newLayout[rowIndex].isCollapsed = !newLayout[rowIndex].isCollapsed; + gridLayoutStateManager.gridLayout$.next(newLayout); + }} + setInteractionEvent={(nextInteractionEvent) => { + if (!nextInteractionEvent) { + gridLayoutStateManager.activePanel$.next(undefined); + } + gridLayoutStateManager.interactionEvent$.next(nextInteractionEvent); + }} + ref={(element: HTMLDivElement | null) => + (gridLayoutStateManager.rowRefs.current[rowIndex] = element) + } + /> + ); + }); + }, [rowCount, gridLayoutStateManager, renderPanelContents]); - return ( - <> - -

{ - setDimensionsRef(divElement); - }} - > - {Array.from({ length: rowCount }, (_, rowIndex) => { - return ( - { - const newLayout = cloneDeep(gridLayoutStateManager.gridLayout$.value); - newLayout[rowIndex].isCollapsed = !newLayout[rowIndex].isCollapsed; - gridLayoutStateManager.gridLayout$.next(newLayout); - }} - setInteractionEvent={(nextInteractionEvent) => { - if (!nextInteractionEvent) { - gridLayoutStateManager.activePanel$.next(undefined); - } - gridLayoutStateManager.interactionEvent$.next(nextInteractionEvent); - }} - ref={(element) => (gridLayoutStateManager.rowRefs.current[rowIndex] = element)} - /> - ); - })} -
- - - ); - } -); + return ( + +
{ + setDimensionsRef(divElement); + }} + > + {children} +
+
+ ); +}; diff --git a/packages/kbn-grid-layout/grid/grid_panel.tsx b/packages/kbn-grid-layout/grid/grid_panel.tsx index 822cb2328c4a..a44a321a7b18 100644 --- a/packages/kbn-grid-layout/grid/grid_panel.tsx +++ b/packages/kbn-grid-layout/grid/grid_panel.tsx @@ -129,84 +129,93 @@ export const GridPanel = forwardRef< [] ); + /** + * Memoize panel contents to prevent unnecessary re-renders + */ + const panelContents = useMemo(() => { + return renderPanelContents(panelId); + }, [panelId, renderPanelContents]); + return ( -
- - {/* drag handle */} -
+
+ interactionStart('drag', e)} - onMouseUp={(e) => interactionStart('drop', e)} > - -
- {/* Resize handle */} -
interactionStart('resize', e)} - onMouseUp={(e) => interactionStart('drop', e)} - css={css` - right: 0; - bottom: 0; - opacity: 0; - margin: -2px; - position: absolute; - width: ${euiThemeVars.euiSizeL}; - height: ${euiThemeVars.euiSizeL}; - transition: opacity 0.2s, border 0.2s; - border-radius: 7px 0 7px 0; - border-bottom: 2px solid ${euiThemeVars.euiColorSuccess}; - border-right: 2px solid ${euiThemeVars.euiColorSuccess}; - :hover { - opacity: 1; - background-color: ${transparentize(euiThemeVars.euiColorSuccess, 0.05)}; - cursor: se-resize; - } - `} - /> -
interactionStart('drag', e)} + onMouseUp={(e) => interactionStart('drop', e)} + > + +
+ {/* Resize handle */} +
interactionStart('resize', e)} + onMouseUp={(e) => interactionStart('drop', e)} + css={css` + right: 0; + bottom: 0; + opacity: 0; + margin: -2px; + position: absolute; + width: ${euiThemeVars.euiSizeL}; + height: ${euiThemeVars.euiSizeL}; + transition: opacity 0.2s, border 0.2s; + border-radius: 7px 0 7px 0; + border-bottom: 2px solid ${euiThemeVars.euiColorSuccess}; + border-right: 2px solid ${euiThemeVars.euiColorSuccess}; + :hover { + opacity: 1; + background-color: ${transparentize(euiThemeVars.euiColorSuccess, 0.05)}; + cursor: se-resize; + } + `} + /> +
- {renderPanelContents(panelId)} -
- -
+ `} + > + {panelContents} +
+ +
+ ); } ); diff --git a/packages/kbn-grid-layout/grid/grid_row.tsx b/packages/kbn-grid-layout/grid/grid_row.tsx index ff97b32efcdb..01466a440b4c 100644 --- a/packages/kbn-grid-layout/grid/grid_row.tsx +++ b/packages/kbn-grid-layout/grid/grid_row.tsx @@ -155,6 +155,51 @@ export const GridRow = forwardRef< [rowIndex] ); + /** + * Memoize panel children components to prevent unnecessary re-renders + */ + const children = useMemo(() => { + return panelIds.map((panelId) => ( + { + e.preventDefault(); + e.stopPropagation(); + const panelRef = gridLayoutStateManager.panelRefs.current[rowIndex][panelId]; + if (!panelRef) return; + + const panelRect = panelRef.getBoundingClientRect(); + if (type === 'drop') { + setInteractionEvent(undefined); + } else { + setInteractionEvent({ + type, + id: panelId, + panelDiv: panelRef, + targetRowIndex: rowIndex, + mouseOffsets: { + top: e.clientY - panelRect.top, + left: e.clientX - panelRect.left, + right: e.clientX - panelRect.right, + bottom: e.clientY - panelRect.bottom, + }, + }); + } + }} + ref={(element) => { + if (!gridLayoutStateManager.panelRefs.current[rowIndex]) { + gridLayoutStateManager.panelRefs.current[rowIndex] = {}; + } + gridLayoutStateManager.panelRefs.current[rowIndex][panelId] = element; + }} + /> + )); + }, [panelIds, rowIndex, gridLayoutStateManager, renderPanelContents, setInteractionEvent]); + return ( <> {rowIndex !== 0 && ( @@ -186,46 +231,7 @@ export const GridRow = forwardRef< ${initialStyles}; `} > - {panelIds.map((panelId) => ( - { - e.preventDefault(); - e.stopPropagation(); - const panelRef = gridLayoutStateManager.panelRefs.current[rowIndex][panelId]; - if (!panelRef) return; - - const panelRect = panelRef.getBoundingClientRect(); - if (type === 'drop') { - setInteractionEvent(undefined); - } else { - setInteractionEvent({ - type, - id: panelId, - panelDiv: panelRef, - targetRowIndex: rowIndex, - mouseOffsets: { - top: e.clientY - panelRect.top, - left: e.clientX - panelRect.left, - right: e.clientX - panelRect.right, - bottom: e.clientY - panelRect.bottom, - }, - }); - } - }} - ref={(element) => { - if (!gridLayoutStateManager.panelRefs.current[rowIndex]) { - gridLayoutStateManager.panelRefs.current[rowIndex] = {}; - } - gridLayoutStateManager.panelRefs.current[rowIndex][panelId] = element; - }} - /> - ))} - + {children}
)} diff --git a/packages/kbn-grid-layout/grid/types.ts b/packages/kbn-grid-layout/grid/types.ts index 004669e69b18..3979b86f05a0 100644 --- a/packages/kbn-grid-layout/grid/types.ts +++ b/packages/kbn-grid-layout/grid/types.ts @@ -10,8 +10,6 @@ import { BehaviorSubject } from 'rxjs'; import type { ObservedSize } from 'use-resize-observer/polyfilled'; -import { SerializableRecord } from '@kbn/utility-types'; - export interface GridCoordinate { column: number; row: number; @@ -106,18 +104,6 @@ export interface PanelInteractionEvent { }; } -/** - * The external API provided through the GridLayout component - */ -export interface GridLayoutApi { - addPanel: (panelId: string, placementSettings: PanelPlacementSettings) => void; - removePanel: (panelId: string) => void; - replacePanel: (oldPanelId: string, newPanelId: string) => void; - - getPanelCount: () => number; - serializeState: () => GridLayoutData & SerializableRecord; -} - // TODO: Remove from Dashboard plugin as part of https://github.com/elastic/kibana/issues/190446 export enum PanelPlacementStrategy { /** Place on the very top of the grid layout, add the height of this panel to all other panels. */ diff --git a/packages/kbn-grid-layout/grid/use_grid_layout_api.ts b/packages/kbn-grid-layout/grid/use_grid_layout_api.ts deleted file mode 100644 index 1a950ee93417..000000000000 --- a/packages/kbn-grid-layout/grid/use_grid_layout_api.ts +++ /dev/null @@ -1,109 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { useMemo } from 'react'; -import { cloneDeep } from 'lodash'; - -import { SerializableRecord } from '@kbn/utility-types'; - -import { GridLayoutApi, GridLayoutData, GridLayoutStateManager } from './types'; -import { compactGridRow } from './utils/resolve_grid_row'; -import { runPanelPlacementStrategy } from './utils/run_panel_placement'; - -export const useGridLayoutApi = ({ - gridLayoutStateManager, -}: { - gridLayoutStateManager: GridLayoutStateManager; -}): GridLayoutApi => { - const api: GridLayoutApi = useMemo(() => { - return { - addPanel: (panelId, placementSettings) => { - const currentLayout = gridLayoutStateManager.gridLayout$.getValue(); - const [firstRow, ...rest] = currentLayout; // currently, only adding panels to the first row is supported - const { columnCount: gridColumnCount } = gridLayoutStateManager.runtimeSettings$.getValue(); - const nextRow = runPanelPlacementStrategy( - firstRow, - { - id: panelId, - width: placementSettings.width, - height: placementSettings.height, - }, - gridColumnCount, - placementSettings?.strategy - ); - gridLayoutStateManager.gridLayout$.next([nextRow, ...rest]); - }, - - removePanel: (panelId) => { - const currentLayout = gridLayoutStateManager.gridLayout$.getValue(); - - // find the row where the panel exists and delete it from the corresponding panels object - let rowIndex = 0; - let updatedPanels; - for (rowIndex; rowIndex < currentLayout.length; rowIndex++) { - const row = currentLayout[rowIndex]; - if (Object.keys(row.panels).includes(panelId)) { - updatedPanels = { ...row.panels }; // prevent mutation of original panel object - delete updatedPanels[panelId]; - break; - } - } - - // if the panels were updated (i.e. the panel was successfully found and deleted), update the layout - if (updatedPanels) { - const newLayout = cloneDeep(currentLayout); - newLayout[rowIndex] = compactGridRow({ - ...newLayout[rowIndex], - panels: updatedPanels, - }); - gridLayoutStateManager.gridLayout$.next(newLayout); - } - }, - - replacePanel: (oldPanelId, newPanelId) => { - const currentLayout = gridLayoutStateManager.gridLayout$.getValue(); - - // find the row where the panel exists and update its ID to trigger a re-render - let rowIndex = 0; - let updatedPanels; - for (rowIndex; rowIndex < currentLayout.length; rowIndex++) { - const row = { ...currentLayout[rowIndex] }; - if (Object.keys(row.panels).includes(oldPanelId)) { - updatedPanels = { ...row.panels }; // prevent mutation of original panel object - const oldPanel = updatedPanels[oldPanelId]; - delete updatedPanels[oldPanelId]; - updatedPanels[newPanelId] = { ...oldPanel, id: newPanelId }; - break; - } - } - - // if the panels were updated (i.e. the panel was successfully found and replaced), update the layout - if (updatedPanels) { - const newLayout = cloneDeep(currentLayout); - newLayout[rowIndex].panels = updatedPanels; - gridLayoutStateManager.gridLayout$.next(newLayout); - } - }, - - getPanelCount: () => { - return gridLayoutStateManager.gridLayout$.getValue().reduce((prev, row) => { - return prev + Object.keys(row.panels).length; - }, 0); - }, - - serializeState: () => { - const currentLayout = gridLayoutStateManager.gridLayout$.getValue(); - return cloneDeep(currentLayout) as GridLayoutData & SerializableRecord; - }, - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return api; -}; diff --git a/packages/kbn-grid-layout/grid/use_grid_layout_state.ts b/packages/kbn-grid-layout/grid/use_grid_layout_state.ts index fe657ae25310..a107cbacef2f 100644 --- a/packages/kbn-grid-layout/grid/use_grid_layout_state.ts +++ b/packages/kbn-grid-layout/grid/use_grid_layout_state.ts @@ -22,9 +22,11 @@ import { } from './types'; export const useGridLayoutState = ({ - getCreationOptions, + layout, + gridSettings, }: { - getCreationOptions: () => { initialLayout: GridLayoutData; gridSettings: GridSettings }; + layout: GridLayoutData; + gridSettings: GridSettings; }): { gridLayoutStateManager: GridLayoutStateManager; setDimensionsRef: (instance: HTMLDivElement | null) => void; @@ -32,11 +34,8 @@ export const useGridLayoutState = ({ const rowRefs = useRef>([]); const panelRefs = useRef>([]); - // eslint-disable-next-line react-hooks/exhaustive-deps - const { initialLayout, gridSettings } = useMemo(() => getCreationOptions(), []); - const gridLayoutStateManager = useMemo(() => { - const gridLayout$ = new BehaviorSubject(initialLayout); + const gridLayout$ = new BehaviorSubject(layout); const gridDimensions$ = new BehaviorSubject({ width: 0, height: 0 }); const interactionEvent$ = new BehaviorSubject(undefined); const activePanel$ = new BehaviorSubject(undefined); @@ -45,7 +44,7 @@ export const useGridLayoutState = ({ columnPixelWidth: 0, }); const panelIds$ = new BehaviorSubject( - initialLayout.map(({ panels }) => Object.keys(panels)) + layout.map(({ panels }) => Object.keys(panels)) ); return { diff --git a/packages/kbn-grid-layout/index.ts b/packages/kbn-grid-layout/index.ts index 924369fe5ab4..be46f9d5a7b8 100644 --- a/packages/kbn-grid-layout/index.ts +++ b/packages/kbn-grid-layout/index.ts @@ -8,12 +8,6 @@ */ export { GridLayout } from './grid/grid_layout'; -export type { - GridLayoutApi, - GridLayoutData, - GridPanelData, - GridRowData, - GridSettings, -} from './grid/types'; +export type { GridLayoutData, GridPanelData, GridRowData, GridSettings } from './grid/types'; export { isLayoutEqual } from './grid/utils/equality_checks'; diff --git a/packages/kbn-grid-layout/tsconfig.json b/packages/kbn-grid-layout/tsconfig.json index 14ab38ba76ba..f0dd3232a42d 100644 --- a/packages/kbn-grid-layout/tsconfig.json +++ b/packages/kbn-grid-layout/tsconfig.json @@ -19,6 +19,5 @@ "kbn_references": [ "@kbn/ui-theme", "@kbn/i18n", - "@kbn/utility-types", ] } diff --git a/packages/kbn-grouping/kibana.jsonc b/packages/kbn-grouping/kibana.jsonc index ce91c91cefcd..36e06aaaca56 100644 --- a/packages/kbn-grouping/kibana.jsonc +++ b/packages/kbn-grouping/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/grouping", - "owner": "@elastic/response-ops" + "owner": "@elastic/response-ops", + "group": "platform", + "visibility": "shared" } diff --git a/packages/kbn-grouping/src/hooks/state/reducer.test.ts b/packages/kbn-grouping/src/hooks/state/reducer.test.ts index c056565b7bf1..7d00d64eadd2 100644 --- a/packages/kbn-grouping/src/hooks/state/reducer.test.ts +++ b/packages/kbn-grouping/src/hooks/state/reducer.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useReducer } from 'react'; import { groupActions, groupsReducerWithStorage, initialState } from '.'; import { defaultGroup, LOCAL_STORAGE_GROUPING_KEY } from '../..'; diff --git a/packages/kbn-grouping/src/hooks/use_get_group_selector.test.tsx b/packages/kbn-grouping/src/hooks/use_get_group_selector.test.tsx index 312ccde33e32..d29e1b63f1ea 100644 --- a/packages/kbn-grouping/src/hooks/use_get_group_selector.test.tsx +++ b/packages/kbn-grouping/src/hooks/use_get_group_selector.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useGetGroupSelector, useGetGroupSelectorStateless } from './use_get_group_selector'; import { initialState } from './state'; diff --git a/packages/kbn-grouping/src/hooks/use_grouping.test.tsx b/packages/kbn-grouping/src/hooks/use_grouping.test.tsx index 22957548de31..834db5acaa39 100644 --- a/packages/kbn-grouping/src/hooks/use_grouping.test.tsx +++ b/packages/kbn-grouping/src/hooks/use_grouping.test.tsx @@ -8,9 +8,8 @@ */ import React from 'react'; -import { act, renderHook } from '@testing-library/react-hooks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; -import { render } from '@testing-library/react'; +import { render, waitFor, renderHook } from '@testing-library/react'; import { useGrouping } from './use_grouping'; @@ -46,92 +45,86 @@ const groupingArgs = { describe('useGrouping', () => { it('Renders child component without grouping table wrapper when no group is selected', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useGrouping(defaultArgs)); - await waitForNextUpdate(); - await waitForNextUpdate(); - const { getByTestId, queryByTestId } = render( - - {result.current.getGrouping({ - ...groupingArgs, - data: { - groupsCount: { - value: 9, - }, - groupByFields: { - buckets: [ - { - key: ['critical hosts', 'description'], - key_as_string: 'critical hosts|description', - doc_count: 3, - unitsCount: { - value: 3, - }, + const { result } = renderHook(() => useGrouping(defaultArgs)); + await waitFor(() => new Promise((resolve) => resolve(null))); + const { getByTestId, queryByTestId } = render( + + {result.current.getGrouping({ + ...groupingArgs, + data: { + groupsCount: { + value: 9, + }, + groupByFields: { + buckets: [ + { + key: ['critical hosts', 'description'], + key_as_string: 'critical hosts|description', + doc_count: 3, + unitsCount: { + value: 3, }, - ], - }, - unitsCount: { - value: 18, - }, + }, + ], + }, + unitsCount: { + value: 18, }, - renderChildComponent: () =>

{'hello'}

, - selectedGroup: 'none', - })} -
- ); + }, + renderChildComponent: () =>

{'hello'}

, + selectedGroup: 'none', + })} +
+ ); - expect(getByTestId('innerTable')).toBeInTheDocument(); - expect(queryByTestId('grouping-table')).not.toBeInTheDocument(); - }); + expect(getByTestId('innerTable')).toBeInTheDocument(); + expect(queryByTestId('grouping-table')).not.toBeInTheDocument(); }); it('Renders child component with grouping table wrapper when group is selected', async () => { - await act(async () => { - const getItem = jest.spyOn(window.localStorage.__proto__, 'getItem'); - getItem.mockReturnValue( - JSON.stringify({ - 'test-table': { - itemsPerPageOptions: [10, 25, 50, 100], - itemsPerPage: 25, - activeGroup: 'kibana.alert.rule.name', - options: defaultGroupingOptions, - }, - }) - ); + const getItem = jest.spyOn(window.localStorage.__proto__, 'getItem'); + getItem.mockReturnValue( + JSON.stringify({ + 'test-table': { + itemsPerPageOptions: [10, 25, 50, 100], + itemsPerPage: 25, + activeGroup: 'kibana.alert.rule.name', + options: defaultGroupingOptions, + }, + }) + ); - const { result, waitForNextUpdate } = renderHook(() => useGrouping(defaultArgs)); - await waitForNextUpdate(); - await waitForNextUpdate(); - const { getByTestId } = render( - - {result.current.getGrouping({ - ...groupingArgs, - data: { - groupsCount: { - value: 9, - }, - groupByFields: { - buckets: [ - { - key: ['critical hosts', 'description'], - key_as_string: 'critical hosts|description', - doc_count: 3, - unitsCount: { - value: 3, - }, + const { result } = renderHook(() => useGrouping(defaultArgs)); + await waitFor(() => new Promise((resolve) => resolve(null))); + const { getByTestId } = render( + + {result.current.getGrouping({ + ...groupingArgs, + data: { + groupsCount: { + value: 9, + }, + groupByFields: { + buckets: [ + { + key: ['critical hosts', 'description'], + key_as_string: 'critical hosts|description', + doc_count: 3, + unitsCount: { + value: 3, }, - ], - }, - unitsCount: { - value: 18, - }, + }, + ], + }, + unitsCount: { + value: 18, }, - renderChildComponent: jest.fn(), - selectedGroup: 'test', - })} - - ); + }, + renderChildComponent: jest.fn(), + selectedGroup: 'test', + })} + + ); - expect(getByTestId('grouping-table')).toBeInTheDocument(); - }); + expect(getByTestId('grouping-table')).toBeInTheDocument(); }); }); diff --git a/packages/kbn-guided-onboarding/kibana.jsonc b/packages/kbn-guided-onboarding/kibana.jsonc index 6b7815910f2f..8a3ec04df9f3 100644 --- a/packages/kbn-guided-onboarding/kibana.jsonc +++ b/packages/kbn-guided-onboarding/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/guided-onboarding", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/classic_version/guide_filters.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/classic_version/guide_filters.tsx index 1a2842c3a659..73f39f8ce5a6 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/classic_version/guide_filters.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/classic_version/guide_filters.tsx @@ -75,7 +75,7 @@ export const GuideFilters = ({ activeFilter, setActiveFilter, application }: Gui >
diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_filters.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_filters.tsx index 81d25e5a7d63..00152936a58b 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_filters.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide/guide_filters.tsx @@ -76,7 +76,7 @@ export const GuideFilters = ({ >
diff --git a/packages/kbn-handlebars/kibana.jsonc b/packages/kbn-handlebars/kibana.jsonc index 59b3c28ddb39..52c2290705f3 100644 --- a/packages/kbn-handlebars/kibana.jsonc +++ b/packages/kbn-handlebars/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/handlebars", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-hapi-mocks/kibana.jsonc b/packages/kbn-hapi-mocks/kibana.jsonc index f88e6e29df49..2938ef8711ac 100644 --- a/packages/kbn-hapi-mocks/kibana.jsonc +++ b/packages/kbn-hapi-mocks/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/hapi-mocks", - "owner": "@elastic/kibana-core" + "owner": "@elastic/kibana-core", + "group": "platform", + "visibility": "private" } diff --git a/packages/kbn-health-gateway-server/kibana.jsonc b/packages/kbn-health-gateway-server/kibana.jsonc index 6f9470fac54e..befe8f806bb5 100644 --- a/packages/kbn-health-gateway-server/kibana.jsonc +++ b/packages/kbn-health-gateway-server/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-server", "id": "@kbn/health-gateway-server", - "owner": "@elastic/kibana-core" + "owner": "@elastic/kibana-core", + "group": "platform", + "visibility": "private" } diff --git a/packages/kbn-i18n-react/kibana.jsonc b/packages/kbn-i18n-react/kibana.jsonc index 090a4f0fa61b..f4eb824a88f7 100644 --- a/packages/kbn-i18n-react/kibana.jsonc +++ b/packages/kbn-i18n-react/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/i18n-react", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-i18n/kibana.jsonc b/packages/kbn-i18n/kibana.jsonc index 8d4bdf6f003c..c0d9b8067d9c 100644 --- a/packages/kbn-i18n/kibana.jsonc +++ b/packages/kbn-i18n/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/i18n", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-import-resolver/kibana.jsonc b/packages/kbn-import-resolver/kibana.jsonc index 6b7ae00f9da8..10ddd61b4344 100644 --- a/packages/kbn-import-resolver/kibana.jsonc +++ b/packages/kbn-import-resolver/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/import-resolver", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-index-adapter/kibana.jsonc b/packages/kbn-index-adapter/kibana.jsonc index 575d95f5a3e3..94b33ec7774b 100644 --- a/packages/kbn-index-adapter/kibana.jsonc +++ b/packages/kbn-index-adapter/kibana.jsonc @@ -2,5 +2,6 @@ "type": "shared-server", "id": "@kbn/index-adapter", "owner": "@elastic/security-threat-hunting", - "visibility": "shared" + "group": "security", + "visibility": "private" } diff --git a/packages/kbn-interpreter/kibana.jsonc b/packages/kbn-interpreter/kibana.jsonc index 9f11015263cf..b92d389c38ae 100644 --- a/packages/kbn-interpreter/kibana.jsonc +++ b/packages/kbn-interpreter/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/interpreter", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-investigation-shared/kibana.jsonc b/packages/kbn-investigation-shared/kibana.jsonc index ffc2802ff3af..5c10ef56f3b0 100644 --- a/packages/kbn-investigation-shared/kibana.jsonc +++ b/packages/kbn-investigation-shared/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/investigation-shared", - "owner": "@elastic/obs-ux-management-team" -} + "owner": [ + "@elastic/obs-ux-management-team" + ], + "group": "observability", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-io-ts-utils/kibana.jsonc b/packages/kbn-io-ts-utils/kibana.jsonc index ea3032a472dc..7bb571ab335e 100644 --- a/packages/kbn-io-ts-utils/kibana.jsonc +++ b/packages/kbn-io-ts-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/io-ts-utils", - "owner": "@elastic/obs-knowledge-team" -} + "owner": [ + "@elastic/obs-knowledge-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-ipynb/kibana.jsonc b/packages/kbn-ipynb/kibana.jsonc index 74aa3e338fb6..1c6b0245c66e 100644 --- a/packages/kbn-ipynb/kibana.jsonc +++ b/packages/kbn-ipynb/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ipynb", - "owner": "@elastic/search-kibana" -} + "owner": [ + "@elastic/search-kibana" + ], + "group": "search", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-item-buffer/kibana.jsonc b/packages/kbn-item-buffer/kibana.jsonc index 624939e78dbf..ce47802b7f4b 100644 --- a/packages/kbn-item-buffer/kibana.jsonc +++ b/packages/kbn-item-buffer/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/item-buffer", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-jest-serializers/kibana.jsonc b/packages/kbn-jest-serializers/kibana.jsonc index b10e32373357..2eafbc3f93cd 100644 --- a/packages/kbn-jest-serializers/kibana.jsonc +++ b/packages/kbn-jest-serializers/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/jest-serializers", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-journeys/kibana.jsonc b/packages/kbn-journeys/kibana.jsonc index 227c4b20cf08..7c9bc16442b6 100644 --- a/packages/kbn-journeys/kibana.jsonc +++ b/packages/kbn-journeys/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "test-helper", "id": "@kbn/journeys", - "owner": ["@elastic/kibana-operations", "@elastic/appex-qa"], + "owner": [ + "@elastic/kibana-operations", + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "private", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-language-documentation/kibana.jsonc b/packages/kbn-language-documentation/kibana.jsonc index 5bd04503e639..a4b7d35aec96 100644 --- a/packages/kbn-language-documentation/kibana.jsonc +++ b/packages/kbn-language-documentation/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/language-documentation", - "owner": "@elastic/kibana-esql" -} + "owner": [ + "@elastic/kibana-esql" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-language-documentation/src/sections/generated/aggregation_functions.tsx b/packages/kbn-language-documentation/src/sections/generated/aggregation_functions.tsx index 6fe761e489c0..96cbe74ab208 100644 --- a/packages/kbn-language-documentation/src/sections/generated/aggregation_functions.tsx +++ b/packages/kbn-language-documentation/src/sections/generated/aggregation_functions.tsx @@ -318,6 +318,40 @@ export const functions = { FROM airports | STATS centroid=ST_CENTROID_AGG(location) \`\`\` + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + ignoreTag: true, + } + )} + /> + ), + }, + // Do not edit manually... automatically generated by scripts/generate_esql_docs.ts + { + label: i18n.translate('languageDocumentation.documentationESQL.std_dev', { + defaultMessage: 'STD_DEV', + }), + preview: false, + description: ( + + + ### STD_DEV + The standard deviation of a numeric field. + + \`\`\` + FROM employees + | STATS STD_DEV(height) + \`\`\` `, description: 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', diff --git a/packages/kbn-language-documentation/src/sections/generated/scalar_functions.tsx b/packages/kbn-language-documentation/src/sections/generated/scalar_functions.tsx index 8444ae16b644..55eadf85a09f 100644 --- a/packages/kbn-language-documentation/src/sections/generated/scalar_functions.tsx +++ b/packages/kbn-language-documentation/src/sections/generated/scalar_functions.tsx @@ -1280,11 +1280,11 @@ export const functions = { Performs a match query on the specified field. Returns true if the provided query matches the row. \`\`\` - from books - | where match(author, "Faulkner") - | keep book_no, author - | sort book_no - | limit 5; + FROM books + | WHERE MATCH(author, "Faulkner") + | KEEP book_no, author + | SORT book_no + | LIMIT 5; \`\`\` `, description: @@ -1996,11 +1996,11 @@ export const functions = { Performs a query string query. Returns true if the provided query string matches the row. \`\`\` - from books - | where qstr("author: Faulkner") - | keep book_no, author - | sort book_no - | limit 5; + FROM books + | WHERE QSTR("author: Faulkner") + | KEEP book_no, author + | SORT book_no + | LIMIT 5; \`\`\` `, description: diff --git a/packages/kbn-lens-embeddable-utils/kibana.jsonc b/packages/kbn-lens-embeddable-utils/kibana.jsonc index 6889324eefad..f3bd7abbc88b 100644 --- a/packages/kbn-lens-embeddable-utils/kibana.jsonc +++ b/packages/kbn-lens-embeddable-utils/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/lens-embeddable-utils", - "owner": ["@elastic/obs-ux-infra_services-team", "@elastic/kibana-visualizations"] -} + "owner": [ + "@elastic/obs-ux-infra_services-team", + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-lens-formula-docs/kibana.jsonc b/packages/kbn-lens-formula-docs/kibana.jsonc index 11135fcff7d7..ae66fff0b3b8 100644 --- a/packages/kbn-lens-formula-docs/kibana.jsonc +++ b/packages/kbn-lens-formula-docs/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/lens-formula-docs", - "owner": ["@elastic/kibana-visualizations"] -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-logging-mocks/kibana.jsonc b/packages/kbn-logging-mocks/kibana.jsonc index 78fdda54a8a9..962665f7369a 100644 --- a/packages/kbn-logging-mocks/kibana.jsonc +++ b/packages/kbn-logging-mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/logging-mocks", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-logging/kibana.jsonc b/packages/kbn-logging/kibana.jsonc index 77e12786e908..314c64b0aa86 100644 --- a/packages/kbn-logging/kibana.jsonc +++ b/packages/kbn-logging/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/logging", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-managed-content-badge/kibana.jsonc b/packages/kbn-managed-content-badge/kibana.jsonc index e679a5e83d36..784bff1465af 100644 --- a/packages/kbn-managed-content-badge/kibana.jsonc +++ b/packages/kbn-managed-content-badge/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/managed-content-badge", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-management/cards_navigation/kibana.jsonc b/packages/kbn-management/cards_navigation/kibana.jsonc index 3c43395c3e05..b5dcdfe3bca5 100644 --- a/packages/kbn-management/cards_navigation/kibana.jsonc +++ b/packages/kbn-management/cards_navigation/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/management-cards-navigation", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/application/kibana.jsonc b/packages/kbn-management/settings/application/kibana.jsonc index 5ae6bb5132b2..566c99ddfa4a 100644 --- a/packages/kbn-management/settings/application/kibana.jsonc +++ b/packages/kbn-management/settings/application/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/management-settings-application", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_category/kibana.jsonc b/packages/kbn-management/settings/components/field_category/kibana.jsonc index 7161a8c75f49..734757a51987 100644 --- a/packages/kbn-management/settings/components/field_category/kibana.jsonc +++ b/packages/kbn-management/settings/components/field_category/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/management-settings-components-field-category", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx b/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx index 4d3b5ef9a405..7257a5395f2f 100644 --- a/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx +++ b/packages/kbn-management/settings/components/field_input/input/number_input.test.tsx @@ -85,11 +85,11 @@ describe('NumberInput', () => { expect(input).toBeDisabled(); }); - it('recovers if value is null', () => { + it('recovers if value is null', async () => { const { getByTestId } = render( wrap() ); - const input = getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`); - waitFor(() => expect(input).toHaveValue(undefined)); + + await waitFor(() => expect(getByTestId(`${TEST_SUBJ_PREFIX_FIELD}-${id}`)).toHaveValue(null)); }); }); diff --git a/packages/kbn-management/settings/components/field_input/kibana.jsonc b/packages/kbn-management/settings/components/field_input/kibana.jsonc index ce990d295595..95f3fc2fc75a 100644 --- a/packages/kbn-management/settings/components/field_input/kibana.jsonc +++ b/packages/kbn-management/settings/components/field_input/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/management-settings-components-field-input", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/field_row/kibana.jsonc b/packages/kbn-management/settings/components/field_row/kibana.jsonc index e39c157f5325..7a0da59a74af 100644 --- a/packages/kbn-management/settings/components/field_row/kibana.jsonc +++ b/packages/kbn-management/settings/components/field_row/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/management-settings-components-field-row", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/components/form/kibana.jsonc b/packages/kbn-management/settings/components/form/kibana.jsonc index 58daacd2622a..5297d8a292ee 100644 --- a/packages/kbn-management/settings/components/form/kibana.jsonc +++ b/packages/kbn-management/settings/components/form/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/management-settings-components-form", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/field_definition/kibana.jsonc b/packages/kbn-management/settings/field_definition/kibana.jsonc index 23459871d788..f56c69ad4766 100644 --- a/packages/kbn-management/settings/field_definition/kibana.jsonc +++ b/packages/kbn-management/settings/field_definition/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/management-settings-field-definition", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/section_registry/kibana.jsonc b/packages/kbn-management/settings/section_registry/kibana.jsonc index 53a9aea666df..2c367093133a 100644 --- a/packages/kbn-management/settings/section_registry/kibana.jsonc +++ b/packages/kbn-management/settings/section_registry/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-browser", "id": "@kbn/management-settings-section-registry", - "owner": "@elastic/appex-sharedux @elastic/kibana-management" -} + "owner": [ + "@elastic/appex-sharedux", + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/setting_ids/kibana.jsonc b/packages/kbn-management/settings/setting_ids/kibana.jsonc index 62b1f89566d5..e47caf903a14 100644 --- a/packages/kbn-management/settings/setting_ids/kibana.jsonc +++ b/packages/kbn-management/settings/setting_ids/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/management-settings-ids", - "owner": "@elastic/appex-sharedux @elastic/kibana-management" -} + "owner": [ + "@elastic/appex-sharedux", + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/types/kibana.jsonc b/packages/kbn-management/settings/types/kibana.jsonc index 54a3836a4838..994e4ba3bbee 100644 --- a/packages/kbn-management/settings/types/kibana.jsonc +++ b/packages/kbn-management/settings/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/management-settings-types", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-management/settings/utilities/category/get_category_name.ts b/packages/kbn-management/settings/utilities/category/get_category_name.ts index 5569479b48ad..8ff9851e5862 100644 --- a/packages/kbn-management/settings/utilities/category/get_category_name.ts +++ b/packages/kbn-management/settings/utilities/category/get_category_name.ts @@ -62,7 +62,7 @@ const names: Record = { defaultMessage: 'Reporting', }), [SEARCH_CATEGORY]: i18n.translate('management.settings.categoryNames.searchLabel', { - defaultMessage: 'Search', + defaultMessage: 'Elasticsearch', }), [SECURITY_SOLUTION_CATEGORY]: i18n.translate( 'management.settings.categoryNames.securitySolutionLabel', diff --git a/packages/kbn-management/settings/utilities/kibana.jsonc b/packages/kbn-management/settings/utilities/kibana.jsonc index a32f319da8f6..cecf1ee6537d 100644 --- a/packages/kbn-management/settings/utilities/kibana.jsonc +++ b/packages/kbn-management/settings/utilities/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/management-settings-utilities", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-manifest/kibana.jsonc b/packages/kbn-manifest/kibana.jsonc index 27f2d95e6550..b6ce7ae0c286 100644 --- a/packages/kbn-manifest/kibana.jsonc +++ b/packages/kbn-manifest/kibana.jsonc @@ -1,5 +1,6 @@ { "type": "shared-server", "id": "@kbn/manifest", - "owner": "@elastic/kibana-core" + "owner": "@elastic/kibana-core", + "devOnly": true } diff --git a/packages/kbn-mapbox-gl/kibana.jsonc b/packages/kbn-mapbox-gl/kibana.jsonc index 6cc7e1f7b2b3..c0174f25e37e 100644 --- a/packages/kbn-mapbox-gl/kibana.jsonc +++ b/packages/kbn-mapbox-gl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/mapbox-gl", - "owner": "@elastic/kibana-presentation" -} + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-mock-idp-utils/kibana.jsonc b/packages/kbn-mock-idp-utils/kibana.jsonc index 443dd39d1e6d..e88f8a1fa4bb 100644 --- a/packages/kbn-mock-idp-utils/kibana.jsonc +++ b/packages/kbn-mock-idp-utils/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/mock-idp-utils", - "owner": "@elastic/kibana-security", - "devOnly": true, + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private", + "devOnly": true } \ No newline at end of file diff --git a/packages/kbn-monaco/kibana.jsonc b/packages/kbn-monaco/kibana.jsonc index 32e962e27ca3..1f04833d036b 100644 --- a/packages/kbn-monaco/kibana.jsonc +++ b/packages/kbn-monaco/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/monaco", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts b/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts index c2a200e65080..ba70ee4c2f06 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts @@ -95,6 +95,7 @@ describe('ESQL Theme', () => { 'setting_ws', 'metrics_ws', 'closing_metrics_ws', + 'join_ws', ]; // First, check that every valid exception is actually valid diff --git a/packages/kbn-monaco/src/esql/lib/esql_theme.ts b/packages/kbn-monaco/src/esql/lib/esql_theme.ts index 07a4d723b63e..d8223df99bbd 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_theme.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_theme.ts @@ -74,9 +74,15 @@ export const buildESQLTheme = ({ 'as', 'limit', 'dev_lookup', + 'dev_join_lookup', + 'dev_join', + 'dev_join_full', + 'dev_join_left', + 'dev_join_right', 'null', 'enrich', 'on', + 'using', 'with', 'asc', 'desc', @@ -138,6 +144,8 @@ export const buildESQLTheme = ({ 'lookup_multiline_comment', 'lookup_field_line_comment', 'lookup_field_multiline_comment', + 'join_line_comment', + 'join_multiline_comment', 'show_line_comment', 'show_multiline_comment', 'setting', diff --git a/packages/kbn-object-versioning-utils/kibana.jsonc b/packages/kbn-object-versioning-utils/kibana.jsonc index deba859afd36..b80f4dc13bec 100644 --- a/packages/kbn-object-versioning-utils/kibana.jsonc +++ b/packages/kbn-object-versioning-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/object-versioning-utils", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-object-versioning/kibana.jsonc b/packages/kbn-object-versioning/kibana.jsonc index 00f87ac804f4..5d20eefe37bd 100644 --- a/packages/kbn-object-versioning/kibana.jsonc +++ b/packages/kbn-object-versioning/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/object-versioning", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-openapi-common/kibana.jsonc b/packages/kbn-openapi-common/kibana.jsonc index 4254feb1b8a7..1c8f5a009dfc 100644 --- a/packages/kbn-openapi-common/kibana.jsonc +++ b/packages/kbn-openapi-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/openapi-common", - "owner": "@elastic/security-detection-rule-management" -} + "owner": [ + "@elastic/security-detection-rule-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-optimizer-webpack-helpers/kibana.jsonc b/packages/kbn-optimizer-webpack-helpers/kibana.jsonc index 1fa8375008f2..f4f1607d7b9e 100644 --- a/packages/kbn-optimizer-webpack-helpers/kibana.jsonc +++ b/packages/kbn-optimizer-webpack-helpers/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/optimizer-webpack-helpers", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index e1e9b6aa8189..caa5c3b53d19 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -42,6 +42,7 @@ pageLoadAssetSize: embeddableEnhanced: 22107 enterpriseSearch: 66810 entityManager: 17175 + entityManagerApp: 20378 esql: 37000 esqlDataGrid: 24582 esUiShared: 326654 @@ -143,6 +144,7 @@ pageLoadAssetSize: searchHomepage: 19831 searchIndices: 20519 searchInferenceEndpoints: 20470 + searchNavigation: 19233 searchNotebooks: 18942 searchPlayground: 19325 searchprofiler: 67080 @@ -161,6 +163,7 @@ pageLoadAssetSize: stackAlerts: 58316 stackConnectors: 67227 streams: 16742 + streamsApp: 20537 synthetics: 55971 telemetry: 51957 telemetryManagementSection: 38586 diff --git a/packages/kbn-osquery-io-ts-types/kibana.jsonc b/packages/kbn-osquery-io-ts-types/kibana.jsonc index d989501855da..261ffcf60a1d 100644 --- a/packages/kbn-osquery-io-ts-types/kibana.jsonc +++ b/packages/kbn-osquery-io-ts-types/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/osquery-io-ts-types", - "owner": "@elastic/security-asset-management" + "owner": "@elastic/security-asset-management", + "group": "platform", + "visibility": "shared" } diff --git a/packages/kbn-panel-loader/kibana.jsonc b/packages/kbn-panel-loader/kibana.jsonc index 5fc518a8983c..381e86db160a 100644 --- a/packages/kbn-panel-loader/kibana.jsonc +++ b/packages/kbn-panel-loader/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/panel-loader", - "owner": "@elastic/kibana-presentation" -} + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-peggy/kibana.jsonc b/packages/kbn-peggy/kibana.jsonc index a1cdcc8f802a..f3a821456e4a 100644 --- a/packages/kbn-peggy/kibana.jsonc +++ b/packages/kbn-peggy/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/peggy", - "owner": "@elastic/kibana-operations", + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-profiling-utils/kibana.jsonc b/packages/kbn-profiling-utils/kibana.jsonc index d41a4db71299..36840c5274ca 100644 --- a/packages/kbn-profiling-utils/kibana.jsonc +++ b/packages/kbn-profiling-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/profiling-utils", - "owner": "@elastic/obs-ux-infra_services-team" -} + "owner": [ + "@elastic/obs-ux-infra_services-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-react-field/kibana.jsonc b/packages/kbn-react-field/kibana.jsonc index 5b65d59d4dbb..7a8158adf140 100644 --- a/packages/kbn-react-field/kibana.jsonc +++ b/packages/kbn-react-field/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/react-field", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-react-hooks/kibana.jsonc b/packages/kbn-react-hooks/kibana.jsonc index d968b4340b35..3c5d2e78d0ed 100644 --- a/packages/kbn-react-hooks/kibana.jsonc +++ b/packages/kbn-react-hooks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/react-hooks", - "owner": "@elastic/obs-ux-logs-team" -} + "owner": [ + "@elastic/obs-ux-logs-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-react-hooks/src/use_boolean/use_boolean.test.ts b/packages/kbn-react-hooks/src/use_boolean/use_boolean.test.ts index 8d5583e926ba..9711d54823d3 100644 --- a/packages/kbn-react-hooks/src/use_boolean/use_boolean.test.ts +++ b/packages/kbn-react-hooks/src/use_boolean/use_boolean.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, cleanup, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act, cleanup } from '@testing-library/react'; import { useBoolean } from './use_boolean'; diff --git a/packages/kbn-recently-accessed/kibana.jsonc b/packages/kbn-recently-accessed/kibana.jsonc index 0ec9917dc6b7..48b007b3e99b 100644 --- a/packages/kbn-recently-accessed/kibana.jsonc +++ b/packages/kbn-recently-accessed/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/recently-accessed", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-repo-info/kibana.jsonc b/packages/kbn-repo-info/kibana.jsonc index ab12583890df..bf7f4bc9bacc 100644 --- a/packages/kbn-repo-info/kibana.jsonc +++ b/packages/kbn-repo-info/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/repo-info", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-repo-packages/index.js b/packages/kbn-repo-packages/index.js index b8784b3ef4b2..067d75b6e169 100644 --- a/packages/kbn-repo-packages/index.js +++ b/packages/kbn-repo-packages/index.js @@ -35,6 +35,7 @@ const { parseKbnImportReq } = require('./modern/parse_kbn_import_req'); const { getRepoRels, getRepoRelsSync } = require('./modern/get_repo_rels'); const Jsonc = require('./utils/jsonc'); const { getPluginPackagesFilter, getPluginSearchPaths } = require('./modern/plugins'); +const { readPackageJson } = require('./modern/parse_package_json'); module.exports = { Package, @@ -52,4 +53,5 @@ module.exports = { parseKbnImportReq, getRepoRels, getRepoRelsSync, + readPackageJson, }; diff --git a/packages/kbn-repo-packages/kibana.jsonc b/packages/kbn-repo-packages/kibana.jsonc index 868735cc2c1a..2bb82ef6495c 100644 --- a/packages/kbn-repo-packages/kibana.jsonc +++ b/packages/kbn-repo-packages/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/repo-packages", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-repo-path/kibana.jsonc b/packages/kbn-repo-path/kibana.jsonc index 2c0bc6368ff6..b32e21e3fdd6 100644 --- a/packages/kbn-repo-path/kibana.jsonc +++ b/packages/kbn-repo-path/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/repo-path", - "owner": "@elastic/kibana-operations", + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", "devOnly": true -} +} \ No newline at end of file diff --git a/packages/kbn-repo-source-classifier/index.ts b/packages/kbn-repo-source-classifier/index.ts index f82499bfd640..8a9d65246601 100644 --- a/packages/kbn-repo-source-classifier/index.ts +++ b/packages/kbn-repo-source-classifier/index.ts @@ -7,5 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +export type { ModuleId } from './src/module_id'; export type { ModuleType } from './src/module_type'; export { RepoSourceClassifier } from './src/repo_source_classifier'; diff --git a/packages/kbn-reporting/common/kibana.jsonc b/packages/kbn-reporting/common/kibana.jsonc index 9b0385483691..198147ed5a90 100644 --- a/packages/kbn-reporting/common/kibana.jsonc +++ b/packages/kbn-reporting/common/kibana.jsonc @@ -2,4 +2,6 @@ "type": "shared-common", "id": "@kbn/reporting-common", "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/kbn-reporting/export_types/csv/kibana.jsonc b/packages/kbn-reporting/export_types/csv/kibana.jsonc index e638d40ede56..417605c641a1 100644 --- a/packages/kbn-reporting/export_types/csv/kibana.jsonc +++ b/packages/kbn-reporting/export_types/csv/kibana.jsonc @@ -1,5 +1,9 @@ { - "type": "shared-server", - "id": "@kbn/reporting-export-types-csv", - "owner": "@elastic/appex-sharedux" - } \ No newline at end of file + "type": "shared-server", + "id": "@kbn/reporting-export-types-csv", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-reporting/export_types/csv_common/kibana.jsonc b/packages/kbn-reporting/export_types/csv_common/kibana.jsonc index 10e347c55ab5..a12125b4da07 100644 --- a/packages/kbn-reporting/export_types/csv_common/kibana.jsonc +++ b/packages/kbn-reporting/export_types/csv_common/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/reporting-export-types-csv-common", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/kbn-reporting/export_types/pdf/kibana.jsonc b/packages/kbn-reporting/export_types/pdf/kibana.jsonc index 1cc83f4395e2..716c3bbb8473 100644 --- a/packages/kbn-reporting/export_types/pdf/kibana.jsonc +++ b/packages/kbn-reporting/export_types/pdf/kibana.jsonc @@ -1,5 +1,9 @@ { - "type": "shared-server", - "id": "@kbn/reporting-export-types-pdf", - "owner": "@elastic/appex-sharedux" - } \ No newline at end of file + "type": "shared-server", + "id": "@kbn/reporting-export-types-pdf", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-reporting/export_types/pdf_common/kibana.jsonc b/packages/kbn-reporting/export_types/pdf_common/kibana.jsonc index a9aecce08286..1785e8f062eb 100644 --- a/packages/kbn-reporting/export_types/pdf_common/kibana.jsonc +++ b/packages/kbn-reporting/export_types/pdf_common/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/reporting-export-types-pdf-common", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/kbn-reporting/export_types/png/kibana.jsonc b/packages/kbn-reporting/export_types/png/kibana.jsonc index b8923806260f..db64ceb40fb9 100644 --- a/packages/kbn-reporting/export_types/png/kibana.jsonc +++ b/packages/kbn-reporting/export_types/png/kibana.jsonc @@ -1,5 +1,9 @@ { - "type": "shared-server", - "id": "@kbn/reporting-export-types-png", - "owner": "@elastic/appex-sharedux" - } \ No newline at end of file + "type": "shared-server", + "id": "@kbn/reporting-export-types-png", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-reporting/export_types/png_common/kibana.jsonc b/packages/kbn-reporting/export_types/png_common/kibana.jsonc index 0c0b7259864c..8b041ebc1bae 100644 --- a/packages/kbn-reporting/export_types/png_common/kibana.jsonc +++ b/packages/kbn-reporting/export_types/png_common/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/reporting-export-types-png-common", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/kbn-reporting/get_csv_panel_actions/kibana.jsonc b/packages/kbn-reporting/get_csv_panel_actions/kibana.jsonc index a37c3dbc2d61..1548edc6488d 100644 --- a/packages/kbn-reporting/get_csv_panel_actions/kibana.jsonc +++ b/packages/kbn-reporting/get_csv_panel_actions/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/reporting-csv-share-panel", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-reporting/mocks_server/kibana.jsonc b/packages/kbn-reporting/mocks_server/kibana.jsonc index e9c9707b67a2..ce43a094b699 100644 --- a/packages/kbn-reporting/mocks_server/kibana.jsonc +++ b/packages/kbn-reporting/mocks_server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/reporting-mocks-server", - "owner": "@elastic/appex-sharedux" + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" } diff --git a/packages/kbn-reporting/public/kibana.jsonc b/packages/kbn-reporting/public/kibana.jsonc index c813e18f70e8..16b3330e2fb2 100644 --- a/packages/kbn-reporting/public/kibana.jsonc +++ b/packages/kbn-reporting/public/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/reporting-public", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-reporting/server/__snapshots__/config_schema.test.ts.snap b/packages/kbn-reporting/server/__snapshots__/config_schema.test.ts.snap index fecc985c76f3..d52630ab6820 100644 --- a/packages/kbn-reporting/server/__snapshots__/config_schema.test.ts.snap +++ b/packages/kbn-reporting/server/__snapshots__/config_schema.test.ts.snap @@ -7,7 +7,6 @@ Object { }, "csv": Object { "checkForFormulas": true, - "enablePanelActionDownload": false, "escapeFormulaValues": false, "maxConcurrentShardRequests": 5, "maxSizeBytes": ByteSizeValue { @@ -70,7 +69,6 @@ Object { }, "csv": Object { "checkForFormulas": true, - "enablePanelActionDownload": false, "escapeFormulaValues": false, "maxConcurrentShardRequests": 5, "maxSizeBytes": ByteSizeValue { diff --git a/packages/kbn-reporting/server/config_schema.ts b/packages/kbn-reporting/server/config_schema.ts index a4117341d27c..e14bc30ab56f 100644 --- a/packages/kbn-reporting/server/config_schema.ts +++ b/packages/kbn-reporting/server/config_schema.ts @@ -60,7 +60,7 @@ const CaptureSchema = schema.object({ const CsvSchema = schema.object({ checkForFormulas: schema.boolean({ defaultValue: true }), escapeFormulaValues: schema.boolean({ defaultValue: false }), - enablePanelActionDownload: schema.boolean({ defaultValue: false }), // unused as of 9.0 + enablePanelActionDownload: schema.maybe(schema.boolean({ defaultValue: false })), // unused as of 9.0 maxSizeBytes: schema.oneOf([schema.number(), schema.byteSize()], { defaultValue: ByteSizeValue.parse('250mb'), }), diff --git a/packages/kbn-reporting/server/export_type.ts b/packages/kbn-reporting/server/export_type.ts index 2ddd69b826b6..32be67ddad3f 100644 --- a/packages/kbn-reporting/server/export_type.ts +++ b/packages/kbn-reporting/server/export_type.ts @@ -8,7 +8,6 @@ */ import type { IClusterClient } from '@kbn/core-elasticsearch-server'; -import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; import type { FakeRawRequest, Headers, @@ -28,6 +27,7 @@ import type { ScreenshottingStart } from '@kbn/screenshotting-plugin/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; import type { CreateJobFn, RunTaskFn } from './types'; import type { ReportingConfigType } from '.'; @@ -126,7 +126,7 @@ export abstract class ExportType< headers, path: '/', }; - const fakeRequest = CoreKibanaRequest.from(rawRequest); + const fakeRequest = kibanaRequestFactory(rawRequest); const spacesService = this.setupDeps.spaces?.spacesService; if (spacesService) { diff --git a/packages/kbn-reporting/server/kibana.jsonc b/packages/kbn-reporting/server/kibana.jsonc index a2440596bd9d..e4c95256bc2f 100644 --- a/packages/kbn-reporting/server/kibana.jsonc +++ b/packages/kbn-reporting/server/kibana.jsonc @@ -2,9 +2,10 @@ "type": "shared-server", "id": "@kbn/reporting-server", "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private", "plugin": { "server": true, "browser": false, } } - diff --git a/packages/kbn-reporting/server/tsconfig.json b/packages/kbn-reporting/server/tsconfig.json index 7981ce1c3f99..94b082afdc2e 100644 --- a/packages/kbn-reporting/server/tsconfig.json +++ b/packages/kbn-reporting/server/tsconfig.json @@ -31,8 +31,8 @@ "@kbn/licensing-plugin", "@kbn/spaces-plugin", "@kbn/core-elasticsearch-server", - "@kbn/core-http-router-server-internal", "@kbn/core-http-request-handler-context-server", "@kbn/config-schema", + "@kbn/core-http-server-utils", ] } diff --git a/packages/kbn-resizable-layout/kibana.jsonc b/packages/kbn-resizable-layout/kibana.jsonc index abc49ac47ffb..e02d6599bba9 100644 --- a/packages/kbn-resizable-layout/kibana.jsonc +++ b/packages/kbn-resizable-layout/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/resizable-layout", - "description": "A component for creating resizable layouts containing a fixed width panel and a flexible panel, with support for horizontal and vertical layouts.", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", + "description": "A component for creating resizable layouts containing a fixed width panel and a flexible panel, with support for horizontal and vertical layouts." +} \ No newline at end of file diff --git a/packages/kbn-rison/kibana.jsonc b/packages/kbn-rison/kibana.jsonc index c0e6145d04a7..0962a3a02987 100644 --- a/packages/kbn-rison/kibana.jsonc +++ b/packages/kbn-rison/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/rison", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-router-to-openapispec/kibana.jsonc b/packages/kbn-router-to-openapispec/kibana.jsonc index a14f443ce27e..800f12d33159 100644 --- a/packages/kbn-router-to-openapispec/kibana.jsonc +++ b/packages/kbn-router-to-openapispec/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/router-to-openapispec", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-router-utils/kibana.jsonc b/packages/kbn-router-utils/kibana.jsonc index c255dacb11c7..18b5c54a9c78 100644 --- a/packages/kbn-router-utils/kibana.jsonc +++ b/packages/kbn-router-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/router-utils", - "owner": "@elastic/obs-ux-logs-team" -} + "owner": [ + "@elastic/obs-ux-logs-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-rrule/kibana.jsonc b/packages/kbn-rrule/kibana.jsonc index 08878a6cfb1e..5b5374ff6a1a 100644 --- a/packages/kbn-rrule/kibana.jsonc +++ b/packages/kbn-rrule/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/rrule", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-rule-data-utils/kibana.jsonc b/packages/kbn-rule-data-utils/kibana.jsonc index 4d9d77fbeeb7..733ffbdfa67f 100644 --- a/packages/kbn-rule-data-utils/kibana.jsonc +++ b/packages/kbn-rule-data-utils/kibana.jsonc @@ -5,5 +5,7 @@ "@elastic/security-detections-response", "@elastic/response-ops", "@elastic/obs-ux-management-team" - ] -} + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-safer-lodash-set/kibana.jsonc b/packages/kbn-safer-lodash-set/kibana.jsonc index d01d41b9a621..5f4ad3f4395d 100644 --- a/packages/kbn-safer-lodash-set/kibana.jsonc +++ b/packages/kbn-safer-lodash-set/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/safer-lodash-set", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-saved-objects-settings/kibana.jsonc b/packages/kbn-saved-objects-settings/kibana.jsonc index 40486e1ef0cf..c03224f017a2 100644 --- a/packages/kbn-saved-objects-settings/kibana.jsonc +++ b/packages/kbn-saved-objects-settings/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/saved-objects-settings", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-screenshotting-server/kibana.jsonc b/packages/kbn-screenshotting-server/kibana.jsonc index 1f2aa1c0f579..a71e7b15d2b8 100644 --- a/packages/kbn-screenshotting-server/kibana.jsonc +++ b/packages/kbn-screenshotting-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/screenshotting-server", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-search-api-keys-components/kibana.jsonc b/packages/kbn-search-api-keys-components/kibana.jsonc index bedd4c213760..54160e857656 100644 --- a/packages/kbn-search-api-keys-components/kibana.jsonc +++ b/packages/kbn-search-api-keys-components/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/search-api-keys-components", - "owner": "@elastic/search-kibana" + "owner": [ + "@elastic/search-kibana" + ], + "group": "search", + "visibility": "private" } \ No newline at end of file diff --git a/packages/kbn-search-api-keys-server/kibana.jsonc b/packages/kbn-search-api-keys-server/kibana.jsonc index 52c6cd965396..5102ebdeda7f 100644 --- a/packages/kbn-search-api-keys-server/kibana.jsonc +++ b/packages/kbn-search-api-keys-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/search-api-keys-server", - "owner": "@elastic/search-kibana" + "owner": [ + "@elastic/search-kibana" + ], + "group": "search", + "visibility": "private" } \ No newline at end of file diff --git a/packages/kbn-search-api-panels/components/cloud_details.tsx b/packages/kbn-search-api-panels/components/cloud_details.tsx index 26710fb17436..df2cc6bb6837 100644 --- a/packages/kbn-search-api-panels/components/cloud_details.tsx +++ b/packages/kbn-search-api-panels/components/cloud_details.tsx @@ -82,7 +82,7 @@ export const CloudDetailsPanel = ({ @@ -123,7 +123,7 @@ export const CloudDetailsPanel = ({ {Boolean(cloudId) && (
diff --git a/packages/kbn-search-api-panels/kibana.jsonc b/packages/kbn-search-api-panels/kibana.jsonc index 3e346c91d555..98b03521b624 100644 --- a/packages/kbn-search-api-panels/kibana.jsonc +++ b/packages/kbn-search-api-panels/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/search-api-panels", - "owner": "@elastic/search-kibana" + "owner": [ + "@elastic/search-kibana" + ], + // FIXME? @kbn/index-management-plugin depends on it + "group": "platform", + "visibility": "shared" } diff --git a/packages/kbn-search-connectors/components/configuration/connector_configuration_field.tsx b/packages/kbn-search-connectors/components/configuration/connector_configuration_field.tsx index 08b91ffc1842..ab851495187c 100644 --- a/packages/kbn-search-connectors/components/configuration/connector_configuration_field.tsx +++ b/packages/kbn-search-connectors/components/configuration/connector_configuration_field.tsx @@ -20,7 +20,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, - EuiIcon, + EuiIconTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -110,7 +110,7 @@ export const ConfigSensitiveTextArea: React.FC = ({

{label}

- + ) : ( @@ -228,7 +228,7 @@ export const ConnectorConfigurationField: React.FC{label}

} + label={label} onChange={(event) => { validateAndSetConfigValue(event.target.checked); }} @@ -263,27 +263,21 @@ export const ConnectorConfigurationField: React.FC - -

{label}

-
- - - - - ) : ( -

{label}

- ) - } - onChange={(event) => { - validateAndSetConfigValue(event.target.checked); - }} - /> + + { + validateAndSetConfigValue(event.target.checked); + }} + /> + {tooltip && ( + + + + )} + ); default: diff --git a/packages/kbn-search-connectors/components/configuration/connector_configuration_form_items.tsx b/packages/kbn-search-connectors/components/configuration/connector_configuration_form_items.tsx index 8d90aa9a39d9..a53ece70c807 100644 --- a/packages/kbn-search-connectors/components/configuration/connector_configuration_form_items.tsx +++ b/packages/kbn-search-connectors/components/configuration/connector_configuration_form_items.tsx @@ -9,7 +9,7 @@ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -66,7 +66,9 @@ export const ConnectorConfigurationFormItems: React.FC{label}

- +
+ +
) : ( @@ -77,46 +79,42 @@ export const ConnectorConfigurationFormItems: React.FC - - - { - setConfigEntry(configEntry.key, value); - }} - /> - - + + { + setConfigEntry(configEntry.key, value); + }} + /> + ); } return ( - - - { - setConfigEntry(configEntry.key, value); - }} - /> - - + + { + setConfigEntry(configEntry.key, value); + }} + /> + ); })} diff --git a/packages/kbn-search-connectors/kibana.jsonc b/packages/kbn-search-connectors/kibana.jsonc index d5254ac9b68c..d784b6c15c67 100644 --- a/packages/kbn-search-connectors/kibana.jsonc +++ b/packages/kbn-search-connectors/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/search-connectors", - "owner": "@elastic/search-kibana" + "owner": [ + "@elastic/search-kibana" + ], + // FIXME? search-connectors-plugin depends on it + "group": "platform", + "visibility": "shared" } diff --git a/packages/kbn-search-errors/kibana.jsonc b/packages/kbn-search-errors/kibana.jsonc index 9d4e7e2472b9..208da0ca9222 100644 --- a/packages/kbn-search-errors/kibana.jsonc +++ b/packages/kbn-search-errors/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/search-errors", - "owner": "@elastic/kibana-data-discovery" + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" } diff --git a/packages/kbn-search-index-documents/components/result/result_field.tsx b/packages/kbn-search-index-documents/components/result/result_field.tsx index acd495f71cd4..cfc9263cf87f 100644 --- a/packages/kbn-search-index-documents/components/result/result_field.tsx +++ b/packages/kbn-search-index-documents/components/result/result_field.tsx @@ -85,6 +85,12 @@ export const ResultField: React.FC = ({ setIsPopoverOpen(!isPopoverOpen)} iconType={iconType || (fieldType ? iconMap[fieldType] : defaultToken)} /> diff --git a/packages/kbn-search-index-documents/kibana.jsonc b/packages/kbn-search-index-documents/kibana.jsonc index a0a69aff312c..f28c08c52577 100644 --- a/packages/kbn-search-index-documents/kibana.jsonc +++ b/packages/kbn-search-index-documents/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/search-index-documents", - "owner": "@elastic/search-kibana" + "owner": "@elastic/search-kibana", + "group": "search", + "visibility": "private" } diff --git a/packages/kbn-search-response-warnings/kibana.jsonc b/packages/kbn-search-response-warnings/kibana.jsonc index 5c8127c27319..530a97c7e3bb 100644 --- a/packages/kbn-search-response-warnings/kibana.jsonc +++ b/packages/kbn-search-response-warnings/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/search-response-warnings", - "owner": "@elastic/kibana-data-discovery" + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" } diff --git a/packages/kbn-search-types/kibana.jsonc b/packages/kbn-search-types/kibana.jsonc index 2f61b7444a0d..f2b5a43ad63f 100644 --- a/packages/kbn-search-types/kibana.jsonc +++ b/packages/kbn-search-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/search-types", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-security-hardening/kibana.jsonc b/packages/kbn-security-hardening/kibana.jsonc index 42b778b24fcc..b434a95e7098 100644 --- a/packages/kbn-security-hardening/kibana.jsonc +++ b/packages/kbn-security-hardening/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/security-hardening", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-autocomplete/kibana.jsonc b/packages/kbn-securitysolution-autocomplete/kibana.jsonc index 0446d8bc04ed..42e6564593c8 100644 --- a/packages/kbn-securitysolution-autocomplete/kibana.jsonc +++ b/packages/kbn-securitysolution-autocomplete/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-autocomplete", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-ecs/kibana.jsonc b/packages/kbn-securitysolution-ecs/kibana.jsonc index f288e4b2951c..8a5b40f54fcb 100644 --- a/packages/kbn-securitysolution-ecs/kibana.jsonc +++ b/packages/kbn-securitysolution-ecs/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-ecs", - "owner": "@elastic/security-threat-hunting-explore" -} + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/kibana.jsonc b/packages/kbn-securitysolution-endpoint-exceptions-common/kibana.jsonc index 76a63cc5ac56..3351c559d082 100644 --- a/packages/kbn-securitysolution-endpoint-exceptions-common/kibana.jsonc +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-endpoint-exceptions-common", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-es-utils/kibana.jsonc b/packages/kbn-securitysolution-es-utils/kibana.jsonc index 6cc0af9e25de..c7befc64d888 100644 --- a/packages/kbn-securitysolution-es-utils/kibana.jsonc +++ b/packages/kbn-securitysolution-es-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-es-utils", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-exception-list-components/kibana.jsonc b/packages/kbn-securitysolution-exception-list-components/kibana.jsonc index 9d280a7c5098..e616d5d8d941 100644 --- a/packages/kbn-securitysolution-exception-list-components/kibana.jsonc +++ b/packages/kbn-securitysolution-exception-list-components/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-exception-list-components", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-exceptions-common/kibana.jsonc b/packages/kbn-securitysolution-exceptions-common/kibana.jsonc index ff47bd8f5a58..4ca98e71a925 100644 --- a/packages/kbn-securitysolution-exceptions-common/kibana.jsonc +++ b/packages/kbn-securitysolution-exceptions-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-exceptions-common", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-hook-utils/kibana.jsonc b/packages/kbn-securitysolution-hook-utils/kibana.jsonc index abea333eaf08..e05c4329e8c3 100644 --- a/packages/kbn-securitysolution-hook-utils/kibana.jsonc +++ b/packages/kbn-securitysolution-hook-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-hook-utils", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/kibana.jsonc b/packages/kbn-securitysolution-io-ts-alerting-types/kibana.jsonc index 6749cb99ac47..813f13d89ff9 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/kibana.jsonc +++ b/packages/kbn-securitysolution-io-ts-alerting-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-io-ts-alerting-types", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-io-ts-list-types/kibana.jsonc b/packages/kbn-securitysolution-io-ts-list-types/kibana.jsonc index ce40047794df..7c835b05d02e 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/kibana.jsonc +++ b/packages/kbn-securitysolution-io-ts-list-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-io-ts-list-types", - "owner": "@elastic/security-detection-engine" + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" } diff --git a/packages/kbn-securitysolution-io-ts-types/kibana.jsonc b/packages/kbn-securitysolution-io-ts-types/kibana.jsonc index c099c55f08a0..0eff2e1ce902 100644 --- a/packages/kbn-securitysolution-io-ts-types/kibana.jsonc +++ b/packages/kbn-securitysolution-io-ts-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-io-ts-types", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-io-ts-utils/kibana.jsonc b/packages/kbn-securitysolution-io-ts-utils/kibana.jsonc index 3628e51de2c9..f0b84ddb6c7e 100644 --- a/packages/kbn-securitysolution-io-ts-utils/kibana.jsonc +++ b/packages/kbn-securitysolution-io-ts-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-io-ts-utils", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-list-api/kibana.jsonc b/packages/kbn-securitysolution-list-api/kibana.jsonc index 70a32bbf8c31..e3408f02a5ab 100644 --- a/packages/kbn-securitysolution-list-api/kibana.jsonc +++ b/packages/kbn-securitysolution-list-api/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-list-api", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-list-constants/kibana.jsonc b/packages/kbn-securitysolution-list-constants/kibana.jsonc index 9185bef63b5c..38cc48cd8710 100644 --- a/packages/kbn-securitysolution-list-constants/kibana.jsonc +++ b/packages/kbn-securitysolution-list-constants/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-list-constants", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-list-hooks/kibana.jsonc b/packages/kbn-securitysolution-list-hooks/kibana.jsonc index f0d26c1e126e..fc5331b30960 100644 --- a/packages/kbn-securitysolution-list-hooks/kibana.jsonc +++ b/packages/kbn-securitysolution-list-hooks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-list-hooks", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-list-utils/kibana.jsonc b/packages/kbn-securitysolution-list-utils/kibana.jsonc index ee3b8f0e4510..bbf6b2449969 100644 --- a/packages/kbn-securitysolution-list-utils/kibana.jsonc +++ b/packages/kbn-securitysolution-list-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-list-utils", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-lists-common/kibana.jsonc b/packages/kbn-securitysolution-lists-common/kibana.jsonc index 614314f10e8b..4e00ce7b91d9 100644 --- a/packages/kbn-securitysolution-lists-common/kibana.jsonc +++ b/packages/kbn-securitysolution-lists-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-lists-common", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-rules/kibana.jsonc b/packages/kbn-securitysolution-rules/kibana.jsonc index 1bb40e4eada6..1c4bb22857b5 100644 --- a/packages/kbn-securitysolution-rules/kibana.jsonc +++ b/packages/kbn-securitysolution-rules/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-rules", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-t-grid/kibana.jsonc b/packages/kbn-securitysolution-t-grid/kibana.jsonc index 4e08fb680466..81f74a1116e1 100644 --- a/packages/kbn-securitysolution-t-grid/kibana.jsonc +++ b/packages/kbn-securitysolution-t-grid/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-t-grid", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-securitysolution-t-grid/src/mock/mock_event_details.ts b/packages/kbn-securitysolution-t-grid/src/mock/mock_event_details.ts index dc2497bb19ce..5152132cda4b 100644 --- a/packages/kbn-securitysolution-t-grid/src/mock/mock_event_details.ts +++ b/packages/kbn-securitysolution-t-grid/src/mock/mock_event_details.ts @@ -291,6 +291,29 @@ export const eventDetailsFormattedFields = [ originalValue: [`{"lon":118.7778,"lat":32.0617}`], values: [`{"lon":118.7778,"lat":32.0617}`], }, + { + category: 'threat', + field: 'threat.enrichments', + isObjectArray: true, + originalValue: [ + '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', + '{"matched.field":["matched_field_2"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["other_you"],"indicator.type":["custom"],"matched.atomic":["matched_atomic_2"],"lazer":[{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}]}', + '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["FFEtSYIBZ61VHL7LvV2j"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.architecture"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["x86_64"]}', + '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["CFErSYIBZ61VHL7LIV1N"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + ], + values: [ + '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', + '{"matched.field":["matched_field_2"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["other_you"],"indicator.type":["custom"],"matched.atomic":["matched_atomic_2"],"lazer":[{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}]}', + '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["FFEtSYIBZ61VHL7LvV2j"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.architecture"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["x86_64"]}', + '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["CFErSYIBZ61VHL7LIV1N"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + ], + }, { category: 'threat', field: 'threat.enrichments.matched.field', @@ -376,27 +399,4 @@ export const eventDetailsFormattedFields = [ originalValue: ['FFEtSYIBZ61VHL7LvV2j', 'E1EtSYIBZ61VHL7Ltl3m', 'CFErSYIBZ61VHL7LIV1N'], values: ['FFEtSYIBZ61VHL7LvV2j', 'E1EtSYIBZ61VHL7Ltl3m', 'CFErSYIBZ61VHL7LIV1N'], }, - { - category: 'threat', - field: 'threat.enrichments', - isObjectArray: true, - originalValue: [ - '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', - '{"matched.field":["matched_field_2"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["other_you"],"indicator.type":["custom"],"matched.atomic":["matched_atomic_2"],"lazer":[{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}]}', - '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["FFEtSYIBZ61VHL7LvV2j"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.architecture"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["x86_64"]}', - '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["CFErSYIBZ61VHL7LIV1N"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - ], - values: [ - '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', - '{"matched.field":["matched_field_2"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["other_you"],"indicator.type":["custom"],"matched.atomic":["matched_atomic_2"],"lazer":[{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}]}', - '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["FFEtSYIBZ61VHL7LvV2j"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.architecture"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["x86_64"]}', - '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["CFErSYIBZ61VHL7LIV1N"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - ], - }, ]; diff --git a/packages/kbn-securitysolution-utils/index.ts b/packages/kbn-securitysolution-utils/index.ts index d29b356f3178..8769a281e220 100644 --- a/packages/kbn-securitysolution-utils/index.ts +++ b/packages/kbn-securitysolution-utils/index.ts @@ -12,3 +12,4 @@ export * from './src/axios'; export * from './src/transform_data_to_ndjson'; export * from './src/path_validations'; export * from './src/esql'; +export * from './src/debounce_async/debounce_async'; diff --git a/packages/kbn-securitysolution-utils/kibana.jsonc b/packages/kbn-securitysolution-utils/kibana.jsonc index 53dec9673e49..5e6ccf9096ea 100644 --- a/packages/kbn-securitysolution-utils/kibana.jsonc +++ b/packages/kbn-securitysolution-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-utils", - "owner": "@elastic/security-detection-engine" -} + "owner": [ + "@elastic/security-detection-engine" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.test.ts b/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.test.ts similarity index 64% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.test.ts rename to packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.test.ts index 6c5cadf41a7a..d7e1201e44e8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.test.ts +++ b/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.test.ts @@ -1,22 +1,20 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ -import { debounceAsync } from './validators'; +import { debounceAsync } from './debounce_async'; jest.useFakeTimers({ legacyFakeTimers: true }); describe('debounceAsync', () => { - let fn: jest.Mock; - - beforeEach(() => { - fn = jest.fn().mockResolvedValueOnce('first'); - }); - it('resolves with the underlying invocation result', async () => { + const fn = jest.fn().mockResolvedValueOnce('first'); + const debounced = debounceAsync(fn, 0); const promise = debounced(); jest.runOnlyPendingTimers(); @@ -25,6 +23,8 @@ describe('debounceAsync', () => { }); it('resolves intermediate calls when the next invocation resolves', async () => { + const fn = jest.fn().mockResolvedValueOnce('first'); + const debounced = debounceAsync(fn, 200); fn.mockResolvedValueOnce('second'); @@ -39,6 +39,8 @@ describe('debounceAsync', () => { }); it('debounces the function', async () => { + const fn = jest.fn().mockResolvedValueOnce('first'); + const debounced = debounceAsync(fn, 200); debounced(); diff --git a/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts b/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts new file mode 100644 index 000000000000..99fe653b0e21 --- /dev/null +++ b/packages/kbn-securitysolution-utils/src/debounce_async/debounce_async.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +/** + * Unlike lodash's debounce, which resolves intermediate calls with the most + * recent value, this implementation waits to resolve intermediate calls until + * the next invocation resolves. + * + * @param fn an async function + * + * @returns A debounced async function that resolves on the next invocation + */ +export function debounceAsync( + fn: (...args: Args) => Result, + intervalMs: number +): (...args: Args) => Promise> { + let timeoutId: ReturnType | undefined; + let resolve: (value: Awaited) => void; + let promise = new Promise>((_resolve) => { + resolve = _resolve; + }); + + return (...args) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + + timeoutId = setTimeout(async () => { + resolve(await fn(...args)); + promise = new Promise((_resolve) => { + resolve = _resolve; + }); + }, intervalMs); + + return promise; + }; +} diff --git a/packages/kbn-server-http-tools/kibana.jsonc b/packages/kbn-server-http-tools/kibana.jsonc index f540a5be0912..30c15aa7f732 100644 --- a/packages/kbn-server-http-tools/kibana.jsonc +++ b/packages/kbn-server-http-tools/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/server-http-tools", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-server-route-repository-client/kibana.jsonc b/packages/kbn-server-route-repository-client/kibana.jsonc index 5a3974d7ab3c..cdb59a56ca04 100644 --- a/packages/kbn-server-route-repository-client/kibana.jsonc +++ b/packages/kbn-server-route-repository-client/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/server-route-repository-client", - "owner": "@elastic/obs-knowledge-team" -} + "owner": [ + "@elastic/obs-knowledge-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-server-route-repository-client/src/create_repository_client.ts b/packages/kbn-server-route-repository-client/src/create_repository_client.ts index 015db2b9948d..325f484103d0 100644 --- a/packages/kbn-server-route-repository-client/src/create_repository_client.ts +++ b/packages/kbn-server-route-repository-client/src/create_repository_client.ts @@ -15,12 +15,12 @@ import { } from '@kbn/server-route-repository-utils'; import { httpResponseIntoObservable } from '@kbn/sse-utils-client'; import { from } from 'rxjs'; -import { HttpFetchOptions, HttpFetchQuery, HttpResponse } from '@kbn/core-http-browser'; +import { HttpFetchQuery, HttpResponse } from '@kbn/core-http-browser'; import { omit } from 'lodash'; export function createRepositoryClient< TRepository extends ServerRouteRepository, - TClientOptions extends HttpFetchOptions = {} + TClientOptions extends Record = {} >(core: CoreStart | CoreSetup): RouteRepositoryClient { const fetch = ( endpoint: string, diff --git a/packages/kbn-server-route-repository-utils/index.ts b/packages/kbn-server-route-repository-utils/index.ts index 14322f9b64c8..a1e3ec45bd6f 100644 --- a/packages/kbn-server-route-repository-utils/index.ts +++ b/packages/kbn-server-route-repository-utils/index.ts @@ -18,7 +18,6 @@ export type { EndpointOf, ReturnOf, RouteRepositoryClient, - RouteState, ClientRequestParamsOf, DecodedRequestParamsOf, ServerRouteRepository, diff --git a/packages/kbn-server-route-repository-utils/kibana.jsonc b/packages/kbn-server-route-repository-utils/kibana.jsonc index 3a7e69d98a06..d536b152126f 100644 --- a/packages/kbn-server-route-repository-utils/kibana.jsonc +++ b/packages/kbn-server-route-repository-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/server-route-repository-utils", - "owner": "@elastic/obs-knowledge-team" -} + "owner": [ + "@elastic/obs-knowledge-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-server-route-repository-utils/src/parse_endpoint.ts b/packages/kbn-server-route-repository-utils/src/parse_endpoint.ts index e16590c9a666..b81b3285ec8f 100644 --- a/packages/kbn-server-route-repository-utils/src/parse_endpoint.ts +++ b/packages/kbn-server-route-repository-utils/src/parse_endpoint.ts @@ -7,22 +7,20 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -type Method = 'get' | 'post' | 'put' | 'patch' | 'delete'; +import type { RouteMethod } from '@kbn/core-http-server'; + +const validMethods: RouteMethod[] = ['delete', 'get', 'patch', 'post', 'put']; export function parseEndpoint(endpoint: string) { const parts = endpoint.split(' '); - const method = parts[0].trim().toLowerCase() as Method; + const method = parts[0].trim().toLowerCase() as Exclude; const pathname = parts[1].trim(); const version = parts[2]?.trim(); - if (!['get', 'post', 'put', 'patch', 'delete'].includes(method)) { + if (!validMethods.includes(method)) { throw new Error(`Endpoint ${endpoint} was not prefixed with a valid HTTP method`); } - if (!version && pathname.startsWith('/api')) { - throw new Error(`Missing version for public endpoint ${endpoint}`); - } - return { method, pathname, version }; } diff --git a/packages/kbn-server-route-repository-utils/src/typings.ts b/packages/kbn-server-route-repository-utils/src/typings.ts index 35a2f41054c9..5db4a87b8b32 100644 --- a/packages/kbn-server-route-repository-utils/src/typings.ts +++ b/packages/kbn-server-route-repository-utils/src/typings.ts @@ -8,7 +8,7 @@ */ import type { HttpFetchOptions } from '@kbn/core-http-browser'; -import type { IKibanaResponse } from '@kbn/core-http-server'; +import type { IKibanaResponse, RouteAccess, RouteSecurity } from '@kbn/core-http-server'; import type { KibanaRequest, KibanaResponseFactory, @@ -22,7 +22,7 @@ import { z } from '@kbn/zod'; import * as t from 'io-ts'; import { Observable } from 'rxjs'; import { Readable } from 'stream'; -import { RequiredKeys, ValuesType } from 'utility-types'; +import { Required, RequiredKeys, ValuesType } from 'utility-types'; type MaybeOptional }> = RequiredKeys< T['params'] @@ -50,24 +50,37 @@ export type ZodParamsObject = z.ZodObject<{ export type IoTsParamsObject = WithoutIncompatibleMethods>; export type RouteParamsRT = IoTsParamsObject | ZodParamsObject; +export type ServerRouteHandlerResources = Record; -export interface RouteState { - [endpoint: string]: ServerRoute; +export interface ServerRouteCreateOptions { + [x: string]: any; } -export type ServerRouteHandlerResources = Record; -export type ServerRouteCreateOptions = Record; +type RouteMethodOf = TEndpoint extends `${infer TRouteMethod} ${string}` + ? Lowercase extends RouteMethod + ? Lowercase + : never + : never; -type ValidateEndpoint = string extends TEndpoint +type IsPublicEndpoint< + TEndpoint extends string, + TRouteAccess extends RouteAccess | undefined +> = TRouteAccess extends 'public' ? true - : TEndpoint extends `${string} ${string} ${string}` + : TRouteAccess extends 'internal' + ? false + : TEndpoint extends `${string} /api${string}` ? true - : TEndpoint extends `${string} ${infer TPathname}` - ? TPathname extends `/internal/${string}` - ? true - : false : false; +type IsVersionSpecified = + TEndpoint extends `${string} ${string} ${string}` ? true : false; + +type ValidateEndpoint< + TEndpoint extends string, + TRouteAccess extends RouteAccess | undefined +> = IsPublicEndpoint extends true ? IsVersionSpecified : true; + type IsAny = 1 | 0 extends (T extends never ? 1 : 0) ? true : false; // this ensures only plain objects can be returned, if it's not one @@ -127,17 +140,27 @@ type ServerRouteHandler< export type CreateServerRouteFactory< TRouteHandlerResources extends ServerRouteHandlerResources, - TRouteCreateOptions extends ServerRouteCreateOptions + TRouteCreateOptions extends DefaultRouteCreateOptions | undefined > = < TEndpoint extends string, TReturnType extends ServerRouteHandlerReturnType, - TRouteParamsRT extends RouteParamsRT | undefined = undefined + TRouteParamsRT extends RouteParamsRT | undefined = undefined, + TRouteAccess extends RouteAccess | undefined = undefined >( options: { - endpoint: ValidateEndpoint extends true ? TEndpoint : never; + endpoint: ValidateEndpoint extends true ? TEndpoint : never; handler: ServerRouteHandler; params?: TRouteParamsRT; - } & TRouteCreateOptions + security?: RouteSecurity; + } & Required< + { + options?: (TRouteCreateOptions extends DefaultRouteCreateOptions ? TRouteCreateOptions : {}) & + RouteConfigOptions> & { + access?: TRouteAccess; + }; + }, + RequiredKeys extends never ? never : 'options' + > ) => Record< TEndpoint, ServerRoute< @@ -154,16 +177,17 @@ export type ServerRoute< TRouteParamsRT extends RouteParamsRT | undefined, TRouteHandlerResources extends ServerRouteHandlerResources, TReturnType extends ServerRouteHandlerReturnType, - TRouteCreateOptions extends ServerRouteCreateOptions + TRouteCreateOptions extends DefaultRouteCreateOptions | undefined > = { endpoint: TEndpoint; handler: ServerRouteHandler; -} & TRouteCreateOptions & - (TRouteParamsRT extends RouteParamsRT ? { params: TRouteParamsRT } : {}); + security?: RouteSecurity; +} & (TRouteParamsRT extends RouteParamsRT ? { params: TRouteParamsRT } : {}) & + (TRouteCreateOptions extends DefaultRouteCreateOptions ? { options: TRouteCreateOptions } : {}); export type ServerRouteRepository = Record< string, - ServerRoute> + ServerRoute >; type ClientRequestParamsOfType = @@ -194,13 +218,7 @@ export type EndpointOf = export type ReturnOf< TServerRouteRepository extends ServerRouteRepository, TEndpoint extends keyof TServerRouteRepository -> = TServerRouteRepository[TEndpoint] extends ServerRoute< - any, - any, - any, - infer TReturnType, - ServerRouteCreateOptions -> +> = TServerRouteRepository[TEndpoint] extends ServerRoute ? TReturnType extends IKibanaResponse ? TWrappedResponseType : TReturnType @@ -209,13 +227,7 @@ export type ReturnOf< export type DecodedRequestParamsOf< TServerRouteRepository extends ServerRouteRepository, TEndpoint extends keyof TServerRouteRepository -> = TServerRouteRepository[TEndpoint] extends ServerRoute< - any, - infer TRouteParamsRT, - any, - any, - ServerRouteCreateOptions -> +> = TServerRouteRepository[TEndpoint] extends ServerRoute ? TRouteParamsRT extends RouteParamsRT ? DecodedRequestParamsOfType : {} @@ -229,7 +241,7 @@ export type ClientRequestParamsOf< infer TRouteParamsRT, any, any, - ServerRouteCreateOptions + ServerRouteCreateOptions | undefined > ? TRouteParamsRT extends RouteParamsRT ? ClientRequestParamsOfType @@ -249,13 +261,17 @@ export interface RouteRepositoryClient< fetch>( endpoint: TEndpoint, ...args: MaybeOptionalArgs< - ClientRequestParamsOf & TAdditionalClientOptions + ClientRequestParamsOf & + TAdditionalClientOptions & + HttpFetchOptions > ): Promise>; stream>( endpoint: TEndpoint, ...args: MaybeOptionalArgs< - ClientRequestParamsOf & TAdditionalClientOptions + ClientRequestParamsOf & + TAdditionalClientOptions & + HttpFetchOptions > ): ReturnOf extends Observable ? TReturnType extends ServerSentEvent @@ -276,6 +292,4 @@ export interface DefaultRouteHandlerResources extends CoreRouteHandlerResources logger: Logger; } -export interface DefaultRouteCreateOptions { - options?: RouteConfigOptions; -} +export type DefaultRouteCreateOptions = RouteConfigOptions>; diff --git a/packages/kbn-server-route-repository/index.ts b/packages/kbn-server-route-repository/index.ts index a922c2ebb1e1..16958e744417 100644 --- a/packages/kbn-server-route-repository/index.ts +++ b/packages/kbn-server-route-repository/index.ts @@ -23,7 +23,6 @@ export type { ServerRouteRepository, ServerRoute, RouteParamsRT, - RouteState, DefaultRouteCreateOptions, DefaultRouteHandlerResources, IoTsParamsObject, diff --git a/packages/kbn-server-route-repository/kibana.jsonc b/packages/kbn-server-route-repository/kibana.jsonc index 5137b5593cfd..22da9d601081 100644 --- a/packages/kbn-server-route-repository/kibana.jsonc +++ b/packages/kbn-server-route-repository/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/server-route-repository", - "owner": ["@elastic/obs-knowledge-team"] -} + "owner": [ + "@elastic/obs-knowledge-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-server-route-repository/src/create_server_route_factory.ts b/packages/kbn-server-route-repository/src/create_server_route_factory.ts index be375bd06948..34fc375b0d9a 100644 --- a/packages/kbn-server-route-repository/src/create_server_route_factory.ts +++ b/packages/kbn-server-route-repository/src/create_server_route_factory.ts @@ -8,16 +8,17 @@ */ import type { - DefaultRouteCreateOptions, DefaultRouteHandlerResources, - ServerRouteCreateOptions, ServerRouteHandlerResources, } from '@kbn/server-route-repository-utils'; -import type { CreateServerRouteFactory } from '@kbn/server-route-repository-utils/src/typings'; +import type { + CreateServerRouteFactory, + DefaultRouteCreateOptions, +} from '@kbn/server-route-repository-utils/src/typings'; export function createServerRouteFactory< TRouteHandlerResources extends ServerRouteHandlerResources = DefaultRouteHandlerResources, - TRouteCreateOptions extends ServerRouteCreateOptions = DefaultRouteCreateOptions + TRouteCreateOptions extends DefaultRouteCreateOptions | undefined = undefined >(): CreateServerRouteFactory { return (route) => ({ [route.endpoint]: route } as any); } diff --git a/packages/kbn-server-route-repository/src/register_routes.test.ts b/packages/kbn-server-route-repository/src/register_routes.test.ts index 249f81df3a8a..b13592c57ba5 100644 --- a/packages/kbn-server-route-repository/src/register_routes.test.ts +++ b/packages/kbn-server-route-repository/src/register_routes.test.ts @@ -15,6 +15,7 @@ import { NEVER } from 'rxjs'; import * as makeZodValidationObject from './make_zod_validation_object'; import { registerRoutes } from './register_routes'; import { passThroughValidationObject, noParamsValidationObject } from './validation_objects'; +import { ServerRouteRepository } from '@kbn/server-route-repository-utils'; describe('registerRoutes', () => { const post = jest.fn(); @@ -54,44 +55,82 @@ describe('registerRoutes', () => { 'POST /internal/route': { endpoint: 'POST /internal/route', handler: jest.fn(), - options: { - internal: true, - }, }, 'POST /api/public_route version': { endpoint: 'POST /api/public_route version', handler: jest.fn(), + }, + 'POST /api/internal_but_looks_like_public version': { + endpoint: 'POST /api/internal_but_looks_like_public version', options: { - public: true, + access: 'internal', }, + handler: jest.fn(), }, - }); + 'POST /internal/route_with_security': { + endpoint: `POST /internal/route_with_security`, + handler: jest.fn(), + security: { + authz: { + enabled: false, + reason: 'whatever', + }, + }, + }, + 'POST /api/route_with_security version': { + endpoint: `POST /api/route_with_security version`, + handler: jest.fn(), + security: { + authz: { + enabled: false, + reason: 'whatever', + }, + }, + }, + } satisfies ServerRouteRepository); expect(createRouter).toHaveBeenCalledTimes(1); - expect(post).toHaveBeenCalledTimes(1); - const [internalRoute] = post.mock.calls[0]; expect(internalRoute.path).toEqual('/internal/route'); expect(internalRoute.options).toEqual({ - internal: true, + access: 'internal', }); expect(internalRoute.validate).toEqual(noParamsValidationObject); - expect(postWithVersion).toHaveBeenCalledTimes(1); + const [internalRouteWithSecurity] = post.mock.calls[1]; + + expect(internalRouteWithSecurity.path).toEqual('/internal/route_with_security'); + expect(internalRouteWithSecurity.security).toEqual({ + authz: { + enabled: false, + reason: 'whatever', + }, + }); + const [publicRoute] = postWithVersion.mock.calls[0]; expect(publicRoute.path).toEqual('/api/public_route'); - expect(publicRoute.options).toEqual({ - public: true, - }); expect(publicRoute.access).toEqual('public'); - expect(postAddVersion).toHaveBeenCalledTimes(1); + const [apiInternalRoute] = postWithVersion.mock.calls[1]; + expect(apiInternalRoute.path).toEqual('/api/internal_but_looks_like_public'); + expect(apiInternalRoute.access).toEqual('internal'); + const [versionedRoute] = postAddVersion.mock.calls[0]; expect(versionedRoute.version).toEqual('version'); expect(versionedRoute.validate).toEqual({ request: noParamsValidationObject, }); + + const [publicRouteWithSecurity] = postWithVersion.mock.calls[2]; + + expect(publicRouteWithSecurity.path).toEqual('/api/route_with_security'); + expect(publicRouteWithSecurity.security).toEqual({ + authz: { + enabled: false, + reason: 'whatever', + }, + }); }); it('does not allow any params if no schema is provided', () => { diff --git a/packages/kbn-server-route-repository/src/register_routes.ts b/packages/kbn-server-route-repository/src/register_routes.ts index 5e0fa51a4544..6201ffcd869e 100644 --- a/packages/kbn-server-route-repository/src/register_routes.ts +++ b/packages/kbn-server-route-repository/src/register_routes.ts @@ -15,19 +15,20 @@ import { isKibanaResponse } from '@kbn/core-http-server'; import type { CoreSetup } from '@kbn/core-lifecycle-server'; import type { Logger } from '@kbn/logging'; import { + DefaultRouteCreateOptions, + RouteParamsRT, ServerRoute, - ServerRouteCreateOptions, ZodParamsObject, parseEndpoint, } from '@kbn/server-route-repository-utils'; +import { ServerSentEvent } from '@kbn/sse-utils'; import { observableIntoEventSourceStream } from '@kbn/sse-utils-server'; import { isZod } from '@kbn/zod'; -import { merge } from 'lodash'; +import { merge, omit } from 'lodash'; import { Observable, isObservable } from 'rxjs'; -import { ServerSentEvent } from '@kbn/sse-utils'; -import { passThroughValidationObject, noParamsValidationObject } from './validation_objects'; -import { validateAndDecodeParams } from './validate_and_decode_params'; import { makeZodValidationObject } from './make_zod_validation_object'; +import { validateAndDecodeParams } from './validate_and_decode_params'; +import { noParamsValidationObject, passThroughValidationObject } from './validation_objects'; const CLIENT_CLOSED_REQUEST = { statusCode: 499, @@ -43,7 +44,7 @@ export function registerRoutes>({ dependencies, }: { core: CoreSetup; - repository: Record>; + repository: Record>; logger: Logger; dependencies: TDependencies; }) { @@ -52,7 +53,11 @@ export function registerRoutes>({ const router = core.http.createRouter(); routes.forEach((route) => { - const { params, endpoint, options, handler } = route; + const { endpoint, handler, security } = route; + + const params = 'params' in route ? route.params : undefined; + + const options: DefaultRouteCreateOptions = 'options' in route ? route.options : {}; const { method, pathname, version } = parseEndpoint(endpoint); @@ -137,11 +142,18 @@ export function registerRoutes>({ validationObject = passThroughValidationObject; } + const access = options?.access ?? (pathname.startsWith('/internal/') ? 'internal' : 'public'); + if (!version) { router[method]( { path: pathname, - options, + // @ts-expect-error we are essentially calling multiple methods at the same type so TS gets confused + options: { + ...options, + access, + }, + security, validate: validationObject, }, wrappedHandler @@ -149,8 +161,10 @@ export function registerRoutes>({ } else { router.versioned[method]({ path: pathname, - access: pathname.startsWith('/internal/') ? 'internal' : 'public', - options, + access, + // @ts-expect-error we are essentially calling multiple methods at the same type so TS gets confused + options: omit(options, 'access', 'description', 'summary', 'deprecated', 'discontinued'), + security, }).addVersion( { version, diff --git a/packages/kbn-server-route-repository/src/test_types.ts b/packages/kbn-server-route-repository/src/test_types.ts index acef5a524eb6..6b099c158f07 100644 --- a/packages/kbn-server-route-repository/src/test_types.ts +++ b/packages/kbn-server-route-repository/src/test_types.ts @@ -83,32 +83,44 @@ createServerRouteFactory<{ context: { getSpaceId: () => string } }, {}>()({ }); // Create options are available when registering a route. -createServerRouteFactory<{}, { options: { tags: string[] } }>()({ +createServerRouteFactory<{}, {}>()({ endpoint: 'GET /internal/endpoint_with_params', params: t.type({ path: t.type({ serviceName: t.string, }), }), - options: { - tags: [], - }, handler: async (resources) => { assertType<{ params: { path: { serviceName: string } } }>(resources); }, }); // Public APIs should be versioned -createServerRouteFactory<{}, { options: { tags: string[] } }>()({ +createServerRouteFactory<{}, { tags: string[] }>()({ // @ts-expect-error + endpoint: 'GET /api/endpoint_with_params', + tags: [], + handler: async (resources) => {}, +}); + +// `access` is respected +createServerRouteFactory<{}, { tags: string[] }>()({ endpoint: 'GET /api/endpoint_with_params', options: { tags: [], + access: 'internal', }, handler: async (resources) => {}, }); -createServerRouteFactory<{}, { options: { tags: string[] } }>()({ +// specifying additional options makes them required +// @ts-expect-error +createServerRouteFactory<{}, { tags: string[] }>()({ + endpoint: 'GET /api/endpoint_with_params 2023-10-31', + handler: async (resources) => {}, +}); + +createServerRouteFactory<{}, { tags: string[] }>()({ endpoint: 'GET /api/endpoint_with_params 2023-10-31', options: { tags: [], diff --git a/packages/kbn-shared-svg/kibana.jsonc b/packages/kbn-shared-svg/kibana.jsonc index 89949f5b7d27..709dce31f58a 100644 --- a/packages/kbn-shared-svg/kibana.jsonc +++ b/packages/kbn-shared-svg/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/shared-svg", - "owner": "@elastic/obs-ux-infra_services-team" + "owner": "@elastic/obs-ux-infra_services-team", + "group": "platform", + "visibility": "shared" } diff --git a/packages/kbn-shared-ux-utility/kibana.jsonc b/packages/kbn-shared-ux-utility/kibana.jsonc index 23205fc09e20..8fc1bc88c27f 100644 --- a/packages/kbn-shared-ux-utility/kibana.jsonc +++ b/packages/kbn-shared-ux-utility/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-utility", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-some-dev-log/kibana.jsonc b/packages/kbn-some-dev-log/kibana.jsonc index d83704903bdf..3163f7a887c5 100644 --- a/packages/kbn-some-dev-log/kibana.jsonc +++ b/packages/kbn-some-dev-log/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/some-dev-log", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-sort-predicates/kibana.jsonc b/packages/kbn-sort-predicates/kibana.jsonc index c07088597a01..2f25996de748 100644 --- a/packages/kbn-sort-predicates/kibana.jsonc +++ b/packages/kbn-sort-predicates/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/sort-predicates", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-sse-utils-client/kibana.jsonc b/packages/kbn-sse-utils-client/kibana.jsonc index eda99336320f..60f2a2dc0252 100644 --- a/packages/kbn-sse-utils-client/kibana.jsonc +++ b/packages/kbn-sse-utils-client/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/sse-utils-client", - "owner": "@elastic/obs-knowledge-team" -} + "owner": [ + "@elastic/obs-knowledge-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-sse-utils-server/kibana.jsonc b/packages/kbn-sse-utils-server/kibana.jsonc index 9e06e575b798..8c533af1953e 100644 --- a/packages/kbn-sse-utils-server/kibana.jsonc +++ b/packages/kbn-sse-utils-server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/sse-utils-server", - "owner": "@elastic/obs-knowledge-team" -} + "owner": [ + "@elastic/obs-knowledge-team" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-sse-utils/kibana.jsonc b/packages/kbn-sse-utils/kibana.jsonc index 3bd583763b4d..fa3beec29fe8 100644 --- a/packages/kbn-sse-utils/kibana.jsonc +++ b/packages/kbn-sse-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/sse-utils", - "owner": "@elastic/obs-knowledge-team" -} + "owner": [ + "@elastic/obs-knowledge-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-std/kibana.jsonc b/packages/kbn-std/kibana.jsonc index 062ba5970f53..4b42cb289bf6 100644 --- a/packages/kbn-std/kibana.jsonc +++ b/packages/kbn-std/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/std", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-stdio-dev-helpers/kibana.jsonc b/packages/kbn-stdio-dev-helpers/kibana.jsonc index 4cb58f510906..b7181e0f3067 100644 --- a/packages/kbn-stdio-dev-helpers/kibana.jsonc +++ b/packages/kbn-stdio-dev-helpers/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/stdio-dev-helpers", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-storybook/kibana.jsonc b/packages/kbn-storybook/kibana.jsonc index 4faf58c30926..c1db579312fc 100644 --- a/packages/kbn-storybook/kibana.jsonc +++ b/packages/kbn-storybook/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/storybook", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-telemetry-tools/kibana.jsonc b/packages/kbn-telemetry-tools/kibana.jsonc index cf1d0def824e..d1168b66d69f 100644 --- a/packages/kbn-telemetry-tools/kibana.jsonc +++ b/packages/kbn-telemetry-tools/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-server", "id": "@kbn/telemetry-tools", - "devOnly": true, - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-test-eui-helpers/kibana.jsonc b/packages/kbn-test-eui-helpers/kibana.jsonc index fc97c720f827..029240a41861 100644 --- a/packages/kbn-test-eui-helpers/kibana.jsonc +++ b/packages/kbn-test-eui-helpers/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/test-eui-helpers", - "devOnly": true, - "owner": ["@elastic/kibana-visualizations"], -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-test-jest-helpers/kibana.jsonc b/packages/kbn-test-jest-helpers/kibana.jsonc index fdabbb9780d6..5f98c5273338 100644 --- a/packages/kbn-test-jest-helpers/kibana.jsonc +++ b/packages/kbn-test-jest-helpers/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "shared-common", "id": "@kbn/test-jest-helpers", - "devOnly": true, - "owner": ["@elastic/kibana-operations", "@elastic/appex-qa"], -} + "owner": [ + "@elastic/kibana-operations", + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-test-subj-selector/kibana.jsonc b/packages/kbn-test-subj-selector/kibana.jsonc index 8026708a83e3..b88ad46dd17c 100644 --- a/packages/kbn-test-subj-selector/kibana.jsonc +++ b/packages/kbn-test-subj-selector/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "shared-common", "id": "@kbn/test-subj-selector", - "owner": ["@elastic/kibana-operations", "@elastic/appex-qa"], + "owner": [ + "@elastic/kibana-operations", + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "shared", "devOnly": true } diff --git a/packages/kbn-test/kibana.jsonc b/packages/kbn-test/kibana.jsonc index c39f82b52a11..c99b14a03bfa 100644 --- a/packages/kbn-test/kibana.jsonc +++ b/packages/kbn-test/kibana.jsonc @@ -1,6 +1,11 @@ { "type": "shared-common", "id": "@kbn/test", - "devOnly": true, - "owner": ["@elastic/kibana-operations", "@elastic/appex-qa"], -} + "owner": [ + "@elastic/kibana-operations", + "@elastic/appex-qa" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_tests.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_tests.ts index c84d78fe1d20..2303704ea43f 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_tests.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_tests.ts @@ -57,7 +57,11 @@ export const loadTests = ({ updateBaselines, }; - decorateSnapshotUi({ lifecycle, updateSnapshots, isCi: !!process.env.CI }); + decorateSnapshotUi({ + lifecycle, + updateSnapshots, + isCi: !!process.env.CI, + }); function loadTestFile(path: string) { if (typeof path !== 'string' || !isAbsolute(path)) { diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 78e2b8d7e680..3d00eae7f22f 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -33,11 +33,13 @@ const globalState: { registered: boolean; currentTest: Test | null; snapshotStates: Record; + deploymentAgnostic: boolean; } = { updateSnapshot: 'none', registered: false, currentTest: null, snapshotStates: {}, + deploymentAgnostic: false, }; const modifyStackTracePrepareOnce = once(() => { @@ -125,7 +127,7 @@ export function decorateSnapshotUi({ const snapshotState = globalState.snapshotStates[file]; if (snapshotState && !test.isPassed()) { - snapshotState.markSnapshotsAsCheckedForTest(test.fullTitle()); + snapshotState.markSnapshotsAsCheckedForTest(getTestTitle(test)); } }); @@ -194,7 +196,7 @@ export function expectSnapshot(received: any) { const context: SnapshotContext = { snapshotState, - currentTestName: test.fullTitle(), + currentTestName: getTestTitle(test), }; return { @@ -204,6 +206,18 @@ export function expectSnapshot(received: any) { }; } +function getTestTitle(test: Test) { + return ( + test + .fullTitle() + // remove deployment type from test title so that a single snapshot can be used for all deployment types + .replace( + /^(Serverless|Stateful)\s+([^\-]+)\s*-?\s*Deployment-agnostic/g, + 'Deployment-agnostic' + ) + ); +} + function expectToMatchSnapshot(snapshotContext: SnapshotContext, received: any) { const matcher = toMatchSnapshot.bind(snapshotContext as any); const result = matcher(received) as SyncExpectationResult; diff --git a/packages/kbn-timelion-grammar/kibana.jsonc b/packages/kbn-timelion-grammar/kibana.jsonc index 88b61e0c1587..a7d321fd99a1 100644 --- a/packages/kbn-timelion-grammar/kibana.jsonc +++ b/packages/kbn-timelion-grammar/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/timelion-grammar", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-timerange/kibana.jsonc b/packages/kbn-timerange/kibana.jsonc index e3f2e74f771a..dc2e47f2a9c6 100644 --- a/packages/kbn-timerange/kibana.jsonc +++ b/packages/kbn-timerange/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/timerange", - "owner": "@elastic/obs-ux-logs-team" -} + "owner": [ + "@elastic/obs-ux-logs-team" + ], + "group": "observability", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-tinymath/kibana.jsonc b/packages/kbn-tinymath/kibana.jsonc index dd790aee9fe9..149662a6beb7 100644 --- a/packages/kbn-tinymath/kibana.jsonc +++ b/packages/kbn-tinymath/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/tinymath", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-tooling-log/kibana.jsonc b/packages/kbn-tooling-log/kibana.jsonc index d855a7dc46c7..c942e6d37594 100644 --- a/packages/kbn-tooling-log/kibana.jsonc +++ b/packages/kbn-tooling-log/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/tooling-log", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared", + "devOnly": true +} \ No newline at end of file diff --git a/packages/kbn-transpose-utils/kibana.jsonc b/packages/kbn-transpose-utils/kibana.jsonc index d891291d0720..1b79c9189ba5 100644 --- a/packages/kbn-transpose-utils/kibana.jsonc +++ b/packages/kbn-transpose-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/transpose-utils", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-triggers-actions-ui-types/kibana.jsonc b/packages/kbn-triggers-actions-ui-types/kibana.jsonc index cb14c1d7fdf4..964233667ea2 100644 --- a/packages/kbn-triggers-actions-ui-types/kibana.jsonc +++ b/packages/kbn-triggers-actions-ui-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/triggers-actions-ui-types", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-try-in-console/kibana.jsonc b/packages/kbn-try-in-console/kibana.jsonc index c5988280943d..77ac2de3095f 100644 --- a/packages/kbn-try-in-console/kibana.jsonc +++ b/packages/kbn-try-in-console/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/try-in-console", - "owner": "@elastic/search-kibana" -} + "owner": [ + "@elastic/search-kibana" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-ts-projects/ts_project.ts b/packages/kbn-ts-projects/ts_project.ts index 22c28931da14..4870bc7d0e95 100644 --- a/packages/kbn-ts-projects/ts_project.ts +++ b/packages/kbn-ts-projects/ts_project.ts @@ -14,6 +14,7 @@ import { REPO_ROOT } from '@kbn/repo-info'; import { makeMatcher } from '@kbn/picomatcher'; import { type Package, findPackageForPath, getRepoRelsSync } from '@kbn/repo-packages'; import { createFailError } from '@kbn/dev-cli-errors'; +import { readPackageJson } from '@kbn/repo-packages'; import { readTsConfig, parseTsConfig, TsConfig } from './ts_configfile'; @@ -151,6 +152,8 @@ export class TsProject { public readonly directory: string; /** the package this tsconfig file is within, if any */ public readonly pkg?: Package; + /** the package is esm or not */ + public readonly isEsm?: boolean; /** * if this project is within a package then this will * be set to the import request that maps to the root of this project @@ -187,6 +190,7 @@ export class TsProject { : undefined; this._disableTypeCheck = !!opts?.disableTypeCheck; + this.isEsm = readPackageJson(`${this.dir}/package.json`)?.type === 'module'; } private _name: string | undefined; diff --git a/packages/kbn-typed-react-router-config/index.ts b/packages/kbn-typed-react-router-config/index.ts index 3075cb48a951..6aff73c55e6c 100644 --- a/packages/kbn-typed-react-router-config/index.ts +++ b/packages/kbn-typed-react-router-config/index.ts @@ -17,3 +17,4 @@ export * from './src/use_match_routes'; export * from './src/use_params'; export * from './src/use_router'; export * from './src/use_route_path'; +export * from './src/breadcrumbs'; diff --git a/packages/kbn-typed-react-router-config/kibana.jsonc b/packages/kbn-typed-react-router-config/kibana.jsonc index c004d263a604..2f2bda611d6b 100644 --- a/packages/kbn-typed-react-router-config/kibana.jsonc +++ b/packages/kbn-typed-react-router-config/kibana.jsonc @@ -1,5 +1,10 @@ { - "type": "shared-common", + "type": "shared-browser", "id": "@kbn/typed-react-router-config", - "owner": ["@elastic/obs-knowledge-team", "@elastic/obs-ux-infra_services-team"] -} + "owner": [ + "@elastic/obs-knowledge-team", + "@elastic/obs-ux-infra_services-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-typed-react-router-config/src/breadcrumbs/breadcrumb.tsx b/packages/kbn-typed-react-router-config/src/breadcrumbs/breadcrumb.tsx new file mode 100644 index 000000000000..94a32c76c21f --- /dev/null +++ b/packages/kbn-typed-react-router-config/src/breadcrumbs/breadcrumb.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { RequiredKeys } from 'utility-types'; +import { useRouterBreadcrumb } from './use_router_breadcrumb'; +import { PathsOf, RouteMap, TypeOf } from '../types'; + +type AsParamsProps> = RequiredKeys extends never + ? {} + : { params: TObject }; + +export type RouterBreadcrumb = < + TRoutePath extends PathsOf +>({}: { + title: string; + children: React.ReactNode; + path: TRoutePath; +} & AsParamsProps>) => React.ReactElement; + +export function RouterBreadcrumb< + TRouteMap extends RouteMap, + TRoutePath extends PathsOf +>({ + title, + path, + params, + children, +}: { + title: string; + path: TRoutePath; + children: React.ReactElement; + params?: Record; +}) { + useRouterBreadcrumb( + () => ({ + title, + path, + params, + }), + [] + ); + + return children; +} diff --git a/packages/kbn-typed-react-router-config/src/breadcrumbs/context.tsx b/packages/kbn-typed-react-router-config/src/breadcrumbs/context.tsx new file mode 100644 index 000000000000..21d6a30567b1 --- /dev/null +++ b/packages/kbn-typed-react-router-config/src/breadcrumbs/context.tsx @@ -0,0 +1,113 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public'; +import { compact, isEqual } from 'lodash'; +import React, { createContext, useMemo, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { useBreadcrumbs } from './use_breadcrumbs'; +import { + PathsOf, + Route, + RouteMap, + RouteMatch, + TypeAsArgs, + TypeAsParams, + TypeOf, + useMatchRoutes, + useRouter, +} from '../..'; + +export type Breadcrumb< + TRouteMap extends RouteMap = RouteMap, + TPath extends PathsOf = PathsOf +> = { + title: string; + path: TPath; +} & TypeAsParams>; + +interface BreadcrumbApi { + set>( + route: Route, + breadcrumb: Array> + ): void; + unset(route: Route): void; + getBreadcrumbs(matches: RouteMatch[]): Array>>; +} + +export const BreadcrumbsContext = createContext(undefined); + +export function BreadcrumbsContextProvider({ + children, +}: { + children: React.ReactNode; +}) { + const [, forceUpdate] = useState({}); + + const breadcrumbs = useMemo(() => { + return new Map>>(); + }, []); + + const history = useHistory() as ScopedHistory; + + const router = useRouter(); + + const matches: RouteMatch[] = useMatchRoutes(); + + const api = useMemo>( + () => ({ + set(route, breadcrumb) { + if (!isEqual(breadcrumbs.get(route), breadcrumb)) { + breadcrumbs.set(route, breadcrumb); + forceUpdate({}); + } + }, + unset(route) { + if (breadcrumbs.has(route)) { + breadcrumbs.delete(route); + forceUpdate({}); + } + }, + getBreadcrumbs(currentMatches: RouteMatch[]) { + return compact( + currentMatches.flatMap((match) => { + const breadcrumb = breadcrumbs.get(match.route); + + return breadcrumb; + }) + ); + }, + }), + [breadcrumbs] + ); + + const formattedBreadcrumbs: ChromeBreadcrumb[] = api + .getBreadcrumbs(matches) + .map((breadcrumb, index, array) => { + return { + text: breadcrumb.title, + ...(index === array.length - 1 + ? {} + : { + href: history.createHref({ + pathname: router.link( + breadcrumb.path, + ...(('params' in breadcrumb ? [breadcrumb.params] : []) as TypeAsArgs< + TypeOf, false> + >) + ), + }), + }), + }; + }); + + useBreadcrumbs(formattedBreadcrumbs); + + return {children}; +} diff --git a/packages/kbn-typed-react-router-config/src/breadcrumbs/create_router_breadcrumb_component.tsx b/packages/kbn-typed-react-router-config/src/breadcrumbs/create_router_breadcrumb_component.tsx new file mode 100644 index 000000000000..04c8cf4e9d24 --- /dev/null +++ b/packages/kbn-typed-react-router-config/src/breadcrumbs/create_router_breadcrumb_component.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { RouteMap } from '../types'; +import { RouterBreadcrumb } from './breadcrumb'; + +export function createRouterBreadcrumbComponent< + TRouteMap extends RouteMap +>(): RouterBreadcrumb { + return RouterBreadcrumb as RouterBreadcrumb; +} diff --git a/packages/kbn-typed-react-router-config/src/breadcrumbs/index.tsx b/packages/kbn-typed-react-router-config/src/breadcrumbs/index.tsx new file mode 100644 index 000000000000..721ba433cc01 --- /dev/null +++ b/packages/kbn-typed-react-router-config/src/breadcrumbs/index.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { createRouterBreadcrumbComponent } from './create_router_breadcrumb_component'; +export { createUseBreadcrumbs } from './use_router_breadcrumb'; +export { BreadcrumbsContextProvider } from './context'; diff --git a/packages/kbn-typed-react-router-config/src/breadcrumbs/use_breadcrumbs.ts b/packages/kbn-typed-react-router-config/src/breadcrumbs/use_breadcrumbs.ts new file mode 100644 index 000000000000..3d63c8a0f27d --- /dev/null +++ b/packages/kbn-typed-react-router-config/src/breadcrumbs/use_breadcrumbs.ts @@ -0,0 +1,101 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { i18n } from '@kbn/i18n'; +import { ApplicationStart, ChromeBreadcrumb, ChromeStart } from '@kbn/core/public'; +import { MouseEvent, useEffect, useMemo } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ChromeBreadcrumbsAppendExtension } from '@kbn/core-chrome-browser'; +import type { ServerlessPluginStart } from '@kbn/serverless/public'; + +function addClickHandlers( + breadcrumbs: ChromeBreadcrumb[], + navigateToHref?: (url: string) => Promise +) { + return breadcrumbs.map((bc) => ({ + ...bc, + ...(bc.href + ? { + onClick: (event: MouseEvent) => { + if (navigateToHref && bc.href) { + event.preventDefault(); + navigateToHref(bc.href); + } + }, + } + : {}), + })); +} + +function getTitleFromBreadCrumbs(breadcrumbs: ChromeBreadcrumb[]) { + return breadcrumbs.map(({ text }) => text?.toString() ?? '').reverse(); +} + +export const useBreadcrumbs = ( + extraCrumbs: ChromeBreadcrumb[], + options?: { + app?: { id: string; label: string }; + breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension; + serverless?: ServerlessPluginStart; + } +) => { + const { app, breadcrumbsAppendExtension, serverless } = options ?? {}; + + const { + services: { + chrome: { docTitle, setBreadcrumbs: chromeSetBreadcrumbs, setBreadcrumbsAppendExtension }, + application: { getUrlForApp, navigateToUrl }, + }, + } = useKibana<{ + application: ApplicationStart; + chrome: ChromeStart; + }>(); + + const setTitle = docTitle.change; + const appPath = getUrlForApp(app?.id ?? 'observability-overview') ?? ''; + + const setBreadcrumbs = useMemo( + () => serverless?.setBreadcrumbs ?? chromeSetBreadcrumbs, + [serverless, chromeSetBreadcrumbs] + ); + + useEffect(() => { + if (breadcrumbsAppendExtension) { + setBreadcrumbsAppendExtension(breadcrumbsAppendExtension); + } + return () => { + if (breadcrumbsAppendExtension) { + setBreadcrumbsAppendExtension(undefined); + } + }; + }, [breadcrumbsAppendExtension, setBreadcrumbsAppendExtension]); + + useEffect(() => { + const breadcrumbs = serverless + ? extraCrumbs + : [ + { + text: + app?.label ?? + i18n.translate('xpack.observabilityShared.breadcrumbs.observabilityLinkText', { + defaultMessage: 'Observability', + }), + href: appPath + '/overview', + }, + ...extraCrumbs, + ]; + + if (setBreadcrumbs) { + setBreadcrumbs(addClickHandlers(breadcrumbs, navigateToUrl)); + } + if (setTitle) { + setTitle(getTitleFromBreadCrumbs(breadcrumbs)); + } + }, [app?.label, appPath, extraCrumbs, navigateToUrl, serverless, setBreadcrumbs, setTitle]); +}; diff --git a/packages/kbn-typed-react-router-config/src/breadcrumbs/use_router_breadcrumb.ts b/packages/kbn-typed-react-router-config/src/breadcrumbs/use_router_breadcrumb.ts new file mode 100644 index 000000000000..47003cbce5a2 --- /dev/null +++ b/packages/kbn-typed-react-router-config/src/breadcrumbs/use_router_breadcrumb.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { useContext, useEffect, useRef } from 'react'; +import { castArray } from 'lodash'; +import { PathsOf, RouteMap, useCurrentRoute } from '../..'; +import { Breadcrumb, BreadcrumbsContext } from './context'; + +type UseBreadcrumbs = >( + callback: () => Breadcrumb | Array>, + fnDeps: unknown[] +) => void; + +export function useRouterBreadcrumb(callback: () => Breadcrumb | Breadcrumb[], fnDeps: any[]) { + const api = useContext(BreadcrumbsContext); + + if (!api) { + throw new Error('Missing Breadcrumb API in context'); + } + + const { match } = useCurrentRoute(); + + const matchedRoute = useRef(match?.route); + + useEffect(() => { + if (matchedRoute.current && matchedRoute.current !== match?.route) { + api.unset(matchedRoute.current); + } + + matchedRoute.current = match?.route; + + if (matchedRoute.current) { + api.set(matchedRoute.current, castArray(callback())); + } + + return () => { + if (matchedRoute.current) { + api.unset(matchedRoute.current); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [matchedRoute.current, match?.route, ...fnDeps]); +} + +export function createUseBreadcrumbs(): UseBreadcrumbs { + return useRouterBreadcrumb; +} diff --git a/packages/kbn-typed-react-router-config/src/create_router.ts b/packages/kbn-typed-react-router-config/src/create_router.ts index 467fe096ffec..4321f4c0744a 100644 --- a/packages/kbn-typed-react-router-config/src/create_router.ts +++ b/packages/kbn-typed-react-router-config/src/create_router.ts @@ -11,7 +11,7 @@ import { deepExactRt, mergeRt } from '@kbn/io-ts-utils'; import { isLeft } from 'fp-ts/lib/Either'; import { Location } from 'history'; import { PathReporter } from 'io-ts/lib/PathReporter'; -import { compact, findLastIndex, merge, orderBy } from 'lodash'; +import { compact, findLastIndex, mapValues, merge, orderBy } from 'lodash'; import qs from 'query-string'; import { MatchedRoute, @@ -139,7 +139,9 @@ export function createRouter(routes: TRoutes): Router< if (route?.params) { const decoded = deepExactRt(route.params).decode( merge({}, route.defaults ?? {}, { - path: matchedRoute.match.params, + path: mapValues(matchedRoute.match.params, (value) => { + return decodeURIComponent(value); + }), query: qs.parse(location.search, { decode: true }), }) ); @@ -179,7 +181,7 @@ export function createRouter(routes: TRoutes): Router< .split('/') .map((part) => { const match = part.match(/(?:{([a-zA-Z]+)})/); - return match ? paramsWithBuiltInDefaults.path[match[1]] : part; + return match ? encodeURIComponent(paramsWithBuiltInDefaults.path[match[1]]) : part; }) .join('/'); diff --git a/packages/kbn-typed-react-router-config/src/types/index.ts b/packages/kbn-typed-react-router-config/src/types/index.ts index 3b4c36c42af5..dbab588619db 100644 --- a/packages/kbn-typed-react-router-config/src/types/index.ts +++ b/packages/kbn-typed-react-router-config/src/types/index.ts @@ -106,6 +106,12 @@ export type TypeAsArgs = keyof TObject extends never ? [TObject] | [] : [TObject]; +export type TypeAsParams = keyof TObject extends never + ? {} + : RequiredKeys extends never + ? never + : { params: TObject }; + export type FlattenRoutesOf = Array< ValuesType<{ [key in keyof MapRoutes]: ValuesType[key]>; diff --git a/packages/kbn-typed-react-router-config/src/use_router.tsx b/packages/kbn-typed-react-router-config/src/use_router.tsx index 531bc5aea53b..af92e33b8952 100644 --- a/packages/kbn-typed-react-router-config/src/use_router.tsx +++ b/packages/kbn-typed-react-router-config/src/use_router.tsx @@ -20,7 +20,7 @@ export const RouterContextProvider = ({ children: React.ReactNode; }) => {children}; -export function useRouter(): Router { +export function useRouter(): Router { const router = useContext(RouterContext); if (!router) { diff --git a/packages/kbn-typed-react-router-config/tsconfig.json b/packages/kbn-typed-react-router-config/tsconfig.json index efda70173609..f9a133f48e8d 100644 --- a/packages/kbn-typed-react-router-config/tsconfig.json +++ b/packages/kbn-typed-react-router-config/tsconfig.json @@ -14,7 +14,12 @@ ], "kbn_references": [ "@kbn/io-ts-utils", - "@kbn/shared-ux-router" + "@kbn/shared-ux-router", + "@kbn/core", + "@kbn/i18n", + "@kbn/kibana-react-plugin", + "@kbn/core-chrome-browser", + "@kbn/serverless" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-ui-actions-browser/kibana.jsonc b/packages/kbn-ui-actions-browser/kibana.jsonc index 3fbdc46e64b1..d7133132af33 100644 --- a/packages/kbn-ui-actions-browser/kibana.jsonc +++ b/packages/kbn-ui-actions-browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ui-actions-browser", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps-npm/kibana.jsonc b/packages/kbn-ui-shared-deps-npm/kibana.jsonc index 91ab8cdfc2b1..6dad4bd9bde4 100644 --- a/packages/kbn-ui-shared-deps-npm/kibana.jsonc +++ b/packages/kbn-ui-shared-deps-npm/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ui-shared-deps-npm", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps-src/kibana.jsonc b/packages/kbn-ui-shared-deps-src/kibana.jsonc index 39b71b0bd00e..5626d0a01046 100644 --- a/packages/kbn-ui-shared-deps-src/kibana.jsonc +++ b/packages/kbn-ui-shared-deps-src/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ui-shared-deps-src", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-ui-theme/kibana.jsonc b/packages/kbn-ui-theme/kibana.jsonc index db8230d520c0..e49ac0435f91 100644 --- a/packages/kbn-ui-theme/kibana.jsonc +++ b/packages/kbn-ui-theme/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ui-theme", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-unified-data-table/kibana.jsonc b/packages/kbn-unified-data-table/kibana.jsonc index b80f8b6a5559..c7af5075e95c 100644 --- a/packages/kbn-unified-data-table/kibana.jsonc +++ b/packages/kbn-unified-data-table/kibana.jsonc @@ -1,9 +1,11 @@ { "type": "shared-browser", "id": "@kbn/unified-data-table", - "description": "Contains functionality for the unified data table which can be integrated into apps", "owner": [ "@elastic/kibana-data-discovery", "@elastic/security-threat-hunting-investigations" - ] -} + ], + "group": "platform", + "visibility": "shared", + "description": "Contains functionality for the unified data table which can be integrated into apps" +} \ No newline at end of file diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.test.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.test.tsx index ff2b7c46616f..bbbf1ddcbb30 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.test.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.test.tsx @@ -11,8 +11,7 @@ import { EuiDataGridCellValueElementProps, EuiDataGridSetCellProps } from '@elas import { buildDataTableRecord } from '@kbn/discover-utils'; import { generateEsHits, additionalFieldGroups } from '@kbn/discover-utils/src/__mocks__'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; -import { render, screen } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, screen, renderHook } from '@testing-library/react'; import React from 'react'; import { ReactNode, useState } from 'react'; import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.test.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.test.tsx index ccf8368c9527..716db2f05227 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.test.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.test.tsx @@ -13,9 +13,8 @@ import { FIELD_COLUMN_WIDTH, useComparisonColumns, } from './use_comparison_columns'; -import { renderHook } from '@testing-library/react-hooks'; import type { EuiDataGridColumn, EuiDataGridColumnActions } from '@elastic/eui'; -import { render, screen } from '@testing-library/react'; +import { render, screen, renderHook } from '@testing-library/react'; import React from 'react'; import userEvent from '@testing-library/user-event'; import { generateEsHits } from '@kbn/discover-utils/src/__mocks__'; diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_css.test.ts b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_css.test.ts index 36abc40a8d1c..010edbecd8cf 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_css.test.ts +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_css.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useComparisonCss } from './use_comparison_css'; describe('useComparisonCss', () => { diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.test.ts b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.test.ts index 7a73402ad655..c92b15f6ce6b 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.test.ts +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { buildDataTableRecord } from '@kbn/discover-utils'; import { MAX_COMPARISON_FIELDS, diff --git a/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx index f034cb63287c..31b1b42b7c6d 100644 --- a/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx +++ b/packages/kbn-unified-data-table/src/hooks/use_data_grid_columns.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useColumns } from './use_data_grid_columns'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { configMock } from '../../__mocks__/config'; diff --git a/packages/kbn-unified-data-table/src/hooks/use_data_grid_density.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_data_grid_density.test.tsx index b0563d0d33ea..e105581b6d4c 100644 --- a/packages/kbn-unified-data-table/src/hooks/use_data_grid_density.test.tsx +++ b/packages/kbn-unified-data-table/src/hooks/use_data_grid_density.test.tsx @@ -8,7 +8,7 @@ */ import type { Storage } from '@kbn/kibana-utils-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useDataGridDensity } from './use_data_grid_density'; import { DATA_GRID_STYLE_EXPANDED, DataGridDensity } from '../constants'; diff --git a/packages/kbn-unified-data-table/src/hooks/use_full_screen_watcher.test.ts b/packages/kbn-unified-data-table/src/hooks/use_full_screen_watcher.test.ts index 3a7d94283ba0..3736cdbd03f1 100644 --- a/packages/kbn-unified-data-table/src/hooks/use_full_screen_watcher.test.ts +++ b/packages/kbn-unified-data-table/src/hooks/use_full_screen_watcher.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { EUI_DATA_GRID_FULL_SCREEN_CLASS, UNIFIED_DATA_TABLE_FULL_SCREEN_CLASS, @@ -40,7 +40,7 @@ const nextTick = () => { return act(() => { return new Promise((resolve) => requestAnimationFrame(() => { - resolve(); + resolve(null); }) ); }); diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_height.test.tsx b/packages/kbn-unified-data-table/src/hooks/use_row_height.test.tsx index ac8b6257aa14..48c4ee4484e6 100644 --- a/packages/kbn-unified-data-table/src/hooks/use_row_height.test.tsx +++ b/packages/kbn-unified-data-table/src/hooks/use_row_height.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createLocalStorageMock } from '../../__mocks__/local_storage_mock'; import { useRowHeight } from './use_row_height'; import { RowHeightMode } from '../components/row_height_settings'; diff --git a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.ts b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.ts index 33a7e98c4b8c..052dc15c09a8 100644 --- a/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.ts +++ b/packages/kbn-unified-data-table/src/hooks/use_row_heights_options.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useRowHeightsOptions } from './use_row_heights_options'; describe('useRowHeightsOptions', () => { diff --git a/packages/kbn-unified-data-table/src/hooks/use_selected_docs.test.ts b/packages/kbn-unified-data-table/src/hooks/use_selected_docs.test.ts index 3865e412bdcd..f49c4f7f7b32 100644 --- a/packages/kbn-unified-data-table/src/hooks/use_selected_docs.test.ts +++ b/packages/kbn-unified-data-table/src/hooks/use_selected_docs.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { buildDataTableRecord } from '@kbn/discover-utils'; import { useSelectedDocs } from './use_selected_docs'; import { generateEsHits } from '@kbn/discover-utils/src/__mocks__'; diff --git a/packages/kbn-unified-doc-viewer/kibana.jsonc b/packages/kbn-unified-doc-viewer/kibana.jsonc index 272c2ec69ce8..15c968fadf09 100644 --- a/packages/kbn-unified-doc-viewer/kibana.jsonc +++ b/packages/kbn-unified-doc-viewer/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/unified-doc-viewer", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-unified-field-list/kibana.jsonc b/packages/kbn-unified-field-list/kibana.jsonc index 4c3201f503f8..65147718f539 100644 --- a/packages/kbn-unified-field-list/kibana.jsonc +++ b/packages/kbn-unified-field-list/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-browser", "id": "@kbn/unified-field-list", - "description": "Contains functionality for the field list and field stats which can be integrated into apps", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "shared", + "description": "Contains functionality for the field list and field stats which can be integrated into apps" +} \ No newline at end of file diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/get_sidebar_visibility.test.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/get_sidebar_visibility.test.tsx index 85f223e4b1cf..01c3bb2677e3 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/get_sidebar_visibility.test.tsx +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/get_sidebar_visibility.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; import { getSidebarVisibility } from './get_sidebar_visibility'; const localStorageKey = 'test-sidebar-visibility'; diff --git a/packages/kbn-unified-field-list/src/hooks/use_existing_fields.test.tsx b/packages/kbn-unified-field-list/src/hooks/use_existing_fields.test.tsx index 06b043014c2f..c3b77ad72f5a 100644 --- a/packages/kbn-unified-field-list/src/hooks/use_existing_fields.test.tsx +++ b/packages/kbn-unified-field-list/src/hooks/use_existing_fields.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { createStubDataView, stubFieldSpecMap } from '@kbn/data-plugin/public/stubs'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; @@ -94,7 +94,7 @@ describe('UnifiedFieldList useExistingFields', () => { }; }); - const hookFetcher = renderHook(useExistingFieldsFetcher, { + renderHook(useExistingFieldsFetcher, { initialProps: { dataViews: [dataView], services: mockedServices, @@ -107,7 +107,7 @@ describe('UnifiedFieldList useExistingFields', () => { const hookReader = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( expect.objectContaining({ @@ -167,7 +167,7 @@ describe('UnifiedFieldList useExistingFields', () => { const hookReader1 = renderHook(useExistingFieldsReader); const hookReader2 = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalled(); @@ -199,7 +199,7 @@ describe('UnifiedFieldList useExistingFields', () => { throw new Error('test'); }); - const hookFetcher = renderHook(useExistingFieldsFetcher, { + renderHook(useExistingFieldsFetcher, { initialProps: { dataViews: [dataView], services: mockedServices, @@ -212,7 +212,7 @@ describe('UnifiedFieldList useExistingFields', () => { const hookReader = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalled(); @@ -232,7 +232,7 @@ describe('UnifiedFieldList useExistingFields', () => { } ); - const hookFetcher = renderHook(useExistingFieldsFetcher, { + renderHook(useExistingFieldsFetcher, { initialProps: { dataViews: [dataView, anotherDataView, dataViewWithRestrictions], services: mockedServices, @@ -244,7 +244,7 @@ describe('UnifiedFieldList useExistingFields', () => { }); const hookReader = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const currentResult = hookReader.result.current; @@ -310,8 +310,7 @@ describe('UnifiedFieldList useExistingFields', () => { }); const hookReader = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); - await hookFetcher.waitFor(() => !hookFetcher.result.current.isProcessing); + await waitFor(() => () => !hookFetcher.result.current.isProcessing); expect(dataViewWithRestrictions.getAggregationRestrictions).toHaveBeenCalled(); expect(ExistingFieldsServiceApi.loadFieldExisting).not.toHaveBeenCalled(); @@ -346,7 +345,7 @@ describe('UnifiedFieldList useExistingFields', () => { }); const hookReader = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( expect.objectContaining({ @@ -370,7 +369,7 @@ describe('UnifiedFieldList useExistingFields', () => { dataViews: [dataView, anotherDataView], }); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenNthCalledWith( 2, @@ -424,7 +423,7 @@ describe('UnifiedFieldList useExistingFields', () => { }); const hookReader = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( expect.objectContaining({ @@ -447,32 +446,32 @@ describe('UnifiedFieldList useExistingFields', () => { query: { query: 'test', language: 'kuery' }, }); - await hookFetcher.waitForNextUpdate(); - - expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - fromDate: '2021-01-01', - toDate: '2022-01-01', - dslQuery: { - bool: { - filter: [ - { - multi_match: { - lenient: true, - query: 'test', - type: 'best_fields', + await waitFor(() => + expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + fromDate: '2021-01-01', + toDate: '2022-01-01', + dslQuery: { + bool: { + filter: [ + { + multi_match: { + lenient: true, + query: 'test', + type: 'best_fields', + }, }, - }, - ], - must: [], - must_not: [], - should: [], + ], + must: [], + must_not: [], + should: [], + }, }, - }, - dataView, - timeFieldName: dataView.timeFieldName, - }) + dataView, + timeFieldName: dataView.timeFieldName, + }) + ) ); }); @@ -497,7 +496,7 @@ describe('UnifiedFieldList useExistingFields', () => { }); const hookReader = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( expect.objectContaining({ @@ -522,7 +521,7 @@ describe('UnifiedFieldList useExistingFields', () => { toDate: '2022-01-01', }); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenNthCalledWith( 2, @@ -558,12 +557,12 @@ describe('UnifiedFieldList useExistingFields', () => { query: { query: '', language: 'lucene' }, filters: [], }; - const hookFetcher = renderHook(useExistingFieldsFetcher, { + renderHook(useExistingFieldsFetcher, { initialProps: params, }); const hookReader = renderHook(useExistingFieldsReader); - await hookFetcher.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(ExistingFieldsServiceApi.loadFieldExisting).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/packages/kbn-unified-field-list/src/hooks/use_field_filters.test.tsx b/packages/kbn-unified-field-list/src/hooks/use_field_filters.test.tsx index d0adb4125ab3..60f84e9cdf2c 100644 --- a/packages/kbn-unified-field-list/src/hooks/use_field_filters.test.tsx +++ b/packages/kbn-unified-field-list/src/hooks/use_field_filters.test.tsx @@ -7,8 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; -import { act } from 'react-test-renderer'; +import { act, renderHook } from '@testing-library/react'; import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; import type { DataViewField } from '@kbn/data-views-plugin/common'; import { coreMock } from '@kbn/core/public/mocks'; diff --git a/packages/kbn-unified-field-list/src/hooks/use_grouped_fields.test.tsx b/packages/kbn-unified-field-list/src/hooks/use_grouped_fields.test.tsx index a0a8e055e6d6..946396c83f2d 100644 --- a/packages/kbn-unified-field-list/src/hooks/use_grouped_fields.test.tsx +++ b/packages/kbn-unified-field-list/src/hooks/use_grouped_fields.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { waitFor, renderHook, act } from '@testing-library/react'; import { stubDataViewWithoutTimeField, stubLogstashDataView as dataView, @@ -56,11 +56,11 @@ describe('UnifiedFieldList useGroupedFields()', () => { allFields: null, services: mockedServices, }; - const { result, waitForNextUpdate, rerender } = renderHook(useGroupedFields, { + const { result, rerender } = renderHook(useGroupedFields, { initialProps: props, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); let fieldListGroupedProps = result.current.fieldListGroupedProps; expect(fieldListGroupedProps.fieldGroups).toMatchSnapshot(); @@ -101,11 +101,11 @@ describe('UnifiedFieldList useGroupedFields()', () => { }) ); - const { result, waitForNextUpdate, rerender } = renderHook(useGroupedFields, { + const { result, rerender } = renderHook(useGroupedFields, { initialProps: props, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); let fieldListGroupedProps = result.current.fieldListGroupedProps; const fieldGroups = fieldListGroupedProps.fieldGroups; @@ -163,11 +163,11 @@ describe('UnifiedFieldList useGroupedFields()', () => { }) ); - const { result, waitForNextUpdate, rerender } = renderHook(useGroupedFields, { + const { result, rerender } = renderHook(useGroupedFields, { initialProps: props, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); let fieldListGroupedProps = result.current.fieldListGroupedProps; const fieldGroups = fieldListGroupedProps.fieldGroups; @@ -235,11 +235,11 @@ describe('UnifiedFieldList useGroupedFields()', () => { }) ); - const { result, waitForNextUpdate, rerender } = renderHook(useGroupedFields, { + const { result, rerender } = renderHook(useGroupedFields, { initialProps: props, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); let fieldListGroupedProps = result.current.fieldListGroupedProps; const fieldGroups = fieldListGroupedProps.fieldGroups; @@ -292,11 +292,11 @@ describe('UnifiedFieldList useGroupedFields()', () => { allFields: allFieldsIncludingUnmapped, services: mockedServices, }; - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: props, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); let fieldGroups = result.current.fieldListGroupedProps.fieldGroups; @@ -373,11 +373,11 @@ describe('UnifiedFieldList useGroupedFields()', () => { allFields, services: mockedServices, }; - const { result, waitForNextUpdate, rerender } = renderHook(useGroupedFields, { + const { result, rerender } = renderHook(useGroupedFields, { initialProps: props, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const scrollToTopResetCounter1 = result.current.fieldListGroupedProps.scrollToTopResetCounter; @@ -392,7 +392,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }); it('should work correctly when custom unsupported fields are skipped', async () => { - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields, @@ -401,7 +401,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const fieldGroups = result.current.fieldListGroupedProps.fieldGroups; @@ -422,7 +422,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }); it('should work correctly when selected fields are present', async () => { - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields, @@ -432,7 +432,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const fieldGroups = result.current.fieldListGroupedProps.fieldGroups; @@ -531,7 +531,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }; } }); - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields, @@ -540,7 +540,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const fieldGroups = result.current.fieldListGroupedProps.fieldGroups; @@ -572,10 +572,10 @@ describe('UnifiedFieldList useGroupedFields()', () => { }) ); - const { result, waitForNextUpdate, rerender } = renderHook(useGroupedFields, { + const { result, rerender } = renderHook(useGroupedFields, { initialProps: props, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); let fieldListGroupedProps = result.current.fieldListGroupedProps; fieldGroups = fieldListGroupedProps.fieldGroups; @@ -604,7 +604,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { allFields: anotherDataView.fields, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); fieldListGroupedProps = result.current.fieldListGroupedProps; fieldGroups = fieldListGroupedProps.fieldGroups; @@ -633,7 +633,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { // `bytes` is popular, but we are skipping it here to test that it would not be shown under Popular and Available const onSupportedFieldFilter = jest.fn((field) => field.name !== 'bytes'); - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields, @@ -643,7 +643,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const fieldGroups = result.current.fieldListGroupedProps.fieldGroups; @@ -668,7 +668,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }); it('should work correctly when global filters are set', async () => { - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields: [], @@ -677,14 +677,14 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const fieldGroups = result.current.fieldListGroupedProps.fieldGroups; expect(fieldGroups).toMatchSnapshot(); }); it('should work correctly and show unmapped fields separately', async () => { - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields: allFieldsIncludingUnmapped, @@ -692,7 +692,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const fieldGroups = result.current.fieldListGroupedProps.fieldGroups; @@ -718,7 +718,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { allFields[2], allFields[0], ]; - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields, @@ -727,7 +727,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const fieldGroups = result.current.fieldListGroupedProps.fieldGroups; @@ -750,7 +750,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }); it('should include filters props', async () => { - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields, @@ -758,7 +758,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { fieldListFiltersProps, fieldListGroupedProps } = result.current; const fieldGroups = fieldListGroupedProps.fieldGroups; @@ -806,7 +806,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { const additionalFieldGroups = { smartFields, }; - const { result, waitForNextUpdate } = renderHook(useGroupedFields, { + const { result } = renderHook(useGroupedFields, { initialProps: { dataViewId: dataView.id!, allFields, @@ -815,7 +815,7 @@ describe('UnifiedFieldList useGroupedFields()', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const fieldListGroupedProps = result.current.fieldListGroupedProps; const fieldGroups = fieldListGroupedProps.fieldGroups; expect(fieldGroups.SmartFields?.fields?.length).toBe(1); diff --git a/packages/kbn-unified-field-list/src/hooks/use_new_fields.test.tsx b/packages/kbn-unified-field-list/src/hooks/use_new_fields.test.tsx index b091ed015e31..4f34f5d16dc5 100644 --- a/packages/kbn-unified-field-list/src/hooks/use_new_fields.test.tsx +++ b/packages/kbn-unified-field-list/src/hooks/use_new_fields.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { DataViewField } from '@kbn/data-views-plugin/common'; import { useNewFields, type UseNewFieldsParams } from './use_new_fields'; diff --git a/packages/kbn-unsaved-changes-badge/kibana.jsonc b/packages/kbn-unsaved-changes-badge/kibana.jsonc index 0f64c4d4143f..d52497ccc81e 100644 --- a/packages/kbn-unsaved-changes-badge/kibana.jsonc +++ b/packages/kbn-unsaved-changes-badge/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/unsaved-changes-badge", - "owner": "@elastic/kibana-data-discovery" -} + "owner": [ + "@elastic/kibana-data-discovery" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/kbn-unsaved-changes-prompt/kibana.jsonc b/packages/kbn-unsaved-changes-prompt/kibana.jsonc index d41be0d07af3..e186e2cf5117 100644 --- a/packages/kbn-unsaved-changes-prompt/kibana.jsonc +++ b/packages/kbn-unsaved-changes-prompt/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/unsaved-changes-prompt", - "owner": "@elastic/kibana-management" + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" } diff --git a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx index 3b8c38caeef5..5b155d657c48 100644 --- a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx +++ b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx @@ -8,7 +8,8 @@ */ import { createMemoryHistory } from 'history'; -import { renderHook, act, cleanup } from '@testing-library/react-hooks'; + +import { renderHook, act, cleanup, waitFor } from '@testing-library/react'; import { coreMock } from '@kbn/core/public/mocks'; import { CoreScopedHistory } from '@kbn/core/public'; @@ -73,7 +74,7 @@ describe('useUnsavedChangesPrompt', () => { act(() => history.push('/test')); // needed because we have an async useEffect - await act(() => new Promise((resolve) => resolve())); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(navigateToUrl).toBeCalledWith('/mock/test', expect.anything()); expect(coreStart.overlays.openConfirm).toBeCalled(); diff --git a/packages/kbn-user-profile-components/kibana.jsonc b/packages/kbn-user-profile-components/kibana.jsonc index ecd571c98781..78a445a4d63d 100644 --- a/packages/kbn-user-profile-components/kibana.jsonc +++ b/packages/kbn-user-profile-components/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/user-profile-components", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-utility-types-jest/kibana.jsonc b/packages/kbn-utility-types-jest/kibana.jsonc index 1eea6e6c96a6..73a570118415 100644 --- a/packages/kbn-utility-types-jest/kibana.jsonc +++ b/packages/kbn-utility-types-jest/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/utility-types-jest", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-utility-types/kibana.jsonc b/packages/kbn-utility-types/kibana.jsonc index 4a6528d2ac65..f4c6c948e9fc 100644 --- a/packages/kbn-utility-types/kibana.jsonc +++ b/packages/kbn-utility-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/utility-types", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-utility-types/src/dot.ts b/packages/kbn-utility-types/src/dot.ts index 40af0ce14c69..54b8cb5f5ac3 100644 --- a/packages/kbn-utility-types/src/dot.ts +++ b/packages/kbn-utility-types/src/dot.ts @@ -23,7 +23,9 @@ type DedotKey< export type DedotObject> = UnionToIntersection< Exclude< ValuesType<{ - [TKey in keyof TObject]: {} extends Pick + [TKey in keyof TObject as string]: string extends TKey + ? Record + : {} extends Pick ? DeepPartial>> : DedotKey; }>, diff --git a/packages/kbn-utility-types/src/tsd_tests/test_d/dot.ts b/packages/kbn-utility-types/src/tsd_tests/test_d/dot.ts index 855b84a3cbc1..1911db2e141b 100644 --- a/packages/kbn-utility-types/src/tsd_tests/test_d/dot.ts +++ b/packages/kbn-utility-types/src/tsd_tests/test_d/dot.ts @@ -7,9 +7,10 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { expectAssignable, expectNotType, expectType } from 'tsd'; import { DedotObject, DotObject } from '../../dot'; +function isAssignable(t: T) {} + interface TestA { 'my.dotted.key': string; 'my.dotted.partial.key'?: string; @@ -60,20 +61,39 @@ const dedotted1 = {} as DedotObject; const dotted1 = {} as DotObject; -expectAssignable>({} as Dedotted); -expectAssignable>({} as Dotted); -expectAssignable({} as DedotObject); -expectAssignable({} as DotObject); - -expectType(dedotted1.ym?.dotted?.partial?.key?.toString()); -expectType(dotted1['my.undotted.key'].toString()); -expectNotType(dotted1['my.partial.key']); -expectType(dotted1['my.partial.key']?.toString()); -expectNotType<{ baz: string }>({} as DedotObject); -expectNotType<{ baz: string }>({} as DotObject); -expectNotType<{ my: { dotted: { key: string }; partial: { key: number } } }>( - {} as DedotObject -); +isAssignable>({} as Dedotted); + +isAssignable>({} as Dotted); +isAssignable({} as DedotObject); +isAssignable({} as DotObject); + +isAssignable(dedotted1.ym?.dotted?.partial?.key); + +isAssignable(dotted1['my.undotted.key'].toString()); +// @ts-expect-error +isAssignable(dotted1['my.partial.key']); + +isAssignable(dotted1['my.partial.key']?.toString()); + +// @ts-expect-error +isAssignable<{ baz: string }>({} as DedotObject); +// @ts-expect-error +isAssignable<{ baz: string }>({} as DotObject); + +// @ts-expect-error +isAssignable<{ my: { dotted: { key: string }; partial: { key: number } } }, DedotObject>(); + +type WithStringKey = { + [x: string]: string; +} & { + count: number; +}; + +type WithStringKeyDedotted = DedotObject; + +isAssignable({} as WithStringKey); + +isAssignable({} as WithStringKeyDedotted); interface ObjectWithArray { span: { @@ -88,7 +108,7 @@ interface ObjectWithArray { }; } -expectType>({ +isAssignable>({ 'span.links.span.id': [''], 'span.links.trace.id': [''], }); diff --git a/packages/kbn-utils/kibana.jsonc b/packages/kbn-utils/kibana.jsonc index 516d42bb2e8b..1fc67d63c1e3 100644 --- a/packages/kbn-utils/kibana.jsonc +++ b/packages/kbn-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/utils", - "owner": "@elastic/kibana-operations" -} + "owner": [ + "@elastic/kibana-operations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-visualization-ui-components/kibana.jsonc b/packages/kbn-visualization-ui-components/kibana.jsonc index c17c88cb88ad..e0015f90f747 100644 --- a/packages/kbn-visualization-ui-components/kibana.jsonc +++ b/packages/kbn-visualization-ui-components/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/visualization-ui-components", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-visualization-utils/kibana.jsonc b/packages/kbn-visualization-utils/kibana.jsonc index 6589338ddb57..fef0661ec402 100644 --- a/packages/kbn-visualization-utils/kibana.jsonc +++ b/packages/kbn-visualization-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/visualization-utils", - "owner": "@elastic/kibana-visualizations" -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/kbn-visualization-utils/src/debounced_value.test.ts b/packages/kbn-visualization-utils/src/debounced_value.test.ts index 1f0d3af1d4f3..5ea090106744 100644 --- a/packages/kbn-visualization-utils/src/debounced_value.test.ts +++ b/packages/kbn-visualization-utils/src/debounced_value.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useDebouncedValue } from './debounced_value'; describe('useDebouncedValue', () => { diff --git a/packages/kbn-xstate-utils/kibana.jsonc b/packages/kbn-xstate-utils/kibana.jsonc index 1fb3507854b9..3b1bcf6bf8d7 100644 --- a/packages/kbn-xstate-utils/kibana.jsonc +++ b/packages/kbn-xstate-utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/xstate-utils", - "owner": "@elastic/obs-ux-logs-team" + "owner": [ + "@elastic/obs-ux-logs-team" + ], + "group": "observability", + "visibility": "private" } diff --git a/packages/kbn-zod-helpers/kibana.jsonc b/packages/kbn-zod-helpers/kibana.jsonc index 9f7ad63233d3..905c0f02caa6 100644 --- a/packages/kbn-zod-helpers/kibana.jsonc +++ b/packages/kbn-zod-helpers/kibana.jsonc @@ -1,6 +1,10 @@ { - "devOnly": false, + "type": "shared-common", "id": "@kbn/zod-helpers", - "owner": "@elastic/security-detection-rule-management", - "type": "shared-common" -} + "owner": [ + "@elastic/security-detection-rule-management" + ], + "group": "platform", + "visibility": "shared", + "devOnly": false +} \ No newline at end of file diff --git a/packages/kbn-zod/kibana.jsonc b/packages/kbn-zod/kibana.jsonc index 1e85fceb5528..1594934b9c28 100644 --- a/packages/kbn-zod/kibana.jsonc +++ b/packages/kbn-zod/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/zod", - "owner": "@elastic/kibana-core" -} + "owner": [ + "@elastic/kibana-core" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/presentation/presentation_containers/kibana.jsonc b/packages/presentation/presentation_containers/kibana.jsonc index 4e16421b3620..e123312e55c7 100644 --- a/packages/presentation/presentation_containers/kibana.jsonc +++ b/packages/presentation/presentation_containers/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/presentation-containers", - "owner": "@elastic/kibana-presentation" -} + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index 2b96c6d353ee..d3b9c44f6fd3 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -108,6 +108,7 @@ export { type PhaseEventType, type PublishesPhaseEvents, } from './interfaces/publishes_phase_events'; +export { apiPublishesRendered, type PublishesRendered } from './interfaces/publishes_rendered'; export { apiPublishesSavedObjectId, type PublishesSavedObjectId, diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_rendered.ts b/packages/presentation/presentation_publishing/interfaces/publishes_rendered.ts new file mode 100644 index 000000000000..9acbd8c3f258 --- /dev/null +++ b/packages/presentation/presentation_publishing/interfaces/publishes_rendered.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { PublishingSubject } from '../publishing_subject'; + +export interface PublishesRendered { + rendered$: PublishingSubject; +} + +export const apiPublishesRendered = ( + unknownApi: null | unknown +): unknownApi is PublishesRendered => { + return Boolean(unknownApi && (unknownApi as PublishesRendered)?.rendered$ !== undefined); +}; diff --git a/packages/presentation/presentation_publishing/kibana.jsonc b/packages/presentation/presentation_publishing/kibana.jsonc index 6063d383c3f9..524ad06a0722 100644 --- a/packages/presentation/presentation_publishing/kibana.jsonc +++ b/packages/presentation/presentation_publishing/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/presentation-publishing", - "owner": "@elastic/kibana-presentation" -} + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/react/kibana_context/common/kibana.jsonc b/packages/react/kibana_context/common/kibana.jsonc index b52bc6a40d0c..e7254c3e2296 100644 --- a/packages/react/kibana_context/common/kibana.jsonc +++ b/packages/react/kibana_context/common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/react-kibana-context-common", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/react/kibana_context/render/kibana.jsonc b/packages/react/kibana_context/render/kibana.jsonc index ffcc5c826cdf..1349ec47c9c0 100644 --- a/packages/react/kibana_context/render/kibana.jsonc +++ b/packages/react/kibana_context/render/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/react-kibana-context-render", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/react/kibana_context/root/kibana.jsonc b/packages/react/kibana_context/root/kibana.jsonc index 740d92da927c..1dc0779f86f0 100644 --- a/packages/react/kibana_context/root/kibana.jsonc +++ b/packages/react/kibana_context/root/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/react-kibana-context-root", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/react/kibana_context/styled/kibana.jsonc b/packages/react/kibana_context/styled/kibana.jsonc index 5974cd3a50a6..32b7917f5b09 100644 --- a/packages/react/kibana_context/styled/kibana.jsonc +++ b/packages/react/kibana_context/styled/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/react-kibana-context-styled", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/react/kibana_context/theme/kibana.jsonc b/packages/react/kibana_context/theme/kibana.jsonc index 56ae8b57a668..96f255cb467a 100644 --- a/packages/react/kibana_context/theme/kibana.jsonc +++ b/packages/react/kibana_context/theme/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/react-kibana-context-theme", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/react/kibana_mount/kibana.jsonc b/packages/react/kibana_mount/kibana.jsonc index 2c554ac7acb9..9072cc0f5181 100644 --- a/packages/react/kibana_mount/kibana.jsonc +++ b/packages/react/kibana_mount/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/react-kibana-mount", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/response-ops/rule_params/kibana.jsonc b/packages/response-ops/rule_params/kibana.jsonc index 6a6744a58c4a..131530325894 100644 --- a/packages/response-ops/rule_params/kibana.jsonc +++ b/packages/response-ops/rule_params/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/response-ops-rule-params", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/serverless/project_switcher/kibana.jsonc b/packages/serverless/project_switcher/kibana.jsonc index 6e37bb95cafd..a0722bf662a4 100644 --- a/packages/serverless/project_switcher/kibana.jsonc +++ b/packages/serverless/project_switcher/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/serverless-project-switcher", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/serverless/settings/common/kibana.jsonc b/packages/serverless/settings/common/kibana.jsonc index 57e57b4a5d82..89a0efe2937d 100644 --- a/packages/serverless/settings/common/kibana.jsonc +++ b/packages/serverless/settings/common/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/serverless-common-settings", - "owner": "@elastic/appex-sharedux @elastic/kibana-management" -} + "owner": [ + "@elastic/appex-sharedux", + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/serverless/settings/observability_project/kibana.jsonc b/packages/serverless/settings/observability_project/kibana.jsonc index 316786b21dc6..1535ff08de87 100644 --- a/packages/serverless/settings/observability_project/kibana.jsonc +++ b/packages/serverless/settings/observability_project/kibana.jsonc @@ -1,5 +1,11 @@ { "type": "shared-common", "id": "@kbn/serverless-observability-settings", - "owner": "@elastic/appex-sharedux @elastic/kibana-management @elastic/obs-ux-management-team" -} + "owner": [ + "@elastic/appex-sharedux", + "@elastic/kibana-management", + "@elastic/obs-ux-management-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/serverless/settings/search_project/kibana.jsonc b/packages/serverless/settings/search_project/kibana.jsonc index db71259a8ea6..b1b1171e929c 100644 --- a/packages/serverless/settings/search_project/kibana.jsonc +++ b/packages/serverless/settings/search_project/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/serverless-search-settings", - "owner": "@elastic/search-kibana @elastic/kibana-management" -} + "owner": [ + "@elastic/search-kibana", + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/serverless/settings/security_project/kibana.jsonc b/packages/serverless/settings/security_project/kibana.jsonc index fdf4f2d206f0..55cc4ee537d7 100644 --- a/packages/serverless/settings/security_project/kibana.jsonc +++ b/packages/serverless/settings/security_project/kibana.jsonc @@ -1,5 +1,10 @@ { "type": "shared-common", "id": "@kbn/serverless-security-settings", - "owner": "@elastic/security-solution @elastic/kibana-management" -} + "owner": [ + "@elastic/security-solution", + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/serverless/types/kibana.jsonc b/packages/serverless/types/kibana.jsonc index 0b5a8fffe84b..89901c4c56d4 100644 --- a/packages/serverless/types/kibana.jsonc +++ b/packages/serverless/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/serverless-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/avatar/solution/kibana.jsonc b/packages/shared-ux/avatar/solution/kibana.jsonc index bec67862823b..a93d1f7e200c 100644 --- a/packages/shared-ux/avatar/solution/kibana.jsonc +++ b/packages/shared-ux/avatar/solution/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-avatar-solution", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/button/exit_full_screen/kibana.jsonc b/packages/shared-ux/button/exit_full_screen/kibana.jsonc index 8cd27723b051..c1878fd3ddc0 100644 --- a/packages/shared-ux/button/exit_full_screen/kibana.jsonc +++ b/packages/shared-ux/button/exit_full_screen/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-button-exit-full-screen", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/button_toolbar/kibana.jsonc b/packages/shared-ux/button_toolbar/kibana.jsonc index 8e5d78dff0e5..1a61c5ffc3d4 100644 --- a/packages/shared-ux/button_toolbar/kibana.jsonc +++ b/packages/shared-ux/button_toolbar/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-button-toolbar", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/card/no_data/impl/kibana.jsonc b/packages/shared-ux/card/no_data/impl/kibana.jsonc index 0dc1330988cc..f96d65188e70 100644 --- a/packages/shared-ux/card/no_data/impl/kibana.jsonc +++ b/packages/shared-ux/card/no_data/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-card-no-data", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/card/no_data/mocks/kibana.jsonc b/packages/shared-ux/card/no_data/mocks/kibana.jsonc index e332bfd1da0b..a7d83ef725eb 100644 --- a/packages/shared-ux/card/no_data/mocks/kibana.jsonc +++ b/packages/shared-ux/card/no_data/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-card-no-data-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/card/no_data/types/kibana.jsonc b/packages/shared-ux/card/no_data/types/kibana.jsonc index b78a1a7b9b99..b35e72493bbc 100644 --- a/packages/shared-ux/card/no_data/types/kibana.jsonc +++ b/packages/shared-ux/card/no_data/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-card-no-data-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/chrome/navigation/kibana.jsonc b/packages/shared-ux/chrome/navigation/kibana.jsonc index 60bfed4d5796..ea7ed68c19df 100644 --- a/packages/shared-ux/chrome/navigation/kibana.jsonc +++ b/packages/shared-ux/chrome/navigation/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-chrome-navigation", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/code_editor/impl/kibana.jsonc b/packages/shared-ux/code_editor/impl/kibana.jsonc index d66e88d40710..f94d8229514b 100644 --- a/packages/shared-ux/code_editor/impl/kibana.jsonc +++ b/packages/shared-ux/code_editor/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/code-editor", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/code_editor/mocks/kibana.jsonc b/packages/shared-ux/code_editor/mocks/kibana.jsonc index c8e8a8e8e54e..f977d535067c 100644 --- a/packages/shared-ux/code_editor/mocks/kibana.jsonc +++ b/packages/shared-ux/code_editor/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/code-editor-mock", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/error_boundary/kibana.jsonc b/packages/shared-ux/error_boundary/kibana.jsonc index c7e6f9b51796..d23469ef1180 100644 --- a/packages/shared-ux/error_boundary/kibana.jsonc +++ b/packages/shared-ux/error_boundary/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-error-boundary", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/file/context/kibana.jsonc b/packages/shared-ux/file/context/kibana.jsonc index c4b2a631f0c9..9612a47c01f6 100644 --- a/packages/shared-ux/file/context/kibana.jsonc +++ b/packages/shared-ux/file/context/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-file-context", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/file/file_picker/impl/kibana.jsonc b/packages/shared-ux/file/file_picker/impl/kibana.jsonc index 733d78bcd303..9d7bc72bb634 100644 --- a/packages/shared-ux/file/file_picker/impl/kibana.jsonc +++ b/packages/shared-ux/file/file_picker/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-file-picker", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/file/file_upload/impl/kibana.jsonc b/packages/shared-ux/file/file_upload/impl/kibana.jsonc index 0ebcd24bf98e..ed5b6366e2a7 100644 --- a/packages/shared-ux/file/file_upload/impl/kibana.jsonc +++ b/packages/shared-ux/file/file_upload/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-file-upload", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/file/image/impl/kibana.jsonc b/packages/shared-ux/file/image/impl/kibana.jsonc index e7bc1629fe4f..3153d4e3d017 100644 --- a/packages/shared-ux/file/image/impl/kibana.jsonc +++ b/packages/shared-ux/file/image/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-file-image", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/file/image/mocks/kibana.jsonc b/packages/shared-ux/file/image/mocks/kibana.jsonc index abf9037fe049..8c80fd88b962 100644 --- a/packages/shared-ux/file/image/mocks/kibana.jsonc +++ b/packages/shared-ux/file/image/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-file-image-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/file/mocks/kibana.jsonc b/packages/shared-ux/file/mocks/kibana.jsonc index ea930e806051..820c83e5383a 100644 --- a/packages/shared-ux/file/mocks/kibana.jsonc +++ b/packages/shared-ux/file/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-file-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/file/types/kibana.jsonc b/packages/shared-ux/file/types/kibana.jsonc index 5d9790747338..ce351a0152f2 100644 --- a/packages/shared-ux/file/types/kibana.jsonc +++ b/packages/shared-ux/file/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-file-types", - "owner": "@elastic/appex-sharedux" + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" } diff --git a/packages/shared-ux/file/util/kibana.jsonc b/packages/shared-ux/file/util/kibana.jsonc index 80c644db509e..eb884e270867 100644 --- a/packages/shared-ux/file/util/kibana.jsonc +++ b/packages/shared-ux/file/util/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-file-util", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/link/redirect_app/impl/kibana.jsonc b/packages/shared-ux/link/redirect_app/impl/kibana.jsonc index 9521500eae23..4f0ae057cdbc 100644 --- a/packages/shared-ux/link/redirect_app/impl/kibana.jsonc +++ b/packages/shared-ux/link/redirect_app/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-link-redirect-app", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/link/redirect_app/mocks/kibana.jsonc b/packages/shared-ux/link/redirect_app/mocks/kibana.jsonc index 1c070c199863..7e8c763f12d0 100644 --- a/packages/shared-ux/link/redirect_app/mocks/kibana.jsonc +++ b/packages/shared-ux/link/redirect_app/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-link-redirect-app-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/link/redirect_app/types/kibana.jsonc b/packages/shared-ux/link/redirect_app/types/kibana.jsonc index ca657f445f43..c9cfba6aef0a 100644 --- a/packages/shared-ux/link/redirect_app/types/kibana.jsonc +++ b/packages/shared-ux/link/redirect_app/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-link-redirect-app-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/markdown/impl/kibana.jsonc b/packages/shared-ux/markdown/impl/kibana.jsonc index a6a615abb33e..3ae0b724fc2f 100644 --- a/packages/shared-ux/markdown/impl/kibana.jsonc +++ b/packages/shared-ux/markdown/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-markdown", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/markdown/mocks/kibana.jsonc b/packages/shared-ux/markdown/mocks/kibana.jsonc index c1183a70ceda..17764e5ed013 100644 --- a/packages/shared-ux/markdown/mocks/kibana.jsonc +++ b/packages/shared-ux/markdown/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-markdown-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/markdown/types/kibana.jsonc b/packages/shared-ux/markdown/types/kibana.jsonc index 18412b08bd7a..8ec1ceba818f 100644 --- a/packages/shared-ux/markdown/types/kibana.jsonc +++ b/packages/shared-ux/markdown/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-markdown-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/modal/tabbed/kibana.jsonc b/packages/shared-ux/modal/tabbed/kibana.jsonc index 4abd1fe7543e..a81399f0f8a2 100644 --- a/packages/shared-ux/modal/tabbed/kibana.jsonc +++ b/packages/shared-ux/modal/tabbed/kibana.jsonc @@ -1,5 +1,9 @@ { - "type": "shared-common", - "id": "@kbn/shared-ux-tabbed-modal", - "owner": "@elastic/appex-sharedux" - } \ No newline at end of file + "type": "shared-common", + "id": "@kbn/shared-ux-tabbed-modal", + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/modal/tabbed/src/context/index.test.tsx b/packages/shared-ux/modal/tabbed/src/context/index.test.tsx index a25504421b33..8c42dea9080c 100644 --- a/packages/shared-ux/modal/tabbed/src/context/index.test.tsx +++ b/packages/shared-ux/modal/tabbed/src/context/index.test.tsx @@ -8,7 +8,7 @@ */ import React, { type ComponentProps, type ComponentType } from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useModalContext, ModalContextProvider } from '.'; type ModalContextProviderProps = ComponentProps; diff --git a/packages/shared-ux/page/kibana_no_data/impl/kibana.jsonc b/packages/shared-ux/page/kibana_no_data/impl/kibana.jsonc index ed790a80bf24..ac9eb2478184 100644 --- a/packages/shared-ux/page/kibana_no_data/impl/kibana.jsonc +++ b/packages/shared-ux/page/kibana_no_data/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-kibana-no-data", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/page/kibana_no_data/mocks/kibana.jsonc b/packages/shared-ux/page/kibana_no_data/mocks/kibana.jsonc index 6d757ebb856b..115afdaf9cd1 100644 --- a/packages/shared-ux/page/kibana_no_data/mocks/kibana.jsonc +++ b/packages/shared-ux/page/kibana_no_data/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-page-kibana-no-data-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/page/kibana_no_data/types/kibana.jsonc b/packages/shared-ux/page/kibana_no_data/types/kibana.jsonc index 6cae33b2ae21..8f181a7f5e69 100644 --- a/packages/shared-ux/page/kibana_no_data/types/kibana.jsonc +++ b/packages/shared-ux/page/kibana_no_data/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-kibana-no-data-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/page/kibana_template/impl/kibana.jsonc b/packages/shared-ux/page/kibana_template/impl/kibana.jsonc index b87289883e69..2c030cb8b666 100644 --- a/packages/shared-ux/page/kibana_template/impl/kibana.jsonc +++ b/packages/shared-ux/page/kibana_template/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-kibana-template", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/kibana_template/mocks/kibana.jsonc b/packages/shared-ux/page/kibana_template/mocks/kibana.jsonc index 2ced9b008651..d786f5d945ac 100644 --- a/packages/shared-ux/page/kibana_template/mocks/kibana.jsonc +++ b/packages/shared-ux/page/kibana_template/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-kibana-template-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/kibana_template/types/kibana.jsonc b/packages/shared-ux/page/kibana_template/types/kibana.jsonc index cd8d55e4993b..af638ab37d1f 100644 --- a/packages/shared-ux/page/kibana_template/types/kibana.jsonc +++ b/packages/shared-ux/page/kibana_template/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-kibana-template-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/no_data/impl/kibana.jsonc b/packages/shared-ux/page/no_data/impl/kibana.jsonc index 811b1371037c..2b73286e43fb 100644 --- a/packages/shared-ux/page/no_data/impl/kibana.jsonc +++ b/packages/shared-ux/page/no_data/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-no-data", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/no_data/mocks/kibana.jsonc b/packages/shared-ux/page/no_data/mocks/kibana.jsonc index 78d4d58d477e..556789fff4de 100644 --- a/packages/shared-ux/page/no_data/mocks/kibana.jsonc +++ b/packages/shared-ux/page/no_data/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-page-no-data-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/no_data/types/kibana.jsonc b/packages/shared-ux/page/no_data/types/kibana.jsonc index d4de4b00fbc5..0881756f3975 100644 --- a/packages/shared-ux/page/no_data/types/kibana.jsonc +++ b/packages/shared-ux/page/no_data/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-no-data-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/no_data_config/impl/kibana.jsonc b/packages/shared-ux/page/no_data_config/impl/kibana.jsonc index e6c838b06132..2668487c0f89 100644 --- a/packages/shared-ux/page/no_data_config/impl/kibana.jsonc +++ b/packages/shared-ux/page/no_data_config/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-no-data-config", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/no_data_config/mocks/kibana.jsonc b/packages/shared-ux/page/no_data_config/mocks/kibana.jsonc index 19341c81182a..0fbbdb79a737 100644 --- a/packages/shared-ux/page/no_data_config/mocks/kibana.jsonc +++ b/packages/shared-ux/page/no_data_config/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-page-no-data-config-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/no_data_config/types/kibana.jsonc b/packages/shared-ux/page/no_data_config/types/kibana.jsonc index f2e1ed111e2c..18ed5f19579f 100644 --- a/packages/shared-ux/page/no_data_config/types/kibana.jsonc +++ b/packages/shared-ux/page/no_data_config/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-page-no-data-config-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/page/solution_nav/kibana.jsonc b/packages/shared-ux/page/solution_nav/kibana.jsonc index e8aa313b0f60..a25a8801bc54 100644 --- a/packages/shared-ux/page/solution_nav/kibana.jsonc +++ b/packages/shared-ux/page/solution_nav/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-page-solution-nav", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/prompt/no_data_views/impl/kibana.jsonc b/packages/shared-ux/prompt/no_data_views/impl/kibana.jsonc index 963fc95624f2..8f2ad49cf2d7 100644 --- a/packages/shared-ux/prompt/no_data_views/impl/kibana.jsonc +++ b/packages/shared-ux/prompt/no_data_views/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-prompt-no-data-views", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/prompt/no_data_views/mocks/kibana.jsonc b/packages/shared-ux/prompt/no_data_views/mocks/kibana.jsonc index a962fd080e86..2fdec6e9814c 100644 --- a/packages/shared-ux/prompt/no_data_views/mocks/kibana.jsonc +++ b/packages/shared-ux/prompt/no_data_views/mocks/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-prompt-no-data-views-mocks", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/prompt/no_data_views/types/kibana.jsonc b/packages/shared-ux/prompt/no_data_views/types/kibana.jsonc index 3ea8eacae340..ff1f00dec660 100644 --- a/packages/shared-ux/prompt/no_data_views/types/kibana.jsonc +++ b/packages/shared-ux/prompt/no_data_views/types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-prompt-no-data-views-types", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/prompt/not_found/kibana.jsonc b/packages/shared-ux/prompt/not_found/kibana.jsonc index fe3ad23f35c8..183c6f933053 100644 --- a/packages/shared-ux/prompt/not_found/kibana.jsonc +++ b/packages/shared-ux/prompt/not_found/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-prompt-not-found", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/router/impl/kibana.jsonc b/packages/shared-ux/router/impl/kibana.jsonc index 57433ea556f5..0f0222f7f701 100644 --- a/packages/shared-ux/router/impl/kibana.jsonc +++ b/packages/shared-ux/router/impl/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-router", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/storybook/mock/kibana.jsonc b/packages/shared-ux/storybook/mock/kibana.jsonc index ac21cf1df4b2..7d6451c4d2e6 100644 --- a/packages/shared-ux/storybook/mock/kibana.jsonc +++ b/packages/shared-ux/storybook/mock/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-storybook-mock", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/packages/shared-ux/table_persist/kibana.jsonc b/packages/shared-ux/table_persist/kibana.jsonc index 4a29533855f9..1885d2403820 100644 --- a/packages/shared-ux/table_persist/kibana.jsonc +++ b/packages/shared-ux/table_persist/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/shared-ux-table-persist", - "owner": "@elastic/appex-sharedux" -} + "owner": [ + "@elastic/appex-sharedux" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/packages/shared-ux/table_persist/src/use_table_persist.test.ts b/packages/shared-ux/table_persist/src/use_table_persist.test.ts index 51fbd93f7a21..9c88c97b8244 100644 --- a/packages/shared-ux/table_persist/src/use_table_persist.test.ts +++ b/packages/shared-ux/table_persist/src/use_table_persist.test.ts @@ -8,7 +8,7 @@ */ import { CriteriaWithPagination } from '@elastic/eui'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useEuiTablePersist } from './use_table_persist'; import { createStorage } from './storage'; // Mock this if it's external diff --git a/renovate.json b/renovate.json index bc42fa647803..9ec7446ebf5d 100644 --- a/renovate.json +++ b/renovate.json @@ -137,17 +137,11 @@ "groupName": "@elastic/appex-qa dependencies", "matchDepNames": [ "cheerio", - "@istanbuljs/nyc-config-typescript", - "@istanbuljs/schema", - "@types/enzyme", "@types/faker", "@types/pixelmatch", "@types/pngjs", "@types/supertest", - "@wojtekmaj/enzyme-adapter-react-17", "babel-plugin-istanbul", - "enzyme", - "enzyme-to-json", "faker", "nyc", "oboe", @@ -172,6 +166,217 @@ ], "enabled": true }, + { + "groupName": "@elastic/appex-sharedux dependencies", + "matchDepNames": [ + "@elastic/filesaver", + "@elastic/numeral", + "@wojtekmaj/enzyme-adapter-react-17", + "base64-js", + "blurhash", + "classnames", + "deep-freeze-strict", + "enzyme", + "enzyme-to-json", + "fflate", + "history", + "lz-string", + "monaco-editor", + "rison-node", + "styled-components", + "@types/base64-js", + "@types/classnames", + "@types/deep-freeze-strict", + "@types/enzyme", + "@types/history", + "@types/lz-string", + "@types/styled-components" + ], + "reviewers": [ + "team:appex-sharedux" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:SharedUX", + "release_note:skip", + "backport:all-open" + ], + "enabled": true + }, + { + "groupName": "@elastic/appex-sharedux emotion dependencies", + "matchDepNames": [ + "@emotion/cache", + "@emotion/css", + "@emotion/react", + "@emotion/serialize", + "@emotion/server", + "@emotion/styled" + ], + "reviewers": [ + "team:appex-sharedux" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:SharedUX", + "release_note:skip", + "backport:all-open" + ], + "enabled": true + }, + { + "groupName": "@elastic/appex-sharedux react dependencies", + "matchDepNames": [ + "prop-types", + "react", + "react-dom", + "react-monaco-editor", + "react-router", + "react-router-config", + "react-router-dom", + "react-router-dom-v5-compat", + "react-use", + "@types/prop-types", + "@types/react", + "@types/react-dom", + "@types/react-router", + "@types/react-router-config", + "@types/react-router-dom" + ], + "reviewers": [ + "team:appex-sharedux" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:SharedUX", + "release_note:skip", + "backport:all-open" + ], + "enabled": true + }, + { + "groupName": "@elastic/fleet dependencies", + "matchDepNames": [ + "exponential-backoff", + "@paralleldrive/cuid2", + "isbinaryfile", + "js-search", + "openpgp", + "remark-gfm", + "@types/js-search" + ], + "reviewers": [ + "team:fleet" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:Fleet", + "release_note:skip", + "backport:all-open" + ], + "enabled": true + }, + { + "groupName": "@elastic/kibana-cloud-security-posture dependencies", + "matchDepNames": [ + "@mswjs/http-middleware", + "@types/byte-size", + "byte-size", + "msw", + "xterm" + ], + "reviewers": [ + "team:kibana-cloud-security-posture" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:Cloud Security", + "release_note:skip", + "backport:all-open" + ], + "enabled": true, + "minimumReleaseAge": "7 days" + }, + { + "groupName": "formatjs dependencies", + "matchDepNames": [ + "@formatjs/icu-messageformat-parser", + "@formatjs/intl", + "@formatjs/intl-pluralrules", + "@formatjs/intl-relativetimeformat", + "@formatjs/intl-utils", + "@formatjs/ts-transformer" + ], + "reviewers": [ + "team:kibana-core" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:Core", + "release_note:skip", + "backport:all-open" + ], + "enabled": true, + "minimumReleaseAge": "7 days" + }, + { + "groupName": "@elastic/kibana-core dependencies", + "matchDepNames": [ + "@elastic/request-crypto", + "ansi-regex", + "axios", + "cacheable-lookup", + "getos", + "has-ansi", + "joi-to-json", + "json5", + "load-json-file", + "mime-types", + "mock-fs", + "node-fetch", + "react-intl", + "reflect-metadata", + "type-detect", + "utility-types", + "@types/getos", + "@types/hapi__cookie", + "@types/hapi__h2o2", + "@types/hapi__hapi", + "@types/hapi__inert", + "@types/has-ansi", + "@types/json5", + "@types/mime", + "@types/mime-types", + "@types/mock-fs", + "@types/node-fetch", + "@types/type-detect" + ], + "reviewers": [ + "team:kibana-core" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:Core", + "release_note:skip", + "backport:all-open" + ], + "enabled": true, + "minimumReleaseAge": "7 days" + }, { "groupName": "@elastic/charts", "matchDepNames": [ @@ -352,7 +557,7 @@ "labels": [ "release_note:skip", "Team:Core", - "backport:skip" + "backport:all-open" ], "enabled": true }, @@ -375,29 +580,12 @@ ], "enabled": true }, - { - "groupName": "ansi-regex", - "matchDepNames": [ - "ansi-regex" - ], - "reviewers": [ - "team:kibana-core" - ], - "matchBaseBranches": [ - "main" - ], - "labels": [ - "release_note:skip", - "Team:Core", - "backport:skip" - ], - "minimumReleaseAge": "7 days", - "enabled": true - }, { "groupName": "OpenAPI Spec", "matchDepNames": [ - "@redocly/cli" + "@apidevtools/swagger-parser", + "@redocly/cli", + "openapi-types" ], "reviewers": [ "team:kibana-core" @@ -408,7 +596,7 @@ "labels": [ "release_note:skip", "Team:Core", - "backport:skip" + "backport:all-open" ], "minimumReleaseAge": "7 days", "enabled": true @@ -925,27 +1113,6 @@ "minimumReleaseAge": "7 days", "enabled": true }, - { - "groupName": "TTY Output", - "matchDepNames": [ - "xterm", - "byte-size", - "@types/byte-size" - ], - "reviewers": [ - "team:sec-cloudnative-integrations" - ], - "matchBaseBranches": [ - "main" - ], - "labels": [ - "Team: AWP: Visualization", - "release_note:skip", - "backport:skip" - ], - "minimumReleaseAge": "7 days", - "enabled": true - }, { "groupName": "Cloud Defend", "matchDepNames": [ @@ -1078,25 +1245,6 @@ "minimumReleaseAge": "7 days", "enabled": true }, - { - "groupName": "MSW", - "matchPackageNames": [ - "msw" - ], - "reviewers": [ - "team:kibana-cloud-security-posture" - ], - "matchBaseBranches": [ - "main" - ], - "labels": [ - "Team:Cloud Security", - "release_note:skip", - "backport:skip" - ], - "minimumReleaseAge": "7 days", - "enabled": true - }, { "groupName": "re2js", "matchDepNames": [ @@ -1157,9 +1305,9 @@ "enabled": true }, { - "groupName": "@mswjs/http-middleware", + "groupName": "@xyflow/react", "matchPackageNames": [ - "@mswjs/http-middleware" + "@xyflow/react" ], "reviewers": [ "team:kibana-cloud-security-posture" @@ -1170,15 +1318,15 @@ "labels": [ "Team:Cloud Security", "release_note:skip", - "backport:skip" + "backport:all-open" ], "minimumReleaseAge": "7 days", "enabled": true }, { - "groupName": "@xyflow/react", + "groupName": "@dagrejs/dagre", "matchPackageNames": [ - "@xyflow/react" + "@dagrejs/dagre" ], "reviewers": [ "team:kibana-cloud-security-posture" @@ -1189,36 +1337,28 @@ "labels": [ "Team:Cloud Security", "release_note:skip", - "backport:skip" + "backport:all-open" ], "minimumReleaseAge": "7 days", "enabled": true }, { - "groupName": "@dagrejs/dagre", - "matchPackageNames": [ - "@dagrejs/dagre" + "groupName": "@elastic/request-converter", + "matchDepNames": [ + "@elastic/request-converter" ], "reviewers": [ - "team:kibana-cloud-security-posture" + "team:kibana-management" ], "matchBaseBranches": [ "main" ], "labels": [ - "Team:Cloud Security", "release_note:skip", - "backport:skip" + "backport:skip", + "Team:Kibana Management", + "Feature:Console" ], - "minimumReleaseAge": "7 days", - "enabled": true - }, - { - "groupName": "@elastic/request-converter", - "matchDepNames": ["@elastic/request-converter"], - "reviewers": ["team:kibana-management"], - "matchBaseBranches": ["main"], - "labels": ["release_note:skip", "backport:skip", "Team:Kibana Management", "Feature:Console"], "enabled": true } ], diff --git a/scripts/dependency_usage.sh b/scripts/dependency_usage.sh new file mode 100755 index 000000000000..ccee69e62d34 --- /dev/null +++ b/scripts/dependency_usage.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Need to tun the script with ts-node/esm since dependency-cruiser is only available as an ESM module. +# We specify the correct tsconfig.json file to ensure compatibility, as our current setup doesn’t fully support ESM modules. +# Should be resolved after https://github.com/elastic/kibana/issues/198790 is done. +NODE_NO_WARNINGS=1 TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=packages/kbn-dependency-usage/tsconfig.json \ +node --loader ts-node/esm packages/kbn-dependency-usage/src/cli.ts "$@" diff --git a/src/core/kibana.jsonc b/src/core/kibana.jsonc index 40487ceb89e4..60cca7e819e2 100644 --- a/src/core/kibana.jsonc +++ b/src/core/kibana.jsonc @@ -2,6 +2,8 @@ "type": "core", "id": "@kbn/core", "owner": "@elastic/kibana-core", + "group": "platform", + "visibility": "shared", "description": "The core plugin has core functionality", "serviceFolders": ["http", "saved_objects", "chrome", "application"] } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 52149cd611be..e222f2db927b 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -157,7 +157,7 @@ export type { } from '@kbn/core-user-profile-server'; export { CspConfig } from '@kbn/core-http-server-internal'; -export { CoreKibanaRequest, kibanaResponseFactory } from '@kbn/core-http-router-server-internal'; +export { kibanaResponseFactory } from '@kbn/core-http-router-server-internal'; export type { AuthenticationHandler, @@ -200,7 +200,6 @@ export type { KibanaRequestRoute, KibanaRequestRouteOptions, IKibanaResponse, - LifecycleResponseFactory, KnownHeaders, ErrorHttpResponseOptions, IKibanaSocket, diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index f16c956107c7..fe08e3737800 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -108,7 +108,7 @@ describe('checking migration metadata changes on all registered SO types', () => "fleet-agent-policies": "f57d3b70e4175a19a18f18ee72a379ceec82e1fc", "fleet-fleet-server-host": "69be15f6b6f2a2875ad3c7050ddea7a87f505417", "fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f", - "fleet-package-policies": "2f4d524adb49a5281d3af0b66bb3003ba0ff2e44", + "fleet-package-policies": "0206c20f27286787b91814a2e7872f06dc1e8e47", "fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e", "fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d", "fleet-setup-lock": "0dc784792c79b5af5a6e6b5dcac06b0dbaa90bde", @@ -124,7 +124,7 @@ describe('checking migration metadata changes on all registered SO types', () => "ingest-agent-policies": "5e95e539826a40ad08fd0c1d161da0a4d86ffc6d", "ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d", "ingest-outputs": "55988d5f778bbe0e76caa7e6468707a0a056bdd8", - "ingest-package-policies": "53a94064674835fdb35e5186233bcd7052eabd22", + "ingest-package-policies": "60d43f475f91417d14d9df05476acf2e63e99435", "ingest_manager_settings": "111a616eb72627c002029c19feb9e6c439a10505", "inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83", "kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/incompatible_cluster_routing_allocation.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/incompatible_cluster_routing_allocation.test.ts index 8213c880c0fa..e8a523b5de50 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/incompatible_cluster_routing_allocation.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/incompatible_cluster_routing_allocation.test.ts @@ -92,7 +92,8 @@ async function updateRoutingAllocations( }); } -describe('incompatible_cluster_routing_allocation', () => { +// Failing ES promotion: https://github.com/elastic/kibana/issues/158318 +describe.skip('incompatible_cluster_routing_allocation', () => { let client: ElasticsearchClient; let root: Root; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts index 6898962077b9..5be7b4671ef7 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/multiple_es_nodes.test.ts @@ -96,7 +96,8 @@ function createRoot({ logFileName, hosts }: RootConfig) { }); } -describe('migration v2', () => { +// Failing ES promotion: https://github.com/elastic/kibana/issues/167676 +describe.skip('migration v2', () => { let esServer: TestElasticsearchUtils; let root: Root; const migratedIndexAlias = `.kibana_${pkg.version}`; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/read_batch_size.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/read_batch_size.test.ts index 9f970ed234d7..6ebc7ee5a66f 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/read_batch_size.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/read_batch_size.test.ts @@ -19,7 +19,9 @@ import { getFips } from 'crypto'; const logFilePath = join(__dirname, 'read_batch_size.log'); -describe('migration v2 - read batch size', () => { +// Failing ES promotion: https://github.com/elastic/kibana/issues/163254 +// Failing ES promotion: https://github.com/elastic/kibana/issues/163255 +describe.skip('migration v2 - read batch size', () => { let esServer: TestElasticsearchUtils; let root: Root; let logs: string; diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index 7a64ada1bfff..8ff44c13ff44 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -51,7 +51,7 @@ export async function runDockerGenerator( */ if (flags.baseImage === 'wolfi') baseImageName = - 'docker.elastic.co/wolfi/chainguard-base:latest@sha256:26caa6beaee2bbf739a82e91a35173892dfe888d0a744b9e46cdc19a90d8656f'; + 'docker.elastic.co/wolfi/chainguard-base:latest@sha256:55b297da5151d2a2997e8ab9729fe1304e4869389d7090ab7031cc29530f69f8'; let imageFlavor = ''; if (flags.baseImage === 'ubi') imageFlavor += `-ubi`; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile index f7849bff06ea..9993381fb433 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile @@ -4,7 +4,7 @@ ################################################################################ ARG BASE_REGISTRY=registry1.dso.mil ARG BASE_IMAGE=ironbank/redhat/ubi/ubi9 -ARG BASE_TAG=9.4 +ARG BASE_TAG=9.5 FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} as prep_files diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml index d1d76367b4ec..7d063b49151b 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml @@ -11,7 +11,7 @@ tags: # Build args passed to Dockerfile ARGs args: BASE_IMAGE: 'redhat/ubi/ubi9' - BASE_TAG: "9.4" + BASE_TAG: "9.5" # Docker image labels labels: org.opencontainers.image.title: 'kibana' @@ -85,4 +85,4 @@ maintainers: - email: 'klepal_alexander@bah.com' name: 'Alexander Klepal' username: 'alexander.klepal' - cht_member: true \ No newline at end of file + cht_member: true diff --git a/src/dev/code_coverage/nyc_config/nyc.functional.config.js b/src/dev/code_coverage/nyc_config/nyc.functional.config.js deleted file mode 100644 index d3a6160b8b6a..000000000000 --- a/src/dev/code_coverage/nyc_config/nyc.functional.config.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -const defaultExclude = require('@istanbuljs/schema/default-exclude'); -const extraExclude = [ - 'data/optimize/**', - '**/{__jest__,__test__,__examples__,__fixtures__,__snapshots__,__stories__,*mock*,*storybook,target,types}/**/*', - '**/{integration_tests,test,tests,test_helpers,test_data,test_samples,test_utils,test_utilities,*scripts}/**/*', - '**/{*e2e*,fixtures,manual_tests,stub*}/**', - '**/*mock*.{ts,tsx}', - '**/*.test.{ts,tsx}', - '**/*.spec.{ts,tsx}', - '**/types.ts', - '**/*.d.ts', - '**/index.{js,ts,tsx}', -]; -// const path = require('path'); - -module.exports = { - // 'temp-dir': process.env.COVERAGE_TEMP_DIR - // ? path.resolve(process.env.COVERAGE_TEMP_DIR, 'functional') - // : 'target/kibana-coverage/functional', - 'temp-dir': process.env.COVERAGE_TEMP_DIR - ? process.env.COVERAGE_TEMP_DIR - : 'target/kibana-coverage/functional', - 'report-dir': 'target/kibana-coverage/functional-combined', - reporter: ['html', 'json-summary'], - include: [ - 'src/{core,plugins}/**/*.{js,mjs,jsx,ts,tsx}', - 'x-pack/plugins/**/*.{js,mjs,jsx,ts,tsx}', - ], - exclude: extraExclude.concat(defaultExclude), -}; diff --git a/src/dev/code_coverage/nyc_config/nyc.server.config.js b/src/dev/code_coverage/nyc_config/nyc.server.config.js deleted file mode 100644 index d9432adb7572..000000000000 --- a/src/dev/code_coverage/nyc_config/nyc.server.config.js +++ /dev/null @@ -1,34 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -const path = require('path'); - -module.exports = { - extends: '@istanbuljs/nyc-config-typescript', - 'report-dir': process.env.KIBANA_DIR - ? path.resolve(process.env.KIBANA_DIR, 'target/kibana-coverage/functional') - : 'target/kibana-coverage/functional', - reporter: ['json'], - all: true, - include: [ - 'src/{core,plugins}/**/*.{js,mjs,jsx,ts,tsx}', - 'x-pack/plugins/**/*.{js,mjs,jsx,ts,tsx}', - ], - exclude: [ - '**/{__jest__,__test__,__examples__,__fixtures__,__snapshots__,__stories__,*mock*,*storybook,target,types}/**/*', - '**/{integration_tests,test,tests,test_helpers,test_data,test_samples,test_utils,test_utilities,*scripts}/**/*', - '**/{*e2e*,fixtures,manual_tests,stub*}/**', - '**/*mock*.{ts,tsx}', - '**/*.test.{ts,tsx}', - '**/*.spec.{ts,tsx}', - '**/types.ts', - '**/*.d.ts', - '**/index.{js,ts,tsx}', - ], -}; diff --git a/src/dev/eslint/run_eslint_with_types.ts b/src/dev/eslint/run_eslint_with_types.ts index 8a70945688dd..0a872a248dc5 100644 --- a/src/dev/eslint/run_eslint_with_types.ts +++ b/src/dev/eslint/run_eslint_with_types.ts @@ -28,7 +28,7 @@ export function runEslintWithTypes() { async ({ log, flags }) => { const ignoreFilePath = Path.resolve(REPO_ROOT, '.eslintignore'); const configTemplate = Fs.readFileSync( - Path.resolve(__dirname, 'types.eslint.config.template.js'), + Path.resolve(__dirname, 'types.eslint.config.template.cjs'), 'utf8' ); @@ -65,7 +65,7 @@ export function runEslintWithTypes() { const failures = await Rx.lastValueFrom( Rx.from(projects).pipe( mergeMap(async (project) => { - const configFilePath = Path.resolve(project.directory, 'types.eslint.config.js'); + const configFilePath = Path.resolve(project.directory, 'types.eslint.config.cjs'); Fs.writeFileSync( configFilePath, diff --git a/src/dev/eslint/types.eslint.config.template.js b/src/dev/eslint/types.eslint.config.template.cjs similarity index 100% rename from src/dev/eslint/types.eslint.config.template.js rename to src/dev/eslint/types.eslint.config.template.cjs diff --git a/src/plugins/charts/public/mocks.ts b/src/plugins/charts/public/mocks.ts index be8abb0dec22..aa7518d1df9f 100644 --- a/src/plugins/charts/public/mocks.ts +++ b/src/plugins/charts/public/mocks.ts @@ -10,7 +10,6 @@ import { ChartsPlugin } from './plugin'; import { themeServiceMock } from './services/theme/mock'; import { activeCursorMock } from './services/active_cursor/mock'; -import { colorsServiceMock } from './services/legacy_colors/mock'; import { getPaletteRegistry, paletteServiceMock } from './services/palettes/mock'; export { MOCK_SPARKLINE_THEME } from './services/theme/mock'; @@ -19,16 +18,14 @@ export type Setup = jest.Mocked>; export type Start = jest.Mocked>; const createSetupContract = (): Setup => ({ - legacyColors: colorsServiceMock, theme: themeServiceMock, - palettes: paletteServiceMock.setup({} as any), + palettes: paletteServiceMock.setup(), }); const createStartContract = (): Start => ({ - legacyColors: colorsServiceMock, theme: themeServiceMock, activeCursor: activeCursorMock, - palettes: paletteServiceMock.setup({} as any), + palettes: paletteServiceMock.setup(), }); export const chartPluginMock = { diff --git a/src/plugins/charts/public/plugin.ts b/src/plugins/charts/public/plugin.ts index f376a1664a51..4d058c3beea3 100644 --- a/src/plugins/charts/public/plugin.ts +++ b/src/plugins/charts/public/plugin.ts @@ -11,7 +11,7 @@ import { Plugin, CoreSetup } from '@kbn/core/public'; import { ExpressionsSetup } from '@kbn/expressions-plugin/public'; import { palette, systemPalette } from '../common'; -import { ThemeService, LegacyColorsService } from './services'; +import { ThemeService } from './services'; import { PaletteService } from './services/palettes/service'; import { ActiveCursor } from './services/active_cursor'; @@ -21,7 +21,6 @@ interface SetupDependencies { /** @public */ export interface ChartsPluginSetup { - legacyColors: Omit; theme: Omit; palettes: ReturnType; } @@ -34,7 +33,6 @@ export type ChartsPluginStart = ChartsPluginSetup & { /** @public */ export class ChartsPlugin implements Plugin { private readonly themeService = new ThemeService(); - private readonly legacyColorsService = new LegacyColorsService(); private readonly paletteService = new PaletteService(); private readonly activeCursor = new ActiveCursor(); @@ -44,13 +42,11 @@ export class ChartsPlugin implements Plugin(); - -describe('Vislib Color Service', () => { - const colors = new LegacyColorsService(); - const mockUiSettings = coreMock.createSetup().uiSettings; - mockUiSettings.get.mockImplementation((a) => config.get(a)); - mockUiSettings.set.mockImplementation((...a) => config.set(...a) as any); - colors.init(mockUiSettings); - - let color: any; - let previousConfig: any; - - const arr = ['good', 'better', 'best', 'never', 'let', 'it', 'rest']; - const arrayOfNumbers = [1, 2, 3, 4, 5]; - const arrayOfUndefinedValues = [undefined, undefined, undefined]; - const arrayOfObjects = [{}, {}, {}]; - const arrayOfBooleans = [true, false, true]; - const arrayOfNullValues = [null, null, null]; - const emptyObject = {}; - const nullValue = null; - - beforeEach(() => { - previousConfig = config.get(COLOR_MAPPING_SETTING); - config.set(COLOR_MAPPING_SETTING, {}); - color = colors.createColorLookupFunction(arr, {}); - }); - - afterEach(() => { - config.set(COLOR_MAPPING_SETTING, previousConfig); - }); - - it('should throw error if not initialized', () => { - const colorsBad = new LegacyColorsService(); - - expect(() => colorsBad.createColorLookupFunction(arr, {})).toThrowError(); - }); - - it('should throw an error if input is not an array', () => { - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction(200); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction('help'); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction(true); - }).toThrowError(); - - expect(() => { - colors.createColorLookupFunction(); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction(nullValue); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction(emptyObject); - }).toThrowError(); - }); - - describe('when array is not composed of numbers, strings, or undefined values', () => { - it('should throw an error', () => { - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction(arrayOfObjects); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction(arrayOfBooleans); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction(arrayOfNullValues); - }).toThrowError(); - }); - }); - - describe('when input is an array of strings, numbers, or undefined values', () => { - it('should not throw an error', () => { - expect(() => { - colors.createColorLookupFunction(arr); - }).not.toThrowError(); - - expect(() => { - colors.createColorLookupFunction(arrayOfNumbers); - }).not.toThrowError(); - - expect(() => { - // @ts-expect-error - colors.createColorLookupFunction(arrayOfUndefinedValues); - }).not.toThrowError(); - }); - }); - - it('should be a function', () => { - expect(typeof colors.createColorLookupFunction).toBe('function'); - }); - - it('should return a function', () => { - expect(typeof color).toBe('function'); - }); - - it('should return the first hex color in the seed colors array', () => { - expect(color(arr[0])).toBe(seedColors[0]); - }); - - it('should return the value from the mapped colors', () => { - expect(color(arr[1])).toBe(colors.mappedColors.get(arr[1])); - }); - - it('should return the value from the specified color mapping overrides', () => { - const colorFn = colors.createColorLookupFunction(arr, { good: 'red' }); - expect(colorFn('good')).toBe('red'); - }); -}); diff --git a/src/plugins/charts/public/services/legacy_colors/colors.ts b/src/plugins/charts/public/services/legacy_colors/colors.ts deleted file mode 100644 index b2369dfdc7eb..000000000000 --- a/src/plugins/charts/public/services/legacy_colors/colors.ts +++ /dev/null @@ -1,64 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import _ from 'lodash'; - -import { CoreSetup } from '@kbn/core/public'; - -import { MappedColors } from '../mapped_colors'; -import { seedColors } from '../../static/colors'; - -/** - * Accepts an array of strings or numbers that are used to create a - * a lookup table that associates the values (key) with a hex color (value). - * Returns a function that accepts a value (i.e. a string or number) - * and returns a hex color associated with that value. - */ -export class LegacyColorsService { - private _mappedColors?: MappedColors; - - public readonly seedColors = seedColors; - - public get mappedColors() { - if (!this._mappedColors) { - throw new Error('ColorService not yet initialized'); - } - - return this._mappedColors; - } - - init(uiSettings: CoreSetup['uiSettings']) { - this._mappedColors = new MappedColors(uiSettings); - } - - createColorLookupFunction( - arrayOfStringsOrNumbers?: Array, - colorMapping: Partial> = {} - ) { - if (!Array.isArray(arrayOfStringsOrNumbers)) { - throw new Error( - `createColorLookupFunction expects an array but recived: ${typeof arrayOfStringsOrNumbers}` - ); - } - - arrayOfStringsOrNumbers.forEach(function (val) { - if (!_.isString(val) && !_.isNumber(val) && !_.isUndefined(val)) { - throw new TypeError( - 'createColorLookupFunction expects an array of strings, numbers, or undefined values' - ); - } - }); - - this.mappedColors.mapKeys(arrayOfStringsOrNumbers); - - return (value: string | number) => { - return colorMapping[value] || this.mappedColors.get(value); - }; - } -} diff --git a/src/plugins/charts/public/services/legacy_colors/colors_palette.test.ts b/src/plugins/charts/public/services/legacy_colors/colors_palette.test.ts deleted file mode 100644 index b055256ca8b2..000000000000 --- a/src/plugins/charts/public/services/legacy_colors/colors_palette.test.ts +++ /dev/null @@ -1,87 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { seedColors } from '../../static/colors'; -import { createColorPalette } from '../../static/colors'; - -describe('Color Palette', () => { - const num1 = 45; - const num2 = 72; - const num3 = 90; - const string = 'Welcome'; - const bool = true; - const nullValue = null; - const emptyArr: [] = []; - const emptyObject = {}; - let colorPalette: string[]; - - beforeEach(() => { - colorPalette = createColorPalette(num1); - }); - - it('should throw an error if input is not a number', () => { - expect(() => { - // @ts-expect-error - createColorPalette(string); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - createColorPalette(bool); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - createColorPalette(nullValue); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - createColorPalette(emptyArr); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - createColorPalette(emptyObject); - }).toThrowError(); - - expect(() => { - // @ts-expect-error - createColorPalette(); - }).toThrowError(); - }); - - it('should be a function', () => { - expect(typeof createColorPalette).toBe('function'); - }); - - it('should return an array', () => { - expect(colorPalette).toBeInstanceOf(Array); - }); - - it('should return an array of the same length as the input', () => { - expect(colorPalette.length).toBe(num1); - }); - - it('should return the seed color array when input length is 72', () => { - expect(createColorPalette(num2)[71]).toBe(seedColors[71]); - }); - - it('should return an array of the same length as the input when input is greater than 72', () => { - expect(createColorPalette(num3).length).toBe(num3); - }); - - it('should create new darker colors when input is greater than 72', () => { - expect(createColorPalette(num3)[72]).not.toEqual(seedColors[0]); - }); - - it('should create new colors and convert them correctly', () => { - expect(createColorPalette(num3)[72]).toEqual('#404ABF'); - }); -}); diff --git a/src/plugins/charts/public/services/mapped_colors/mapped_colors.test.ts b/src/plugins/charts/public/services/mapped_colors/mapped_colors.test.ts index 9ca3d6275824..bee3ac2006f6 100644 --- a/src/plugins/charts/public/services/mapped_colors/mapped_colors.test.ts +++ b/src/plugins/charts/public/services/mapped_colors/mapped_colors.test.ts @@ -8,128 +8,33 @@ */ import _ from 'lodash'; -import Color from 'color'; - -import { coreMock } from '@kbn/core/public/mocks'; -import { COLOR_MAPPING_SETTING } from '../../../common'; -import { seedColors } from '../../static/colors'; import { MappedColors } from './mapped_colors'; -// Local state for config -const config = new Map(); - describe('Mapped Colors', () => { - const mockUiSettings = coreMock.createSetup().uiSettings; - mockUiSettings.get.mockImplementation((a) => config.get(a)); - mockUiSettings.set.mockImplementation((...a) => config.set(...a) as any); - - const mappedColors = new MappedColors(mockUiSettings); - let previousConfig: any; - - beforeEach(() => { - previousConfig = config.get(COLOR_MAPPING_SETTING); - mappedColors.purge(); - }); - - afterEach(() => { - config.set(COLOR_MAPPING_SETTING, previousConfig); - }); - it('should properly map keys to unique colors', () => { - config.set(COLOR_MAPPING_SETTING, {}); - - const arr = [1, 2, 3, 4, 5]; - mappedColors.mapKeys(arr); - expect(_(mappedColors.mapping).values().uniq().size()).toBe(arr.length); - }); - - it('should not include colors used by the config', () => { - const newConfig = { bar: seedColors[0] }; - config.set(COLOR_MAPPING_SETTING, newConfig); - - const arr = ['foo', 'baz', 'qux']; - mappedColors.mapKeys(arr); - - const colorValues = _(mappedColors.mapping).values(); - expect(colorValues).not.toContain(seedColors[0]); - expect(colorValues.uniq().size()).toBe(arr.length); - }); - - it('should create a unique array of colors even when config is set', () => { - const newConfig = { bar: seedColors[0] }; - config.set(COLOR_MAPPING_SETTING, newConfig); - - const arr = ['foo', 'bar', 'baz', 'qux']; - mappedColors.mapKeys(arr); - - const expectedSize = _(arr).difference(_.keys(newConfig)).size(); - expect(_(mappedColors.mapping).values().uniq().size()).toBe(expectedSize); - expect(mappedColors.get(arr[0])).not.toBe(seedColors[0]); - }); - - it('should treat different formats of colors as equal', () => { - const color = new Color(seedColors[0]); - const rgb = `rgb(${color.red()}, ${color.green()}, ${color.blue()})`; - const newConfig = { bar: rgb }; - config.set(COLOR_MAPPING_SETTING, newConfig); - - const arr = ['foo', 'bar', 'baz', 'qux']; - mappedColors.mapKeys(arr); - - const expectedSize = _(arr).difference(_.keys(newConfig)).size(); - expect(_(mappedColors.mapping).values().uniq().size()).toBe(expectedSize); - expect(mappedColors.get(arr[0])).not.toBe(seedColors[0]); - expect(mappedColors.get('bar')).toBe(seedColors[0]); - }); - - it('should have a flush method that moves the current map to the old map', function () { + const mappedColors = new MappedColors(); const arr = [1, 2, 3, 4, 5]; mappedColors.mapKeys(arr); - expect(_.keys(mappedColors.mapping).length).toBe(5); - expect(_.keys(mappedColors.oldMap).length).toBe(0); - - mappedColors.flush(); - - expect(_.keys(mappedColors.oldMap).length).toBe(5); - expect(_.keys(mappedColors.mapping).length).toBe(0); - - mappedColors.flush(); - - expect(_.keys(mappedColors.oldMap).length).toBe(0); - expect(_.keys(mappedColors.mapping).length).toBe(0); - }); - it('should use colors in the oldMap if they are available', function () { - const arr = [1, 2, 3, 4, 5]; - mappedColors.mapKeys(arr); + expect(_(mappedColors.mapping).values().uniq().size()).toBe(arr.length); expect(_.keys(mappedColors.mapping).length).toBe(5); - expect(_.keys(mappedColors.oldMap).length).toBe(0); - - mappedColors.flush(); - - mappedColors.mapKeys([3, 4, 5]); - expect(_.keys(mappedColors.oldMap).length).toBe(5); - expect(_.keys(mappedColors.mapping).length).toBe(3); - - expect(mappedColors.mapping[1]).toBe(undefined); - expect(mappedColors.mapping[2]).toBe(undefined); - expect(mappedColors.mapping[3]).toEqual(mappedColors.oldMap[3]); - expect(mappedColors.mapping[4]).toEqual(mappedColors.oldMap[4]); - expect(mappedColors.mapping[5]).toEqual(mappedColors.oldMap[5]); }); - it('should have a purge method that clears both maps', function () { + it('should allow to map keys multiple times and add new colors when doing so', function () { + const mappedColors = new MappedColors(); const arr = [1, 2, 3, 4, 5]; mappedColors.mapKeys(arr); - mappedColors.flush(); - mappedColors.mapKeys(arr); - - expect(_.keys(mappedColors.mapping).length).toBe(5); - expect(_.keys(mappedColors.oldMap).length).toBe(5); - - mappedColors.purge(); - - expect(_.keys(mappedColors.mapping).length).toBe(0); - expect(_.keys(mappedColors.oldMap).length).toBe(0); + mappedColors.mapKeys([6, 7]); + + expect(_.keys(mappedColors.mapping).length).toBe(7); + expect(mappedColors.mapping).toEqual({ + '1': '#00a69b', + '2': '#57c17b', + '3': '#6f87d8', + '4': '#663db8', + '5': '#bc52bc', + '6': '#9e3533', + '7': '#daa05d', + }); }); }); diff --git a/src/plugins/charts/public/services/mapped_colors/mapped_colors.ts b/src/plugins/charts/public/services/mapped_colors/mapped_colors.ts index 9295d7642c97..a1342ff076c4 100644 --- a/src/plugins/charts/public/services/mapped_colors/mapped_colors.ts +++ b/src/plugins/charts/public/services/mapped_colors/mapped_colors.ts @@ -8,84 +8,37 @@ */ import _ from 'lodash'; -import Color from 'color'; - -import { CoreSetup } from '@kbn/core/public'; - -import { COLOR_MAPPING_SETTING } from '../../../common'; import { createColorPalette } from '../../static/colors'; -const standardizeColor = (color: string) => new Color(color).hex().toLowerCase(); - /** * Maintains a lookup table that associates the value (key) with a hex color (value) * across the visualizations. * Provides functions to interact with the lookup table */ export class MappedColors { - private _oldMap: any; private _mapping: any; - constructor( - private uiSettings?: CoreSetup['uiSettings'], - private colorPaletteFn: (num: number) => string[] = createColorPalette - ) { - this._oldMap = {}; + constructor(private colorPaletteFn: (num: number) => string[] = createColorPalette) { this._mapping = {}; } - private getConfigColorMapping(): Record { - return _.mapValues(this.uiSettings?.get(COLOR_MAPPING_SETTING) || {}, standardizeColor); - } - - public get oldMap(): any { - return this._oldMap; - } - public get mapping(): any { return this._mapping; } get(key: string | number) { - return this.getConfigColorMapping()[key as any] || this._mapping[key]; - } - - getColorFromConfig(key: string | number) { - return this.getConfigColorMapping()[key as any]; - } - - flush() { - this._oldMap = _.clone(this._mapping); - this._mapping = {}; - } - - purge() { - this._oldMap = {}; - this._mapping = {}; + return this._mapping[key]; } mapKeys(keys: Array) { - const configMapping = this.getConfigColorMapping(); - const configColors = _.values(configMapping); - const oldColors = _.values(this._oldMap); - const keysToMap: Array = []; _.each(keys, (key) => { - // If this key is mapped in the config, it's unnecessary to have it mapped here - if (configMapping[key as any]) delete this._mapping[key]; - - // If this key is mapped to a color used by the config color mapping, we need to remap it - if (_.includes(configColors, this._mapping[key])) keysToMap.push(key); - - // if key exist in oldMap, move it to mapping - if (this._oldMap[key]) this._mapping[key] = this._oldMap[key]; - // If this key isn't mapped, we need to map it if (this.get(key) == null) keysToMap.push(key); }); // Generate a color palette big enough that all new keys can have unique color values - const allColors = _(this._mapping).values().union(configColors).union(oldColors).value(); + const allColors = _(this._mapping).values().value(); const colorPalette = this.colorPaletteFn(allColors.length + keysToMap.length); let newColors = _.difference(colorPalette, allColors); diff --git a/src/plugins/charts/public/services/palettes/palettes.test.tsx b/src/plugins/charts/public/services/palettes/palettes.test.tsx index ead33f6d099b..a9567b0c1217 100644 --- a/src/plugins/charts/public/services/palettes/palettes.test.tsx +++ b/src/plugins/charts/public/services/palettes/palettes.test.tsx @@ -7,14 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { PaletteDefinition } from '@kbn/coloring'; -import { createColorPalette as createLegacyColorPalette } from '../..'; import { buildPalettes } from './palettes'; -import { colorsServiceMock } from '../legacy_colors/mock'; import { euiPaletteColorBlind, euiPaletteColorBlindBehindText } from '@elastic/eui'; describe('palettes', () => { - const palettes: Record = buildPalettes(colorsServiceMock); + const palettes = buildPalettes(); + describe('default palette', () => { describe('syncColors: false', () => { it('should return different colors based on behind text flag', () => { @@ -294,147 +292,6 @@ describe('palettes', () => { }); }); - describe('legacy palette', () => { - const palette = palettes.kibana_palette; - - beforeEach(() => { - (colorsServiceMock.mappedColors.mapKeys as jest.Mock).mockClear(); - (colorsServiceMock.mappedColors.getColorFromConfig as jest.Mock).mockReset(); - (colorsServiceMock.mappedColors.get as jest.Mock).mockClear(); - }); - - describe('syncColors: false', () => { - it('should not query legacy color service', () => { - palette.getCategoricalColor( - [ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - ], - { - syncColors: false, - } - ); - expect(colorsServiceMock.mappedColors.mapKeys).not.toHaveBeenCalled(); - expect(colorsServiceMock.mappedColors.get).not.toHaveBeenCalled(); - }); - - it('should respect the advanced settings color mapping', () => { - const configColorGetter = colorsServiceMock.mappedColors.getColorFromConfig as jest.Mock; - configColorGetter.mockImplementation(() => 'blue'); - const result = palette.getCategoricalColor( - [ - { - name: 'abc', - rankAtDepth: 2, - totalSeriesAtDepth: 10, - }, - { - name: 'def', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - ], - { - syncColors: false, - } - ); - expect(result).toEqual('blue'); - expect(configColorGetter).toHaveBeenCalledWith('abc'); - }); - - it('should return a color from the legacy palette based on position of first series', () => { - const result = palette.getCategoricalColor( - [ - { - name: 'abc', - rankAtDepth: 2, - totalSeriesAtDepth: 10, - }, - { - name: 'def', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - ], - { - syncColors: false, - } - ); - expect(result).toEqual(createLegacyColorPalette(20)[2]); - }); - }); - - describe('syncColors: true', () => { - it('should query legacy color service', () => { - palette.getCategoricalColor( - [ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - ], - { - syncColors: true, - } - ); - expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']); - expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); - }); - - it('should respect the advanced settings color mapping', () => { - const configColorGetter = colorsServiceMock.mappedColors.getColorFromConfig as jest.Mock; - configColorGetter.mockImplementation(() => 'blue'); - const result = palette.getCategoricalColor( - [ - { - name: 'abc', - rankAtDepth: 2, - totalSeriesAtDepth: 10, - }, - { - name: 'def', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - ], - { - syncColors: false, - } - ); - expect(result).toEqual('blue'); - expect(configColorGetter).toHaveBeenCalledWith('abc'); - }); - - it('should always use root series', () => { - palette.getCategoricalColor( - [ - { - name: 'abc', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - { - name: 'def', - rankAtDepth: 0, - totalSeriesAtDepth: 10, - }, - ], - { - syncColors: true, - } - ); - expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledTimes(1); - expect(colorsServiceMock.mappedColors.mapKeys).toHaveBeenCalledWith(['abc']); - expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledTimes(1); - expect(colorsServiceMock.mappedColors.get).toHaveBeenCalledWith('abc'); - }); - }); - }); - describe('custom palette', () => { const palette = palettes.custom; it('should return different colors based on rank at current series', () => { diff --git a/src/plugins/charts/public/services/palettes/palettes.tsx b/src/plugins/charts/public/services/palettes/palettes.tsx index 512606762d53..fc9d2370694b 100644 --- a/src/plugins/charts/public/services/palettes/palettes.tsx +++ b/src/plugins/charts/public/services/palettes/palettes.tsx @@ -23,19 +23,20 @@ import { } from '@elastic/eui'; import type { ChartColorConfiguration, PaletteDefinition, SeriesLayer } from '@kbn/coloring'; import { flatten, zip } from 'lodash'; -import { ChartsPluginSetup, createColorPalette as createLegacyColorPalette } from '../..'; +import { createColorPalette as createLegacyColorPalette } from '../..'; import { lightenColor } from './lighten_color'; -import { LegacyColorsService } from '../legacy_colors'; import { MappedColors } from '../mapped_colors'; import { workoutColorForValue } from './helpers'; -function buildRoundRobinCategoricalWithMappedColors(): Omit { - const colors = euiPaletteColorBlind({ rotations: 2 }); - const behindTextColors = euiPaletteColorBlindBehindText({ rotations: 2 }); +function buildRoundRobinCategoricalWithMappedColors( + id = 'default', + colors = euiPaletteColorBlind({ rotations: 2 }), + behindTextColors = euiPaletteColorBlindBehindText({ rotations: 2 }) +): Omit { const behindTextColorMap: Record = Object.fromEntries( zip(colors, behindTextColors) ); - const mappedColors = new MappedColors(undefined, (num: number) => { + const mappedColors = new MappedColors((num: number) => { return flatten(new Array(Math.ceil(num / 10)).fill(colors)).map((color) => color.toLowerCase()); }); function getColor( @@ -61,9 +62,9 @@ function buildRoundRobinCategoricalWithMappedColors(): Omit euiPaletteColorBlind(), + getCategoricalColors: () => colors.slice(0, 10), toExpression: () => ({ type: 'expression', chain: [ @@ -71,7 +72,7 @@ function buildRoundRobinCategoricalWithMappedColors(): Omit { - const staticColors = createLegacyColorPalette(20); - function getColor(series: SeriesLayer[], chartConfiguration: ChartColorConfiguration = {}) { - let outputColor: string; - if (chartConfiguration.syncColors) { - colors.mappedColors.mapKeys([series[0].name]); - outputColor = colors.mappedColors.get(series[0].name); - } else { - const configColor = colors.mappedColors.getColorFromConfig(series[0].name); - outputColor = configColor || staticColors[series[0].rankAtDepth % staticColors.length]; - } - - if (!chartConfiguration.maxDepth || chartConfiguration.maxDepth === 1) { - return outputColor; - } - - return lightenColor(outputColor, series.length, chartConfiguration.maxDepth); - } - return { - id: 'kibana_palette', - getCategoricalColor: getColor, - getCategoricalColors: () => colors.seedColors.slice(0, 10), - toExpression: () => ({ - type: 'expression', - chain: [ - { - type: 'function', - function: 'system_palette', - arguments: { - name: ['kibana_palette'], - }, - }, - ], - }), - }; -} - function buildCustomPalette(): PaletteDefinition { return { id: 'custom', @@ -258,54 +220,50 @@ function buildCustomPalette(): PaletteDefinition { } as PaletteDefinition; } -export const buildPalettes: ( - legacyColorsService: LegacyColorsService -) => Record = (legacyColorsService) => { - return { - default: { - title: i18n.translate('charts.palettes.defaultPaletteLabel', { defaultMessage: 'Default' }), - ...buildRoundRobinCategoricalWithMappedColors(), - }, - status: { - title: i18n.translate('charts.palettes.statusLabel', { defaultMessage: 'Status' }), - ...buildGradient('status', euiPaletteForStatus), - }, - temperature: { - title: i18n.translate('charts.palettes.temperatureLabel', { defaultMessage: 'Temperature' }), - ...buildGradient('temperature', euiPaletteForTemperature), - }, - complementary: { - title: i18n.translate('charts.palettes.complementaryLabel', { - defaultMessage: 'Complementary', - }), - ...buildGradient('complementary', euiPaletteComplementary), - }, - negative: { - title: i18n.translate('charts.palettes.negativeLabel', { defaultMessage: 'Negative' }), - ...buildGradient('negative', euiPaletteRed), - }, - positive: { - title: i18n.translate('charts.palettes.positiveLabel', { defaultMessage: 'Positive' }), - ...buildGradient('positive', euiPaletteGreen), - }, - cool: { - title: i18n.translate('charts.palettes.coolLabel', { defaultMessage: 'Cool' }), - ...buildGradient('cool', euiPaletteCool), - }, - warm: { - title: i18n.translate('charts.palettes.warmLabel', { defaultMessage: 'Warm' }), - ...buildGradient('warm', euiPaletteWarm), - }, - gray: { - title: i18n.translate('charts.palettes.grayLabel', { defaultMessage: 'Gray' }), - ...buildGradient('gray', euiPaletteGray), - }, - kibana_palette: { - title: i18n.translate('charts.palettes.kibanaPaletteLabel', { - defaultMessage: 'Compatibility', - }), - ...buildSyncedKibanaPalette(legacyColorsService), - }, - custom: buildCustomPalette() as PaletteDefinition, - }; -}; +export const buildPalettes = (): Record => ({ + default: { + title: i18n.translate('charts.palettes.defaultPaletteLabel', { defaultMessage: 'Default' }), + ...buildRoundRobinCategoricalWithMappedColors(), + }, + status: { + title: i18n.translate('charts.palettes.statusLabel', { defaultMessage: 'Status' }), + ...buildGradient('status', euiPaletteForStatus), + }, + temperature: { + title: i18n.translate('charts.palettes.temperatureLabel', { defaultMessage: 'Temperature' }), + ...buildGradient('temperature', euiPaletteForTemperature), + }, + complementary: { + title: i18n.translate('charts.palettes.complementaryLabel', { + defaultMessage: 'Complementary', + }), + ...buildGradient('complementary', euiPaletteComplementary), + }, + negative: { + title: i18n.translate('charts.palettes.negativeLabel', { defaultMessage: 'Negative' }), + ...buildGradient('negative', euiPaletteRed), + }, + positive: { + title: i18n.translate('charts.palettes.positiveLabel', { defaultMessage: 'Positive' }), + ...buildGradient('positive', euiPaletteGreen), + }, + cool: { + title: i18n.translate('charts.palettes.coolLabel', { defaultMessage: 'Cool' }), + ...buildGradient('cool', euiPaletteCool), + }, + warm: { + title: i18n.translate('charts.palettes.warmLabel', { defaultMessage: 'Warm' }), + ...buildGradient('warm', euiPaletteWarm), + }, + gray: { + title: i18n.translate('charts.palettes.grayLabel', { defaultMessage: 'Gray' }), + ...buildGradient('gray', euiPaletteGray), + }, + kibana_palette: { + title: i18n.translate('charts.palettes.kibanaPaletteLabel', { + defaultMessage: 'Compatibility', + }), + ...buildRoundRobinCategoricalWithMappedColors('kibana_palette', createLegacyColorPalette(20)), + }, + custom: buildCustomPalette() as PaletteDefinition, +}); diff --git a/src/plugins/charts/public/services/palettes/service.ts b/src/plugins/charts/public/services/palettes/service.ts index 920486c9dcbb..30c58172ad42 100644 --- a/src/plugins/charts/public/services/palettes/service.ts +++ b/src/plugins/charts/public/services/palettes/service.ts @@ -11,7 +11,6 @@ import type { PaletteRegistry, PaletteDefinition } from '@kbn/coloring'; import { getActivePaletteName } from '@kbn/coloring'; import type { ExpressionsSetup } from '@kbn/expressions-plugin/public'; import type { ChartsPluginSetup } from '../..'; -import type { LegacyColorsService } from '../legacy_colors'; export interface PaletteSetupPlugins { expressions: ExpressionsSetup; @@ -22,12 +21,12 @@ export class PaletteService { private palettes: Record> | undefined = undefined; constructor() {} - public setup(colorsService: LegacyColorsService) { + public setup() { return { getPalettes: async (): Promise => { if (!this.palettes) { const { buildPalettes } = await import('./palettes'); - this.palettes = buildPalettes(colorsService); + this.palettes = buildPalettes(); } return { get: (name: string) => { diff --git a/src/plugins/charts/public/services/theme/theme.test.tsx b/src/plugins/charts/public/services/theme/theme.test.tsx index ef77405edf27..da89ec475d57 100644 --- a/src/plugins/charts/public/services/theme/theme.test.tsx +++ b/src/plugins/charts/public/services/theme/theme.test.tsx @@ -10,8 +10,7 @@ import React from 'react'; import { from } from 'rxjs'; import { take } from 'rxjs'; -import { renderHook, act } from '@testing-library/react-hooks'; -import { render, act as renderAct } from '@testing-library/react'; +import { render, act as renderAct, renderHook, act } from '@testing-library/react'; import { LIGHT_THEME, DARK_THEME } from '@elastic/charts'; diff --git a/src/plugins/charts/server/plugin.ts b/src/plugins/charts/server/plugin.ts index 9be0b9172c15..9859cc72edd0 100644 --- a/src/plugins/charts/server/plugin.ts +++ b/src/plugins/charts/server/plugin.ts @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { CoreSetup, Plugin } from '@kbn/core/server'; import { ExpressionsServerSetup } from '@kbn/expressions-plugin/server'; -import { COLOR_MAPPING_SETTING, LEGACY_TIME_AXIS, palette, systemPalette } from '../common'; +import { LEGACY_TIME_AXIS, palette, systemPalette } from '../common'; interface SetupDependencies { expressions: ExpressionsServerSetup; @@ -22,32 +22,6 @@ export class ChartsServerPlugin implements Plugin { dependencies.expressions.registerFunction(palette); dependencies.expressions.registerFunction(systemPalette); core.uiSettings.register({ - [COLOR_MAPPING_SETTING]: { - name: i18n.translate('charts.advancedSettings.visualization.colorMappingTitle', { - defaultMessage: 'Color mapping', - }), - value: JSON.stringify({ - Count: '#00A69B', - }), - type: 'json', - description: i18n.translate('charts.advancedSettings.visualization.colorMappingText', { - defaultMessage: - 'Maps values to specific colors in charts using the Compatibility palette.', - values: { strong: (chunks) => `${chunks}` }, - }), - deprecation: { - message: i18n.translate( - 'charts.advancedSettings.visualization.colorMappingTextDeprecation', - { - defaultMessage: - 'This setting is deprecated and will not be supported in a future version.', - } - ), - docLinksKey: 'visualizationSettings', - }, - category: ['visualization'], - schema: schema.string(), - }, [LEGACY_TIME_AXIS]: { name: i18n.translate('charts.advancedSettings.visualization.useLegacyTimeAxis.name', { defaultMessage: 'Legacy chart time axis', diff --git a/src/plugins/console/public/application/containers/editor/hooks/use_set_initial_value.test.ts b/src/plugins/console/public/application/containers/editor/hooks/use_set_initial_value.test.ts index 0605a0c903ee..4dab86712707 100644 --- a/src/plugins/console/public/application/containers/editor/hooks/use_set_initial_value.test.ts +++ b/src/plugins/console/public/application/containers/editor/hooks/use_set_initial_value.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useSetInitialValue } from './use_set_initial_value'; import { IToasts } from '@kbn/core-notifications-browser'; import { decompressFromEncodedURIComponent } from 'lz-string'; diff --git a/src/plugins/console/public/application/containers/editor/monaco_editor.tsx b/src/plugins/console/public/application/containers/editor/monaco_editor.tsx index bc174b772bb1..3e4728da27ae 100644 --- a/src/plugins/console/public/application/containers/editor/monaco_editor.tsx +++ b/src/plugins/console/public/application/containers/editor/monaco_editor.tsx @@ -211,9 +211,11 @@ export const MonacoEditor = ({ localStorageValue, value, setValue }: EditorProps fontSize: settings.fontSize, wordWrap: settings.wrapMode === true ? 'on' : 'off', theme: CONSOLE_THEME_ID, - // Make the quick-fix window be fixed to the window rather than clipped by - // the parent content set with overflow: hidden/auto - fixedOverflowWidgets: true, + // Force the hover views to always render below the cursor to avoid clipping + // when the cursor is near the top of the editor. + hover: { + above: false, + }, }} suggestionProvider={suggestionProvider} enableFindAction={true} diff --git a/src/plugins/console/public/application/containers/editor/utils/requests_utils.test.ts b/src/plugins/console/public/application/containers/editor/utils/requests_utils.test.ts index 29c975300db6..504afdadb603 100644 --- a/src/plugins/console/public/application/containers/editor/utils/requests_utils.test.ts +++ b/src/plugins/console/public/application/containers/editor/utils/requests_utils.test.ts @@ -426,6 +426,17 @@ describe('requests_utils', () => { expect(request).toEqual({ method: 'GET', url: '_search', data: ['{\n "query": {}\n}'] }); }); + it('correctly handles nested braces', () => { + const content = ['GET _search', '{', ' "query": "{a} {b}"', '}', '{', ' "query": {}', '}']; + const model = getMockModel(content); + const request = getRequestFromEditor(model, 1, 7); + expect(request).toEqual({ + method: 'GET', + url: '_search', + data: ['{\n "query": "{a} {b}"\n}', '{\n "query": {}\n}'], + }); + }); + it('works for several request bodies', () => { const content = ['GET _search', '{', ' "query": {}', '}', '{', ' "query": {}', '}']; const model = getMockModel(content); diff --git a/src/plugins/console/public/application/containers/editor/utils/requests_utils.ts b/src/plugins/console/public/application/containers/editor/utils/requests_utils.ts index 123daf919cf2..628e111df783 100644 --- a/src/plugins/console/public/application/containers/editor/utils/requests_utils.ts +++ b/src/plugins/console/public/application/containers/editor/utils/requests_utils.ts @@ -271,23 +271,61 @@ const replaceVariables = ( return text; }; +/** + * Splits a concatenated string of JSON objects into individual JSON objects. + * + * This function takes a string containing one or more JSON objects concatenated together, + * separated by optional whitespace, and splits them into an array of individual JSON strings. + * It ensures that nested objects and strings containing braces do not interfere with the splitting logic. + * + * Example inputs: + * - '{ "query": "test"} { "query": "test" }' -> ['{ "query": "test"}', '{ "query": "test" }'] + * - '{ "query": "test"}' -> ['{ "query": "test"}'] + * - '{ "query": "{a} {b}"}' -> ['{ "query": "{a} {b}"}'] + * + */ const splitDataIntoJsonObjects = (dataString: string): string[] => { - const jsonSplitRegex = /}\s*{/; - if (dataString.match(jsonSplitRegex)) { - return dataString.split(jsonSplitRegex).map((part, index, parts) => { - let restoredBracketsString = part; - // add an opening bracket to all parts except the 1st - if (index > 0) { - restoredBracketsString = `{${restoredBracketsString}`; + const jsonObjects = []; + // Tracks the depth of nested braces + let depth = 0; + // Holds the current JSON object as we iterate + let currentObject = ''; + // Tracks whether the current position is inside a string + let insideString = false; + + // Iterate through each character in the input string + for (let i = 0; i < dataString.length; i++) { + const char = dataString[i]; + // Append the character to the current JSON object string + currentObject += char; + + // If the character is a double quote and it is not escaped, toggle the `insideString` state + if (char === '"' && dataString[i - 1] !== '\\') { + insideString = !insideString; + } else if (!insideString) { + // Only modify depth if not inside a string + + if (char === '{') { + depth++; + } else if (char === '}') { + depth--; } - // add a closing bracket to all parts except the last - if (index < parts.length - 1) { - restoredBracketsString = `${restoredBracketsString}}`; + + // If depth is zero, we have completed a JSON object + if (depth === 0) { + jsonObjects.push(currentObject.trim()); + currentObject = ''; } - return restoredBracketsString; - }); + } } - return [dataString]; + + // If there's remaining data in currentObject, add it as the last JSON object + if (currentObject.trim()) { + jsonObjects.push(currentObject.trim()); + } + + // Filter out any empty strings from the result array + return jsonObjects.filter((obj) => obj !== ''); }; const cleanUpWhitespaces = (line: string): string => { diff --git a/src/plugins/content_management/public/content_client/content_client_mutation_hooks.test.tsx b/src/plugins/content_management/public/content_client/content_client_mutation_hooks.test.tsx index d2ad215f46e4..6e77a6c9c47d 100644 --- a/src/plugins/content_management/public/content_client/content_client_mutation_hooks.test.tsx +++ b/src/plugins/content_management/public/content_client/content_client_mutation_hooks.test.tsx @@ -8,7 +8,7 @@ */ import React, { FC, PropsWithChildren } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { ContentClientProvider } from './content_client_context'; import { ContentClient } from './content_client'; import { createCrudClientMock } from '../crud_client/crud_client.mock'; @@ -46,12 +46,10 @@ describe('useCreateContentMutation', () => { const input: CreateIn = { contentTypeId: 'testType', data: { foo: 'bar' }, version: 2 }; const output = { test: 'test' }; crudClient.create.mockResolvedValueOnce(output); - const { result, waitFor } = renderHook(() => useCreateContentMutation(), { wrapper: Wrapper }); + const { result } = renderHook(() => useCreateContentMutation(), { wrapper: Wrapper }); result.current.mutate(input); - await waitFor(() => result.current.isSuccess); - - expect(result.current.data).toEqual(output); + await waitFor(() => expect(result.current.data).toEqual(output)); }); }); @@ -66,12 +64,10 @@ describe('useUpdateContentMutation', () => { }; const output = { test: 'test' }; crudClient.update.mockResolvedValueOnce(output); - const { result, waitFor } = renderHook(() => useUpdateContentMutation(), { wrapper: Wrapper }); + const { result } = renderHook(() => useUpdateContentMutation(), { wrapper: Wrapper }); result.current.mutate(input); - await waitFor(() => result.current.isSuccess); - - expect(result.current.data).toEqual(output); + await waitFor(() => expect(result.current.data).toEqual(output)); }); }); @@ -81,11 +77,9 @@ describe('useDeleteContentMutation', () => { const input: DeleteIn = { contentTypeId: 'testType', id: 'test', version: 2 }; const output = { test: 'test' }; crudClient.delete.mockResolvedValueOnce(output); - const { result, waitFor } = renderHook(() => useDeleteContentMutation(), { wrapper: Wrapper }); + const { result } = renderHook(() => useDeleteContentMutation(), { wrapper: Wrapper }); result.current.mutate(input); - await waitFor(() => result.current.isSuccess); - - expect(result.current.data).toEqual(output); + await waitFor(() => expect(result.current.data).toEqual(output)); }); }); diff --git a/src/plugins/content_management/public/content_client/content_client_query_hooks.test.tsx b/src/plugins/content_management/public/content_client/content_client_query_hooks.test.tsx index 8cffde7ebd05..266e94641936 100644 --- a/src/plugins/content_management/public/content_client/content_client_query_hooks.test.tsx +++ b/src/plugins/content_management/public/content_client/content_client_query_hooks.test.tsx @@ -8,7 +8,7 @@ */ import React, { FC, PropsWithChildren } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { ContentClientProvider } from './content_client_context'; import { ContentClient } from './content_client'; import { createCrudClientMock } from '../crud_client/crud_client.mock'; @@ -42,9 +42,8 @@ describe('useGetContentQuery', () => { const input: GetIn = { id: 'test', contentTypeId: 'testType', version: 2 }; const output = { test: 'test' }; crudClient.get.mockResolvedValueOnce(output); - const { result, waitFor } = renderHook(() => useGetContentQuery(input), { wrapper: Wrapper }); - await waitFor(() => result.current.isSuccess); - expect(result.current.data).toEqual(output); + const { result } = renderHook(() => useGetContentQuery(input), { wrapper: Wrapper }); + await waitFor(() => expect(result.current.data).toEqual(output)); }); }); @@ -54,10 +53,9 @@ describe('useSearchContentQuery', () => { const input: SearchIn = { contentTypeId: 'testType', query: {}, version: 2 }; const output = { hits: [{ id: 'test' }] }; crudClient.search.mockResolvedValueOnce(output); - const { result, waitFor } = renderHook(() => useSearchContentQuery(input), { + const { result } = renderHook(() => useSearchContentQuery(input), { wrapper: Wrapper, }); - await waitFor(() => result.current.isSuccess); - expect(result.current.data).toEqual(output); + await waitFor(() => expect(result.current.data).toEqual(output)); }); }); diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index 884d5357bf91..0c9ecd848865 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -67,7 +67,7 @@ export const INTEGRATION_CATEGORY_DISPLAY: { productivity_security: { title: 'Productivity', parent_id: 'security' }, proxy_security: { title: 'Proxy', parent_id: 'security' }, sdk_search: { title: 'SDK', parent_id: 'search' }, - search: { title: 'Search', parent_id: undefined }, + search: { title: 'Elasticsearch', parent_id: undefined }, security: { title: 'Security', parent_id: undefined }, stream_processing: { title: 'Stream Processing', parent_id: 'observability' }, support: { title: 'Support', parent_id: undefined }, diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.test.ts b/src/plugins/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.test.ts index 7a5a77bfc14f..0bd8d95dc791 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.test.ts +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/add_new_panel/use_get_dashboard_panels.test.ts @@ -17,7 +17,7 @@ import { VisualizationsStart, type BaseVisType, } from '@kbn/visualizations-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { uiActionsService, visualizationsService } from '../../../services/kibana_services'; import { useGetDashboardPanels } from './use_get_dashboard_panels'; diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx index 61b7bf402bc1..3c4388903992 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { getDashboardBackupService } from '../../services/dashboard_backup_service'; import { getDashboardContentManagementService } from '../../services/dashboard_content_management_service'; diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx index 6c8c8f11d6a1..e434fe19d7b2 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx @@ -213,6 +213,10 @@ export const useDashboardListingTable = ({ size: listingLimit, hasReference: references, hasNoReference: referencesToExclude, + options: { + // include only tags references in the response to save bandwidth + includeReferences: ['tag'], + }, }) .then(({ total, hits }) => { const searchEndTime = window.performance.now(); diff --git a/src/plugins/dashboard/server/content_management/dashboard_storage.ts b/src/plugins/dashboard/server/content_management/dashboard_storage.ts index e65002802989..d113d509f5e8 100644 --- a/src/plugins/dashboard/server/content_management/dashboard_storage.ts +++ b/src/plugins/dashboard/server/content_management/dashboard_storage.ts @@ -341,7 +341,10 @@ export class DashboardStorage { const soResponse = await soClient.find(soQuery); const hits = soResponse.saved_objects .map((so) => { - const { item } = savedObjectToItem(so, false, soQuery.fields); + const { item } = savedObjectToItem(so, false, { + allowedAttributes: soQuery.fields, + allowedReferences: optionsToLatest?.includeReferences, + }); return item; }) // Ignore any saved objects that failed to convert to items. diff --git a/src/plugins/dashboard/server/content_management/v3/cm_services.ts b/src/plugins/dashboard/server/content_management/v3/cm_services.ts index e086d1cc1460..d2a53309704c 100644 --- a/src/plugins/dashboard/server/content_management/v3/cm_services.ts +++ b/src/plugins/dashboard/server/content_management/v3/cm_services.ts @@ -437,6 +437,7 @@ export const dashboardSearchOptionsSchema = schema.maybe( { onlyTitle: schema.maybe(schema.boolean()), fields: schema.maybe(schema.arrayOf(schema.string())), + includeReferences: schema.maybe(schema.arrayOf(schema.oneOf([schema.literal('tag')]))), kuery: schema.maybe(schema.string()), cursor: schema.maybe(schema.number()), limit: schema.maybe(schema.number()), diff --git a/src/plugins/dashboard/server/content_management/v3/transform_utils.test.ts b/src/plugins/dashboard/server/content_management/v3/transform_utils.test.ts index 627f1c421103..1cb10c8a10de 100644 --- a/src/plugins/dashboard/server/content_management/v3/transform_utils.test.ts +++ b/src/plugins/dashboard/server/content_management/v3/transform_utils.test.ts @@ -432,7 +432,9 @@ describe('savedObjectToItem', () => { }, }; - const { item, error } = savedObjectToItem(input, true, ['title', 'description']); + const { item, error } = savedObjectToItem(input, true, { + allowedAttributes: ['title', 'description'], + }); expect(error).toBeNull(); expect(item).toEqual({ ...commonSavedObject, @@ -457,6 +459,60 @@ describe('savedObjectToItem', () => { expect(item).toBeNull(); expect(error).not.toBe(null); }); + + it('should include only requested references', () => { + const input = { + ...commonSavedObject, + references: [ + { + type: 'tag', + id: 'tag1', + name: 'tag-ref-tag1', + }, + { + type: 'index-pattern', + id: 'index-pattern1', + name: 'index-pattern-ref-index-pattern1', + }, + ], + attributes: { + title: 'title', + description: 'my description', + timeRestore: false, + }, + }; + + { + const { item } = savedObjectToItem(input, true, { + allowedAttributes: ['title', 'description'], + }); + expect(item?.references).toEqual(input.references); + } + + { + const { item } = savedObjectToItem(input, true, { + allowedAttributes: ['title', 'description'], + allowedReferences: ['tag'], + }); + expect(item?.references).toEqual([input.references[0]]); + } + + { + const { item } = savedObjectToItem(input, true, { + allowedAttributes: ['title', 'description'], + allowedReferences: [], + }); + expect(item?.references).toEqual([]); + } + + { + const { item } = savedObjectToItem({ ...input, references: undefined }, true, { + allowedAttributes: ['title', 'description'], + allowedReferences: [], + }); + expect(item?.references).toBeUndefined(); + } + }); }); describe('getResultV3ToV2', () => { diff --git a/src/plugins/dashboard/server/content_management/v3/transform_utils.ts b/src/plugins/dashboard/server/content_management/v3/transform_utils.ts index 843dd59f849f..18c9085df4ec 100644 --- a/src/plugins/dashboard/server/content_management/v3/transform_utils.ts +++ b/src/plugins/dashboard/server/content_management/v3/transform_utils.ts @@ -304,24 +304,35 @@ type PartialSavedObject = Omit>, 'references'> & { references: SavedObjectReference[] | undefined; }; +export interface SavedObjectToItemOptions { + /** + * attributes to include in the output item + */ + allowedAttributes?: string[]; + /** + * references to include in the output item + */ + allowedReferences?: string[]; +} + export function savedObjectToItem( savedObject: SavedObject, partial: false, - allowedAttributes?: string[] + opts?: SavedObjectToItemOptions ): SavedObjectToItemReturn; export function savedObjectToItem( savedObject: PartialSavedObject, partial: true, - allowedAttributes?: string[] + opts?: SavedObjectToItemOptions ): SavedObjectToItemReturn; export function savedObjectToItem( savedObject: | SavedObject | PartialSavedObject, - partial: boolean, - allowedAttributes?: string[] + partial: boolean /* partial arg is used to enforce the correct savedObject type */, + { allowedAttributes, allowedReferences }: SavedObjectToItemOptions = {} ): SavedObjectToItemReturn { const { id, @@ -342,6 +353,12 @@ export function savedObjectToItem( const attributesOut = allowedAttributes ? pick(dashboardAttributesOut(attributes), allowedAttributes) : dashboardAttributesOut(attributes); + + // if includeReferences is provided, only include references of those types + const referencesOut = allowedReferences + ? references?.filter((reference) => allowedReferences.includes(reference.type)) + : references; + return { item: { id, @@ -353,7 +370,7 @@ export function savedObjectToItem( attributes: attributesOut, error, namespaces, - references, + references: referencesOut, version, managed, }, diff --git a/src/plugins/data_views/common/constants.ts b/src/plugins/data_views/common/constants.ts index b1e68fd44745..4b1cf465efcc 100644 --- a/src/plugins/data_views/common/constants.ts +++ b/src/plugins/data_views/common/constants.ts @@ -79,3 +79,12 @@ export const EXISTING_INDICES_PATH = '/internal/data_views/_existing_indices'; export const DATA_VIEWS_FIELDS_EXCLUDED_TIERS = 'data_views:fields_excluded_data_tiers'; export const DEFAULT_DATA_VIEW_ID = 'defaultIndex'; + +/** + * Valid `failureReason` attribute values for `has_es_data` API error responses + */ +export enum HasEsDataFailureReason { + localDataTimeout = 'local_data_timeout', + remoteDataTimeout = 'remote_data_timeout', + unknown = 'unknown', +} diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts index 2b5ca664d56f..d359489681a2 100644 --- a/src/plugins/data_views/common/index.ts +++ b/src/plugins/data_views/common/index.ts @@ -13,6 +13,7 @@ export { META_FIELDS, DATA_VIEW_SAVED_OBJECT_TYPE, MAX_DATA_VIEW_FIELD_DESCRIPTION_LENGTH, + HasEsDataFailureReason, } from './constants'; export { LATEST_VERSION } from './content_management/v1/constants'; diff --git a/src/plugins/data_views/common/types.ts b/src/plugins/data_views/common/types.ts index 45f747691fc4..8c229a01d047 100644 --- a/src/plugins/data_views/common/types.ts +++ b/src/plugins/data_views/common/types.ts @@ -571,4 +571,5 @@ export interface ClientConfigType { scriptedFieldsEnabled?: boolean; dataTiersExcludedForFields?: string; fieldListCachingEnabled?: boolean; + hasEsDataTimeout: number; } diff --git a/src/plugins/data_views/public/services/has_data.test.ts b/src/plugins/data_views/public/services/has_data.test.ts index fc032ee44bc4..1171cd677b64 100644 --- a/src/plugins/data_views/public/services/has_data.test.ts +++ b/src/plugins/data_views/public/services/has_data.test.ts @@ -10,6 +10,7 @@ import { coreMock } from '@kbn/core/public/mocks'; import { HasData } from './has_data'; +import { HttpFetchError } from '@kbn/core-http-browser-internal/src/http_fetch_error'; describe('when calling hasData service', () => { describe('hasDataView', () => { @@ -170,6 +171,78 @@ describe('when calling hasData service', () => { expect(await response).toBe(false); }); + + it('should return true and show an error toast when checking for remote cluster data times out', async () => { + const coreStart = coreMock.createStart(); + const http = coreStart.http; + + // Mock getIndices + const spy = jest.spyOn(http, 'get').mockImplementation(() => + Promise.reject( + new HttpFetchError( + 'Timeout while checking for Elasticsearch data', + 'TimeoutError', + new Request(''), + undefined, + { + statusCode: 504, + message: 'Timeout while checking for Elasticsearch data', + attributes: { + failureReason: 'remote_data_timeout', + }, + } + ) + ) + ); + const hasData = new HasData(); + const hasDataService = hasData.start(coreStart, true); + const response = hasDataService.hasESData(); + + expect(spy).toHaveBeenCalledTimes(1); + expect(await response).toBe(true); + expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledTimes(1); + expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({ + title: 'Remote cluster timeout', + text: 'Checking for data on remote clusters timed out. One or more remote clusters may be unavailable.', + }); + }); + + it('should return true and not show an error toast when checking for remote cluster data times out, but onRemoteDataTimeout is overridden', async () => { + const coreStart = coreMock.createStart(); + const http = coreStart.http; + + // Mock getIndices + const responseBody = { + statusCode: 504, + message: 'Timeout while checking for Elasticsearch data', + attributes: { + failureReason: 'remote_data_timeout', + }, + }; + const spy = jest + .spyOn(http, 'get') + .mockImplementation(() => + Promise.reject( + new HttpFetchError( + 'Timeout while checking for Elasticsearch data', + 'TimeoutError', + new Request(''), + undefined, + responseBody + ) + ) + ); + const hasData = new HasData(); + const hasDataService = hasData.start(coreStart, true); + const onRemoteDataTimeout = jest.fn(); + const response = hasDataService.hasESData({ onRemoteDataTimeout }); + + expect(spy).toHaveBeenCalledTimes(1); + expect(await response).toBe(true); + expect(coreStart.notifications.toasts.addDanger).not.toHaveBeenCalled(); + expect(onRemoteDataTimeout).toHaveBeenCalledTimes(1); + expect(onRemoteDataTimeout).toHaveBeenCalledWith(responseBody); + }); }); describe('resolve/cluster not available', () => { diff --git a/src/plugins/data_views/public/services/has_data.ts b/src/plugins/data_views/public/services/has_data.ts index aad546c446cf..bcf80ca33746 100644 --- a/src/plugins/data_views/public/services/has_data.ts +++ b/src/plugins/data_views/public/services/has_data.ts @@ -8,10 +8,22 @@ */ import { CoreStart, HttpStart } from '@kbn/core/public'; -import { DEFAULT_ASSETS_TO_IGNORE } from '../../common'; +import { IHttpFetchError, ResponseErrorBody, isHttpFetchError } from '@kbn/core-http-browser'; +import { isObject } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { DEFAULT_ASSETS_TO_IGNORE, HasEsDataFailureReason } from '../../common'; import { HasDataViewsResponse, IndicesViaSearchResponse } from '..'; import { IndicesResponse, IndicesResponseModified } from '../types'; +export interface HasEsDataParams { + /** + * Callback to handle the case where checking for remote data times out. + * If not provided, the default behavior is to show a toast notification. + * @param body The error response body + */ + onRemoteDataTimeout?: (body: ResponseErrorBody) => void; +} + export class HasData { private removeAliases = (source: IndicesResponseModified): boolean => !source.item.indices; @@ -38,28 +50,55 @@ export class HasData { return hasLocalESData; }; - const hasESDataViaResolveCluster = async () => { + const hasESDataViaResolveCluster = async ( + onRemoteDataTimeout: (body: ResponseErrorBody) => void + ) => { try { const { hasEsData } = await http.get<{ hasEsData: boolean }>( '/internal/data_views/has_es_data', - { - version: '1', - } + { version: '1' } ); + return hasEsData; } catch (e) { + if ( + this.isResponseError(e) && + e.body?.statusCode === 504 && + e.body?.attributes?.failureReason === HasEsDataFailureReason.remoteDataTimeout + ) { + onRemoteDataTimeout(e.body); + + // In the case of a remote cluster timeout, + // we can't be sure if there is data or not, + // so just assume there is + return true; + } + // fallback to previous implementation return hasESDataViaResolveIndex(); } }; + const showRemoteDataTimeoutToast = () => + core.notifications.toasts.addDanger({ + title: i18n.translate('dataViews.hasData.remoteDataTimeoutTitle', { + defaultMessage: 'Remote cluster timeout', + }), + text: i18n.translate('dataViews.hasData.remoteDataTimeoutText', { + defaultMessage: + 'Checking for data on remote clusters timed out. One or more remote clusters may be unavailable.', + }), + }); + return { /** * Check to see if ES data exists */ - hasESData: async (): Promise => { + hasESData: async ({ + onRemoteDataTimeout = showRemoteDataTimeoutToast, + }: HasEsDataParams = {}): Promise => { if (callResolveCluster) { - return hasESDataViaResolveCluster(); + return hasESDataViaResolveCluster(onRemoteDataTimeout); } return hasESDataViaResolveIndex(); }, @@ -82,6 +121,9 @@ export class HasData { // ES Data + private isResponseError = (e: Error): e is IHttpFetchError => + isHttpFetchError(e) && isObject(e.body) && 'message' in e.body && 'statusCode' in e.body; + private responseToItemArray = (response: IndicesResponse): IndicesResponseModified[] => { const { indices = [], aliases = [] } = response; const source: IndicesResponseModified[] = []; diff --git a/src/plugins/data_views/server/index.ts b/src/plugins/data_views/server/index.ts index d72a50d20e31..143bea2ba5d5 100644 --- a/src/plugins/data_views/server/index.ts +++ b/src/plugins/data_views/server/index.ts @@ -47,7 +47,6 @@ const configSchema = schema.object({ schema.boolean({ defaultValue: false }), schema.never() ), - dataTiersExcludedForFields: schema.conditional( schema.contextRef('serverless'), true, @@ -60,6 +59,7 @@ const configSchema = schema.object({ schema.boolean({ defaultValue: false }), schema.boolean({ defaultValue: true }) ), + hasEsDataTimeout: schema.number({ defaultValue: 5000 }), }); type ConfigType = TypeOf; diff --git a/src/plugins/data_views/server/plugin.ts b/src/plugins/data_views/server/plugin.ts index 8decac6c36b1..9e79da893949 100644 --- a/src/plugins/data_views/server/plugin.ts +++ b/src/plugins/data_views/server/plugin.ts @@ -63,9 +63,11 @@ export class DataViewsServerPlugin registerRoutes({ http: core.http, + logger: this.logger, getStartServices: core.getStartServices, isRollupsEnabled: () => this.rollupsEnabled, dataViewRestCounter, + hasEsDataTimeout: config.hasEsDataTimeout, }); expressions.registerFunction(getIndexPatternLoad({ getStartServices: core.getStartServices })); diff --git a/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.test.ts b/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.test.ts new file mode 100644 index 000000000000..7ca07d25bf77 --- /dev/null +++ b/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.test.ts @@ -0,0 +1,319 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { MockedKeys } from '@kbn/utility-types-jest'; +import { IKibanaResponse, Logger, RequestHandlerContext } from '@kbn/core/server'; +import { httpServerMock } from '@kbn/core/server/mocks'; +import { createHandler, crossClusterPatterns, patterns } from './has_es_data'; +import { loggerMock } from '@kbn/logging-mocks'; + +const mockEsDataTimeout = 5000; + +describe('has_es_data route', () => { + let mockLogger: MockedKeys; + + beforeEach(() => { + mockLogger = loggerMock.create(); + }); + + it('should return hasEsData: true if there are matching local indices', async () => { + const mockESClient = { + indices: { + resolveCluster: jest.fn().mockResolvedValue({ + local: { matching_indices: true }, + }), + }, + }; + const mockContext = { + core: { + elasticsearch: { client: { asCurrentUser: mockESClient } }, + }, + } as unknown as RequestHandlerContext; + const mockRequest = httpServerMock.createKibanaRequest(); + const mockResponse = httpServerMock.createResponseFactory(); + jest + .spyOn(mockResponse, 'ok') + .mockImplementation((params) => params as unknown as IKibanaResponse); + const handler = createHandler(mockLogger, mockEsDataTimeout); + const response = await handler(mockContext, mockRequest, mockResponse); + expect(mockESClient.indices.resolveCluster).toBeCalledTimes(1); + expect(mockESClient.indices.resolveCluster).toBeCalledWith( + { + name: patterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockResponse.ok).toBeCalledTimes(1); + expect(mockResponse.ok).toBeCalledWith({ body: { hasEsData: true } }); + expect(response).toEqual({ body: { hasEsData: true } }); + }); + + it('should return hasEsData: true if there are no matching local indices but matching remote indices', async () => { + const mockESClient = { + indices: { + resolveCluster: jest + .fn() + .mockImplementation(({ name }) => + name === patterns + ? { local: { matching_indices: false } } + : name === crossClusterPatterns + ? { remote: { matching_indices: true } } + : {} + ), + }, + }; + const mockContext = { + core: { + elasticsearch: { client: { asCurrentUser: mockESClient } }, + }, + } as unknown as RequestHandlerContext; + const mockRequest = httpServerMock.createKibanaRequest(); + const mockResponse = httpServerMock.createResponseFactory(); + jest + .spyOn(mockResponse, 'ok') + .mockImplementation((params) => params as unknown as IKibanaResponse); + const handler = createHandler(mockLogger, mockEsDataTimeout); + const response = await handler(mockContext, mockRequest, mockResponse); + expect(mockESClient.indices.resolveCluster).toBeCalledTimes(2); + expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith( + 1, + { + name: patterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith( + 2, + { + name: crossClusterPatterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockResponse.ok).toBeCalledTimes(1); + expect(mockResponse.ok).toBeCalledWith({ body: { hasEsData: true } }); + expect(response).toEqual({ body: { hasEsData: true } }); + }); + + it('should return hasEsData: false if there are no matching local or remote indices', async () => { + const mockESClient = { + indices: { + resolveCluster: jest.fn().mockResolvedValue({ + local: { matching_indices: false }, + remote: { matching_indices: false }, + }), + }, + }; + const mockContext = { + core: { + elasticsearch: { client: { asCurrentUser: mockESClient } }, + }, + } as unknown as RequestHandlerContext; + const mockRequest = httpServerMock.createKibanaRequest(); + const mockResponse = httpServerMock.createResponseFactory(); + jest + .spyOn(mockResponse, 'ok') + .mockImplementation((params) => params as unknown as IKibanaResponse); + const handler = createHandler(mockLogger, mockEsDataTimeout); + const response = await handler(mockContext, mockRequest, mockResponse); + expect(mockESClient.indices.resolveCluster).toBeCalledTimes(2); + expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith( + 1, + { + name: patterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith( + 2, + { + name: crossClusterPatterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockResponse.ok).toBeCalledTimes(1); + expect(mockResponse.ok).toBeCalledWith({ body: { hasEsData: false } }); + expect(response).toEqual({ body: { hasEsData: false } }); + }); + + it('should return a 504 response and log a warning if the local data request times out', async () => { + const mockESClient = { + indices: { + resolveCluster: jest.fn().mockRejectedValue({ name: 'TimeoutError' }), + }, + }; + const mockContext = { + core: { + elasticsearch: { client: { asCurrentUser: mockESClient } }, + }, + } as unknown as RequestHandlerContext; + const mockRequest = httpServerMock.createKibanaRequest(); + const mockResponse = httpServerMock.createResponseFactory(); + jest + .spyOn(mockResponse, 'customError') + .mockImplementation((params) => params as unknown as IKibanaResponse); + const handler = createHandler(mockLogger, mockEsDataTimeout); + const response = await handler(mockContext, mockRequest, mockResponse); + expect(mockESClient.indices.resolveCluster).toBeCalledTimes(1); + expect(mockESClient.indices.resolveCluster).toBeCalledWith( + { + name: patterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockResponse.customError).toBeCalledTimes(1); + expect(mockResponse.customError).toBeCalledWith({ + statusCode: 504, + body: { + message: 'Timeout while checking for Elasticsearch data', + attributes: { failureReason: 'local_data_timeout' }, + }, + }); + expect(response).toEqual({ + statusCode: 504, + body: { + message: 'Timeout while checking for Elasticsearch data', + attributes: { failureReason: 'local_data_timeout' }, + }, + }); + expect(mockLogger.warn).toBeCalledTimes(1); + expect(mockLogger.warn).toBeCalledWith( + 'Timeout while checking for Elasticsearch data: local_data_timeout. Current timeout value is 5000ms. ' + + 'Use "data_views.hasEsDataTimeout" in kibana.yml to change it, or set to 0 to disable timeouts.' + ); + }); + + it('should return a 504 response and log a warning if the remote data request times out', async () => { + const mockESClient = { + indices: { + resolveCluster: jest.fn().mockImplementation(({ name }) => { + if (name === patterns) { + return { local: { matching_indices: false } }; + } + + if (name === crossClusterPatterns) { + // eslint-disable-next-line no-throw-literal + throw { name: 'TimeoutError' }; + } + + return {}; + }), + }, + }; + const mockContext = { + core: { + elasticsearch: { client: { asCurrentUser: mockESClient } }, + }, + } as unknown as RequestHandlerContext; + const mockRequest = httpServerMock.createKibanaRequest(); + const mockResponse = httpServerMock.createResponseFactory(); + jest + .spyOn(mockResponse, 'customError') + .mockImplementation((params) => params as unknown as IKibanaResponse); + const handler = createHandler(mockLogger, mockEsDataTimeout); + const response = await handler(mockContext, mockRequest, mockResponse); + expect(mockESClient.indices.resolveCluster).toBeCalledTimes(2); + expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith( + 1, + { + name: patterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockESClient.indices.resolveCluster).toHaveBeenNthCalledWith( + 2, + { + name: crossClusterPatterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockResponse.customError).toBeCalledTimes(1); + expect(mockResponse.customError).toBeCalledWith({ + statusCode: 504, + body: { + message: 'Timeout while checking for Elasticsearch data', + attributes: { failureReason: 'remote_data_timeout' }, + }, + }); + expect(response).toEqual({ + statusCode: 504, + body: { + message: 'Timeout while checking for Elasticsearch data', + attributes: { failureReason: 'remote_data_timeout' }, + }, + }); + expect(mockLogger.warn).toBeCalledTimes(1); + expect(mockLogger.warn).toBeCalledWith( + 'Timeout while checking for Elasticsearch data: remote_data_timeout. Current timeout value is 5000ms. ' + + 'Use "data_views.hasEsDataTimeout" in kibana.yml to change it, or set to 0 to disable timeouts.' + ); + }); + + it('should return a 500 response and log an error if the request fails for an unknown reason', async () => { + const someError = new Error('Some error'); + const mockESClient = { + indices: { + resolveCluster: jest.fn().mockRejectedValue(someError), + }, + }; + const mockContext = { + core: { + elasticsearch: { client: { asCurrentUser: mockESClient } }, + }, + } as unknown as RequestHandlerContext; + const mockRequest = httpServerMock.createKibanaRequest(); + const mockResponse = httpServerMock.createResponseFactory(); + jest + .spyOn(mockResponse, 'customError') + .mockImplementation((params) => params as unknown as IKibanaResponse); + const handler = createHandler(mockLogger, mockEsDataTimeout); + const response = await handler(mockContext, mockRequest, mockResponse); + expect(mockESClient.indices.resolveCluster).toBeCalledTimes(1); + expect(mockESClient.indices.resolveCluster).toBeCalledWith( + { + name: patterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: mockEsDataTimeout } + ); + expect(mockResponse.customError).toBeCalledTimes(1); + expect(mockResponse.customError).toBeCalledWith({ + statusCode: 500, + body: { + message: 'Error while checking for Elasticsearch data', + attributes: { failureReason: 'unknown' }, + }, + }); + expect(response).toEqual({ + statusCode: 500, + body: { + message: 'Error while checking for Elasticsearch data', + attributes: { failureReason: 'unknown' }, + }, + }); + expect(mockLogger.error).toBeCalledTimes(1); + expect(mockLogger.error).toBeCalledWith(someError); + }); +}); diff --git a/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.ts b/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.ts index 4f3fb9f19a6f..72b2e508ba52 100644 --- a/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.ts +++ b/src/plugins/data_views/server/rest_api_routes/internal/has_es_data.ts @@ -7,34 +7,124 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { IRouter, RequestHandlerContext } from '@kbn/core/server'; -import type { VersionedRoute } from '@kbn/core-http-server'; +import type { ElasticsearchClient, IRouter, Logger, RequestHandlerContext } from '@kbn/core/server'; +import type { KibanaResponseFactory, VersionedRoute } from '@kbn/core-http-server'; import { schema } from '@kbn/config-schema'; -import { DEFAULT_ASSETS_TO_IGNORE } from '../../../common'; +import { DEFAULT_ASSETS_TO_IGNORE, HasEsDataFailureReason } from '../../../common'; type Handler = Parameters['addVersion']>[1]; -const patterns = ['*', '-.*'].concat( +export const patterns = ['*', '-.*'].concat( DEFAULT_ASSETS_TO_IGNORE.DATA_STREAMS_TO_IGNORE.map((ds) => `-${ds}`) ); -const crossClusterPatterns = patterns.map((ds) => `*:${ds}`); +export const crossClusterPatterns = patterns.map((ds) => `*:${ds}`); -export const handler: Handler = async (ctx: RequestHandlerContext, req, res) => { - const core = await ctx.core; - const elasticsearchClient = core.elasticsearch.client.asCurrentUser; - const response = await elasticsearchClient.indices.resolveCluster({ - name: patterns.concat(crossClusterPatterns), - allow_no_indices: true, - ignore_unavailable: true, - }); +export const createHandler = + (parentLogger: Logger, hasEsDataTimeout: number): Handler => + async (ctx, _, res) => { + const logger = parentLogger.get('hasEsData'); + const core = await ctx.core; + const elasticsearchClient = core.elasticsearch.client.asCurrentUser; + const commonParams: Omit = { + elasticsearchClient, + logger, + res, + hasEsDataTimeout, + }; - const hasEsData = !!Object.values(response).find((cluster) => cluster.matching_indices); + const localDataResponse = await hasEsData({ + ...commonParams, + matchPatterns: patterns, + timeoutReason: HasEsDataFailureReason.localDataTimeout, + }); - return res.ok({ body: { hasEsData } }); + if (localDataResponse) { + return localDataResponse; + } + + const remoteDataResponse = await hasEsData({ + ...commonParams, + matchPatterns: crossClusterPatterns, + timeoutReason: HasEsDataFailureReason.remoteDataTimeout, + }); + + if (remoteDataResponse) { + return remoteDataResponse; + } + + return res.ok({ body: { hasEsData: false } }); + }; + +interface HasEsDataParams { + elasticsearchClient: ElasticsearchClient; + logger: Logger; + res: KibanaResponseFactory; + matchPatterns: string[]; + hasEsDataTimeout: number; + timeoutReason: HasEsDataFailureReason; +} + +const timeoutMessage = 'Timeout while checking for Elasticsearch data'; +const errorMessage = 'Error while checking for Elasticsearch data'; + +const hasEsData = async ({ + elasticsearchClient, + logger, + res, + matchPatterns, + hasEsDataTimeout, + timeoutReason, +}: HasEsDataParams) => { + try { + const response = await elasticsearchClient.indices.resolveCluster( + { + name: matchPatterns, + allow_no_indices: true, + ignore_unavailable: true, + }, + { requestTimeout: hasEsDataTimeout === 0 ? undefined : hasEsDataTimeout } + ); + + const hasData = Object.values(response).some((cluster) => cluster.matching_indices); + + if (hasData) { + return res.ok({ body: { hasEsData: true } }); + } + } catch (e) { + if (e.name === 'TimeoutError') { + const warningMessage = + `${timeoutMessage}: ${timeoutReason}. Current timeout value is ${hasEsDataTimeout}ms. ` + + `Use "data_views.hasEsDataTimeout" in kibana.yml to change it, or set to 0 to disable timeouts.`; + + logger.warn(warningMessage); + + return res.customError({ + statusCode: 504, + body: { + message: timeoutMessage, + attributes: { failureReason: timeoutReason }, + }, + }); + } + + logger.error(e); + + return res.customError({ + statusCode: 500, + body: { + message: errorMessage, + attributes: { failureReason: HasEsDataFailureReason.unknown }, + }, + }); + } }; -export const registerHasEsDataRoute = (router: IRouter): void => { +export const registerHasEsDataRoute = ( + router: IRouter, + logger: Logger, + hasEsDataTimeout: number +): void => { router.versioned .get({ path: '/internal/data_views/has_es_data', @@ -51,9 +141,18 @@ export const registerHasEsDataRoute = (router: IRouter): void => { hasEsData: schema.boolean(), }), }, + 400: { + body: () => + schema.object({ + message: schema.string(), + attributes: schema.object({ + failureReason: schema.string(), + }), + }), + }, }, }, }, - handler + createHandler(logger, hasEsDataTimeout) ); }; diff --git a/src/plugins/data_views/server/routes.ts b/src/plugins/data_views/server/routes.ts index e5803423d819..9e8501f928f1 100644 --- a/src/plugins/data_views/server/routes.ts +++ b/src/plugins/data_views/server/routes.ts @@ -7,8 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { HttpServiceSetup, StartServicesAccessor } from '@kbn/core/server'; -import { UsageCounter } from '@kbn/usage-collection-plugin/server'; +import type { HttpServiceSetup, Logger, StartServicesAccessor } from '@kbn/core/server'; +import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { routes } from './rest_api_routes/public'; import type { DataViewsServerPluginStart, DataViewsServerPluginStartDependencies } from './types'; @@ -20,19 +20,23 @@ import { registerFields } from './rest_api_routes/internal/fields'; interface RegisterRoutesArgs { http: HttpServiceSetup; + logger: Logger; getStartServices: StartServicesAccessor< DataViewsServerPluginStartDependencies, DataViewsServerPluginStart >; isRollupsEnabled: () => boolean; dataViewRestCounter?: UsageCounter; + hasEsDataTimeout: number; } export function registerRoutes({ http, + logger, getStartServices, - dataViewRestCounter, isRollupsEnabled, + dataViewRestCounter, + hasEsDataTimeout, }: RegisterRoutesArgs) { const router = http.createRouter(); @@ -42,5 +46,5 @@ export function registerRoutes({ registerFieldForWildcard(router, getStartServices, isRollupsEnabled); registerFields(router, getStartServices, isRollupsEnabled); registerHasDataViewsRoute(router); - registerHasEsDataRoute(router); + registerHasEsDataRoute(router, logger, hasEsDataTimeout); } diff --git a/src/plugins/data_views/tsconfig.json b/src/plugins/data_views/tsconfig.json index 312de968d640..45992b3548f8 100644 --- a/src/plugins/data_views/tsconfig.json +++ b/src/plugins/data_views/tsconfig.json @@ -34,6 +34,9 @@ "@kbn/core-saved-objects-server", "@kbn/logging", "@kbn/crypto-browser", + "@kbn/core-http-browser", + "@kbn/core-http-browser-internal", + "@kbn/logging-mocks", ], "exclude": [ "target/**/*", diff --git a/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx b/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx index acfaaf7daba3..edffaa1c3253 100644 --- a/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx +++ b/src/plugins/discover/public/application/context/hooks/use_context_app_fetch.test.tsx @@ -8,7 +8,7 @@ */ import React from 'react'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock'; import { CONTEXT_TIE_BREAKER_FIELDS_SETTING } from '@kbn/discover-utils'; import { DiscoverServices } from '../../../build_services'; @@ -106,7 +106,7 @@ const initDefaults = (tieBreakerFields: string[], dataViewId = 'the-data-view-id return { result: renderHook(() => useContextAppFetch(props.props), { - wrapper: ({ children }: React.PropsWithChildren<{}>) => ( + wrapper: ({ children }: React.PropsWithChildren) => ( {children} ), }).result, diff --git a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx index e14b5afc30d0..287b5a60386f 100644 --- a/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/use_discover_histogram.test.tsx @@ -9,7 +9,7 @@ import React, { ReactElement } from 'react'; import { AggregateQuery, Query } from '@kbn/es-query'; -import { act, renderHook, WrapperComponent } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { BehaviorSubject, Subject } from 'rxjs'; import { FetchStatus } from '../../../types'; import type { DiscoverStateContainer } from '../../state_management/discover_state'; @@ -121,9 +121,7 @@ describe('useDiscoverHistogram', () => { hideChart, }; - const Wrapper: WrapperComponent> = ({ - children, - }) => ( + const Wrapper = ({ children }: React.PropsWithChildren) => ( {children as ReactElement} ); diff --git a/src/plugins/discover/public/application/main/components/layout/use_fetch_more_records.test.tsx b/src/plugins/discover/public/application/main/components/layout/use_fetch_more_records.test.tsx index 4bab0f75e87e..eeb0cd8ccb1b 100644 --- a/src/plugins/discover/public/application/main/components/layout/use_fetch_more_records.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/use_fetch_more_records.test.tsx @@ -8,10 +8,10 @@ */ import { BehaviorSubject } from 'rxjs'; -import { renderHook, WrapperComponent } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { buildDataTableRecord } from '@kbn/discover-utils'; import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__'; -import { useFetchMoreRecords, UseFetchMoreRecordsParams } from './use_fetch_more_records'; +import { useFetchMoreRecords } from './use_fetch_more_records'; import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; import { DataDocuments$, @@ -47,10 +47,8 @@ describe('useFetchMoreRecords', () => { return stateContainer; }; - const getWrapper = ( - stateContainer: DiscoverStateContainer - ): WrapperComponent> => { - return ({ children }) => ( + const getWrapper = (stateContainer: DiscoverStateContainer) => { + return ({ children }: React.PropsWithChildren) => ( <>{children} diff --git a/src/plugins/discover/public/application/main/components/top_nav/use_top_nav_links.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/use_top_nav_links.test.tsx index 2c51d57a3261..a70b200a7434 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/use_top_nav_links.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/use_top_nav_links.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { useTopNavLinks } from './use_top_nav_links'; import { DiscoverServices } from '../../../../build_services'; diff --git a/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx b/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx index 9f3fe7004f5f..486c477c8833 100644 --- a/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx +++ b/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx @@ -8,8 +8,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { DataViewsContract } from '@kbn/data-plugin/public'; import { discoverServiceMock } from '../../../__mocks__/services'; import { useEsqlMode } from './use_esql_mode'; @@ -76,8 +75,10 @@ const getDataViewsService = () => { }; const getHookContext = (stateContainer: DiscoverStateContainer) => { - return ({ children }: { children: JSX.Element }) => ( - {children} + return ({ children }: React.PropsWithChildren) => ( + + <>{children} + ); }; const renderHookWithContext = ( diff --git a/src/plugins/discover/public/application/main/hooks/use_inspector.test.ts b/src/plugins/discover/public/application/main/hooks/use_inspector.test.ts index 53d66419caba..ccade68ff2c8 100644 --- a/src/plugins/discover/public/application/main/hooks/use_inspector.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_inspector.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { discoverServiceMock } from '../../../__mocks__/services'; import { useInspector } from './use_inspector'; import { Adapters, RequestAdapter } from '@kbn/inspector-plugin/common'; diff --git a/src/plugins/discover/public/application/main/hooks/use_url.test.ts b/src/plugins/discover/public/application/main/hooks/use_url.test.ts index 427765eac132..24c6fe6af303 100644 --- a/src/plugins/discover/public/application/main/hooks/use_url.test.ts +++ b/src/plugins/discover/public/application/main/hooks/use_url.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createSearchSessionMock } from '../../../__mocks__/search_session'; import { useUrl } from './use_url'; import { diff --git a/src/plugins/discover/public/application/main/state_management/discover_state_provider.tsx b/src/plugins/discover/public/application/main/state_management/discover_state_provider.tsx index d5bbbd0b7c39..2266a7d5276f 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_state_provider.tsx +++ b/src/plugins/discover/public/application/main/state_management/discover_state_provider.tsx @@ -57,10 +57,9 @@ export const { export const DiscoverMainProvider = ({ value, children, -}: { +}: React.PropsWithChildren<{ value: DiscoverStateContainer; - children: React.ReactElement; -}) => { +}>) => { return ( diff --git a/src/plugins/discover/public/context_awareness/hooks/use_additional_cell_actions.test.tsx b/src/plugins/discover/public/context_awareness/hooks/use_additional_cell_actions.test.tsx index 88dffdfa1ce6..df96e4ee57a3 100644 --- a/src/plugins/discover/public/context_awareness/hooks/use_additional_cell_actions.test.tsx +++ b/src/plugins/discover/public/context_awareness/hooks/use_additional_cell_actions.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { DISCOVER_CELL_ACTION_TYPE, createCellAction, @@ -76,7 +76,7 @@ describe('useAdditionalCellActions', () => { }; const render = () => { - return renderHook((props) => useAdditionalCellActions(props), { + return renderHook(useAdditionalCellActions, { initialProps, wrapper: ({ children }) => ( {children} @@ -90,6 +90,7 @@ describe('useAdditionalCellActions', () => { afterEach(() => { mockUuid = 0; + jest.clearAllMocks(); }); it('should return metadata', async () => { @@ -108,7 +109,7 @@ describe('useAdditionalCellActions', () => { expect(mockActions).toHaveLength(1); expect(mockTriggerActions[DISCOVER_CELL_ACTIONS_TRIGGER.id]).toEqual(['root-action-2']); await act(() => discoverServiceMock.profilesManager.resolveDataSourceProfile({})); - rerender(); + rerender(initialProps); expect(result.current.instanceId).toEqual('3'); expect(mockActions).toHaveLength(2); expect(mockTriggerActions[DISCOVER_CELL_ACTIONS_TRIGGER.id]).toEqual([ diff --git a/src/plugins/discover/public/context_awareness/hooks/use_profile_accessor.test.ts b/src/plugins/discover/public/context_awareness/hooks/use_profile_accessor.test.ts index 65f6f7fb3f30..b079115d6928 100644 --- a/src/plugins/discover/public/context_awareness/hooks/use_profile_accessor.test.ts +++ b/src/plugins/discover/public/context_awareness/hooks/use_profile_accessor.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { AppliedProfile, getMergedAccessor } from '../composable_profile'; import { useProfileAccessor } from './use_profile_accessor'; import { getDataTableRecords } from '../../__fixtures__/real_hits'; diff --git a/src/plugins/discover/public/context_awareness/hooks/use_profiles.test.tsx b/src/plugins/discover/public/context_awareness/hooks/use_profiles.test.tsx index b42fc1c4b3c4..ad142371aebb 100644 --- a/src/plugins/discover/public/context_awareness/hooks/use_profiles.test.tsx +++ b/src/plugins/discover/public/context_awareness/hooks/use_profiles.test.tsx @@ -8,7 +8,7 @@ */ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import React from 'react'; import { discoverServiceMock } from '../../__mocks__/services'; import type { GetProfilesOptions } from '../profiles_manager'; diff --git a/src/plugins/discover/public/context_awareness/hooks/use_root_profile.test.tsx b/src/plugins/discover/public/context_awareness/hooks/use_root_profile.test.tsx index 26c3aa2df3f1..d6984eb94cb4 100644 --- a/src/plugins/discover/public/context_awareness/hooks/use_root_profile.test.tsx +++ b/src/plugins/discover/public/context_awareness/hooks/use_root_profile.test.tsx @@ -8,7 +8,7 @@ */ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor, act, renderHook } from '@testing-library/react'; import React from 'react'; import { discoverServiceMock } from '../../__mocks__/services'; import { useRootProfile } from './use_root_profile'; @@ -37,31 +37,34 @@ describe('useRootProfile', () => { }); it('should return rootProfileLoading as true', async () => { - const { result, waitForNextUpdate } = render(); + const { result } = render(); expect(result.current.rootProfileLoading).toBe(true); expect((result.current as Record).AppWrapper).toBeUndefined(); // avoid act warning - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); }); it('should return rootProfileLoading as false', async () => { - const { result, waitForNextUpdate } = render(); - await waitForNextUpdate(); - expect(result.current.rootProfileLoading).toBe(false); - expect((result.current as Record).AppWrapper).toBeDefined(); + const { result } = render(); + await waitFor(() => { + expect(result.current.rootProfileLoading).toBe(false); + expect((result.current as Record).AppWrapper).toBeDefined(); + }); }); it('should return rootProfileLoading as true when solutionNavId changes', async () => { - const { result, rerender, waitForNextUpdate } = render(); - await waitForNextUpdate(); - expect(result.current.rootProfileLoading).toBe(false); - expect((result.current as Record).AppWrapper).toBeDefined(); + const { result, rerender } = render(); + await waitFor(() => { + expect(result.current.rootProfileLoading).toBe(false); + expect((result.current as Record).AppWrapper).toBeDefined(); + }); act(() => mockSolutionNavId$.next('newSolutionNavId')); rerender(); expect(result.current.rootProfileLoading).toBe(true); expect((result.current as Record).AppWrapper).toBeUndefined(); - await waitForNextUpdate(); - expect(result.current.rootProfileLoading).toBe(false); - expect((result.current as Record).AppWrapper).toBeDefined(); + await waitFor(() => { + expect(result.current.rootProfileLoading).toBe(false); + expect((result.current as Record).AppWrapper).toBeDefined(); + }); }); }); diff --git a/src/plugins/discover/public/customizations/customization_provider.test.tsx b/src/plugins/discover/public/customizations/customization_provider.test.tsx index 7dbdc9e3c347..4b871c4ae8b7 100644 --- a/src/plugins/discover/public/customizations/customization_provider.test.tsx +++ b/src/plugins/discover/public/customizations/customization_provider.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import React from 'react'; import { getDiscoverStateMock } from '../__mocks__/discover_state.mock'; import { diff --git a/src/plugins/discover/public/embeddable/get_search_embeddable_factory.test.tsx b/src/plugins/discover/public/embeddable/get_search_embeddable_factory.test.tsx index b1c589f3e1d8..621703ad3991 100644 --- a/src/plugins/discover/public/embeddable/get_search_embeddable_factory.test.tsx +++ b/src/plugins/discover/public/embeddable/get_search_embeddable_factory.test.tsx @@ -18,7 +18,7 @@ import { BuildReactEmbeddableApiRegistration } from '@kbn/embeddable-plugin/publ import { PresentationContainer } from '@kbn/presentation-containers'; import { PhaseEvent, PublishesUnifiedSearch, StateComparators } from '@kbn/presentation-publishing'; import { VIEW_MODE } from '@kbn/saved-search-plugin/common'; -import { act, render } from '@testing-library/react'; +import { act, render, waitFor } from '@testing-library/react'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { createDataViewDataSource } from '../../common/data_sources'; @@ -143,8 +143,10 @@ describe('saved search embeddable', () => { expect(api.dataLoading.getValue()).toBe(false); expect(discoverComponent.queryByTestId('embeddedSavedSearchDocTable')).toBeInTheDocument(); - expect(discoverComponent.getByTestId('embeddedSavedSearchDocTable').textContent).toEqual( - 'No results found' + await waitFor(() => + expect(discoverComponent.getByTestId('embeddedSavedSearchDocTable').textContent).toEqual( + 'No results found' + ) ); }); diff --git a/src/plugins/discover/public/hooks/saved_search_alias_match_redirect.test.ts b/src/plugins/discover/public/hooks/saved_search_alias_match_redirect.test.ts index c1c6c4ac0e96..4e4469188d0b 100644 --- a/src/plugins/discover/public/hooks/saved_search_alias_match_redirect.test.ts +++ b/src/plugins/discover/public/hooks/saved_search_alias_match_redirect.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { History } from 'history'; import { useSavedSearchAliasMatchRedirect } from './saved_search_alias_match_redirect'; diff --git a/src/plugins/discover/public/hooks/use_data_view.test.tsx b/src/plugins/discover/public/hooks/use_data_view.test.tsx index 5ef2194ab8db..9e8a1c8a1fd4 100644 --- a/src/plugins/discover/public/hooks/use_data_view.test.tsx +++ b/src/plugins/discover/public/hooks/use_data_view.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useDataView } from './use_data_view'; const adhocDataView = { @@ -38,11 +38,11 @@ const mockServices = { const render = async ({ dataViewId }: { dataViewId: string }) => { const hookResult = renderHook(() => useDataView({ index: dataViewId }), { - wrapper: ({ children }: React.PropsWithChildren<{}>) => ( + wrapper: ({ children }: React.PropsWithChildren) => ( {children} ), }); - await hookResult.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); return hookResult; }; diff --git a/src/plugins/discover/public/hooks/use_navigation_props.test.tsx b/src/plugins/discover/public/hooks/use_navigation_props.test.tsx index 36c86e01a9bb..16b02b44d434 100644 --- a/src/plugins/discover/public/hooks/use_navigation_props.test.tsx +++ b/src/plugins/discover/public/hooks/use_navigation_props.test.tsx @@ -8,7 +8,7 @@ */ import React, { MouseEvent } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useNavigationProps } from './use_navigation_props'; import type { DataView } from '@kbn/data-views-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -65,7 +65,7 @@ const render = async () => { ), } ); - await renderResult.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); return renderResult; }; diff --git a/src/plugins/discover_shared/kibana.jsonc b/src/plugins/discover_shared/kibana.jsonc index 88d67ab96bd6..84729ad8f5fd 100644 --- a/src/plugins/discover_shared/kibana.jsonc +++ b/src/plugins/discover_shared/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/discover-shared-plugin", "owner": ["@elastic/kibana-data-discovery", "@elastic/obs-ux-logs-team"], + "group": "platform", + "visibility": "shared", "description": "A stateful layer to register shared features and provide an access point to discover without a direct dependency", "plugin": { "id": "discoverShared", diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx index 6457f721ddd1..a7a6fab89973 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx @@ -9,8 +9,7 @@ import React from 'react'; import { waitFor } from '@testing-library/react'; -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, renderHook } from '@testing-library/react'; import { HelloWorldEmbeddable, HelloWorldEmbeddableFactoryDefinition, @@ -28,7 +27,7 @@ describe('useEmbeddableFactory', () => { ); doStart(); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useEmbeddableFactory({ factory: getFactory(), input: { id: 'hello' } }) ); @@ -36,10 +35,10 @@ describe('useEmbeddableFactory', () => { expect(loading).toBe(true); - await waitForNextUpdate(); - - const [embeddable] = result.current; - expect(embeddable).toBeDefined(); + await waitFor(() => { + const [embeddable] = result.current; + expect(embeddable).toBeDefined(); + }); }); }); diff --git a/src/plugins/embeddable/public/react_embeddable_system/phase_tracker.test.ts b/src/plugins/embeddable/public/react_embeddable_system/phase_tracker.test.ts new file mode 100644 index 000000000000..700c90f08ce5 --- /dev/null +++ b/src/plugins/embeddable/public/react_embeddable_system/phase_tracker.test.ts @@ -0,0 +1,100 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { BehaviorSubject, skip } from 'rxjs'; +import { PhaseTracker } from './phase_tracker'; + +describe('PhaseTracker', () => { + describe('api does not implement PublishesDataLoading or PublishesRendered', () => { + test(`should emit 'rendered' event`, (done) => { + const phaseTracker = new PhaseTracker(); + phaseTracker + .getPhase$() + .pipe(skip(1)) + .subscribe((phaseEvent) => { + expect(phaseEvent?.status).toBe('rendered'); + done(); + }); + phaseTracker.trackPhaseEvents('1', {}); + }); + }); + + describe('api implements PublishesDataLoading', () => { + test(`should emit 'loading' event when dataLoading is true`, (done) => { + const phaseTracker = new PhaseTracker(); + phaseTracker + .getPhase$() + .pipe(skip(1)) + .subscribe((phaseEvent) => { + expect(phaseEvent?.status).toBe('loading'); + done(); + }); + phaseTracker.trackPhaseEvents('1', { dataLoading: new BehaviorSubject(true) }); + }); + + test(`should emit 'rendered' event when dataLoading is false`, (done) => { + const phaseTracker = new PhaseTracker(); + phaseTracker + .getPhase$() + .pipe(skip(1)) + .subscribe((phaseEvent) => { + expect(phaseEvent?.status).toBe('rendered'); + done(); + }); + phaseTracker.trackPhaseEvents('1', { dataLoading: new BehaviorSubject(false) }); + }); + }); + + describe('api implements PublishesDataLoading and PublishesRendered', () => { + test(`should emit 'loading' event when dataLoading is true`, (done) => { + const phaseTracker = new PhaseTracker(); + phaseTracker + .getPhase$() + .pipe(skip(1)) + .subscribe((phaseEvent) => { + expect(phaseEvent?.status).toBe('loading'); + done(); + }); + phaseTracker.trackPhaseEvents('1', { + dataLoading: new BehaviorSubject(true), + rendered$: new BehaviorSubject(false), + }); + }); + + test(`should emit 'loading' event when dataLoading is false but rendered is false`, (done) => { + const phaseTracker = new PhaseTracker(); + phaseTracker + .getPhase$() + .pipe(skip(1)) + .subscribe((phaseEvent) => { + expect(phaseEvent?.status).toBe('loading'); + done(); + }); + phaseTracker.trackPhaseEvents('1', { + dataLoading: new BehaviorSubject(false), + rendered$: new BehaviorSubject(false), + }); + }); + + test(`should emit 'rendered' event only when rendered is true`, (done) => { + const phaseTracker = new PhaseTracker(); + phaseTracker + .getPhase$() + .pipe(skip(1)) + .subscribe((phaseEvent) => { + expect(phaseEvent?.status).toBe('rendered'); + done(); + }); + phaseTracker.trackPhaseEvents('1', { + dataLoading: new BehaviorSubject(false), + rendered$: new BehaviorSubject(true), + }); + }); + }); +}); diff --git a/src/plugins/embeddable/public/react_embeddable_system/phase_tracker.ts b/src/plugins/embeddable/public/react_embeddable_system/phase_tracker.ts new file mode 100644 index 000000000000..037599ab646c --- /dev/null +++ b/src/plugins/embeddable/public/react_embeddable_system/phase_tracker.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { + PhaseEvent, + apiPublishesDataLoading, + apiPublishesRendered, +} from '@kbn/presentation-publishing'; +import { BehaviorSubject, Subscription, combineLatest } from 'rxjs'; + +export class PhaseTracker { + private firstLoadCompleteTime: number | undefined; + private embeddableStartTime = performance.now(); + private subscriptions = new Subscription(); + private phase$ = new BehaviorSubject(undefined); + + getPhase$() { + return this.phase$; + } + + public trackPhaseEvents(uuid: string, api: unknown) { + const dataLoading$ = apiPublishesDataLoading(api) + ? api.dataLoading + : new BehaviorSubject(false); + const rendered$ = apiPublishesRendered(api) ? api.rendered$ : new BehaviorSubject(true); + + this.subscriptions.add( + combineLatest([dataLoading$, rendered$]).subscribe(([dataLoading, rendered]) => { + if (!this.firstLoadCompleteTime) { + this.firstLoadCompleteTime = performance.now(); + } + const duration = this.firstLoadCompleteTime - this.embeddableStartTime; + const status = dataLoading || !rendered ? 'loading' : 'rendered'; + this.phase$.next({ id: uuid, status, timeToEvent: duration }); + }) + ); + } + + public cleanup() { + this.subscriptions.unsubscribe(); + } +} diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx index c3dc06e198cd..edf52244c2d4 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx @@ -16,12 +16,7 @@ import { SerializedPanelState, } from '@kbn/presentation-containers'; import { PresentationPanel, PresentationPanelProps } from '@kbn/presentation-panel-plugin/public'; -import { - apiPublishesDataLoading, - ComparatorDefinition, - PhaseEvent, - StateComparators, -} from '@kbn/presentation-publishing'; +import { ComparatorDefinition, StateComparators } from '@kbn/presentation-publishing'; import React, { useEffect, useImperativeHandle, useMemo, useRef } from 'react'; import { BehaviorSubject, combineLatest, debounceTime, skip, Subscription, switchMap } from 'rxjs'; import { v4 as generateId } from 'uuid'; @@ -31,6 +26,7 @@ import { DefaultEmbeddableApi, SetReactEmbeddableApiRegistration, } from './types'; +import { PhaseTracker } from './phase_tracker'; const ON_STATE_CHANGE_DEBOUNCE = 100; @@ -69,6 +65,7 @@ export const ReactEmbeddableRenderer = < | 'hideLoader' | 'hideHeader' | 'hideInspector' + | 'getActions' >; hidePanelChrome?: boolean; /** @@ -78,25 +75,12 @@ export const ReactEmbeddableRenderer = < onAnyStateChange?: (state: SerializedPanelState) => void; }) => { const cleanupFunction = useRef<(() => void) | null>(null); - const firstLoadCompleteTime = useRef(null); + const phaseTracker = useRef(new PhaseTracker()); const componentPromise = useMemo( () => { const uuid = maybeId ?? generateId(); - /** - * Phase tracking instrumentation for telemetry - */ - const phase$ = new BehaviorSubject(undefined); - const embeddableStartTime = performance.now(); - const reportPhaseChange = (loading: boolean) => { - if (firstLoadCompleteTime.current === null) { - firstLoadCompleteTime.current = performance.now(); - } - const duration = firstLoadCompleteTime.current - embeddableStartTime; - phase$.next({ id: uuid, status: loading ? 'loading' : 'rendered', timeToEvent: duration }); - }; - /** * Build the embeddable promise */ @@ -126,7 +110,7 @@ export const ReactEmbeddableRenderer = < return { ...apiRegistration, uuid, - phase$, + phase$: phaseTracker.current.getPhase$(), parentApi, hasLockedHoverActions$, lockHoverActions: (lock: boolean) => { @@ -186,6 +170,7 @@ export const ReactEmbeddableRenderer = < cleanupFunction.current = () => { subscriptions.unsubscribe(); + phaseTracker.current.cleanup(); unsavedChanges.cleanup(); }; return fullApi as Api & HasSnapshottableState; @@ -200,13 +185,8 @@ export const ReactEmbeddableRenderer = < lastSavedRuntimeState ); - if (apiPublishesDataLoading(api)) { - subscriptions.add( - api.dataLoading.subscribe((loading) => reportPhaseChange(Boolean(loading))) - ); - } else { - reportPhaseChange(false); - } + phaseTracker.current.trackPhaseEvents(uuid, api); + return { api, Component }; }; diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx index 41f1fd16148f..cac179d45f94 100644 --- a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.test.tsx @@ -20,6 +20,7 @@ import { EmbeddableComponent, FieldBasedIndexPatternColumn, TypedLensByValueInput, + LensByValueInput, } from '@kbn/lens-plugin/public'; import { Datatable } from '@kbn/expressions-plugin/common'; import { render, screen, waitFor } from '@testing-library/react'; @@ -27,7 +28,6 @@ import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { I18nProvider } from '@kbn/i18n-react'; import { GroupPreview } from './group_preview'; -import { LensByValueInput } from '@kbn/lens-plugin/public/embeddable'; import { DATA_LAYER_ID, DATE_HISTOGRAM_COLUMN_ID, getCurrentTimeField } from './lens_attributes'; import { EuiSuperDatePickerTestHarness } from '@kbn/test-eui-helpers'; diff --git a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.tsx b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.tsx index 3f1c47f3a72b..5f03d6709233 100644 --- a/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.tsx +++ b/src/plugins/event_annotation_listing/public/components/group_editor_flyout/group_preview.tsx @@ -198,28 +198,25 @@ export const GroupPreview = ({ justifyContent="center" > -
div { height: 400px; width: 100%; } `} - > - - setChartTimeRange({ - from: new Date(range[0]).toISOString(), - to: new Date(range[1]).toISOString(), - }) - } - searchSessionId={searchSessionId} - /> -
+ data-test-subj="chart" + id="annotation-library-preview" + timeRange={chartTimeRange} + attributes={lensAttributes} + onBrushEnd={({ range }) => + setChartTimeRange({ + from: new Date(range[0]).toISOString(), + to: new Date(range[1]).toISOString(), + }) + } + searchSessionId={searchSessionId} + />
) : ( diff --git a/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.test.ts b/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.test.ts index ebbe6e412901..8f32edaac50a 100644 --- a/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.test.ts +++ b/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useDebouncedValue } from './use_debounced_value'; describe('useDebouncedValue', () => { diff --git a/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.test.ts b/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.test.ts index ac9ff31730f0..9dc3ab684ccb 100644 --- a/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.test.ts +++ b/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.test.ts @@ -8,7 +8,7 @@ */ import type { RefObject } from 'react'; -import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook, act, RenderHookResult } from '@testing-library/react'; import { Subject } from 'rxjs'; import type { IInterpreterRenderHandlers } from '../../common'; import { ExpressionRendererParams, useExpressionRenderer } from './use_expression_renderer'; @@ -23,7 +23,7 @@ describe('useExpressionRenderer', () => { loading$: Subject; render$: Subject; }; - let hook: RenderHookResult>; + let hook: RenderHookResult, ExpressionRendererParams>; beforeEach(() => { nodeRef = { current: document.createElement('div') }; diff --git a/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts b/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts index 2d5f5d6ddd49..06d588263869 100644 --- a/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts +++ b/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts @@ -26,7 +26,7 @@ export interface ExpressionRendererParams extends IExpressionLoaderParams { debounce?: number; expression: string | ExpressionAstExpression; hasCustomErrorRenderer?: boolean; - onData$?( + onData$?( data: TData, adapters?: TInspectorAdapters, partial?: boolean diff --git a/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.test.ts b/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.test.ts index acfa528c932b..9e073c86dbee 100644 --- a/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.test.ts +++ b/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useShallowMemo } from './use_shallow_memo'; describe('useShallowMemo', () => { diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 88d60b1a86b2..ad2dce80fb65 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -143,6 +143,7 @@ export const applicationUsageSchema = { enterpriseSearchSemanticSearch: commonSchema, enterpriseSearchVectorSearch: commonSchema, enterpriseSearchElasticsearch: commonSchema, + entity_manager: commonSchema, appSearch: commonSchema, workplaceSearch: commonSchema, searchExperiences: commonSchema, @@ -182,6 +183,7 @@ export const applicationUsageSchema = { */ siem: commonSchema, space_selector: commonSchema, + streams: commonSchema, uptime: commonSchema, synthetics: commonSchema, ux: commonSchema, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index c4202cf6f92e..0a3aeb2eaa1c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -62,10 +62,6 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'long', _meta: { description: 'Non-default value of setting.' }, }, - 'visualization:colorMapping': { - type: 'text', - _meta: { description: 'Non-default value of setting.' }, - }, 'visualization:useLegacyTimeAxis': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts index 9a9ae9b57f59..164b7a7e319b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts @@ -54,10 +54,15 @@ describe('telemetry_application_usage_collector', () => { test('fetch()', async () => { uiSettingsClient.getUserProvided.mockImplementationOnce(async () => ({ - 'visualization:colorMapping': { userValue: 'red' }, + 'timepicker:timeDefaults': { + userValue: { + from: 'now-7d', + to: 'now-6d', + }, + }, })); await expect(collector.fetch(mockedFetchContext)).resolves.toEqual({ - 'visualization:colorMapping': 'red', + 'timepicker:timeDefaults': { from: 'now-7d', to: 'now-6d' }, }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index e4b10f038a75..979643600735 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -58,7 +58,6 @@ export interface UsageStats { 'observability:aiAssistantSimulatedFunctionCalling': boolean; 'observability:aiAssistantSearchConnectorIndexPattern': string; 'visualization:heatmap:maxBuckets': number; - 'visualization:colorMapping': string; 'visualization:useLegacyTimeAxis': boolean; 'visualization:regionmap:showWarnings': boolean; 'visualization:tileMap:maxPrecision': number; diff --git a/src/plugins/navigation/public/mocks.ts b/src/plugins/navigation/public/mocks.tsx similarity index 54% rename from src/plugins/navigation/public/mocks.ts rename to src/plugins/navigation/public/mocks.tsx index b9977daf5622..5f9f1476b464 100644 --- a/src/plugins/navigation/public/mocks.ts +++ b/src/plugins/navigation/public/mocks.tsx @@ -6,13 +6,24 @@ * your election, the "Elastic License 2.0", the "GNU Affero General Public * License v3.0 only", or the "Server Side Public License, v 1". */ - +import React from 'react'; import { of } from 'rxjs'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { Plugin } from '.'; +import { createTopNav } from './top_nav_menu'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; +// mock mountPointPortal +jest.mock('@kbn/react-kibana-mount', () => { + const original = jest.requireActual('@kbn/react-kibana-mount'); + return { + ...original, + MountPointPortal: jest.fn(({ children }) => children), + }; +}); + const createSetupContract = (): jest.Mocked => { const setupContract = { registerMenuItem: jest.fn(), @@ -21,12 +32,21 @@ const createSetupContract = (): jest.Mocked => { return setupContract; }; +export const unifiedSearchMock = { + ui: { + SearchBar: () =>
, + AggregateQuerySearchBar: () =>
, + }, +} as unknown as UnifiedSearchPublicPluginStart; + const createStartContract = (): jest.Mocked => { const startContract = { ui: { - TopNavMenu: jest.fn(), - createTopNavWithCustomContext: jest.fn().mockImplementation(() => jest.fn()), - AggregateQueryTopNavMenu: jest.fn(), + TopNavMenu: jest.fn().mockImplementation(createTopNav(unifiedSearchMock, [])), + AggregateQueryTopNavMenu: jest.fn().mockImplementation(createTopNav(unifiedSearchMock, [])), + createTopNavWithCustomContext: jest + .fn() + .mockImplementation(createTopNav(unifiedSearchMock, [])), }, addSolutionNavigation: jest.fn(), isSolutionNavEnabled$: of(false), diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index dff09fa0bac3..5ad6e2bbe5dd 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -14,16 +14,9 @@ import { MountPoint } from '@kbn/core/public'; import { TopNavMenu } from './top_nav_menu'; import { TopNavMenuData } from './top_nav_menu_data'; import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { EuiToolTipProps } from '@elastic/eui'; import type { TopNavMenuBadgeProps } from './top_nav_menu_badges'; - -const unifiedSearch = { - ui: { - SearchBar: () =>
, - AggregateQuerySearchBar: () =>
, - }, -} as unknown as UnifiedSearchPublicPluginStart; +import { unifiedSearchMock } from '../mocks'; describe('TopNavMenu', () => { const WRAPPER_SELECTOR = '.kbnTopNavMenu__wrapper'; @@ -97,7 +90,7 @@ describe('TopNavMenu', () => { it('Should render search bar', () => { const component = mountWithIntl( - + ); expect(component.find(WRAPPER_SELECTOR).length).toBe(1); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); @@ -110,7 +103,7 @@ describe('TopNavMenu', () => { appName={'test'} config={menuItems} showSearchBar={true} - unifiedSearch={unifiedSearch} + unifiedSearch={unifiedSearchMock} /> ); expect(component.find(WRAPPER_SELECTOR).length).toBe(1); @@ -124,7 +117,7 @@ describe('TopNavMenu', () => { appName={'test'} config={menuItems} showSearchBar={true} - unifiedSearch={unifiedSearch} + unifiedSearch={unifiedSearchMock} className={'myCoolClass'} /> ); @@ -172,7 +165,7 @@ describe('TopNavMenu', () => { appName={'test'} config={menuItems} showSearchBar={true} - unifiedSearch={unifiedSearch} + unifiedSearch={unifiedSearchMock} setMenuMountPoint={setMountPoint} /> ); @@ -195,7 +188,7 @@ describe('TopNavMenu', () => { appName={'test'} badges={badges} showSearchBar={true} - unifiedSearch={unifiedSearch} + unifiedSearch={unifiedSearchMock} setMenuMountPoint={setMountPoint} /> ); diff --git a/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx b/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx index 298dd98d9a2b..3f06b154fdb5 100644 --- a/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx +++ b/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx @@ -8,8 +8,7 @@ */ import React, { useEffect, useState } from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, screen, fireEvent, renderHook } from '@testing-library/react'; import { usePresentationPanelTitleClickHandler } from './presentation_panel_title'; describe('usePresentationPanelTitleClickHandler', () => { diff --git a/src/plugins/share/public/components/context/index.test.tsx b/src/plugins/share/public/components/context/index.test.tsx new file mode 100644 index 000000000000..162518e505b1 --- /dev/null +++ b/src/plugins/share/public/components/context/index.test.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useShareTabsContext } from '.'; + +describe('share menu context', () => { + describe('useShareTabsContext', () => { + it('throws an error if used outside of ShareMenuProvider tree', () => { + const { result } = renderHook(() => useShareTabsContext()); + + expect(result.error?.message).toEqual( + expect.stringContaining( + 'Failed to call `useShareTabsContext` because the context from ShareMenuProvider is missing.' + ) + ); + }); + }); +}); diff --git a/src/plugins/share/public/components/context/index.tsx b/src/plugins/share/public/components/context/index.tsx index b75df40aaa41..13d6138e42a6 100644 --- a/src/plugins/share/public/components/context/index.tsx +++ b/src/plugins/share/public/components/context/index.tsx @@ -9,7 +9,7 @@ import { ThemeServiceSetup } from '@kbn/core-theme-browser'; import { I18nStart } from '@kbn/core/public'; -import { createContext, useContext } from 'react'; +import React, { type PropsWithChildren, createContext, useContext } from 'react'; import { AnonymousAccessServiceContract } from '../../../common'; import type { @@ -35,6 +35,23 @@ export interface IShareContext extends ShareContext { anchorElement?: HTMLElement; } -export const ShareTabsContext = createContext(null); +const ShareTabsContext = createContext(null); -export const useShareTabsContext = () => useContext(ShareTabsContext); +export const ShareMenuProvider = ({ + shareContext, + children, +}: PropsWithChildren<{ shareContext: IShareContext }>) => { + return {children}; +}; + +export const useShareTabsContext = () => { + const context = useContext(ShareTabsContext); + + if (!context) { + throw new Error( + 'Failed to call `useShareTabsContext` because the context from ShareMenuProvider is missing. Ensure the component or React root is wrapped with ShareMenuProvider' + ); + } + + return context; +}; diff --git a/src/plugins/share/public/components/share_tabs.test.tsx b/src/plugins/share/public/components/share_tabs.test.tsx index b4ad92fce84f..6b04a28304fd 100644 --- a/src/plugins/share/public/components/share_tabs.test.tsx +++ b/src/plugins/share/public/components/share_tabs.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { ShareMenuTabs } from './share_tabs'; -import { ShareTabsContext } from './context'; +import { ShareMenuProvider } from './context'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { KibanaLocation, LocatorGetUrlParams, UrlService } from '../../common/url_service'; import { @@ -77,9 +77,9 @@ describe('Share modal tabs', () => { }, ]; const wrapper = mountWithIntl( - + - + ); expect(wrapper.find('[data-test-subj="export"]').exists()).toBeTruthy(); }); @@ -92,11 +92,13 @@ describe('Share modal tabs', () => { generateExportUrl: mockGenerateExportUrl, }, ]; + const wrapper = mountWithIntl( - + - + ); + expect(wrapper.find('[data-test-subj="export"]').exists()).toBeFalsy(); }); @@ -116,9 +118,9 @@ describe('Share modal tabs', () => { }, ]; const wrapper = mountWithIntl( - + - + ); expect(wrapper.find('[data-test-subj="export"]').exists()).toBeTruthy(); }); diff --git a/src/plugins/share/public/components/share_tabs.tsx b/src/plugins/share/public/components/share_tabs.tsx index 0ed540a61200..94c4ab8655dc 100644 --- a/src/plugins/share/public/components/share_tabs.tsx +++ b/src/plugins/share/public/components/share_tabs.tsx @@ -8,16 +8,16 @@ */ import React, { type FC } from 'react'; -import { TabbedModal } from '@kbn/shared-ux-tabbed-modal'; +import { TabbedModal, type IModalTabDeclaration } from '@kbn/shared-ux-tabbed-modal'; -import { ShareTabsContext, useShareTabsContext, type IShareContext } from './context'; +import { ShareMenuProvider, useShareTabsContext, type IShareContext } from './context'; import { linkTab, embedTab, exportTab } from './tabs'; export const ShareMenu: FC<{ shareContext: IShareContext }> = ({ shareContext }) => { return ( - + - + ); }; @@ -25,15 +25,9 @@ export const ShareMenu: FC<{ shareContext: IShareContext }> = ({ shareContext }) export const ShareMenuTabs = () => { const shareContext = useShareTabsContext(); - if (!shareContext) { - return null; - } - const { allowEmbed, objectTypeMeta, onClose, shareMenuItems, anchorElement } = shareContext; - const tabs = []; - - tabs.push(linkTab); + const tabs: Array> = [linkTab]; const enabledItems = shareMenuItems.filter(({ shareMenuItem }) => !shareMenuItem?.disabled); diff --git a/src/plugins/share/public/components/tabs/link/link_content.test.tsx b/src/plugins/share/public/components/tabs/link/link_content.test.tsx new file mode 100644 index 000000000000..77fac4afc8ce --- /dev/null +++ b/src/plugins/share/public/components/tabs/link/link_content.test.tsx @@ -0,0 +1,191 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { type ComponentProps } from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import userEvent from '@testing-library/user-event'; +import { render, screen, waitFor } from '@testing-library/react'; + +import { urlServiceTestSetup } from '../../../../common/url_service/__tests__/setup'; +import { MockLocatorDefinition } from '../../../../common/url_service/mocks'; +import { BrowserShortUrlClientFactory } from '../../../url_service/short_urls/short_url_client_factory'; +import { + BrowserShortUrlClientHttp, + BrowserShortUrlClient, +} from '../../../url_service/short_urls/short_url_client'; +import { BrowserUrlService } from '../../../types'; +import { LinkContent } from './link_content'; + +const renderComponent = (props: ComponentProps) => { + render( + + + + ); +}; + +describe('LinkContent', () => { + const shareableUrl = 'http://localhost:5601/app/dashboards#/view/123'; + + const http: BrowserShortUrlClientHttp = { + basePath: { + get: () => '/xyz', + }, + fetch: jest.fn(async () => { + return {} as any; + }), + }; + + let urlService: BrowserUrlService; + + // @ts-expect-error there is a type because we override the shortUrls implementation + // eslint-disable-next-line prefer-const + ({ service: urlService } = urlServiceTestSetup({ + shortUrls: ({ locators }) => + new BrowserShortUrlClientFactory({ + http, + locators, + }), + })); + + beforeAll(() => { + Object.defineProperty(document, 'execCommand', { + value: jest.fn(() => true), + }); + }); + + it('uses the delegatedShareUrlHandler to generate the shareable URL when it is provided', async () => { + const user = userEvent.setup(); + const objectType = 'dashboard'; + const objectId = '123'; + const isDirty = false; + + const delegatedShareUrlHandler = jest.fn(); + + renderComponent({ + objectType, + objectId, + isDirty, + shareableUrl, + urlService, + allowShortUrl: true, + delegatedShareUrlHandler, + }); + + await user.click(screen.getByTestId('copyShareUrlButton')); + + expect(delegatedShareUrlHandler).toHaveBeenCalled(); + }); + + it('returns the shareable URL when the delegatedShareUrlHandler is not provided and shortURLs are not allowed', async () => { + const user = userEvent.setup(); + const objectType = 'dashboard'; + const objectId = '123'; + const isDirty = false; + + renderComponent({ + objectType, + objectId, + isDirty, + shareableUrl, + urlService, + allowShortUrl: false, + }); + + const copyButton = screen.getByTestId('copyShareUrlButton'); + + await user.click(copyButton); + + waitFor(() => { + expect(copyButton.getAttribute('data-share-url')).toBe(shareableUrl); + }); + }); + + it('invokes the createWithLocator method on the shortURL service if a locator is present when the copy button is clicked', async () => { + const user = userEvent.setup(); + const objectType = 'dashboard'; + const objectId = '123'; + const isDirty = false; + const shareableUrlLocatorParams = { + locator: new MockLocatorDefinition('TEST_LOCATOR'), + params: {}, + }; + + const shortURL = 'http://localhost:5601/xyz/r/s/yellow-orange-tomato'; + + const createWithLocatorSpy = jest.spyOn(BrowserShortUrlClient.prototype, 'createWithLocator'); + + createWithLocatorSpy.mockResolvedValue({ + // @ts-expect-error we only return locator property, as that's all we need for this test + locator: { + getUrl: jest.fn(() => Promise.resolve(shortURL)), + }, + }); + + renderComponent({ + objectType, + objectId, + isDirty, + shareableUrl, + urlService, + allowShortUrl: true, + // @ts-ignore this locator is passed mainly to test the code path that invokes createWithLocator + shareableUrlLocatorParams, + }); + + const copyButton = screen.getByTestId('copyShareUrlButton'); + + const numberOfClicks = 4; + + for (const _click of Array.from({ length: numberOfClicks })) { + await user.click(copyButton); + } + + // should only invoke once no matter how many times the button is clicked + expect(createWithLocatorSpy).toHaveBeenCalledTimes(1); + expect(copyButton.getAttribute('data-share-url')).toBe(shortURL); + }); + + it('invokes the createFromLongUrl method on the shortURL service if a locator is not present when the copy button is clicked', async () => { + const user = userEvent.setup(); + const objectType = 'dashboard'; + const objectId = '123'; + const isDirty = false; + + const shortURL = 'http://localhost:5601/xyz/r/s/yellow-orange-tomato'; + + const createFromLongUrlSpy = jest.spyOn(BrowserShortUrlClient.prototype, 'createFromLongUrl'); + + // @ts-expect-error we only return url property, as that's all we need for this test + createFromLongUrlSpy.mockResolvedValue({ + url: shortURL, + }); + + renderComponent({ + objectType, + objectId, + isDirty, + shareableUrl, + urlService, + allowShortUrl: true, + }); + + const copyButton = screen.getByTestId('copyShareUrlButton'); + + const numberOfClicks = 4; + + for (const _click of Array.from({ length: numberOfClicks })) { + await user.click(copyButton); + } + + // should only invoke once no matter how many times the button is clicked + expect(createFromLongUrlSpy).toHaveBeenCalledTimes(1); + expect(copyButton.getAttribute('data-share-url')).toBe(shortURL); + }); +}); diff --git a/src/plugins/share/public/components/tabs/link/link_content.tsx b/src/plugins/share/public/components/tabs/link/link_content.tsx index 0871599a524b..6c0d8e6e988e 100644 --- a/src/plugins/share/public/components/tabs/link/link_content.tsx +++ b/src/plugins/share/public/components/tabs/link/link_content.tsx @@ -20,8 +20,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useCallback, useMemo, useState } from 'react'; -import { IShareContext } from '../../context'; +import React, { useCallback, useState, useRef, useEffect } from 'react'; +import type { IShareContext } from '../../context'; type LinkProps = Pick< IShareContext, @@ -42,87 +42,80 @@ interface UrlParams { } export const LinkContent = ({ - objectType, isDirty, + objectType, shareableUrl, urlService, shareableUrlLocatorParams, allowShortUrl, delegatedShareUrlHandler, }: LinkProps) => { - const [url, setUrl] = useState(''); - const [urlParams] = useState(undefined); + const [snapshotUrl, setSnapshotUrl] = useState(''); const [isTextCopied, setTextCopied] = useState(false); - const [shortUrlCache, setShortUrlCache] = useState(undefined); const [isLoading, setIsLoading] = useState(false); + const urlParamsRef = useRef(undefined); + const urlToCopy = useRef(undefined); + const copiedTextToolTipCleanupIdRef = useRef>(); - const getUrlWithUpdatedParams = useCallback( - (tempUrl: string): string => { - const urlWithUpdatedParams = urlParams - ? Object.keys(urlParams).reduce((urlAccumulator, key) => { - const urlParam = urlParams[key]; - return urlParam - ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { - const isQueryParamEnabled = urlParam[queryParam]; - return isQueryParamEnabled - ? queryAccumulator + `&${queryParam}=true` - : queryAccumulator; - }, urlAccumulator) - : urlAccumulator; - }, tempUrl) - : tempUrl; + const getUrlWithUpdatedParams = useCallback((tempUrl: string): string => { + const urlWithUpdatedParams = urlParamsRef.current + ? Object.keys(urlParamsRef.current).reduce((urlAccumulator, key) => { + const urlParam = urlParamsRef.current?.[key]; + return urlParam + ? Object.keys(urlParam).reduce((queryAccumulator, queryParam) => { + const isQueryParamEnabled = urlParam[queryParam]; + return isQueryParamEnabled + ? queryAccumulator + `&${queryParam}=true` + : queryAccumulator; + }, urlAccumulator) + : urlAccumulator; + }, tempUrl) + : tempUrl; - // persist updated url to state - setUrl(urlWithUpdatedParams); - return urlWithUpdatedParams; - }, - [urlParams] - ); + return urlWithUpdatedParams; + }, []); - const getSnapshotUrl = useCallback(() => { - return getUrlWithUpdatedParams(shareableUrl || window.location.href); + useEffect(() => { + setSnapshotUrl(getUrlWithUpdatedParams(shareableUrl || window.location.href)); }, [getUrlWithUpdatedParams, shareableUrl]); const createShortUrl = useCallback(async () => { + const shortUrlService = urlService.shortUrls.get(null); + if (shareableUrlLocatorParams) { - const shortUrls = urlService.shortUrls.get(null); - const shortUrl = await shortUrls.createWithLocator(shareableUrlLocatorParams); - const urlWithLoc = await shortUrl.locator.getUrl(shortUrl.params, { absolute: true }); - setShortUrlCache(urlWithLoc); - return urlWithLoc; + const shortUrl = await shortUrlService.createWithLocator(shareableUrlLocatorParams); + return shortUrl.locator.getUrl(shortUrl.params, { absolute: true }); } else { - const snapshotUrl = getSnapshotUrl(); - const shortUrl = await urlService.shortUrls.get(null).createFromLongUrl(snapshotUrl); - setShortUrlCache(shortUrl.url); - - return shortUrl.url; + return (await shortUrlService.createFromLongUrl(snapshotUrl)).url; } - }, [shareableUrlLocatorParams, urlService.shortUrls, getSnapshotUrl, setShortUrlCache]); + }, [shareableUrlLocatorParams, urlService.shortUrls, snapshotUrl]); const copyUrlHelper = useCallback(async () => { setIsLoading(true); - let urlToCopy = url; - if (!urlToCopy || delegatedShareUrlHandler) { - urlToCopy = delegatedShareUrlHandler - ? delegatedShareUrlHandler?.() + if (!urlToCopy.current) { + urlToCopy.current = delegatedShareUrlHandler + ? delegatedShareUrlHandler() : allowShortUrl ? await createShortUrl() - : getSnapshotUrl(); + : snapshotUrl; } - copyToClipboard(urlToCopy); - setUrl(urlToCopy); - setTextCopied(true); + copyToClipboard(urlToCopy.current); + setTextCopied(() => { + if (copiedTextToolTipCleanupIdRef.current) { + clearInterval(copiedTextToolTipCleanupIdRef.current); + } + + // set up timer to revert copied state to false after specified duration + copiedTextToolTipCleanupIdRef.current = setTimeout(() => setTextCopied(false), 1000); + + // set copied state to true for now + return true; + }); setIsLoading(false); - return urlToCopy; - }, [url, delegatedShareUrlHandler, allowShortUrl, createShortUrl, getSnapshotUrl]); + }, [snapshotUrl, delegatedShareUrlHandler, allowShortUrl, createShortUrl]); - const handleTestUrl = useMemo(() => { - if (objectType !== 'search' || !allowShortUrl) return getSnapshotUrl(); - else if (objectType === 'search' && allowShortUrl) return shortUrlCache; - return copyUrlHelper(); - }, [objectType, getSnapshotUrl, allowShortUrl, shortUrlCache, copyUrlHelper]); return ( <> @@ -157,14 +150,14 @@ export const LinkContent = ({ (objectType === 'lens' && isDirty ? null : setTextCopied(false))} onClick={copyUrlHelper} color={objectType === 'lens' && isDirty ? 'warning' : 'primary'} diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 78f8b4f2f7b3..fe0f599dd6ca 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -3015,6 +3015,137 @@ } } }, + "entity_manager": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "appSearch": { "properties": { "appId": { @@ -7600,6 +7731,137 @@ } } }, + "streams": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "uptime": { "properties": { "appId": { @@ -9960,12 +10222,6 @@ "description": "Non-default value of setting." } }, - "visualization:colorMapping": { - "type": "text", - "_meta": { - "description": "Non-default value of setting." - } - }, "visualization:useLegacyTimeAxis": { "type": "boolean", "_meta": { diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/test_filters.test.ts b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/test_filters.test.ts index 6d0a8c5dfc96..7bcfb2d7aa07 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/test_filters.test.ts +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/test_filters.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { useTableFilters, diff --git a/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.test.tsx b/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.test.tsx index f86cd00f14ce..dac1f46e4b42 100644 --- a/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.test.tsx +++ b/src/plugins/unified_doc_viewer/public/hooks/use_es_doc_search.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { waitFor, renderHook, act } from '@testing-library/react'; import { type EsDocSearchProps, buildSearchBody, useEsDocSearch } from './use_es_doc_search'; import { Subject } from 'rxjs'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -281,13 +281,14 @@ describe('Test of helper / hook', () => { }, }); mockSearchResult.complete(); - await hook.waitForNextUpdate(); }); - expect(hook.result.current.slice(0, 2)).toEqual([ - ElasticRequestState.Found, - buildDataTableRecord(record), - ]); + await waitFor(() => + expect(hook.result.current.slice(0, 2)).toEqual([ + ElasticRequestState.Found, + buildDataTableRecord(record), + ]) + ); }); test('useEsDocSearch for text based languages', async () => { diff --git a/src/plugins/unified_histogram/public/__mocks__/lens_vis.ts b/src/plugins/unified_histogram/public/__mocks__/lens_vis.ts index b27b654a88f2..9b59403e569b 100644 --- a/src/plugins/unified_histogram/public/__mocks__/lens_vis.ts +++ b/src/plugins/unified_histogram/public/__mocks__/lens_vis.ts @@ -32,7 +32,7 @@ export const getLensVisMock = async ({ breakdownField, dataView, allSuggestions, - hasHistogramSuggestionForESQL, + isTransformationalESQL, table, }: { filters: QueryParams['filters']; @@ -44,7 +44,7 @@ export const getLensVisMock = async ({ timeRange?: TimeRange | null; breakdownField: DataViewField | undefined; allSuggestions?: Suggestion[]; - hasHistogramSuggestionForESQL?: boolean; + isTransformationalESQL?: boolean; table?: Datatable; }): Promise<{ lensService: LensVisService; @@ -60,7 +60,9 @@ export const getLensVisMock = async ({ if ('query' in context && context.query === query) { return allSuggestions; } - return hasHistogramSuggestionForESQL ? [histogramESQLSuggestionMock] : []; + return !isTransformationalESQL && dataView.isTimeBased() + ? [histogramESQLSuggestionMock] + : []; } : lensApi.suggestions, }); diff --git a/src/plugins/unified_histogram/public/chart/chart.test.tsx b/src/plugins/unified_histogram/public/chart/chart.test.tsx index ed00c05f6f17..e127e1a4ab41 100644 --- a/src/plugins/unified_histogram/public/chart/chart.test.tsx +++ b/src/plugins/unified_histogram/public/chart/chart.test.tsx @@ -44,7 +44,7 @@ async function mountComponent({ isPlainRecord, hasDashboardPermissions, isChartLoading, - hasHistogramSuggestionForESQL, + isTransformationalESQL, }: { customToggle?: ReactElement; noChart?: boolean; @@ -57,7 +57,7 @@ async function mountComponent({ isPlainRecord?: boolean; hasDashboardPermissions?: boolean; isChartLoading?: boolean; - hasHistogramSuggestionForESQL?: boolean; + isTransformationalESQL?: boolean; } = {}) { (searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation( jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: noHits ? 0 : 2 } } })) @@ -87,7 +87,9 @@ async function mountComponent({ const requestParams = { query: isPlainRecord - ? { esql: 'from logs | limit 10' } + ? isTransformationalESQL + ? { esql: 'from logs | limit 10 | stats var0 = avg(bytes) by extension' } + : { esql: 'from logs | limit 10' } : { language: 'kuery', query: '', @@ -108,7 +110,7 @@ async function mountComponent({ breakdownField: undefined, columns: [], allSuggestions, - hasHistogramSuggestionForESQL, + isTransformationalESQL, }) ).lensService; @@ -211,12 +213,111 @@ describe('Chart', () => { expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeTruthy(); }); - test('render when is text based and not timebased', async () => { - const component = await mountComponent({ isPlainRecord: true, dataView: dataViewMock }); + test('should render when is text based, transformational and non-time-based', async () => { + const component = await mountComponent({ + isPlainRecord: true, + dataView: dataViewMock, + isTransformationalESQL: true, + }); expect( component.find('[data-test-subj="unifiedHistogramToggleChartButton"]').exists() ).toBeTruthy(); expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeTruthy(); + expect( + component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() + ).toBeTruthy(); + expect( + component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists() + ).toBeTruthy(); + }); + + test('should not render when is text based, non-transformational and non-time-based', async () => { + const component = await mountComponent({ + isPlainRecord: true, + dataView: dataViewMock, + isTransformationalESQL: false, + }); + expect( + component.find('[data-test-subj="unifiedHistogramToggleChartButton"]').exists() + ).toBeTruthy(); + expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeFalsy(); + expect( + component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() + ).toBeFalsy(); + expect( + component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists() + ).toBeFalsy(); + }); + + test('should not render when is text based, non-transformational, non-time-based and suggestions are available', async () => { + const component = await mountComponent({ + allSuggestions: allSuggestionsMock, + isPlainRecord: true, + dataView: dataViewMock, + isTransformationalESQL: false, + }); + expect( + component.find('[data-test-subj="unifiedHistogramToggleChartButton"]').exists() + ).toBeTruthy(); + expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeFalsy(); + expect( + component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() + ).toBeFalsy(); + expect( + component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists() + ).toBeFalsy(); + }); + + test('should render when is text based, non-transformational and time-based', async () => { + const component = await mountComponent({ + isPlainRecord: true, + isTransformationalESQL: false, + }); + expect( + component.find('[data-test-subj="unifiedHistogramToggleChartButton"]').exists() + ).toBeTruthy(); + expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeTruthy(); + expect( + component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() + ).toBeTruthy(); + expect( + component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists() + ).toBeTruthy(); + }); + + test('should render when is text based, transformational and time-based', async () => { + const component = await mountComponent({ + isPlainRecord: true, + isTransformationalESQL: true, + }); + expect( + component.find('[data-test-subj="unifiedHistogramToggleChartButton"]').exists() + ).toBeTruthy(); + expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeTruthy(); + expect( + component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() + ).toBeTruthy(); + expect( + component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists() + ).toBeTruthy(); + }); + + test('should not render when is text based, transformational and no suggestions available', async () => { + const component = await mountComponent({ + allSuggestions: [], + isPlainRecord: true, + isTransformationalESQL: true, + }); + expect( + component.find('[data-test-subj="unifiedHistogramToggleChartButton"]').exists() + ).toBeTruthy(); + expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeFalsy(); + expect( + component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() + ).toBeFalsy(); + expect( + component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists() + ).toBeFalsy(); }); test('render progress bar when text based and request is loading', async () => { @@ -267,35 +368,17 @@ describe('Chart', () => { expect(component.find(BreakdownFieldSelector).exists()).toBeFalsy(); }); - it('should render the edit on the fly button when chart is visible and suggestions exist', async () => { - const component = await mountComponent({ - allSuggestions: allSuggestionsMock, - isPlainRecord: true, - }); - expect( - component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() - ).toBeTruthy(); - }); - - it('should not render the edit on the fly button when chart is visible and suggestions dont exist', async () => { + it('should not render the save button when text-based and the dashboard save by value permissions are false', async () => { const component = await mountComponent({ allSuggestions: [], - hasHistogramSuggestionForESQL: false, - isPlainRecord: true, - }); - expect( - component.find('[data-test-subj="unifiedHistogramEditFlyoutVisualization"]').exists() - ).toBeFalsy(); - }); - - it('should render the save button when chart is visible and suggestions exist', async () => { - const component = await mountComponent({ - allSuggestions: allSuggestionsMock, + isTransformationalESQL: false, isPlainRecord: true, + hasDashboardPermissions: false, }); + expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeTruthy(); expect( component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists() - ).toBeTruthy(); + ).toBeFalsy(); }); it('should not render the save button when the dashboard save by value permissions are false', async () => { @@ -303,6 +386,7 @@ describe('Chart', () => { allSuggestions: allSuggestionsMock, hasDashboardPermissions: false, }); + expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeTruthy(); expect( component.find('[data-test-subj="unifiedHistogramSaveVisualization"]').exists() ).toBeFalsy(); diff --git a/src/plugins/unified_histogram/public/chart/chart.tsx b/src/plugins/unified_histogram/public/chart/chart.tsx index 4fb1b9cbe647..164d1eb539e3 100644 --- a/src/plugins/unified_histogram/public/chart/chart.tsx +++ b/src/plugins/unified_histogram/public/chart/chart.tsx @@ -8,7 +8,6 @@ */ import React, { memo, ReactElement, useCallback, useEffect, useMemo, useState } from 'react'; -import type { Observable } from 'rxjs'; import { Subject } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; import { IconButtonGroup, type IconButtonGroupProps } from '@kbn/shared-ux-button-toolbar'; @@ -70,7 +69,7 @@ export interface ChartProps { disabledActions?: LensEmbeddableInput['disabledActions']; input$?: UnifiedHistogramInput$; lensAdapters?: UnifiedHistogramChartLoadEvent['adapters']; - lensEmbeddableOutput$?: Observable; + dataLoading$?: LensEmbeddableOutput['dataLoading']; isChartLoading?: boolean; onChartHiddenChange?: (chartHidden: boolean) => void; onTimeIntervalChange?: (timeInterval: string) => void; @@ -105,7 +104,7 @@ export function Chart({ disabledActions, input$: originalInput$, lensAdapters, - lensEmbeddableOutput$, + dataLoading$, isChartLoading, onChartHiddenChange, onTimeIntervalChange, @@ -383,9 +382,7 @@ export function Chart({ )} {canSaveVisualization && isSaveModalVisible && visContext.attributes && ( {}} onClose={() => setIsSaveModalVisible(false)} isSaveable={false} @@ -393,18 +390,16 @@ export function Chart({ )} {isFlyoutVisible && !!visContext && !!lensVisServiceCurrentSuggestionContext && ( )} diff --git a/src/plugins/unified_histogram/public/chart/chart_config_panel.tsx b/src/plugins/unified_histogram/public/chart/chart_config_panel.tsx index f2d080fcf0e6..edcd831d3f7a 100644 --- a/src/plugins/unified_histogram/public/chart/chart_config_panel.tsx +++ b/src/plugins/unified_histogram/public/chart/chart_config_panel.tsx @@ -8,7 +8,6 @@ */ import React, { ComponentProps, useCallback, useEffect, useRef, useState } from 'react'; -import type { Observable } from 'rxjs'; import type { AggregateQuery, Query } from '@kbn/es-query'; import { isEqual, isObject } from 'lodash'; import type { LensEmbeddableOutput, Suggestion } from '@kbn/lens-plugin/public'; @@ -29,7 +28,7 @@ export function ChartConfigPanel({ services, visContext, lensAdapters, - lensEmbeddableOutput$, + dataLoading$, currentSuggestionContext, isFlyoutVisible, setIsFlyoutVisible, @@ -42,7 +41,7 @@ export function ChartConfigPanel({ isFlyoutVisible: boolean; setIsFlyoutVisible: (flag: boolean) => void; lensAdapters?: UnifiedHistogramChartLoadEvent['adapters']; - lensEmbeddableOutput$?: Observable; + dataLoading$?: LensEmbeddableOutput['dataLoading']; currentSuggestionContext: UnifiedHistogramSuggestionContext; isPlainRecord?: boolean; query?: Query | AggregateQuery; @@ -108,7 +107,7 @@ export function ChartConfigPanel({ updateSuggestion={updateSuggestion} updatePanelState={updatePanelState} lensAdapters={lensAdapters} - output$={lensEmbeddableOutput$} + dataLoading$={dataLoading$} displayFlyoutHeader closeFlyout={() => { setIsFlyoutVisible(false); @@ -141,7 +140,7 @@ export function ChartConfigPanel({ isFlyoutVisible, setIsFlyoutVisible, lensAdapters, - lensEmbeddableOutput$, + dataLoading$, currentSuggestionType, ]); diff --git a/src/plugins/unified_histogram/public/chart/histogram.test.tsx b/src/plugins/unified_histogram/public/chart/histogram.test.tsx index 72b5c0cc0b79..7bef5d4f8555 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.test.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.test.tsx @@ -10,7 +10,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { Histogram } from './histogram'; import React from 'react'; -import { of, Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { unifiedHistogramServicesMock } from '../__mocks__/services'; import { getLensVisMock } from '../__mocks__/lens_vis'; import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; @@ -101,7 +101,7 @@ describe('Histogram', () => { searchSessionId: props.request.searchSessionId, getTimeRange: props.getTimeRange, attributes: (await getMockLensAttributes())!.attributes, - onLoad: lensProps.onLoad, + onLoad: lensProps.onLoad!, }); expect(lensProps).toMatchObject(expect.objectContaining(originalProps)); component.setProps({ request: { ...props.request, searchSessionId: '321' } }).update(); @@ -120,7 +120,7 @@ describe('Histogram', () => { it('should execute onLoad correctly', async () => { const { component, props } = await mountComponent(); const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad; + const onLoad = component.find(embeddable).props().onLoad!; const adapters = createDefaultInspectorAdapters(); adapters.tables.tables.unifiedHistogram = { meta: { statistics: { totalCount: 100 } } } as any; const rawResponse = { @@ -172,25 +172,25 @@ describe('Histogram', () => { jest .spyOn(adapters.requests, 'getRequests') .mockReturnValue([{ response: { json: { rawResponse } } } as any]); - const embeddableOutput$ = jest.fn().mockReturnValue(of('output$')); - onLoad(true, undefined, embeddableOutput$); + const dataLoading$ = new BehaviorSubject(false); + onLoad(true, undefined, dataLoading$); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( UnifiedHistogramFetchStatus.loading, undefined ); - expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters: {}, embeddableOutput$ }); + expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters: {}, dataLoading$ }); expect(buildBucketInterval.buildBucketInterval).not.toHaveBeenCalled(); expect(useTimeRange.useTimeRange).toHaveBeenLastCalledWith( expect.objectContaining({ bucketInterval: undefined }) ); act(() => { - onLoad(false, adapters, embeddableOutput$); + onLoad?.(false, adapters, dataLoading$); }); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( UnifiedHistogramFetchStatus.complete, 100 ); - expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters, embeddableOutput$ }); + expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters, dataLoading$ }); expect(buildBucketInterval.buildBucketInterval).toHaveBeenCalled(); expect(useTimeRange.useTimeRange).toHaveBeenLastCalledWith( expect.objectContaining({ bucketInterval: mockBucketInterval }) @@ -200,12 +200,12 @@ describe('Histogram', () => { it('should execute onLoad correctly when the request has a failure status', async () => { const { component, props } = await mountComponent(); const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad; + const onLoad = component.find(embeddable).props().onLoad!; const adapters = createDefaultInspectorAdapters(); jest .spyOn(adapters.requests, 'getRequests') .mockReturnValue([{ status: RequestStatus.ERROR } as any]); - onLoad(false, adapters); + onLoad?.(false, adapters); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( UnifiedHistogramFetchStatus.error, undefined @@ -216,7 +216,7 @@ describe('Histogram', () => { it('should execute onLoad correctly when the response has shard failures', async () => { const { component, props } = await mountComponent(); const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad; + const onLoad = component.find(embeddable).props().onLoad!; const adapters = createDefaultInspectorAdapters(); adapters.tables.tables.unifiedHistogram = { meta: { statistics: { totalCount: 100 } } } as any; const rawResponse = { @@ -237,7 +237,7 @@ describe('Histogram', () => { .spyOn(adapters.requests, 'getRequests') .mockReturnValue([{ response: { json: { rawResponse } } } as any]); act(() => { - onLoad(false, adapters); + onLoad?.(false, adapters); }); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( UnifiedHistogramFetchStatus.error, @@ -249,7 +249,7 @@ describe('Histogram', () => { it('should execute onLoad correctly for textbased language and no Lens suggestions', async () => { const { component, props } = await mountComponent(true, false); const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad; + const onLoad = component.find(embeddable).props().onLoad!; const adapters = createDefaultInspectorAdapters(); adapters.tables.tables.layerId = { meta: { type: 'es_ql' }, @@ -273,7 +273,7 @@ describe('Histogram', () => { ], } as any; act(() => { - onLoad(false, adapters); + onLoad?.(false, adapters); }); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( UnifiedHistogramFetchStatus.complete, @@ -285,7 +285,7 @@ describe('Histogram', () => { it('should execute onLoad correctly for textbased language and Lens suggestions', async () => { const { component, props } = await mountComponent(true, true); const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad; + const onLoad = component.find(embeddable).props().onLoad!; const adapters = createDefaultInspectorAdapters(); adapters.tables.tables.layerId = { meta: { type: 'es_ql' }, @@ -309,7 +309,7 @@ describe('Histogram', () => { ], } as any; act(() => { - onLoad(false, adapters); + onLoad?.(false, adapters); }); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( UnifiedHistogramFetchStatus.complete, diff --git a/src/plugins/unified_histogram/public/chart/histogram.tsx b/src/plugins/unified_histogram/public/chart/histogram.tsx index e63cf775158a..8e3aa78da8d9 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.tsx @@ -15,13 +15,10 @@ import type { DefaultInspectorAdapters, Datatable } from '@kbn/expressions-plugi import type { IKibanaSearchResponse } from '@kbn/search-types'; import type { estypes } from '@elastic/elasticsearch'; import type { TimeRange } from '@kbn/es-query'; -import type { - EmbeddableComponentProps, - LensEmbeddableInput, - LensEmbeddableOutput, -} from '@kbn/lens-plugin/public'; +import type { EmbeddableComponentProps, LensEmbeddableInput } from '@kbn/lens-plugin/public'; import { RequestStatus } from '@kbn/inspector-plugin/public'; import type { Observable } from 'rxjs'; +import { PublishingSubject } from '@kbn/presentation-publishing'; import { UnifiedHistogramBucketInterval, UnifiedHistogramChartContext, @@ -121,7 +118,7 @@ export function Histogram({ ( isLoading: boolean, adapters: Partial | undefined, - lensEmbeddableOutput$?: Observable + dataLoading$?: PublishingSubject ) => { const lensRequest = adapters?.requests?.getRequests()[0]; const requestFailed = lensRequest?.status === RequestStatus.ERROR; @@ -160,7 +157,7 @@ export function Histogram({ setBucketInterval(newBucketInterval); } - onChartLoad?.({ adapters: adapters ?? {}, embeddableOutput$: lensEmbeddableOutput$ }); + onChartLoad?.({ adapters: adapters ?? {}, dataLoading$ }); } ); diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_chart_actions.test.ts b/src/plugins/unified_histogram/public/chart/hooks/use_chart_actions.test.ts index ab8acc7b694f..94c7c88005f7 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_chart_actions.test.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_chart_actions.test.ts @@ -7,8 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; -import { act } from 'react-test-renderer'; +import { act, renderHook } from '@testing-library/react'; import { UnifiedHistogramChartContext } from '../../types'; import { useChartActions } from './use_chart_actions'; diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts b/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts index e64b41e40e54..626950e53561 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts @@ -9,9 +9,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; -import { act } from 'react-test-renderer'; -import { setTimeout } from 'timers/promises'; +import { waitFor, renderHook } from '@testing-library/react'; import { dataViewMock } from '../../__mocks__/data_view'; import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; import { unifiedHistogramServicesMock } from '../../__mocks__/services'; @@ -44,8 +42,7 @@ describe('useEditVisualization', () => { lensAttributes, }) ); - await act(() => setTimeout(0)); - expect(hook.result.current).toBeDefined(); + await waitFor(() => expect(hook.result.current).toBeDefined()); hook.result.current!(); expect(navigateToPrefilledEditor).toHaveBeenCalledWith({ id: '', @@ -64,8 +61,7 @@ describe('useEditVisualization', () => { lensAttributes: {} as unknown as TypedLensByValueInput['attributes'], }) ); - await act(() => setTimeout(0)); - expect(hook.result.current).toBeUndefined(); + await waitFor(() => expect(hook.result.current).toBeUndefined()); }); it('should return undefined if the data view is not time based', async () => { @@ -78,8 +74,7 @@ describe('useEditVisualization', () => { lensAttributes: {} as unknown as TypedLensByValueInput['attributes'], }) ); - await act(() => setTimeout(0)); - expect(hook.result.current).toBeUndefined(); + await waitFor(() => expect(hook.result.current).toBeUndefined()); }); it('should return undefined if is on text based mode', async () => { @@ -93,8 +88,7 @@ describe('useEditVisualization', () => { isPlainRecord: true, }) ); - await act(() => setTimeout(0)); - expect(hook.result.current).toBeUndefined(); + await waitFor(() => expect(hook.result.current).toBeUndefined()); }); it('should return undefined if the time field is not visualizable', async () => { @@ -113,8 +107,7 @@ describe('useEditVisualization', () => { lensAttributes: {} as unknown as TypedLensByValueInput['attributes'], }) ); - await act(() => setTimeout(0)); - expect(hook.result.current).toBeUndefined(); + await waitFor(() => expect(hook.result.current).toBeUndefined()); }); it('should return undefined if there are no compatible actions', async () => { @@ -127,7 +120,6 @@ describe('useEditVisualization', () => { lensAttributes: {} as unknown as TypedLensByValueInput['attributes'], }) ); - await act(() => setTimeout(0)); - expect(hook.result.current).toBeUndefined(); + await waitFor(() => expect(hook.result.current).toBeUndefined()); }); }); diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_lens_props.test.ts b/src/plugins/unified_histogram/public/chart/hooks/use_lens_props.test.ts index 9d1522398b01..d66856bdb4f0 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_lens_props.test.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_lens_props.test.ts @@ -7,8 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; -import { act } from 'react-test-renderer'; +import { act, renderHook } from '@testing-library/react'; import { Subject } from 'rxjs'; import type { UnifiedHistogramInputMessage } from '../../types'; import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_refetch.test.ts b/src/plugins/unified_histogram/public/chart/hooks/use_refetch.test.ts index 3695e0833761..cf8590009062 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_refetch.test.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_refetch.test.ts @@ -10,7 +10,7 @@ import { useRefetch } from './use_refetch'; import { DataView } from '@kbn/data-views-plugin/common'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { UnifiedHistogramBreakdownContext, UnifiedHistogramChartContext, diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_time_range.test.tsx b/src/plugins/unified_histogram/public/chart/hooks/use_time_range.test.tsx index 0daa6ca6be06..7341dbe451c8 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_time_range.test.tsx +++ b/src/plugins/unified_histogram/public/chart/hooks/use_time_range.test.tsx @@ -9,7 +9,7 @@ import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; import { TimeRange } from '@kbn/data-plugin/common'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { UnifiedHistogramBucketInterval } from '../../types'; import { useTimeRange } from './use_time_range'; diff --git a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.test.ts b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.test.ts index 1a6146e6a4c3..4ba57d490db6 100644 --- a/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.test.ts +++ b/src/plugins/unified_histogram/public/chart/hooks/use_total_hits.test.ts @@ -12,11 +12,10 @@ import { UnifiedHistogramFetchStatus, UnifiedHistogramInput$ } from '../../types import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; import { useTotalHits } from './use_total_hits'; import { useEffect as mockUseEffect } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks'; import { of, Subject, throwError } from 'rxjs'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { DataViewType, SearchSourceSearchOptions } from '@kbn/data-plugin/common'; import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; diff --git a/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts b/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts index 44a36be34d1a..7a2d36da4b1f 100644 --- a/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts +++ b/src/plugins/unified_histogram/public/container/hooks/use_state_props.test.ts @@ -9,8 +9,7 @@ import { DataView, DataViewField, DataViewType } from '@kbn/data-views-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; -import { renderHook } from '@testing-library/react-hooks'; -import { act } from 'react-test-renderer'; +import { waitFor, renderHook, act } from '@testing-library/react'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils'; import { UnifiedHistogramFetchStatus, UnifiedHistogramSuggestionContext } from '../../types'; @@ -81,6 +80,7 @@ describe('useStateProps', () => { "hidden": false, "timeInterval": "auto", }, + "dataLoading$": undefined, "hits": Object { "status": "uninitialized", "total": undefined, @@ -121,7 +121,6 @@ describe('useStateProps', () => { }, }, }, - "lensEmbeddableOutput$": undefined, "onBreakdownFieldChange": [Function], "onChartHiddenChange": [Function], "onChartLoad": [Function], @@ -165,6 +164,7 @@ describe('useStateProps', () => { "hidden": false, "timeInterval": "auto", }, + "dataLoading$": undefined, "hits": Object { "status": "uninitialized", "total": undefined, @@ -205,7 +205,6 @@ describe('useStateProps', () => { }, }, }, - "lensEmbeddableOutput$": undefined, "onBreakdownFieldChange": [Function], "onChartHiddenChange": [Function], "onChartLoad": [Function], @@ -349,6 +348,7 @@ describe('useStateProps', () => { Object { "breakdown": undefined, "chart": undefined, + "dataLoading$": undefined, "hits": Object { "status": "uninitialized", "total": undefined, @@ -389,7 +389,6 @@ describe('useStateProps', () => { }, }, }, - "lensEmbeddableOutput$": undefined, "onBreakdownFieldChange": [Function], "onChartHiddenChange": [Function], "onChartLoad": [Function], @@ -428,6 +427,7 @@ describe('useStateProps', () => { Object { "breakdown": undefined, "chart": undefined, + "dataLoading$": undefined, "hits": Object { "status": "uninitialized", "total": undefined, @@ -468,7 +468,6 @@ describe('useStateProps', () => { }, }, }, - "lensEmbeddableOutput$": undefined, "onBreakdownFieldChange": [Function], "onChartHiddenChange": [Function], "onChartLoad": [Function], @@ -491,7 +490,7 @@ describe('useStateProps', () => { `); }); - it('should execute callbacks correctly', () => { + it('should execute callbacks correctly', async () => { const stateService = getStateService({ initialState }); const { result } = renderHook(() => useStateProps({ @@ -503,6 +502,21 @@ describe('useStateProps', () => { columns: undefined, }) ); + + await waitFor(() => + expect(result.current).toEqual( + expect.objectContaining({ + onTopPanelHeightChange: expect.any(Function), + onTimeIntervalChange: expect.any(Function), + onTotalHitsChange: expect.any(Function), + onChartHiddenChange: expect.any(Function), + onChartLoad: expect.any(Function), + onBreakdownFieldChange: expect.any(Function), + onSuggestionContextChange: expect.any(Function), + }) + ) + ); + const { onTopPanelHeightChange, onTimeIntervalChange, diff --git a/src/plugins/unified_histogram/public/container/hooks/use_state_props.ts b/src/plugins/unified_histogram/public/container/hooks/use_state_props.ts index fcc19fcd78a0..660e47f33cf0 100644 --- a/src/plugins/unified_histogram/public/container/hooks/use_state_props.ts +++ b/src/plugins/unified_histogram/public/container/hooks/use_state_props.ts @@ -27,7 +27,7 @@ import { totalHitsResultSelector, totalHitsStatusSelector, lensAdaptersSelector, - lensEmbeddableOutputSelector$, + lensDataLoadingSelector$, } from '../utils/state_selectors'; import { useStateSelector } from '../utils/use_state_selector'; @@ -52,10 +52,7 @@ export const useStateProps = ({ const totalHitsResult = useStateSelector(stateService?.state$, totalHitsResultSelector); const totalHitsStatus = useStateSelector(stateService?.state$, totalHitsStatusSelector); const lensAdapters = useStateSelector(stateService?.state$, lensAdaptersSelector); - const lensEmbeddableOutput$ = useStateSelector( - stateService?.state$, - lensEmbeddableOutputSelector$ - ); + const lensDataLoading$ = useStateSelector(stateService?.state$, lensDataLoadingSelector$); /** * Contexts */ @@ -162,7 +159,7 @@ export const useStateProps = ({ // We need to store the Lens request adapter in order to inspect its requests stateService?.setLensRequestAdapter(event.adapters.requests); stateService?.setLensAdapters(event.adapters); - stateService?.setLensEmbeddableOutput$(event.embeddableOutput$); + stateService?.setLensDataLoading$(event.dataLoading$); }, [stateService] ); @@ -199,7 +196,7 @@ export const useStateProps = ({ request, isPlainRecord, lensAdapters, - lensEmbeddableOutput$, + dataLoading$: lensDataLoading$, onTopPanelHeightChange, onTimeIntervalChange, onTotalHitsChange, diff --git a/src/plugins/unified_histogram/public/container/services/state_service.test.ts b/src/plugins/unified_histogram/public/container/services/state_service.test.ts index dcce90037ec9..66f0549e9571 100644 --- a/src/plugins/unified_histogram/public/container/services/state_service.test.ts +++ b/src/plugins/unified_histogram/public/container/services/state_service.test.ts @@ -139,8 +139,8 @@ describe('UnifiedHistogramStateService', () => { stateService.setLensAdapters(undefined); newState = { ...newState, lensAdapters: undefined }; expect(state).toEqual(newState); - stateService.setLensEmbeddableOutput$(undefined); - newState = { ...newState, lensEmbeddableOutput$: undefined }; + stateService.setLensDataLoading$(undefined); + newState = { ...newState, dataLoading$: undefined }; expect(state).toEqual(newState); stateService.setTotalHits({ totalHitsStatus: UnifiedHistogramFetchStatus.complete, diff --git a/src/plugins/unified_histogram/public/container/services/state_service.ts b/src/plugins/unified_histogram/public/container/services/state_service.ts index 551773cfe189..c3cf82bf9457 100644 --- a/src/plugins/unified_histogram/public/container/services/state_service.ts +++ b/src/plugins/unified_histogram/public/container/services/state_service.ts @@ -8,8 +8,8 @@ */ import type { RequestAdapter } from '@kbn/inspector-plugin/common'; -import type { LensEmbeddableOutput } from '@kbn/lens-plugin/public'; import { BehaviorSubject, Observable } from 'rxjs'; +import { PublishingSubject } from '@kbn/presentation-publishing'; import { UnifiedHistogramFetchStatus } from '../..'; import type { UnifiedHistogramServices, UnifiedHistogramChartLoadEvent } from '../../types'; import { @@ -49,7 +49,7 @@ export interface UnifiedHistogramState { /** * Lens embeddable output observable */ - lensEmbeddableOutput$?: Observable; + dataLoading$?: PublishingSubject; /** * The current time interval of the chart */ @@ -124,9 +124,7 @@ export interface UnifiedHistogramStateService { * Sets the current Lens adapters */ setLensAdapters: (lensAdapters: UnifiedHistogramChartLoadEvent['adapters'] | undefined) => void; - setLensEmbeddableOutput$: ( - lensEmbeddableOutput$: Observable | undefined - ) => void; + setLensDataLoading$: (dataLoading$: PublishingSubject | undefined) => void; /** * Sets the current total hits status and result */ @@ -214,10 +212,8 @@ export const createStateService = ( setLensAdapters: (lensAdapters: UnifiedHistogramChartLoadEvent['adapters'] | undefined) => { updateState({ lensAdapters }); }, - setLensEmbeddableOutput$: ( - lensEmbeddableOutput$: Observable | undefined - ) => { - updateState({ lensEmbeddableOutput$ }); + setLensDataLoading$: (dataLoading$: PublishingSubject | undefined) => { + updateState({ dataLoading$ }); }, setTotalHits: (totalHits: { diff --git a/src/plugins/unified_histogram/public/container/utils/state_selectors.ts b/src/plugins/unified_histogram/public/container/utils/state_selectors.ts index 6eacbaaef950..9274c4fabd30 100644 --- a/src/plugins/unified_histogram/public/container/utils/state_selectors.ts +++ b/src/plugins/unified_histogram/public/container/utils/state_selectors.ts @@ -16,5 +16,4 @@ export const topPanelHeightSelector = (state: UnifiedHistogramState) => state.to export const totalHitsResultSelector = (state: UnifiedHistogramState) => state.totalHitsResult; export const totalHitsStatusSelector = (state: UnifiedHistogramState) => state.totalHitsStatus; export const lensAdaptersSelector = (state: UnifiedHistogramState) => state.lensAdapters; -export const lensEmbeddableOutputSelector$ = (state: UnifiedHistogramState) => - state.lensEmbeddableOutput$; +export const lensDataLoadingSelector$ = (state: UnifiedHistogramState) => state.dataLoading$; diff --git a/src/plugins/unified_histogram/public/hooks/use_request_params.test.ts b/src/plugins/unified_histogram/public/hooks/use_request_params.test.ts index 33df774f20a3..44624252041f 100644 --- a/src/plugins/unified_histogram/public/hooks/use_request_params.test.ts +++ b/src/plugins/unified_histogram/public/hooks/use_request_params.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { unifiedHistogramServicesMock } from '../__mocks__/services'; const getUseRequestParams = async () => { diff --git a/src/plugins/unified_histogram/public/hooks/use_stable_callback.test.ts b/src/plugins/unified_histogram/public/hooks/use_stable_callback.test.ts index 7e13f153bc62..db498930c98f 100644 --- a/src/plugins/unified_histogram/public/hooks/use_stable_callback.test.ts +++ b/src/plugins/unified_histogram/public/hooks/use_stable_callback.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useStableCallback } from './use_stable_callback'; describe('useStableCallback', () => { diff --git a/src/plugins/unified_histogram/public/layout/layout.tsx b/src/plugins/unified_histogram/public/layout/layout.tsx index 3e34cf4ee69b..b9d9f6fbc446 100644 --- a/src/plugins/unified_histogram/public/layout/layout.tsx +++ b/src/plugins/unified_histogram/public/layout/layout.tsx @@ -9,7 +9,6 @@ import { EuiSpacer, useEuiTheme, useIsWithinBreakpoints } from '@elastic/eui'; import React, { PropsWithChildren, ReactElement, useEffect, useMemo, useState } from 'react'; -import { Observable } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; import { css } from '@emotion/css'; @@ -99,7 +98,7 @@ export interface UnifiedHistogramLayoutProps extends PropsWithChildren */ hits?: UnifiedHistogramHitsContext; lensAdapters?: UnifiedHistogramChartLoadEvent['adapters']; - lensEmbeddableOutput$?: Observable; + dataLoading$?: LensEmbeddableOutput['dataLoading']; /** * Context object for the chart -- leave undefined to hide the chart */ @@ -214,7 +213,7 @@ export const UnifiedHistogramLayout = ({ request, hits, lensAdapters, - lensEmbeddableOutput$, + dataLoading$, chart: originalChart, breakdown, container, @@ -372,7 +371,7 @@ export const UnifiedHistogramLayout = ({ onFilter={onFilter} onBrushEnd={onBrushEnd} lensAdapters={lensAdapters} - lensEmbeddableOutput$={lensEmbeddableOutput$} + dataLoading$={dataLoading$} withDefaultActions={withDefaultActions} columns={columns} /> diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.attributes.test.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.attributes.test.ts index 75734387a936..f338ef955c01 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.attributes.test.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.attributes.test.ts @@ -108,6 +108,7 @@ describe('LensVisService attributes', () => { "sourceField": "timestamp", }, }, + "indexPatternId": "index-pattern-with-timefield-id", }, }, }, @@ -284,6 +285,7 @@ describe('LensVisService attributes', () => { "sourceField": "timestamp", }, }, + "indexPatternId": "index-pattern-with-timefield-id", }, }, }, @@ -434,6 +436,7 @@ describe('LensVisService attributes', () => { "sourceField": "timestamp", }, }, + "indexPatternId": "index-pattern-with-timefield-id", }, }, }, @@ -765,7 +768,7 @@ describe('LensVisService attributes', () => { columns: [], isPlainRecord: true, allSuggestions: [], // none available - hasHistogramSuggestionForESQL: true, + isTransformationalESQL: false, }); expect(lensVis.visContext?.attributes.state.query).toStrictEqual(histogramQuery); }); diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.suggestions.test.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.suggestions.test.ts index 09ee2a68ec24..baeb330180ab 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.suggestions.test.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.suggestions.test.ts @@ -86,7 +86,7 @@ describe('LensVisService suggestions', () => { ], isPlainRecord: true, allSuggestions: [], - hasHistogramSuggestionForESQL: false, + isTransformationalESQL: true, }); expect(lensVis.currentSuggestionContext?.type).toBe(UnifiedHistogramSuggestionType.unsupported); @@ -115,7 +115,7 @@ describe('LensVisService suggestions', () => { ], isPlainRecord: true, allSuggestions: [], - hasHistogramSuggestionForESQL: true, + isTransformationalESQL: false, }); expect(lensVis.currentSuggestionContext?.type).toBe( @@ -153,7 +153,7 @@ describe('LensVisService suggestions', () => { ], isPlainRecord: true, allSuggestions: [], - hasHistogramSuggestionForESQL: true, + isTransformationalESQL: false, }); expect(lensVis.currentSuggestionContext?.type).toBe( @@ -191,7 +191,7 @@ describe('LensVisService suggestions', () => { ], isPlainRecord: true, allSuggestions: [], - hasHistogramSuggestionForESQL: true, + isTransformationalESQL: true, }); expect(lensVis.currentSuggestionContext?.type).toBe(UnifiedHistogramSuggestionType.unsupported); @@ -225,7 +225,7 @@ describe('LensVisService suggestions', () => { ], isPlainRecord: true, allSuggestions: [], - hasHistogramSuggestionForESQL: true, + isTransformationalESQL: false, }); expect(lensVis.currentSuggestionContext?.type).toBe( @@ -276,7 +276,7 @@ describe('LensVisService suggestions', () => { ], isPlainRecord: true, allSuggestions: allSuggestionsMock, - hasHistogramSuggestionForESQL: true, + isTransformationalESQL: false, }); expect(lensVis.currentSuggestionContext?.type).toBe( @@ -307,7 +307,7 @@ describe('LensVisService suggestions', () => { ], isPlainRecord: true, allSuggestions: [], - hasHistogramSuggestionForESQL: true, + isTransformationalESQL: false, }); expect(lensVis.currentSuggestionContext?.type).toBe( diff --git a/src/plugins/unified_histogram/public/services/lens_vis_service.ts b/src/plugins/unified_histogram/public/services/lens_vis_service.ts index 04bf810848f2..5342ef4723b1 100644 --- a/src/plugins/unified_histogram/public/services/lens_vis_service.ts +++ b/src/plugins/unified_histogram/public/services/lens_vis_service.ts @@ -13,6 +13,7 @@ import { removeDropCommandsFromESQLQuery, appendToESQLQuery, isESQLColumnSortable, + hasTransformationalCommand, } from '@kbn/esql-utils'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import type { @@ -50,7 +51,6 @@ import { injectESQLQueryIntoLensLayers, } from '../utils/external_vis_context'; import { computeInterval } from '../utils/compute_interval'; -import { shouldDisplayHistogram } from '../layout/helpers'; import { enrichLensAttributesWithTablesData } from '../utils/lens_vis_from_table'; const UNIFIED_HISTOGRAM_LAYER_ID = 'unifiedHistogram'; @@ -67,7 +67,6 @@ export enum LensVisServiceStatus { interface LensVisServiceState { status: LensVisServiceStatus; - allSuggestions: Suggestion[] | undefined; currentSuggestionContext: UnifiedHistogramSuggestionContext; visContext: UnifiedHistogramVisContext | undefined; } @@ -87,7 +86,6 @@ export class LensVisService { private lensSuggestionsApi: LensSuggestionsApi; status$: Observable; currentSuggestionContext$: Observable; - allSuggestions$: Observable; visContext$: Observable; prevUpdateContext: | { @@ -111,7 +109,6 @@ export class LensVisService { this.state$ = new BehaviorSubject({ status: LensVisServiceStatus.initial, - allSuggestions: undefined, currentSuggestionContext: { suggestion: undefined, type: UnifiedHistogramSuggestionType.unsupported, @@ -121,7 +118,6 @@ export class LensVisService { const stateSelector = stateSelectorFactory(this.state$); this.status$ = stateSelector((state) => state.status); - this.allSuggestions$ = stateSelector((state) => state.allSuggestions); this.currentSuggestionContext$ = stateSelector( (state) => state.currentSuggestionContext, isEqual @@ -152,15 +148,9 @@ export class LensVisService { externalVisContextStatus: UnifiedHistogramExternalVisContextStatus ) => void; }) => { - const allSuggestions = this.getAllSuggestions({ - queryParams, - preferredVisAttributes: externalVisContext?.attributes, - }); - const suggestionState = this.getCurrentSuggestionState({ externalVisContext, queryParams, - allSuggestions, timeInterval, breakdownField, }); @@ -182,7 +172,6 @@ export class LensVisService { this.state$.next({ status: LensVisServiceStatus.completed, - allSuggestions, currentSuggestionContext: suggestionState.currentSuggestionContext, visContext: lensAttributesState.visContext, }); @@ -225,13 +214,11 @@ export class LensVisService { }; private getCurrentSuggestionState = ({ - allSuggestions, externalVisContext, queryParams, timeInterval, breakdownField, }: { - allSuggestions: Suggestion[]; externalVisContext: UnifiedHistogramVisContext | undefined; queryParams: QueryParams; timeInterval: string | undefined; @@ -242,34 +229,41 @@ export class LensVisService { let type = UnifiedHistogramSuggestionType.unsupported; let currentSuggestion: Suggestion | undefined; - // takes lens suggestions if provided - let availableSuggestionsWithType: Array<{ + const availableSuggestionsWithType: Array<{ suggestion: UnifiedHistogramSuggestionContext['suggestion']; type: UnifiedHistogramSuggestionType; }> = []; - if (allSuggestions.length) { - availableSuggestionsWithType.push({ - suggestion: allSuggestions[0], - type: UnifiedHistogramSuggestionType.lensSuggestion, - }); - } - if (queryParams.isPlainRecord) { - // appends an ES|QL histogram - const histogramSuggestionForESQL = this.getHistogramSuggestionForESQL({ - queryParams, - breakdownField, - preferredVisAttributes: externalVisContext?.attributes, - }); - if (histogramSuggestionForESQL) { - // In case if histogram suggestion, we want to empty the array and push the new suggestion - // to ensure that only the histogram suggestion is available - availableSuggestionsWithType = []; - availableSuggestionsWithType.push({ - suggestion: histogramSuggestionForESQL, - type: UnifiedHistogramSuggestionType.histogramForESQL, - }); + if (isOfAggregateQueryType(queryParams.query)) { + if (hasTransformationalCommand(queryParams.query.esql)) { + // appends the first lens suggestion if available + const allSuggestions = this.getAllSuggestions({ + queryParams, + preferredVisAttributes: externalVisContext?.attributes, + }); + + if (allSuggestions.length) { + availableSuggestionsWithType.push({ + suggestion: allSuggestions[0], + type: UnifiedHistogramSuggestionType.lensSuggestion, + }); + } + } else { + // appends an ES|QL histogram if available + const histogramSuggestionForESQL = this.getHistogramSuggestionForESQL({ + queryParams, + breakdownField, + preferredVisAttributes: externalVisContext?.attributes, + }); + + if (histogramSuggestionForESQL) { + availableSuggestionsWithType.push({ + suggestion: histogramSuggestionForESQL, + type: UnifiedHistogramSuggestionType.histogramForESQL, + }); + } + } } } else { // appends histogram for the data view mode @@ -409,7 +403,7 @@ export class LensVisService { const datasourceState = { layers: { - [UNIFIED_HISTOGRAM_LAYER_ID]: { columnOrder, columns }, + [UNIFIED_HISTOGRAM_LAYER_ID]: { columnOrder, columns, indexPatternId: dataView.id }, }, }; @@ -482,10 +476,13 @@ export class LensVisService { const breakdownColumn = breakdownField?.name ? columns?.find((column) => column.name === breakdownField.name) : undefined; - if (dataView.isTimeBased() && query && isOfAggregateQueryType(query) && timeRange) { - const isOnHistogramMode = shouldDisplayHistogram(query); - if (!isOnHistogramMode) return undefined; + if ( + dataView.isTimeBased() && + timeRange && + isOfAggregateQueryType(query) && + !hasTransformationalCommand(query.esql) + ) { const interval = computeInterval(timeRange, this.services.data); const esqlQuery = this.getESQLHistogramQuery({ dataView, @@ -609,13 +606,17 @@ export class LensVisService { }): Suggestion[] => { const { dataView, columns, query, isPlainRecord } = queryParams; + if (!isPlainRecord || !isOfAggregateQueryType(query)) { + return []; + } + const preferredChartType = preferredVisAttributes ? mapVisToChartType(preferredVisAttributes.visualizationType) : undefined; let visAttributes = preferredVisAttributes; - if (query && isOfAggregateQueryType(query) && preferredVisAttributes) { + if (preferredVisAttributes) { visAttributes = injectESQLQueryIntoLensLayers(preferredVisAttributes, query); } @@ -625,17 +626,16 @@ export class LensVisService { textBasedColumns: columns, query: query && isOfAggregateQueryType(query) ? query : undefined, }; - const allSuggestions = isPlainRecord - ? this.lensSuggestionsApi( - context, - dataView, - ['lnsDatatable'], - preferredChartType, - visAttributes - ) ?? [] - : []; - return allSuggestions; + return ( + this.lensSuggestionsApi( + context, + dataView, + ['lnsDatatable'], + preferredChartType, + visAttributes + ) ?? [] + ); }; private getLensAttributesState = ({ diff --git a/src/plugins/unified_histogram/public/types.ts b/src/plugins/unified_histogram/public/types.ts index b777fe89a348..a64000da11df 100644 --- a/src/plugins/unified_histogram/public/types.ts +++ b/src/plugins/unified_histogram/public/types.ts @@ -10,19 +10,15 @@ import type { IUiSettingsClient, Capabilities } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import type { - LensEmbeddableOutput, - LensPublicStart, - TypedLensByValueInput, - Suggestion, -} from '@kbn/lens-plugin/public'; +import type { LensPublicStart, TypedLensByValueInput, Suggestion } from '@kbn/lens-plugin/public'; import type { DataViewField } from '@kbn/data-views-plugin/public'; import type { RequestAdapter } from '@kbn/inspector-plugin/public'; import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; -import type { Observable, Subject } from 'rxjs'; +import type { Subject } from 'rxjs'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import { PublishingSubject } from '@kbn/presentation-publishing'; /** * The fetch status of a Unified Histogram request @@ -72,9 +68,9 @@ export interface UnifiedHistogramChartLoadEvent { */ adapters: UnifiedHistogramAdapters; /** - * Observable of the lens embeddable output + * Observable for the data change subscription */ - embeddableOutput$?: Observable; + dataLoading$?: PublishingSubject; } /** diff --git a/src/plugins/unified_histogram/public/utils/external_vis_context.ts b/src/plugins/unified_histogram/public/utils/external_vis_context.ts index ef5788b4b25b..29e393d14508 100644 --- a/src/plugins/unified_histogram/public/utils/external_vis_context.ts +++ b/src/plugins/unified_histogram/public/utils/external_vis_context.ts @@ -43,7 +43,7 @@ export const exportVisContext = ( ? { suggestionType: visContext.suggestionType, requestData: visContext.requestData, - attributes: removeTablesFromLensAttributes(visContext.attributes), + attributes: removeTablesFromLensAttributes(visContext.attributes).attributes, } : undefined; diff --git a/src/plugins/unified_histogram/public/utils/lens_vis_from_table.ts b/src/plugins/unified_histogram/public/utils/lens_vis_from_table.ts index 95693851db52..bc618343a0a7 100644 --- a/src/plugins/unified_histogram/public/utils/lens_vis_from_table.ts +++ b/src/plugins/unified_histogram/public/utils/lens_vis_from_table.ts @@ -10,6 +10,7 @@ import type { Datatable } from '@kbn/expressions-plugin/common'; import type { LensAttributes } from '@kbn/lens-embeddable-utils'; import type { TextBasedPersistedState } from '@kbn/lens-plugin/public/datasources/text_based/types'; +import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; export const enrichLensAttributesWithTablesData = ({ attributes, @@ -53,6 +54,8 @@ export const enrichLensAttributesWithTablesData = ({ return updatedAttributes; }; -export const removeTablesFromLensAttributes = (attributes: LensAttributes): LensAttributes => { - return enrichLensAttributesWithTablesData({ attributes, table: undefined }); +export const removeTablesFromLensAttributes = ( + attributes: LensAttributes +): TypedLensByValueInput => { + return { attributes: enrichLensAttributesWithTablesData({ attributes, table: undefined }) }; }; diff --git a/src/plugins/unified_histogram/tsconfig.json b/src/plugins/unified_histogram/tsconfig.json index d14adf53889b..68c096665eb7 100644 --- a/src/plugins/unified_histogram/tsconfig.json +++ b/src/plugins/unified_histogram/tsconfig.json @@ -33,6 +33,7 @@ "@kbn/discover-utils", "@kbn/visualization-utils", "@kbn/search-types", + "@kbn/presentation-publishing", "@kbn/data-view-utils", ], "exclude": [ diff --git a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.test.tsx b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.test.tsx index ae51b96522c2..0abc6587b872 100644 --- a/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.test.tsx +++ b/src/plugins/unified_search/public/saved_query_management/saved_query_management_list.test.tsx @@ -155,7 +155,7 @@ describe('Saved query management list component', () => { it('should render the saved queries on the selectable component', async () => { render(wrapSavedQueriesListComponentInContext(props)); - expect(await screen.findAllByRole('option')).toHaveLength(1); + await waitFor(() => expect(screen.queryAllByRole('option')).toHaveLength(1)); expect(screen.getByRole('option', { name: 'Test' })).toBeInTheDocument(); }); diff --git a/src/plugins/vis_types/table/public/utils/use/use_pagination.test.ts b/src/plugins/vis_types/table/public/utils/use/use_pagination.test.ts index be6ea0a4427c..d6ac1fbebc5f 100644 --- a/src/plugins/vis_types/table/public/utils/use/use_pagination.test.ts +++ b/src/plugins/vis_types/table/public/utils/use/use_pagination.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { AggTypes } from '../../../common'; import { usePagination } from './use_pagination'; diff --git a/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts b/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts index 37525bf12626..a6a86426a2f4 100644 --- a/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts +++ b/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook, act } from '@testing-library/react'; import type { PersistedState } from '@kbn/visualizations-plugin/public'; import { TableVisUiState } from '../../types'; import { useUiState } from './use_ui_state'; @@ -39,7 +39,7 @@ describe('useUiState', () => { }); it('should subscribe on uiState changes and update local state', async () => { - const { result, unmount, waitForNextUpdate } = renderHook(() => useUiState(uiState)); + const { result, unmount } = renderHook(() => useUiState(uiState)); expect(uiState.on).toHaveBeenCalledWith('change', expect.any(Function)); // @ts-expect-error @@ -61,18 +61,18 @@ describe('useUiState', () => { updateOnChange(); }); - await waitForNextUpdate(); - // should update local state with new values - expect(result.current).toEqual({ - columnsWidth: [], - sort: { - columnIndex: 1, - direction: 'asc', - }, - setColumnsWidth: expect.any(Function), - setSort: expect.any(Function), - }); + await waitFor(() => + expect(result.current).toEqual({ + columnsWidth: [], + sort: { + columnIndex: 1, + direction: 'asc', + }, + setColumnsWidth: expect.any(Function), + setSort: expect.any(Function), + }) + ); act(() => { updateOnChange(); diff --git a/src/plugins/vis_types/timeseries/kibana.jsonc b/src/plugins/vis_types/timeseries/kibana.jsonc index 03cb4697162e..08d76648220e 100644 --- a/src/plugins/vis_types/timeseries/kibana.jsonc +++ b/src/plugins/vis_types/timeseries/kibana.jsonc @@ -4,6 +4,7 @@ "owner": [ "@elastic/kibana-visualizations" ], + // currently used from both visualisations and observability/infra "group": "platform", "visibility": "shared", "description": "Registers the TSVB visualization. TSVB has its one editor, works with index patterns and index strings and contains 6 types of charts: timeseries, topN, table. markdown, metric and gauge.", @@ -32,4 +33,4 @@ "fieldFormats" ] } -} \ No newline at end of file +} diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/data.js b/src/plugins/vis_types/vislib/public/vislib/lib/data.js index 94c39c95930a..83c6167d4092 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/data.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/data.js @@ -45,15 +45,11 @@ class D3MappableObject { * @param attr {Object|*} Visualization options */ export class Data { - constructor(data, uiState, createColorLookupFunction) { + constructor(data, uiState) { this.uiState = uiState; - this.createColorLookupFunction = createColorLookupFunction; this.data = this.copyDataObj(data); this.type = this.getDataType(); this.labels = this._getLabels(this.data); - this.color = this.labels - ? createColorLookupFunction(this.labels, uiState.get('vis.colors')) - : undefined; this._normalizeOrdered(); } @@ -385,7 +381,7 @@ export class Data { const defaultColors = this.uiState.get('vis.defaultColors'); const overwriteColors = this.uiState.get('vis.colors'); const colors = defaultColors ? _.defaults({}, overwriteColors, defaultColors) : overwriteColors; - return this.createColorLookupFunction(this.getLabels(), colors); + return (value) => colors[value]; } /** diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/vis_config.js b/src/plugins/vis_types/vislib/public/vislib/lib/vis_config.js index bb25a8ddaada..28d0d0c67c27 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/vis_config.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/vis_config.js @@ -25,8 +25,8 @@ const DEFAULT_VIS_CONFIG = { }; export class VisConfig { - constructor(visConfigArgs, data, uiState, el, createColorLookupFunction) { - this.data = new Data(data, uiState, createColorLookupFunction); + constructor(visConfigArgs, data, uiState, el) { + this.data = new Data(data, uiState); const visType = visTypes[visConfigArgs.type]; const typeDefaults = visType(visConfigArgs, this.data); diff --git a/src/plugins/vis_types/vislib/public/vislib/vis.js b/src/plugins/vis_types/vislib/public/vislib/vis.js index 80f7393d69af..35241d999f2f 100644 --- a/src/plugins/vis_types/vislib/public/vislib/vis.js +++ b/src/plugins/vis_types/vislib/public/vislib/vis.js @@ -41,13 +41,7 @@ export class Vis extends EventEmitter { initVisConfig(data, uiState) { this.data = data; this.uiState = uiState; - this.visConfig = new VisConfig( - this.visConfigArgs, - this.data, - this.uiState, - this.element, - this.charts.legacyColors.createColorLookupFunction.bind(this.charts.legacyColors) - ); + this.visConfig = new VisConfig(this.visConfigArgs, this.data, this.uiState, this.element); } /** diff --git a/src/plugins/visualizations/public/embeddable/types.ts b/src/plugins/visualizations/public/embeddable/types.ts index f4215d923e1d..80e7e2d9179e 100644 --- a/src/plugins/visualizations/public/embeddable/types.ts +++ b/src/plugins/visualizations/public/embeddable/types.ts @@ -17,6 +17,7 @@ import { HasSupportedTriggers, PublishesDataLoading, PublishesDataViews, + PublishesRendered, PublishesTimeRange, SerializedTimeRange, SerializedTitles, @@ -92,6 +93,7 @@ export const isVisualizeRuntimeState = (state: unknown): state is VisualizeRunti export type VisualizeApi = Partial & PublishesDataViews & PublishesDataLoading & + PublishesRendered & HasVisualizeConfig & HasInspectorAdapters & HasSupportedTriggers & diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index 7b48521265d6..4993c0168313 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -179,6 +179,7 @@ export const getVisualizeEmbeddableFactory: (deps: { defaultPanelTitle, dataLoading: dataLoading$, dataViews: new BehaviorSubject(initialDataViews), + rendered$: hasRendered$, supportedTriggers: () => [ ACTION_CONVERT_TO_LENS, APPLY_FILTER_TRIGGER, @@ -397,7 +398,6 @@ export const getVisualizeEmbeddableFactory: (deps: { if (hasRendered$.getValue() === true) return; hasRendered$.next(true); - hasRendered$.complete(); }, onEvent: async (event) => { // Visualize doesn't respond to sizing events, so ignore. diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_chrome_visibility.test.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_chrome_visibility.test.ts index 08c201591390..01d90ee2b2a2 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_chrome_visibility.test.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_chrome_visibility.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { chromeServiceMock } from '@kbn/core/public/mocks'; import { useChromeVisibility } from './use_chrome_visibility'; diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.test.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.test.ts index c0f6719a403f..b96185a0270a 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.test.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_editor_updates.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { EventEmitter } from 'events'; import { useEditorUpdates } from './use_editor_updates'; diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.test.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.test.ts index 8cf124ca243c..d979021b7488 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.test.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { EventEmitter } from 'events'; import { useLinkedSearchUpdates } from './use_linked_search_updates'; diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts index 7c32fec75442..c5131460d76b 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_saved_vis_instance.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { EventEmitter } from 'events'; import { setTypes } from '../../../services'; @@ -127,7 +127,7 @@ describe('useSavedVisInstance', () => { describe('edit saved visualization route', () => { test('should load instance and initiate an editor if chrome is set up', async () => { - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined, savedVisId) ); @@ -135,7 +135,7 @@ describe('useSavedVisInstance', () => { expect(mockGetVisualizationInstance).toHaveBeenCalledWith(mockServices, savedVisId); expect(mockGetVisualizationInstance.mock.calls.length).toBe(1); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(mockServices.chrome.setBreadcrumbs).toHaveBeenCalledWith('Test Vis'); expect(mockServices.chrome.docTitle.change).toHaveBeenCalledWith('Test Vis'); expect(getEditBreadcrumbs).toHaveBeenCalledWith( @@ -156,7 +156,7 @@ describe('useSavedVisInstance', () => { }, id: 'panel1', }; - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useSavedVisInstance( mockServices, eventEmitter, @@ -171,7 +171,7 @@ describe('useSavedVisInstance', () => { expect(mockGetVisualizationInstance).toHaveBeenCalledWith(mockServices, savedVisId); expect(mockGetVisualizationInstance.mock.calls.length).toBe(1); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(mockServices.chrome.setBreadcrumbs).toHaveBeenCalledWith('Test Vis'); expect(mockServices.chrome.docTitle.change).toHaveBeenCalledWith('Test Vis'); expect(getEditBreadcrumbs).toHaveBeenCalledWith( @@ -189,13 +189,13 @@ describe('useSavedVisInstance', () => { }); test('should destroy the editor and the savedVis on unmount if chrome exists', async () => { - const { result, unmount, waitForNextUpdate } = renderHook(() => + const { result, unmount } = renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined, savedVisId) ); result.current.visEditorRef.current = document.createElement('div'); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); unmount(); expect(mockDefaultEditorControllerDestroy.mock.calls.length).toBe(1); @@ -215,7 +215,7 @@ describe('useSavedVisInstance', () => { }); test('should create new visualization based on search params', async () => { - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useSavedVisInstance(mockServices, eventEmitter, true, undefined, undefined) ); @@ -226,7 +226,7 @@ describe('useSavedVisInstance', () => { type: 'area', }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(getCreateBreadcrumbs).toHaveBeenCalled(); expect(mockEmbeddableHandlerRender).not.toHaveBeenCalled(); @@ -263,7 +263,7 @@ describe('useSavedVisInstance', () => { describe('embeded mode', () => { test('should create new visualization based on search params', async () => { - const { result, unmount, waitForNextUpdate } = renderHook(() => + const { result, unmount } = renderHook(() => useSavedVisInstance(mockServices, eventEmitter, false, undefined, savedVisId) ); @@ -273,7 +273,7 @@ describe('useSavedVisInstance', () => { expect(mockGetVisualizationInstance).toHaveBeenCalledWith(mockServices, savedVisId); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(mockEmbeddableHandlerRender).toHaveBeenCalled(); expect(result.current.visEditorController).toBeUndefined(); diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_visualize_app_state.test.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_visualize_app_state.test.ts index f92bd7304a7e..7add55761e2a 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_visualize_app_state.test.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_visualize_app_state.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { EventEmitter } from 'events'; import { Observable } from 'rxjs'; @@ -159,11 +159,11 @@ describe('useVisualizeAppState', () => { it('should successfully update vis state and set up app state container', async () => { stateContainerGetStateMock.mockImplementation(() => state); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useVisualizeAppState(mockServices, eventEmitter, savedVisInstance) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { aggs, ...visState } = stateContainer.getState().vis; const expectedNewVisState = { @@ -183,11 +183,11 @@ describe('useVisualizeAppState', () => { ...visualizeAppStateStub, query: { query: 'test', language: 'kuery' }, })); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useVisualizeAppState(mockServices, eventEmitter, savedVisInstance) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { aggs, ...visState } = stateContainer.getState().vis; const expectedNewVisState = { diff --git a/test/functional/apps/dashboard/group6/dashboard_esql_chart.ts b/test/functional/apps/dashboard/group6/dashboard_esql_chart.ts index 35b7db4fabfc..faaacc48fe97 100644 --- a/test/functional/apps/dashboard/group6/dashboard_esql_chart.ts +++ b/test/functional/apps/dashboard/group6/dashboard_esql_chart.ts @@ -18,6 +18,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const monacoEditor = getService('monacoEditor'); const dashboardAddPanel = getService('dashboardAddPanel'); + const dashboardPanelActions = getService('dashboardPanelActions'); + const log = getService('log'); describe('dashboard add ES|QL chart', function () { before(async () => { @@ -30,6 +32,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); + after(async () => { + await dashboard.navigateToApp(); + await testSubjects.click('discard-unsaved-New-Dashboard'); + }); + it('should add an ES|QL datatable chart when the ES|QL panel action is clicked', async () => { await dashboard.navigateToApp(); await dashboard.clickNewDashboard(); @@ -57,6 +64,47 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); + it('should reset to the previous state on edit inline', async () => { + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAddNewPanelFromUIActionLink('ES|QL'); + await dashboardAddPanel.expectEditorMenuClosed(); + await dashboard.waitForRenderComplete(); + + // Save the panel and close the flyout + log.debug('Applies the changes'); + await testSubjects.click('applyFlyoutButton'); + + // now edit the panel and click on Cancel + await dashboardPanelActions.clickInlineEdit(); + + const metricsConfigured = await testSubjects.findAll( + 'lnsDatatable_metrics > lnsLayerPanel-dimensionLink' + ); + // remove the first metric from the configuration + // Lens is x-pack so not available here, make things manually + await testSubjects.moveMouseTo(`lnsDatatable_metrics > indexPattern-dimension-remove`); + await testSubjects.click(`lnsDatatable_metrics > indexPattern-dimension-remove`); + const beforeCancelMetricsConfigured = await testSubjects.findAll( + 'lnsDatatable_metrics > lnsLayerPanel-dimensionLink' + ); + expect(beforeCancelMetricsConfigured.length).to.eql(metricsConfigured.length - 1); + + // now click cancel + await testSubjects.click('cancelFlyoutButton'); + await dashboard.waitForRenderComplete(); + + // re open the inline editor and check that the configured metrics are still the original ones + await dashboardPanelActions.clickInlineEdit(); + const afterCancelMetricsConfigured = await testSubjects.findAll( + 'lnsDatatable_metrics > lnsLayerPanel-dimensionLink' + ); + expect(afterCancelMetricsConfigured.length).to.eql(metricsConfigured.length); + // delete the panel + await testSubjects.click('cancelFlyoutButton'); + const panels = await dashboard.getDashboardPanels(); + await dashboardPanelActions.removePanel(panels[0]); + }); + it('should be able to edit the query and render another chart', async () => { await dashboardAddPanel.clickEditorMenuButton(); await dashboardAddPanel.clickAddNewPanelFromUIActionLink('ES|QL'); @@ -70,5 +118,41 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('applyFlyoutButton'); expect(await testSubjects.exists('mtrVis')).to.be(true); }); + + it('should add a second panel and remove when hitting cancel', async () => { + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAddNewPanelFromUIActionLink('ES|QL'); + await dashboardAddPanel.expectEditorMenuClosed(); + await dashboard.waitForRenderComplete(); + // Cancel + await testSubjects.click('cancelFlyoutButton'); + // Test that there's only 1 panel left + await dashboard.waitForRenderComplete(); + await retry.try(async () => { + const panelCount = await dashboard.getPanelCount(); + expect(panelCount).to.eql(1); + }); + }); + + it('should not remove the first panel of two when editing and cancelling', async () => { + // add a second panel + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAddNewPanelFromUIActionLink('ES|QL'); + await dashboardAddPanel.expectEditorMenuClosed(); + await dashboard.waitForRenderComplete(); + // save it + await testSubjects.click('applyFlyoutButton'); + await dashboard.waitForRenderComplete(); + + // now edit the first one + const [firstPanel] = await dashboard.getDashboardPanels(); + await dashboardPanelActions.clickInlineEdit(firstPanel); + await testSubjects.click('cancelFlyoutButton'); + await dashboard.waitForRenderComplete(); + await retry.try(async () => { + const panelCount = await dashboard.getPanelCount(); + expect(panelCount).to.eql(2); + }); + }); }); } diff --git a/test/functional/apps/dashboard/group6/dashboard_esql_no_data.ts b/test/functional/apps/dashboard/group6/dashboard_esql_no_data.ts index 333ac7f01539..4298ccdfb588 100644 --- a/test/functional/apps/dashboard/group6/dashboard_esql_no_data.ts +++ b/test/functional/apps/dashboard/group6/dashboard_esql_no_data.ts @@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.expectOnDashboard('New Dashboard'); expect(await testSubjects.exists('lnsVisualizationContainer')).to.be(true); - await panelActions.clickInlineEdit(); + await panelActions.clickEdit(); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql(`FROM logs* | LIMIT 10`); }); diff --git a/test/functional/apps/dashboard/group6/view_edit.ts b/test/functional/apps/dashboard/group6/view_edit.ts index 487adc753e65..9304b51d302d 100644 --- a/test/functional/apps/dashboard/group6/view_edit.ts +++ b/test/functional/apps/dashboard/group6/view_edit.ts @@ -25,7 +25,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const filterBar = getService('filterBar'); const security = getService('security'); - describe('dashboard view edit mode', function viewEditModeTests() { + // Failing: See https://github.com/elastic/kibana/issues/200748 + describe.skip('dashboard view edit mode', function viewEditModeTests() { before(async () => { await kibanaServer.savedObjects.cleanStandardList(); await kibanaServer.importExport.load( diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_additional_cell_actions.ts b/test/functional/apps/discover/context_awareness/extensions/_get_additional_cell_actions.ts index a2f8b1efe8dd..23363cdb315d 100644 --- a/test/functional/apps/discover/context_awareness/extensions/_get_additional_cell_actions.ts +++ b/test/functional/apps/discover/context_awareness/extensions/_get_additional_cell_actions.ts @@ -12,7 +12,13 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'discover', 'header', 'unifiedFieldList']); + const PageObjects = getPageObjects([ + 'common', + 'discover', + 'header', + 'unifiedFieldList', + 'context', + ]); const dataViews = getService('dataViews'); const dataGrid = getService('dataGrid'); const browser = getService('browser'); @@ -29,7 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('example-data-source-action'); let alert = await browser.getAlert(); try { @@ -37,7 +43,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } finally { await alert?.dismiss(); } - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('another-example-data-source-action'); alert = await browser.getAlert(); try { @@ -57,7 +63,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 2); + await dataGrid.clickCellExpandButton(0, { columnName: 'message' }); expect(await dataGrid.cellExpandPopoverActionExists('example-data-source-action')).to.be( true ); @@ -76,7 +82,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); expect(await dataGrid.cellExpandPopoverActionExists('example-data-source-action')).to.be( false ); @@ -94,7 +100,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataViews.switchTo('my-example-logs'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('example-data-source-action'); let alert = await browser.getAlert(); try { @@ -102,7 +108,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } finally { await alert?.dismiss(); } - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('another-example-data-source-action'); alert = await browser.getAlert(); try { @@ -118,7 +124,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.refresh(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await PageObjects.context.waitUntilContextLoadingHasFinished(); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('example-data-source-action'); alert = await browser.getAlert(); try { @@ -126,7 +133,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } finally { await alert?.dismiss(); } - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('another-example-data-source-action'); alert = await browser.getAlert(); try { @@ -143,7 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataViews.switchTo('my-example-logs'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 2); + await dataGrid.clickCellExpandButton(0, { columnName: 'message' }); expect(await dataGrid.cellExpandPopoverActionExists('example-data-source-action')).to.be( true ); @@ -159,7 +166,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataViews.switchTo('my-example-metrics'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); expect(await dataGrid.cellExpandPopoverActionExists('example-data-source-action')).to.be( false ); diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index b1fd957f97a6..65d8b7ce698b 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -142,9 +142,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should perform test query correctly', async function () { await timePicker.setDefaultAbsoluteRange(); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); await discover.selectTextBaseLang(); - const testQuery = `from logstash-* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + const testQuery = `from logstash-* | sort @timestamp | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); await header.waitUntilLoadingHasFinished(); @@ -158,7 +162,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render when switching to a time range with no data, then back to a time range with data', async () => { await discover.selectTextBaseLang(); - const testQuery = `from logstash-* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + + const testQuery = `from logstash-* | sort @timestamp | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); await header.waitUntilLoadingHasFinished(); @@ -181,8 +188,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should query an index pattern that doesnt translate to a dataview correctly', async function () { await discover.selectTextBaseLang(); - const testQuery = `from logstash* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + const testQuery = `from logstash* | sort @timestamp | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); await header.waitUntilLoadingHasFinished(); @@ -296,6 +305,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await header.waitUntilLoadingHasFinished(); await discover.waitUntilSearchingHasFinished(); await discover.saveSearch('esql_test2'); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); const testQuery = 'from logstash-* | limit 100 | drop @timestamp'; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); @@ -372,6 +383,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await testSubjects.click('querySubmitButton'); await header.waitUntilLoadingHasFinished(); + // for some reason the chart query is taking a very long time to return (3x the delay) + // so wait for the chart to be loaded + await discover.waitForChartLoadingComplete(1); await browser.execute(() => { window.ELASTIC_ESQL_DELAY_SECONDS = undefined; }); diff --git a/test/functional/apps/discover/group3/_lens_vis.ts b/test/functional/apps/discover/group3/_lens_vis.ts index 03641ee5bcb4..71757ecbfcd2 100644 --- a/test/functional/apps/discover/group3/_lens_vis.ts +++ b/test/functional/apps/discover/group3/_lens_vis.ts @@ -110,17 +110,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return seriesType; } - // FLAKY: https://github.com/elastic/kibana/issues/184600 - describe.skip('discover lens vis', function () { + describe('discover lens vis', function () { before(async () => { await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/many_fields'); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/many_fields_data_view' + ); await browser.setWindowSize(1300, 1000); }); after(async () => { await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await kibanaServer.importExport.unload( + 'test/functional/fixtures/kbn_archiver/many_fields_data_view' + ); + await esArchiver.unload('test/functional/fixtures/es_archiver/many_fields'); await kibanaServer.uiSettings.replace({}); await kibanaServer.savedObjects.cleanStandardList(); }); @@ -193,6 +200,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await discover.getVisContextSuggestionType()).to.be('histogramForDataView'); }); + it('should show no histogram for non-time-based data in data view and ES|QL modes', async () => { + await dataViews.switchToAndValidate('indices-stats*'); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + await checkNoVis('50'); + + await discover.selectTextBaseLang(); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + await checkNoVis('10'); + }); + it('should show ESQL histogram for ES|QL query', async () => { await discover.selectTextBaseLang(); @@ -655,8 +674,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await discover.waitUntilSearchingHasFinished(); await testSubjects.missingOrFail('unsavedChangesBadge'); - await discover.chooseLensSuggestion('pie'); - expect(await getCurrentVisTitle()).to.be('Pie'); + await discover.chooseLensSuggestion('waffle'); + expect(await getCurrentVisTitle()).to.be('Waffle'); await testSubjects.existOrFail('partitionVisChart'); expect(await discover.getVisContextSuggestionType()).to.be('lensSuggestion'); diff --git a/test/functional/apps/discover/group3/_request_counts.ts b/test/functional/apps/discover/group3/_request_counts.ts index 8a029928af0c..32f1be5a62e7 100644 --- a/test/functional/apps/discover/group3/_request_counts.ts +++ b/test/functional/apps/discover/group3/_request_counts.ts @@ -97,7 +97,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expectedRequests?: number; expectedRefreshRequest?: number; }) => { - it(`should send ${expectedRequests} search requests (documents + chart) on page load`, async () => { + it(`should send no more than ${expectedRequests} search requests (documents + chart) on page load`, async () => { await browser.refresh(); await browser.execute(async () => { performance.setResourceTimingBufferSize(Number.MAX_SAFE_INTEGER); @@ -107,20 +107,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(searchCount).to.be(expectedRequests); }); - it(`should send ${expectedRequests} requests (documents + chart) when refreshing`, async () => { + it(`should send no more than ${expectedRequests} requests (documents + chart) when refreshing`, async () => { await expectSearches(type, expectedRequests, async () => { await queryBar.clickQuerySubmitButton(); }); }); - it(`should send ${expectedRequests} requests (documents + chart) when changing the query`, async () => { + it(`should send no more than ${expectedRequests} requests (documents + chart) when changing the query`, async () => { await expectSearches(type, expectedRequests, async () => { await setQuery(query1); await queryBar.clickQuerySubmitButton(); }); }); - it(`should send ${expectedRequests} requests (documents + chart) when changing the time range`, async () => { + it(`should send no more than ${expectedRequests} requests (documents + chart) when changing the time range`, async () => { await expectSearches(type, expectedRequests, async () => { await timePicker.setAbsoluteRange( 'Sep 21, 2015 @ 06:31:44.000', @@ -174,7 +174,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { setQuery: (query) => queryBar.setQuery(query), }); - it(`should send 2 requests (documents + chart) when toggling the chart visibility`, async () => { + it(`should send no more than 2 requests (documents + chart) when toggling the chart visibility`, async () => { await expectSearches(type, 2, async () => { await discover.toggleChartVisibility(); }); @@ -183,7 +183,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should send 2 requests (documents + chart) when adding a filter', async () => { + it('should send no more than 2 requests (documents + chart) when adding a filter', async () => { await expectSearches(type, 2, async () => { await filterBar.addFilter({ field: 'extension', @@ -193,31 +193,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should send 2 requests (documents + chart) when sorting', async () => { + it('should send no more than 2 requests (documents + chart) when sorting', async () => { await expectSearches(type, 2, async () => { await discover.clickFieldSort('@timestamp', 'Sort Old-New'); }); }); - it('should send 2 requests (documents + chart) when changing to a breakdown field without an other bucket', async () => { + it('should send no more than 2 requests (documents + chart) when changing to a breakdown field without an other bucket', async () => { await expectSearches(type, 2, async () => { await discover.chooseBreakdownField('type'); }); }); - it('should send 3 requests (documents + chart + other bucket) when changing to a breakdown field with an other bucket', async () => { + it('should send no more than 3 requests (documents + chart + other bucket) when changing to a breakdown field with an other bucket', async () => { await expectSearches(type, 3, async () => { await discover.chooseBreakdownField('extension.raw'); }); }); - it('should send 2 requests (documents + chart) when changing the chart interval', async () => { + it('should send no more than 2 requests (documents + chart) when changing the chart interval', async () => { await expectSearches(type, 2, async () => { await discover.setChartInterval('Day'); }); }); - it('should send 2 requests (documents + chart) when changing the data view', async () => { + it('should send no more than 2 requests (documents + chart) when changing the data view', async () => { await expectSearches(type, 2, async () => { await discover.selectIndexPattern('long-window-logstash-*'); }); diff --git a/test/functional/apps/discover/group4/_data_view_edit.ts b/test/functional/apps/discover/group4/_data_view_edit.ts index c0c521601019..809902857eac 100644 --- a/test/functional/apps/discover/group4/_data_view_edit.ts +++ b/test/functional/apps/discover/group4/_data_view_edit.ts @@ -25,7 +25,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'unifiedFieldList', ]); - describe('data view flyout', function () { + // Failing: See https://github.com/elastic/kibana/issues/201071 + describe.skip('data view flyout', function () { before(async () => { await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover.json'); diff --git a/test/functional/apps/visualize/group3/_annotation_listing.ts b/test/functional/apps/visualize/group3/_annotation_listing.ts index a6a174343009..1ec8fb8cdea9 100644 --- a/test/functional/apps/visualize/group3/_annotation_listing.ts +++ b/test/functional/apps/visualize/group3/_annotation_listing.ts @@ -177,7 +177,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataView: 'logs*', }); expect(await annotationEditor.showingMissingDataViewPrompt()).to.be(false); - expect(await find.byCssSelector('canvas')).to.be.ok(); + // @TODO: re-enable this once the error bubbling issue is fixed at Lens custom component level + // expect(await find.byCssSelector('canvas')).to.be.ok(); }); await annotationEditor.saveGroup(); diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index e8a0de7fbc34..8feccfd955c7 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -597,6 +597,7 @@ export class DiscoverPageObject extends FtrService { if (await this.testSubjects.exists('select-text-based-language-btn')) { await this.testSubjects.click('select-text-based-language-btn'); await this.header.waitUntilLoadingHasFinished(); + await this.waitUntilSearchingHasFinished(); } } diff --git a/test/functional/page_objects/unified_field_list.ts b/test/functional/page_objects/unified_field_list.ts index 4751769f717b..6a5f16a2cac3 100644 --- a/test/functional/page_objects/unified_field_list.ts +++ b/test/functional/page_objects/unified_field_list.ts @@ -204,10 +204,9 @@ export class UnifiedFieldListPageObject extends FtrService { if (!isActive) { // expand the field to show the "Visualize" button - await field.click(); + await this.clickFieldListItem(fieldName); } - await this.waitUntilFieldPopoverIsOpen(); const visualizeButtonTestSubject = `fieldVisualize-${fieldName}`; // wrap visualize button click in retry to ensure button is clicked and retry if button click is not registered await this.retry.try(async () => { diff --git a/test/functional/services/dashboard/panel_actions.ts b/test/functional/services/dashboard/panel_actions.ts index 75474fef4165..31890d4c4c47 100644 --- a/test/functional/services/dashboard/panel_actions.ts +++ b/test/functional/services/dashboard/panel_actions.ts @@ -12,7 +12,6 @@ import { FtrService } from '../../ftr_provider_context'; const REMOVE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-deletePanel'; const EDIT_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-editPanel'; -const INLINE_EDIT_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-ACTION_CONFIGURE_IN_LENS'; const EDIT_IN_LENS_EDITOR_DATA_TEST_SUBJ = 'navigateToLensEditorLink'; const CLONE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-clonePanel'; const TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-togglePanel'; @@ -128,7 +127,9 @@ export class DashboardPanelActionsService extends FtrService { async navigateToEditorFromFlyout(wrapper?: WebElementWrapper) { this.log.debug('navigateToEditorFromFlyout'); - await this.clickPanelAction(INLINE_EDIT_PANEL_DATA_TEST_SUBJ, wrapper); + // make sure the context menu is open before proceeding + await this.openContextMenu(); + await this.clickPanelAction(EDIT_PANEL_DATA_TEST_SUBJ); await this.header.waitUntilLoadingHasFinished(); await this.testSubjects.clickWhenNotDisabledWithoutRetry(EDIT_IN_LENS_EDITOR_DATA_TEST_SUBJ); const isConfirmModalVisible = await this.testSubjects.exists('confirmModalConfirmButton'); @@ -139,9 +140,9 @@ export class DashboardPanelActionsService extends FtrService { } } - async clickInlineEdit() { + async clickInlineEdit(wrapper?: WebElementWrapper) { this.log.debug('clickInlineEditAction'); - await this.clickPanelAction(INLINE_EDIT_PANEL_DATA_TEST_SUBJ); + await this.clickPanelAction(EDIT_PANEL_DATA_TEST_SUBJ, wrapper); await this.header.waitUntilLoadingHasFinished(); await this.common.waitForTopNavToBeVisible(); } @@ -307,12 +308,9 @@ export class DashboardPanelActionsService extends FtrService { await this.expectExistsPanelAction(REMOVE_PANEL_DATA_TEST_SUBJ, title); } - async expectExistsEditPanelAction(title = '', allowsInlineEditing?: boolean) { + async expectExistsEditPanelAction(title = '') { this.log.debug('expectExistsEditPanelAction'); - let testSubj = EDIT_PANEL_DATA_TEST_SUBJ; - if (allowsInlineEditing) { - testSubj = INLINE_EDIT_PANEL_DATA_TEST_SUBJ; - } + const testSubj = EDIT_PANEL_DATA_TEST_SUBJ; await this.expectExistsPanelAction(testSubj, title); } diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 36e5285a529b..3b17a31d624b 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -174,12 +174,14 @@ export class DataGridService extends FtrService { private async getCellActionButton( rowIndex: number = 0, - columnIndex: number = 0, + { columnIndex = 0, columnName }: { columnIndex?: number; columnName?: string }, selector: string ): Promise { let actionButton: WebElementWrapper | undefined; await this.retry.try(async () => { - const cell = await this.getCellElement(rowIndex, columnIndex); + const cell = columnName + ? await this.getCellElementByColumnName(rowIndex, columnName) + : await this.getCellElement(rowIndex, columnIndex); await cell.moveMouseTo(); await cell.click(); actionButton = await cell.findByTestSubject(selector); @@ -194,11 +196,15 @@ export class DataGridService extends FtrService { * Clicks grid cell 'expand' action button * @param rowIndex data row index starting from 0 (0 means 1st row) * @param columnIndex column index starting from 0 (0 means 1st column) + * @param columnName column/field name */ - public async clickCellExpandButton(rowIndex: number = 0, columnIndex: number = 0) { + public async clickCellExpandButton( + rowIndex: number = 0, + { columnIndex = 0, columnName }: { columnIndex?: number; columnName?: string } + ) { const actionButton = await this.getCellActionButton( rowIndex, - columnIndex, + { columnIndex, columnName }, 'euiDataGridCellExpandButton' ); await actionButton.moveMouseTo(); @@ -218,7 +224,7 @@ export class DataGridService extends FtrService { columnIndex: number = 0 ) { const controlsCount = await this.getControlColumnsCount(); - await this.clickCellExpandButton(rowIndex, controlsCount + columnIndex); + await this.clickCellExpandButton(rowIndex, { columnIndex: controlsCount + columnIndex }); } /** @@ -244,7 +250,11 @@ export class DataGridService extends FtrService { * @param columnIndex column index starting from 0 (0 means 1st column) */ public async clickCellFilterForButton(rowIndex: number = 0, columnIndex: number = 0) { - const actionButton = await this.getCellActionButton(rowIndex, columnIndex, 'filterForButton'); + const actionButton = await this.getCellActionButton( + rowIndex, + { columnIndex }, + 'filterForButton' + ); await actionButton.moveMouseTo(); await actionButton.click(); } @@ -261,7 +271,7 @@ export class DataGridService extends FtrService { const controlsCount = await this.getControlColumnsCount(); const actionButton = await this.getCellActionButton( rowIndex, - controlsCount + columnIndex, + { columnIndex: controlsCount + columnIndex }, 'filterForButton' ); await actionButton.moveMouseTo(); @@ -269,7 +279,11 @@ export class DataGridService extends FtrService { } public async clickCellFilterOutButton(rowIndex: number = 0, columnIndex: number = 0) { - const actionButton = await this.getCellActionButton(rowIndex, columnIndex, 'filterOutButton'); + const actionButton = await this.getCellActionButton( + rowIndex, + { columnIndex }, + 'filterOutButton' + ); await actionButton.moveMouseTo(); await actionButton.click(); } @@ -281,7 +295,7 @@ export class DataGridService extends FtrService { const controlsCount = await this.getControlColumnsCount(); const actionButton = await this.getCellActionButton( rowIndex, - controlsCount + columnIndex, + { columnIndex: controlsCount + columnIndex }, 'filterOutButton' ); await actionButton.moveMouseTo(); diff --git a/test/kibana.jsonc b/test/kibana.jsonc index f133563c47ca..7f31b79cff9d 100644 --- a/test/kibana.jsonc +++ b/test/kibana.jsonc @@ -2,5 +2,7 @@ "type": "test-helper", "id": "@kbn/test-suites-src", "owner": [], + "group": "platform", + "visibility": "shared", "devOnly": true } diff --git a/test/plugin_functional/test_suites/panel_actions/index.ts b/test/plugin_functional/test_suites/panel_actions/index.ts index ad4871e961db..5e18d768acf1 100644 --- a/test/plugin_functional/test_suites/panel_actions/index.ts +++ b/test/plugin_functional/test_suites/panel_actions/index.ts @@ -19,7 +19,8 @@ export default function ({ const kibanaServer = getService('kibanaServer'); const { common, dashboard } = getPageObjects(['common', 'dashboard']); - describe('pluggable panel actions', function () { + // Failing: See https://github.com/elastic/kibana/issues/197475 + describe.skip('pluggable panel actions', function () { before(async () => { await browser.setWindowSize(1300, 900); await kibanaServer.savedObjects.cleanStandardList(); diff --git a/tsconfig.base.json b/tsconfig.base.json index 3e1d80208f5b..d8426dfdef12 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -414,6 +414,8 @@ "@kbn/core-http-server-internal/*": ["packages/core/http/core-http-server-internal/*"], "@kbn/core-http-server-mocks": ["packages/core/http/core-http-server-mocks"], "@kbn/core-http-server-mocks/*": ["packages/core/http/core-http-server-mocks/*"], + "@kbn/core-http-server-utils": ["packages/core/http/core-http-server-utils"], + "@kbn/core-http-server-utils/*": ["packages/core/http/core-http-server-utils/*"], "@kbn/core-i18n-browser": ["packages/core/i18n/core-i18n-browser"], "@kbn/core-i18n-browser/*": ["packages/core/i18n/core-i18n-browser/*"], "@kbn/core-i18n-browser-internal": ["packages/core/i18n/core-i18n-browser-internal"], @@ -754,6 +756,8 @@ "@kbn/default-nav-management/*": ["packages/default-nav/management/*"], "@kbn/default-nav-ml": ["packages/default-nav/ml"], "@kbn/default-nav-ml/*": ["packages/default-nav/ml/*"], + "@kbn/dependency-usage": ["packages/kbn-dependency-usage"], + "@kbn/dependency-usage/*": ["packages/kbn-dependency-usage/*"], "@kbn/dev-cli-errors": ["packages/kbn-dev-cli-errors"], "@kbn/dev-cli-errors/*": ["packages/kbn-dev-cli-errors/*"], "@kbn/dev-cli-runner": ["packages/kbn-dev-cli-runner"], @@ -820,6 +824,8 @@ "@kbn/entities-schema/*": ["x-pack/packages/kbn-entities-schema/*"], "@kbn/entity-manager-fixture-plugin": ["x-pack/test/api_integration/apis/entity_manager/fixture_plugin"], "@kbn/entity-manager-fixture-plugin/*": ["x-pack/test/api_integration/apis/entity_manager/fixture_plugin/*"], + "@kbn/entityManager-app-plugin": ["x-pack/plugins/observability_solution/entity_manager_app"], + "@kbn/entityManager-app-plugin/*": ["x-pack/plugins/observability_solution/entity_manager_app/*"], "@kbn/entityManager-plugin": ["x-pack/plugins/entity_manager"], "@kbn/entityManager-plugin/*": ["x-pack/plugins/entity_manager/*"], "@kbn/error-boundary-example-plugin": ["examples/error_boundary"], @@ -1562,6 +1568,8 @@ "@kbn/search-indices/*": ["x-pack/plugins/search_indices/*"], "@kbn/search-inference-endpoints": ["x-pack/plugins/search_inference_endpoints"], "@kbn/search-inference-endpoints/*": ["x-pack/plugins/search_inference_endpoints/*"], + "@kbn/search-navigation": ["x-pack/plugins/search_solution/search_navigation"], + "@kbn/search-navigation/*": ["x-pack/plugins/search_solution/search_navigation/*"], "@kbn/search-notebooks": ["x-pack/plugins/search_notebooks"], "@kbn/search-notebooks/*": ["x-pack/plugins/search_notebooks/*"], "@kbn/search-playground": ["x-pack/plugins/search_playground"], @@ -1840,6 +1848,8 @@ "@kbn/stdio-dev-helpers/*": ["packages/kbn-stdio-dev-helpers/*"], "@kbn/storybook": ["packages/kbn-storybook"], "@kbn/storybook/*": ["packages/kbn-storybook/*"], + "@kbn/streams-app-plugin": ["x-pack/plugins/streams_app"], + "@kbn/streams-app-plugin/*": ["x-pack/plugins/streams_app/*"], "@kbn/streams-plugin": ["x-pack/plugins/streams"], "@kbn/streams-plugin/*": ["x-pack/plugins/streams/*"], "@kbn/synthetics-e2e": ["x-pack/plugins/observability_solution/synthetics/e2e"], diff --git a/versions.json b/versions.json index c657a16ecc1a..0843e7d91262 100644 --- a/versions.json +++ b/versions.json @@ -8,13 +8,18 @@ "currentMinor": true }, { - "version": "8.17.0", + "version": "8.18.0", "branch": "8.x", "previousMajor": true, "previousMinor": true }, { - "version": "8.16.1", + "version": "8.17.0", + "branch": "8.17", + "previousMajor": true + }, + { + "version": "8.16.2", "branch": "8.16", "previousMajor": true }, diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index e1e8478aa051..213c3f06f34a 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -112,7 +112,9 @@ "xpack.observabilityLogsOverview": [ "packages/observability/logs_overview/src/components" ], - "xpack.osquery": ["plugins/osquery"], + "xpack.osquery": [ + "plugins/osquery" + ], "xpack.painlessLab": "plugins/painless_lab", "xpack.profiling": [ "plugins/observability_solution/profiling" @@ -130,6 +132,7 @@ "xpack.searchSharedUI": "packages/search/shared_ui", "xpack.searchHomepage": "plugins/search_homepage", "xpack.searchIndices": "plugins/search_indices", + "xpack.searchNavigation": "plugins/search_solution/search_navigation", "xpack.searchNotebooks": "plugins/search_notebooks", "xpack.searchPlayground": "plugins/search_playground", "xpack.searchInferenceEndpoints": "plugins/search_inference_endpoints", @@ -147,6 +150,9 @@ "xpack.securitySolutionEss": "plugins/security_solution_ess", "xpack.securitySolutionServerless": "plugins/security_solution_serverless", "xpack.sessionView": "plugins/session_view", + "xpack.streams": [ + "plugins/streams_app" + ], "xpack.slo": "plugins/observability_solution/slo", "xpack.snapshotRestore": "plugins/snapshot_restore", "xpack.spaces": "plugins/spaces", diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx index bebcb0aa8830..04f90dfbb96d 100644 --- a/x-pack/examples/embedded_lens_example/public/app.tsx +++ b/x-pack/examples/embedded_lens_example/public/app.tsx @@ -23,7 +23,6 @@ import type { TypedLensByValueInput, PersistedIndexPatternLayer, XYState, - LensEmbeddableInput, FormulaPublicApi, DateHistogramIndexPatternColumn, } from '@kbn/lens-plugin/public'; @@ -288,7 +287,7 @@ export const App = (props: { /> {isSaveModalVisible && ( {}} onClose={() => setIsSaveModalVisible(false)} /> diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/app.tsx b/x-pack/examples/lens_embeddable_inline_editing_example/public/app.tsx index 055050de3f4c..68d7140badb3 100644 --- a/x-pack/examples/lens_embeddable_inline_editing_example/public/app.tsx +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/app.tsx @@ -24,7 +24,6 @@ import type { CoreStart } from '@kbn/core/public'; import { LensConfigBuilder } from '@kbn/lens-embeddable-utils/config_builder/config_builder'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { StartDependencies } from './plugin'; import { LensChart } from './embeddable'; import { MultiPaneFlyout } from './flyout'; @@ -46,137 +45,128 @@ export const App = (props: { ); return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + - -

#3: Embeddable inside a flyout

-
- - -

- In case you do not want to use a push flyout, you can check this example.{' '} -
- In this example, we have a Lens embeddable inside a flyout and we want to - render the inline editing Component in a second slot of the same flyout. -

-
- - - - { - setIsFlyoutVisible(true); - setPanelActive(3); +

#3: Embeddable inside a flyout

+
+ + +

+ In case you do not want to use a push flyout, you can check this example.
+ In this example, we have a Lens embeddable inside a flyout and we want to render + the inline editing Component in a second slot of the same flyout. +

+
+ + + + { + setIsFlyoutVisible(true); + setPanelActive(3); + }} + > + Show flyout + + {isFlyoutVisible ? ( + { + setIsinlineEditingVisible(false); + if (container) { + ReactDOM.unmountComponentAtNode(container); + } + }} + onCancelCb={() => { + setIsinlineEditingVisible(false); + if (container) { + ReactDOM.unmountComponentAtNode(container); + } + }} + isESQL + isActive + /> + ), + }} + inlineEditingContent={{ + visible: isInlineEditingVisible, + }} + setContainer={setContainer} + onClose={() => { + setIsFlyoutVisible(false); + setIsinlineEditingVisible(false); + setPanelActive(null); + if (container) { + ReactDOM.unmountComponentAtNode(container); + } }} - > - Show flyout -
- {isFlyoutVisible ? ( - { - setIsinlineEditingVisible(false); - if (container) { - ReactDOM.unmountComponentAtNode(container); - } - }} - onCancelCb={() => { - setIsinlineEditingVisible(false); - if (container) { - ReactDOM.unmountComponentAtNode(container); - } - }} - isESQL - isActive - /> - ), - }} - inlineEditingContent={{ - visible: isInlineEditingVisible, - }} - setContainer={setContainer} - onClose={() => { - setIsFlyoutVisible(false); - setIsinlineEditingVisible(false); - setPanelActive(null); - if (container) { - ReactDOM.unmountComponentAtNode(container); - } - }} - /> - ) : null} - - - - - - - - - + /> + ) : null} + + + + + + + + ); }; diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/embeddable.tsx b/x-pack/examples/lens_embeddable_inline_editing_example/public/embeddable.tsx index 717a8b2d20f8..a63264485bf5 100644 --- a/x-pack/examples/lens_embeddable_inline_editing_example/public/embeddable.tsx +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/embeddable.tsx @@ -64,13 +64,13 @@ export const LensChart = (props: { ( isLoading: boolean, adapters: InlineEditLensEmbeddableContext['lensEvent']['adapters'] | undefined, - lensEmbeddableOutput$?: InlineEditLensEmbeddableContext['lensEvent']['embeddableOutput$'] + dataLoading$?: InlineEditLensEmbeddableContext['lensEvent']['dataLoading$'] ) => { const adapterTables = adapters?.tables?.tables; if (adapterTables && !isLoading) { setLensLoadEvent({ adapters, - embeddableOutput$: lensEmbeddableOutput$, + dataLoading$, }); } }, diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/public/mount.tsx b/x-pack/examples/lens_embeddable_inline_editing_example/public/mount.tsx index 411538e2df2c..86bf0757220d 100644 --- a/x-pack/examples/lens_embeddable_inline_editing_example/public/mount.tsx +++ b/x-pack/examples/lens_embeddable_inline_editing_example/public/mount.tsx @@ -10,6 +10,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { EuiCallOut } from '@elastic/eui'; import type { CoreSetup, AppMountParameters } from '@kbn/core/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { StartDependencies } from './plugin'; export const mount = @@ -21,10 +22,15 @@ export const mount = const dataView = await plugins.dataViews.getDefaultDataView(); const stateHelpers = await plugins.lens.stateHelperApi(); - const i18nCore = core.i18n; - const reactElement = ( - + {dataView ? ( You need at least one dataview for this demo to work

)} -
+ ); render(reactElement, element); diff --git a/x-pack/examples/lens_embeddable_inline_editing_example/tsconfig.json b/x-pack/examples/lens_embeddable_inline_editing_example/tsconfig.json index e4727650106b..104bfbeeacd7 100644 --- a/x-pack/examples/lens_embeddable_inline_editing_example/tsconfig.json +++ b/x-pack/examples/lens_embeddable_inline_editing_example/tsconfig.json @@ -19,8 +19,8 @@ "@kbn/developer-examples-plugin", "@kbn/data-views-plugin", "@kbn/ui-actions-plugin", - "@kbn/kibana-react-plugin", "@kbn/lens-embeddable-utils", "@kbn/ui-theme", + "@kbn/react-kibana-context-render", ] } diff --git a/x-pack/examples/testing_embedded_lens/public/app.tsx b/x-pack/examples/testing_embedded_lens/public/app.tsx index 9aa6a40fe20c..699db0d0dc64 100644 --- a/x-pack/examples/testing_embedded_lens/public/app.tsx +++ b/x-pack/examples/testing_embedded_lens/public/app.tsx @@ -29,7 +29,6 @@ import type { TypedLensByValueInput, PersistedIndexPatternLayer, XYState, - LensEmbeddableInput, DateHistogramIndexPatternColumn, DatatableVisualizationState, HeatmapVisualizationState, @@ -42,7 +41,6 @@ import type { MetricVisualizationState, } from '@kbn/lens-plugin/public'; import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { CodeEditor, HJsonLang } from '@kbn/code-editor'; import type { StartDependencies } from './plugin'; import { @@ -496,269 +494,256 @@ export const App = (props: { const [overrides, setOverrides] = useState(); return ( - - - - - - - - - - -

- This app embeds a Lens visualization by specifying the configuration. Data - fetching and rendering is completely managed by Lens itself. -

-

- The editor on the right hand side make it possible to paste a Lens - attributes configuration, and have it rendered. Presets are available to - have a starting configuration, and new presets can be saved as well (not - persisted). -

-

- The Open with Lens button will take the current configuration and navigate - to a prefilled editor. -

- - - - - - - - - - - + + + + + + + + + +

+ This app embeds a Lens visualization by specifying the configuration. Data + fetching and rendering is completely managed by Lens itself. +

+

+ The editor on the right hand side make it possible to paste a Lens attributes + configuration, and have it rendered. Presets are available to have a starting + configuration, and new presets can be saved as well (not persisted). +

+

+ The Open with Lens button will take the current configuration and navigate to + a prefilled editor. +

+ + + + + + + + + + + + + { + setIsSaveModalVisible(true); + }} + > + Save Visualization + + + {props.defaultDataView?.isTimeBased() ? ( { - setIsSaveModalVisible(true); - }} - > - Save Visualization - - - {props.defaultDataView?.isTimeBased() ? ( - - { - setTime( - time.to === 'now' - ? { - from: '2015-09-18T06:31:44.000Z', - to: '2015-09-23T18:31:44.000Z', - } - : { - from: 'now-5d', - to: 'now', - } - ); - }} - > - {time.to === 'now' ? 'Change time range' : 'Reset time range'} - - - ) : null} - - { - props.plugins.lens.navigateToPrefilledEditor( - { - id: '', - timeRange: time, - attributes: currentAttributes, - }, - { - openInNewTab: true, - } + setTime( + time.to === 'now' + ? { + from: '2015-09-18T06:31:44.000Z', + to: '2015-09-23T18:31:44.000Z', + } + : { + from: 'now-5d', + to: 'now', + } ); }} > - Open in Lens (new tab) + {time.to === 'now' ? 'Change time range' : 'Reset time range'} - -

State: {isLoading ? 'Loading...' : 'Rendered'}

-
-
- - - { - setIsLoading(val); - }} - onBrushEnd={({ range }) => { - setTime({ - from: new Date(range[0]).toISOString(), - to: new Date(range[1]).toISOString(), - }); - }} - onFilter={(_data) => { - // call back event for on filter event - }} - onTableRowClick={(_data) => { - // call back event for on table row click event - }} - disableTriggers={!enableTriggers} - viewMode={ViewMode.VIEW} - withDefaultActions={enableDefaultAction} - extraActions={ - enableExtraAction - ? [ - { - id: 'testAction', - type: 'link', - getIconType: () => 'save', - async isCompatible( - context: ActionExecutionContext - ): Promise { - return true; - }, - execute: async (context: ActionExecutionContext) => { - alert('I am an extra action'); - return; - }, - getDisplayName: () => 'Extra action', - }, - ] - : undefined - } - /> - - - - {isSaveModalVisible && ( - {}} - onClose={() => setIsSaveModalVisible(false)} - /> - )} - - - - - - -

Paste or edit here your Lens document

-
-
-
- - - ({ value: i, text: id }))} - value={undefined} - onChange={(e) => switchChartPreset(+e.target.value)} - aria-label="Load from a preset" - prepend={'Load preset'} - /> - - - { - const attributes = checkAndParseSO(currentSO.current); - if (attributes) { - const label = `custom-chart-${chartCounter}`; - addChartConfiguration([ - ...loadedCharts, + ) : null} + + { + props.plugins.lens.navigateToPrefilledEditor( + { + id: '', + timeRange: time, + attributes: currentAttributes, + }, + { + openInNewTab: true, + } + ); + }} + > + Open in Lens (new tab) + + + +

State: {isLoading ? 'Loading...' : 'Rendered'}

+
+
+ + + { + setIsLoading(val); + }} + onBrushEnd={({ range }) => { + setTime({ + from: new Date(range[0]).toISOString(), + to: new Date(range[1]).toISOString(), + }); + }} + onFilter={(_data) => { + // call back event for on filter event + }} + onTableRowClick={(_data) => { + // call back event for on table row click event + }} + disableTriggers={!enableTriggers} + viewMode={ViewMode.VIEW} + withDefaultActions={enableDefaultAction} + extraActions={ + enableExtraAction + ? [ { - id: label, - attributes, + id: 'testAction', + type: 'link', + getIconType: () => 'save', + async isCompatible( + context: ActionExecutionContext + ): Promise { + return true; + }, + execute: async (context: ActionExecutionContext) => { + alert('I am an extra action'); + return; + }, + getDisplayName: () => 'Extra action', }, - ]); - chartCounter++; - alert(`The preset has been saved as "${label}"`); - } - }} - > - Save as preset - - - {hasParsingErrorDebounced && currentSO.current !== currentValid && ( - -

Check the spec

-
- )} - - - - { - const isValid = Boolean(checkAndParseSO(newSO)); - setErrorFlag(!isValid); - currentSO.current = newSO; - if (isValid) { - // reset the debounced error - setErrorDebounced(isValid); - saveValidSO(newSO); - } - }} - /> - - - - - - - - - - - + ] + : undefined + } + /> + + + + {isSaveModalVisible && ( + {}} + onClose={() => setIsSaveModalVisible(false)} + /> + )} + + + + + + +

Paste or edit here your Lens document

+
+
+
+ + + ({ value: i, text: id }))} + value={undefined} + onChange={(e) => switchChartPreset(+e.target.value)} + aria-label="Load from a preset" + prepend={'Load preset'} + /> + + + { + const attributes = checkAndParseSO(currentSO.current); + if (attributes) { + const label = `custom-chart-${chartCounter}`; + addChartConfiguration([ + ...loadedCharts, + { + id: label, + attributes, + }, + ]); + chartCounter++; + alert(`The preset has been saved as "${label}"`); + } + }} + > + Save as preset + + + {hasParsingErrorDebounced && currentSO.current !== currentValid && ( + +

Check the spec

+
+ )} +
+ + + { + const isValid = Boolean(checkAndParseSO(newSO)); + setErrorFlag(!isValid); + currentSO.current = newSO; + if (isValid) { + // reset the debounced error + setErrorDebounced(isValid); + saveValidSO(newSO); + } + }} + /> + + +
+
+ + + + + + ); }; diff --git a/x-pack/examples/testing_embedded_lens/public/mount.tsx b/x-pack/examples/testing_embedded_lens/public/mount.tsx index d0f58eb6050b..04099e125b96 100644 --- a/x-pack/examples/testing_embedded_lens/public/mount.tsx +++ b/x-pack/examples/testing_embedded_lens/public/mount.tsx @@ -11,6 +11,7 @@ import { EuiCallOut } from '@elastic/eui'; import type { CoreSetup, AppMountParameters } from '@kbn/core/public'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { StartDependencies } from './plugin'; export const mount = @@ -24,10 +25,15 @@ export const mount = const dataView = await plugins.data.indexPatterns.getDefault(); const stateHelpers = await plugins.lens.stateHelperApi(); - const i18nCore = core.i18n; - const reactElement = ( - + {dataView ? ( This demo only works if your default index pattern is set and time based

)} -
+ ); render(reactElement, element); diff --git a/x-pack/examples/testing_embedded_lens/tsconfig.json b/x-pack/examples/testing_embedded_lens/tsconfig.json index 90cf691a3529..efa0ebd803d9 100644 --- a/x-pack/examples/testing_embedded_lens/tsconfig.json +++ b/x-pack/examples/testing_embedded_lens/tsconfig.json @@ -21,8 +21,8 @@ "@kbn/developer-examples-plugin", "@kbn/data-views-plugin", "@kbn/ui-actions-plugin", - "@kbn/kibana-react-plugin", "@kbn/core-ui-settings-browser", "@kbn/code-editor", + "@kbn/react-kibana-context-render", ] } diff --git a/x-pack/packages/ai-infra/inference-common/index.ts b/x-pack/packages/ai-infra/inference-common/index.ts index 2791896c801e..4b5ef3a5cfda 100644 --- a/x-pack/packages/ai-infra/inference-common/index.ts +++ b/x-pack/packages/ai-infra/inference-common/index.ts @@ -34,6 +34,9 @@ export { type ChatCompleteStreamResponse, type ChatCompleteResponse, type ChatCompletionTokenCount, + type BoundChatCompleteAPI, + type BoundChatCompleteOptions, + type UnboundChatCompleteOptions, withoutTokenCountEvents, withoutChunkEvents, isChatCompletionMessageEvent, @@ -59,6 +62,9 @@ export { type OutputUpdateEvent, type Output, type OutputEvent, + type BoundOutputAPI, + type BoundOutputOptions, + type UnboundOutputOptions, isOutputCompleteEvent, isOutputUpdateEvent, isOutputEvent, diff --git a/x-pack/packages/ai-infra/inference-common/kibana.jsonc b/x-pack/packages/ai-infra/inference-common/kibana.jsonc index 568755d303c3..0e88162fa1b0 100644 --- a/x-pack/packages/ai-infra/inference-common/kibana.jsonc +++ b/x-pack/packages/ai-infra/inference-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/inference-common", - "owner": "@elastic/appex-ai-infra" -} + "owner": [ + "@elastic/appex-ai-infra" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts b/x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts new file mode 100644 index 000000000000..083620ed99a9 --- /dev/null +++ b/x-pack/packages/ai-infra/inference-common/src/chat_complete/bound_api.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ChatCompleteOptions, ChatCompleteCompositeResponse } from './api'; +import type { ToolOptions } from './tools'; + +/** + * Static options used to call the {@link BoundChatCompleteAPI} + */ +export type BoundChatCompleteOptions< + TToolOptions extends ToolOptions = ToolOptions, + TStream extends boolean = false +> = Pick, 'connectorId' | 'functionCalling'>; + +/** + * Options used to call the {@link BoundChatCompleteAPI} + */ +export type UnboundChatCompleteOptions< + TToolOptions extends ToolOptions = ToolOptions, + TStream extends boolean = false +> = Omit, 'connectorId' | 'functionCalling'>; + +/** + * Version of {@link ChatCompleteAPI} that got pre-bound to a set of static parameters + */ +export type BoundChatCompleteAPI = < + TToolOptions extends ToolOptions = ToolOptions, + TStream extends boolean = false +>( + options: UnboundChatCompleteOptions +) => ChatCompleteCompositeResponse; diff --git a/x-pack/packages/ai-infra/inference-common/src/chat_complete/index.ts b/x-pack/packages/ai-infra/inference-common/src/chat_complete/index.ts index ca69f39b273e..3daa898ab2e1 100644 --- a/x-pack/packages/ai-infra/inference-common/src/chat_complete/index.ts +++ b/x-pack/packages/ai-infra/inference-common/src/chat_complete/index.ts @@ -13,6 +13,11 @@ export type { ChatCompleteStreamResponse, ChatCompleteResponse, } from './api'; +export type { + BoundChatCompleteAPI, + BoundChatCompleteOptions, + UnboundChatCompleteOptions, +} from './bound_api'; export { ChatCompletionEventType, type ChatCompletionMessageEvent, diff --git a/x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts b/x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts new file mode 100644 index 000000000000..967dac20c056 --- /dev/null +++ b/x-pack/packages/ai-infra/inference-common/src/output/bound_api.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { OutputOptions, OutputCompositeResponse } from './api'; +import type { ToolSchema } from '../chat_complete/tool_schema'; + +/** + * Static options used to call the {@link BoundOutputAPI} + */ +export type BoundOutputOptions< + TId extends string = string, + TOutputSchema extends ToolSchema | undefined = ToolSchema | undefined, + TStream extends boolean = false +> = Pick, 'connectorId' | 'functionCalling'>; + +/** + * Options used to call the {@link BoundOutputAPI} + */ +export type UnboundOutputOptions< + TId extends string = string, + TOutputSchema extends ToolSchema | undefined = ToolSchema | undefined, + TStream extends boolean = false +> = Omit, 'connectorId' | 'functionCalling'>; + +/** + * Version of {@link OutputAPI} that got pre-bound to a set of static parameters + */ +export type BoundOutputAPI = < + TId extends string = string, + TOutputSchema extends ToolSchema | undefined = ToolSchema | undefined, + TStream extends boolean = false +>( + options: UnboundOutputOptions +) => OutputCompositeResponse; diff --git a/x-pack/packages/ai-infra/inference-common/src/output/index.ts b/x-pack/packages/ai-infra/inference-common/src/output/index.ts index a3039005b2f7..d4e17967b50f 100644 --- a/x-pack/packages/ai-infra/inference-common/src/output/index.ts +++ b/x-pack/packages/ai-infra/inference-common/src/output/index.ts @@ -12,6 +12,7 @@ export type { OutputResponse, OutputStreamResponse, } from './api'; +export type { BoundOutputAPI, BoundOutputOptions, UnboundOutputOptions } from './bound_api'; export { OutputEventType, type OutputCompleteEvent, diff --git a/x-pack/packages/ai-infra/product-doc-common/kibana.jsonc b/x-pack/packages/ai-infra/product-doc-common/kibana.jsonc index 16336c1fc8e2..ed5332676de2 100644 --- a/x-pack/packages/ai-infra/product-doc-common/kibana.jsonc +++ b/x-pack/packages/ai-infra/product-doc-common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/product-doc-common", - "owner": "@elastic/appex-ai-infra" -} + "owner": [ + "@elastic/appex-ai-infra" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/index-lifecycle-management/index_lifecycle_management_common_shared/kibana.jsonc b/x-pack/packages/index-lifecycle-management/index_lifecycle_management_common_shared/kibana.jsonc index dfaef1d0dfb9..8e17b9e108b6 100644 --- a/x-pack/packages/index-lifecycle-management/index_lifecycle_management_common_shared/kibana.jsonc +++ b/x-pack/packages/index-lifecycle-management/index_lifecycle_management_common_shared/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/index-lifecycle-management-common-shared", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/index-management/index_management_shared_types/kibana.jsonc b/x-pack/packages/index-management/index_management_shared_types/kibana.jsonc index ad87bb2bb479..db696168a35a 100644 --- a/x-pack/packages/index-management/index_management_shared_types/kibana.jsonc +++ b/x-pack/packages/index-management/index_management_shared_types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/index-management-shared-types", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc b/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc index 8babdaccbd2d..41dcc5eac404 100644 --- a/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc +++ b/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc @@ -1,5 +1,9 @@ { + "type": "shared-common", "id": "@kbn/ai-assistant-common", - "owner": "@elastic/search-kibana", - "type": "shared-common" -} + "owner": [ + "@elastic/search-kibana" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-alerting-comparators/kibana.jsonc b/x-pack/packages/kbn-alerting-comparators/kibana.jsonc index 94ac1e532ab1..cbc7950c05df 100644 --- a/x-pack/packages/kbn-alerting-comparators/kibana.jsonc +++ b/x-pack/packages/kbn-alerting-comparators/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/alerting-comparators", - "owner": "@elastic/response-ops" + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "shared" } \ No newline at end of file diff --git a/x-pack/packages/kbn-alerting-state-types/kibana.jsonc b/x-pack/packages/kbn-alerting-state-types/kibana.jsonc index 6c37f923f760..03fa08fd14a0 100644 --- a/x-pack/packages/kbn-alerting-state-types/kibana.jsonc +++ b/x-pack/packages/kbn-alerting-state-types/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/alerting-state-types", - "owner": "@elastic/response-ops" -} + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-cloud-security-posture/common/kibana.jsonc b/x-pack/packages/kbn-cloud-security-posture/common/kibana.jsonc index f3bd18f10c7a..02e924b91e13 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/kibana.jsonc +++ b/x-pack/packages/kbn-cloud-security-posture/common/kibana.jsonc @@ -1,5 +1,9 @@ { + "type": "shared-common", "id": "@kbn/cloud-security-posture-common", - "owner": "@elastic/kibana-cloud-security-posture", - "type": "shared-common" -} + "owner": [ + "@elastic/kibana-cloud-security-posture" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-cloud-security-posture/graph/kibana.jsonc b/x-pack/packages/kbn-cloud-security-posture/graph/kibana.jsonc index 513861b34705..72fe9f90b58c 100644 --- a/x-pack/packages/kbn-cloud-security-posture/graph/kibana.jsonc +++ b/x-pack/packages/kbn-cloud-security-posture/graph/kibana.jsonc @@ -1,5 +1,9 @@ { + "type": "shared-browser", "id": "@kbn/cloud-security-posture-graph", - "owner": "@elastic/kibana-cloud-security-posture", - "type": "shared-browser" -} + "owner": [ + "@elastic/kibana-cloud-security-posture" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-cloud-security-posture/public/kibana.jsonc b/x-pack/packages/kbn-cloud-security-posture/public/kibana.jsonc index 811a1ab5dad4..1e30b747cc41 100644 --- a/x-pack/packages/kbn-cloud-security-posture/public/kibana.jsonc +++ b/x-pack/packages/kbn-cloud-security-posture/public/kibana.jsonc @@ -1,5 +1,9 @@ { + "type": "shared-browser", "id": "@kbn/cloud-security-posture", - "owner": "@elastic/kibana-cloud-security-posture", - "type": "shared-browser" -} + "owner": [ + "@elastic/kibana-cloud-security-posture" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_misconfigurations.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_misconfigurations.ts new file mode 100644 index 000000000000..9b126332b567 --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_misconfigurations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { useMisconfigurationPreview } from './use_misconfiguration_preview'; + +export const useHasMisconfigurations = (field: 'host.name' | 'user.name', value: string) => { + const { data } = useMisconfigurationPreview({ + query: buildEntityFlyoutPreviewQuery(field, value), + sort: [], + enabled: true, + pageSize: 1, + }); + + const passedFindings = data?.count.passed || 0; + const failedFindings = data?.count.failed || 0; + + const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; + + return { + passedFindings, + failedFindings, + hasMisconfigurationFindings, + }; +}; diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_vulnerabilities.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_vulnerabilities.ts new file mode 100644 index 000000000000..336892ee888a --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_has_vulnerabilities.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { useVulnerabilitiesPreview } from './use_vulnerabilities_preview'; +import { hasVulnerabilitiesData } from '../utils/vulnerability_helpers'; + +export const useHasVulnerabilities = (field: 'host.name' | 'user.name', value: string) => { + const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({ + query: buildEntityFlyoutPreviewQuery(field, value), + sort: [], + enabled: true, + pageSize: 1, + }); + + const { + CRITICAL = 0, + HIGH = 0, + MEDIUM = 0, + LOW = 0, + NONE = 0, + } = vulnerabilitiesData?.count || {}; + + const counts = { + critical: CRITICAL, + high: HIGH, + medium: MEDIUM, + low: LOW, + none: NONE, + }; + + const hasVulnerabilitiesFindings = hasVulnerabilitiesData(counts); + + return { counts, hasVulnerabilitiesFindings }; +}; diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_navigate_findings.test.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_navigate_findings.test.ts index 30dcd8c7f2dd..d5bec5e200ff 100644 --- a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_navigate_findings.test.ts +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_navigate_findings.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks/dom'; +import { renderHook, act } from '@testing-library/react'; import { useNavigateVulnerabilities, useNavigateFindings } from './use_navigate_findings'; import { useHistory } from 'react-router-dom'; diff --git a/x-pack/packages/kbn-data-forge/kibana.jsonc b/x-pack/packages/kbn-data-forge/kibana.jsonc index 7a09830de0b3..8b2eb5fa3d0d 100644 --- a/x-pack/packages/kbn-data-forge/kibana.jsonc +++ b/x-pack/packages/kbn-data-forge/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/data-forge", - "owner": "@elastic/obs-ux-management-team" + "owner": [ + "@elastic/obs-ux-management-team" + ], + "group": "platform", + "visibility": "shared" } diff --git a/x-pack/packages/kbn-elastic-assistant-common/kibana.jsonc b/x-pack/packages/kbn-elastic-assistant-common/kibana.jsonc index fe36d45dd2a4..a509f30fa001 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/kibana.jsonc +++ b/x-pack/packages/kbn-elastic-assistant-common/kibana.jsonc @@ -1,5 +1,9 @@ { + "type": "shared-common", "id": "@kbn/elastic-assistant-common", - "owner": "@elastic/security-generative-ai", - "type": "shared-common" -} + "owner": [ + "@elastic/security-generative-ai" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-elastic-assistant/kibana.jsonc b/x-pack/packages/kbn-elastic-assistant/kibana.jsonc index 29490e1bd93c..3c6916f819b0 100644 --- a/x-pack/packages/kbn-elastic-assistant/kibana.jsonc +++ b/x-pack/packages/kbn-elastic-assistant/kibana.jsonc @@ -1,5 +1,9 @@ { + "type": "shared-browser", "id": "@kbn/elastic-assistant", - "owner": "@elastic/security-generative-ai", - "type": "shared-browser" -} + "owner": [ + "@elastic/security-generative-ai" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-entities-schema/kibana.jsonc b/x-pack/packages/kbn-entities-schema/kibana.jsonc index 732a640df908..a5e6fd29fe09 100644 --- a/x-pack/packages/kbn-entities-schema/kibana.jsonc +++ b/x-pack/packages/kbn-entities-schema/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/entities-schema", - "owner": "@elastic/obs-entities" -} + "owner": [ + "@elastic/obs-entities" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-entities-schema/src/schema/entity.ts b/x-pack/packages/kbn-entities-schema/src/schema/entity.ts index 7bfe505face1..5df10e11bb7e 100644 --- a/x-pack/packages/kbn-entities-schema/src/schema/entity.ts +++ b/x-pack/packages/kbn-entities-schema/src/schema/entity.ts @@ -23,6 +23,13 @@ export interface MetadataRecord { [key: string]: string[] | MetadataRecord | string; } +export interface EntityV2 { + 'entity.id': string; + 'entity.last_seen_timestamp': string; + 'entity.type': string; + [metadata: string]: any; +} + const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]); type Literal = z.infer; diff --git a/x-pack/packages/kbn-infra-forge/kibana.jsonc b/x-pack/packages/kbn-infra-forge/kibana.jsonc index a450d148358a..b68f360a7dee 100644 --- a/x-pack/packages/kbn-infra-forge/kibana.jsonc +++ b/x-pack/packages/kbn-infra-forge/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/infra-forge", - "owner": "@elastic/obs-ux-management-team" + "owner": [ + "@elastic/obs-ux-management-team", + ], + "group": "platform", + "visibility": "private" } diff --git a/x-pack/packages/kbn-langchain/kibana.jsonc b/x-pack/packages/kbn-langchain/kibana.jsonc index 5ef91bd2c8e6..5fa6ad589440 100644 --- a/x-pack/packages/kbn-langchain/kibana.jsonc +++ b/x-pack/packages/kbn-langchain/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/langchain", - "owner": "@elastic/security-generative-ai" -} + "owner": [ + "@elastic/security-generative-ai" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/bedrock_runtime_client.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/bedrock_runtime_client.ts index 359342870a8b..7f20591bd51a 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/bedrock_runtime_client.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/bedrock_runtime_client.ts @@ -8,12 +8,17 @@ import { BedrockRuntimeClient as _BedrockRuntimeClient, BedrockRuntimeClientConfig, + ConverseCommand, + ConverseResponse, + ConverseStreamCommand, + ConverseStreamResponse, } from '@aws-sdk/client-bedrock-runtime'; import { constructStack } from '@smithy/middleware-stack'; +import { HttpHandlerOptions } from '@smithy/types'; import { PublicMethodsOf } from '@kbn/utility-types'; import type { ActionsClient } from '@kbn/actions-plugin/server'; -import { NodeHttpHandler } from './node_http_handler'; +import { prepareMessages } from '../../utils/bedrock'; export interface CustomChatModelInput extends BedrockRuntimeClientConfig { actionsClient: PublicMethodsOf; @@ -23,15 +28,51 @@ export interface CustomChatModelInput extends BedrockRuntimeClientConfig { export class BedrockRuntimeClient extends _BedrockRuntimeClient { middlewareStack: _BedrockRuntimeClient['middlewareStack']; + streaming: boolean; + actionsClient: PublicMethodsOf; + connectorId: string; constructor({ actionsClient, connectorId, ...fields }: CustomChatModelInput) { super(fields ?? {}); - this.config.requestHandler = new NodeHttpHandler({ - streaming: fields.streaming ?? true, - actionsClient, - connectorId, - }); + this.streaming = fields.streaming ?? true; + this.actionsClient = actionsClient; + this.connectorId = connectorId; // eliminate middleware steps that handle auth as Kibana connector handles auth this.middlewareStack = constructStack() as _BedrockRuntimeClient['middlewareStack']; } + + public async send( + command: ConverseCommand | ConverseStreamCommand, + optionsOrCb?: HttpHandlerOptions | ((err: unknown, data: unknown) => void) + ) { + const options = typeof optionsOrCb !== 'function' ? optionsOrCb : {}; + if (command.input.messages) { + // without this, our human + human messages do not work and result in error: + // A conversation must alternate between user and assistant roles. + command.input.messages = prepareMessages(command.input.messages); + } + const data = (await this.actionsClient.execute({ + actionId: this.connectorId, + params: { + subAction: 'bedrockClientSend', + subActionParams: { + command, + signal: options?.abortSignal, + }, + }, + })) as { + data: ConverseResponse | ConverseStreamResponse; + status: string; + message?: string; + serviceMessage?: string; + }; + + if (data.status === 'error') { + throw new Error( + `ActionsClient BedrockRuntimeClient: action result status is error: ${data?.message} - ${data?.serviceMessage}` + ); + } + + return data.data; + } } diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/node_http_handler.test.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/node_http_handler.test.ts deleted file mode 100644 index ba8a1db1fbb0..000000000000 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/node_http_handler.test.ts +++ /dev/null @@ -1,125 +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 { NodeHttpHandler } from './node_http_handler'; -import { HttpRequest } from '@smithy/protocol-http'; -import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client/actions_client.mock'; -import { Readable } from 'stream'; -import { fromUtf8 } from '@smithy/util-utf8'; - -const mockActionsClient = actionsClientMock.create(); -const connectorId = 'mock-connector-id'; -const mockOutput = { - output: { - message: { - role: 'assistant', - content: [{ text: 'This is a response from the assistant.' }], - }, - }, - stopReason: 'end_turn', - usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 }, - metrics: { latencyMs: 123 }, - additionalModelResponseFields: {}, - trace: { guardrail: { modelOutput: ['Output text'] } }, -}; -describe('NodeHttpHandler', () => { - let handler: NodeHttpHandler; - - beforeEach(() => { - jest.clearAllMocks(); - handler = new NodeHttpHandler({ - streaming: false, - actionsClient: mockActionsClient, - connectorId, - }); - - mockActionsClient.execute.mockResolvedValue({ - data: mockOutput, - actionId: 'mock-action-id', - status: 'ok', - }); - }); - - it('handles non-streaming requests successfully', async () => { - const request = new HttpRequest({ - body: JSON.stringify({ messages: [] }), - }); - - const result = await handler.handle(request); - - expect(result.response.statusCode).toBe(200); - expect(result.response.headers['content-type']).toBe('application/json'); - expect(result.response.body).toStrictEqual(fromUtf8(JSON.stringify(mockOutput))); - }); - - it('handles streaming requests successfully', async () => { - handler = new NodeHttpHandler({ - streaming: true, - actionsClient: mockActionsClient, - connectorId, - }); - - const request = new HttpRequest({ - body: JSON.stringify({ messages: [] }), - }); - - const readable = new Readable(); - readable.push('streaming data'); - readable.push(null); - - mockActionsClient.execute.mockResolvedValue({ - data: readable, - status: 'ok', - actionId: 'mock-action-id', - }); - - const result = await handler.handle(request); - - expect(result.response.statusCode).toBe(200); - expect(result.response.body).toBe(readable); - }); - - it('throws an error for non-streaming requests with error status', async () => { - const request = new HttpRequest({ - body: JSON.stringify({ messages: [] }), - }); - - mockActionsClient.execute.mockResolvedValue({ - status: 'error', - message: 'error message', - serviceMessage: 'service error message', - actionId: 'mock-action-id', - }); - - await expect(handler.handle(request)).rejects.toThrow( - 'ActionsClientBedrockChat: action result status is error: error message - service error message' - ); - }); - - it('throws an error for streaming requests with error status', async () => { - handler = new NodeHttpHandler({ - streaming: true, - actionsClient: mockActionsClient, - connectorId, - }); - - const request = new HttpRequest({ - body: JSON.stringify({ messages: [] }), - }); - - mockActionsClient.execute.mockResolvedValue({ - status: 'error', - message: 'error message', - serviceMessage: 'service error message', - actionId: 'mock-action-id', - }); - - await expect(handler.handle(request)).rejects.toThrow( - 'ActionsClientBedrockChat: action result status is error: error message - service error message' - ); - }); -}); diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/node_http_handler.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/node_http_handler.ts deleted file mode 100644 index bd5143ef45d4..000000000000 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_bedrock_converse/node_http_handler.ts +++ /dev/null @@ -1,88 +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 { NodeHttpHandler as _NodeHttpHandler } from '@smithy/node-http-handler'; -import { HttpRequest, HttpResponse } from '@smithy/protocol-http'; -import { HttpHandlerOptions, NodeHttpHandlerOptions } from '@smithy/types'; -import { PublicMethodsOf } from '@kbn/utility-types'; -import type { ActionsClient } from '@kbn/actions-plugin/server'; -import { Readable } from 'stream'; -import { fromUtf8 } from '@smithy/util-utf8'; -import { ConverseResponse } from '@aws-sdk/client-bedrock-runtime'; -import { prepareMessages } from '../../utils/bedrock'; - -interface NodeHandlerOptions extends NodeHttpHandlerOptions { - streaming: boolean; - actionsClient: PublicMethodsOf; - connectorId: string; -} - -export class NodeHttpHandler extends _NodeHttpHandler { - streaming: boolean; - actionsClient: PublicMethodsOf; - connectorId: string; - constructor(options: NodeHandlerOptions) { - super(options); - this.streaming = options.streaming; - this.actionsClient = options.actionsClient; - this.connectorId = options.connectorId; - } - - async handle( - request: HttpRequest, - options: HttpHandlerOptions = {} - ): Promise<{ response: HttpResponse }> { - const body = JSON.parse(request.body); - const messages = prepareMessages(body.messages); - - if (this.streaming) { - const data = (await this.actionsClient.execute({ - actionId: this.connectorId, - params: { - subAction: 'converseStream', - subActionParams: { ...body, messages, signal: options.abortSignal }, - }, - })) as { data: Readable; status: string; message?: string; serviceMessage?: string }; - - if (data.status === 'error') { - throw new Error( - `ActionsClientBedrockChat: action result status is error: ${data?.message} - ${data?.serviceMessage}` - ); - } - - return { - response: { - statusCode: 200, - headers: {}, - body: data.data, - }, - }; - } - - const data = (await this.actionsClient.execute({ - actionId: this.connectorId, - params: { - subAction: 'converse', - subActionParams: { ...body, messages, signal: options.abortSignal }, - }, - })) as { data: ConverseResponse; status: string; message?: string; serviceMessage?: string }; - - if (data.status === 'error') { - throw new Error( - `ActionsClientBedrockChat: action result status is error: ${data?.message} - ${data?.serviceMessage}` - ); - } - - return { - response: { - statusCode: 200, - headers: { 'content-type': 'application/json' }, - body: fromUtf8(JSON.stringify(data.data)), - }, - }; - } -} diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.test.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.test.ts index 07fe252bd507..69086ffcd108 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.test.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.test.ts @@ -13,6 +13,7 @@ import { BaseMessage, HumanMessage, SystemMessage } from '@langchain/core/messag import { ActionsClientChatVertexAI } from './chat_vertex'; import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager'; import { GeminiContent } from '@langchain/google-common'; +import { FinishReason } from '@google/generative-ai'; const connectorId = 'mock-connector-id'; @@ -55,6 +56,74 @@ const mockStreamExecute = jest.fn().mockImplementation(() => { }; }); +const mockStreamExecuteWithGoodStopEvents = jest.fn().mockImplementation(() => { + const passThrough = new PassThrough(); + + // Write the data chunks to the stream + setTimeout(() => { + passThrough.write( + Buffer.from( + `data: {"candidates": [{"content": {"role": "model","parts": [{"text": "token1"}]}}],"modelVersion": "gemini-1.5-pro-001"}` + ) + ); + }); + setTimeout(() => { + passThrough.write( + Buffer.from( + `data: {"candidates": [{"content": {"role": "model","parts": [{"text": "token2"}]}}],"modelVersion": "gemini-1.5-pro-001"}` + ) + ); + }); + setTimeout(() => { + passThrough.write( + Buffer.from( + `data: {"candidates": [{"content": {"role": "model","parts": [{"text": "token3"}]},"finishReason": "${FinishReason.STOP}","safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.060086742,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.17106095},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE","probabilityScore": 0.16776322,"severity": "HARM_SEVERITY_LOW","severityScore": 0.37113687},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.124212936,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.17441037},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.05419875,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.03461887}]}],"usageMetadata": {"promptTokenCount": 1062,"candidatesTokenCount": 15,"totalTokenCount": 1077},"modelVersion": "gemini-1.5-pro-002"}` + ) + ); + // End the stream + passThrough.end(); + }); + + return { + data: passThrough, // PassThrough stream will act as the async iterator + status: 'ok', + }; +}); + +const mockStreamExecuteWithBadStopEvents = jest.fn().mockImplementation(() => { + const passThrough = new PassThrough(); + + // Write the data chunks to the stream + setTimeout(() => { + passThrough.write( + Buffer.from( + `data: {"candidates": [{"content": {"role": "model","parts": [{"text": "token1"}]}}],"modelVersion": "gemini-1.5-pro-001"}` + ) + ); + }); + setTimeout(() => { + passThrough.write( + Buffer.from( + `data: {"candidates": [{"content": {"role": "model","parts": [{"text": "token2"}]}}],"modelVersion": "gemini-1.5-pro-001"}` + ) + ); + }); + setTimeout(() => { + passThrough.write( + Buffer.from( + `data: {"candidates": [{"content": {"role": "model","parts": [{"text": "token3"}]},"finishReason": "${FinishReason.SAFETY}","safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE","probabilityScore": 0.060086742,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.17106095},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "HIGH","probabilityScore": 0.96776322,"severity": "HARM_SEVERITY_HIGH","severityScore": 0.97113687,"blocked":true},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE","probabilityScore": 0.124212936,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.17441037},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE","probabilityScore": 0.05419875,"severity": "HARM_SEVERITY_NEGLIGIBLE","severityScore": 0.03461887}]}],"usageMetadata": {"promptTokenCount": 1062,"candidatesTokenCount": 15,"totalTokenCount": 1077},"modelVersion": "gemini-1.5-pro-002"}` + ) + ); + // End the stream + passThrough.end(); + }); + + return { + data: passThrough, // PassThrough stream will act as the async iterator + status: 'ok', + }; +}); + const systemInstruction = 'Answer the following questions truthfully and as best you can.'; const callMessages = [ @@ -198,6 +267,59 @@ describe('ActionsClientChatVertexAI', () => { expect(handleLLMNewToken).toHaveBeenCalledWith('token2'); expect(handleLLMNewToken).toHaveBeenCalledWith('token3'); }); + it('includes tokens from finishReason: STOP', async () => { + actionsClient.execute.mockImplementationOnce(mockStreamExecuteWithGoodStopEvents); + + const actionsClientChatVertexAI = new ActionsClientChatVertexAI({ + ...defaultArgs, + actionsClient, + streaming: true, + }); + + const gen = actionsClientChatVertexAI._streamResponseChunks( + callMessages, + callOptions, + callRunManager + ); + + const chunks = []; + + for await (const chunk of gen) { + chunks.push(chunk); + } + + expect(chunks.map((c) => c.text)).toEqual(['token1', 'token2', 'token3']); + expect(handleLLMNewToken).toHaveBeenCalledTimes(3); + expect(handleLLMNewToken).toHaveBeenCalledWith('token1'); + expect(handleLLMNewToken).toHaveBeenCalledWith('token2'); + expect(handleLLMNewToken).toHaveBeenCalledWith('token3'); + }); + it('throws an error on bad stop events', async () => { + actionsClient.execute.mockImplementationOnce(mockStreamExecuteWithBadStopEvents); + + const actionsClientChatVertexAI = new ActionsClientChatVertexAI({ + ...defaultArgs, + actionsClient, + streaming: true, + }); + + const gen = actionsClientChatVertexAI._streamResponseChunks( + callMessages, + callOptions, + callRunManager + ); + + const chunks = []; + await expect(async () => { + for await (const chunk of gen) { + chunks.push(chunk); + } + }).rejects.toEqual( + Error( + `Gemini Utils: action result status is error. Candidate was blocked due to SAFETY - HARM_CATEGORY_DANGEROUS_CONTENT: HARM_SEVERITY_HIGH` + ) + ); + }); }); describe('message formatting', () => { diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.ts index 745c273c7958..7cea2d421a9d 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/chat_vertex/chat_vertex.ts @@ -130,7 +130,12 @@ export class ActionsClientChatVertexAI extends ChatVertexAI { partialStreamChunk += nextChunk; } - if (parsedStreamChunk !== null && !parsedStreamChunk.candidates?.[0]?.finishReason) { + if (parsedStreamChunk !== null) { + const errorMessage = convertResponseBadFinishReasonToErrorMsg(parsedStreamChunk); + if (errorMessage != null) { + throw new Error(errorMessage); + } + const response = { ...parsedStreamChunk, functionCalls: () => @@ -178,12 +183,6 @@ export class ActionsClientChatVertexAI extends ChatVertexAI { yield chunk; await runManager?.handleLLMNewToken(chunk.text ?? ''); } - } else if (parsedStreamChunk) { - // handle bad finish reason - const errorMessage = convertResponseBadFinishReasonToErrorMsg(parsedStreamChunk); - if (errorMessage != null) { - throw new Error(errorMessage); - } } } } diff --git a/x-pack/packages/kbn-langchain/server/language_models/gemini_chat.ts b/x-pack/packages/kbn-langchain/server/language_models/gemini_chat.ts index 700e26d5a0a1..f8755af19d78 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/gemini_chat.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/gemini_chat.ts @@ -199,7 +199,11 @@ export class ActionsClientGeminiChatModel extends ChatGoogleGenerativeAI { partialStreamChunk += nextChunk; } - if (parsedStreamChunk !== null && !parsedStreamChunk.candidates?.[0]?.finishReason) { + if (parsedStreamChunk !== null) { + const errorMessage = convertResponseBadFinishReasonToErrorMsg(parsedStreamChunk); + if (errorMessage != null) { + throw new Error(errorMessage); + } const response = { ...parsedStreamChunk, functionCalls: () => @@ -247,12 +251,6 @@ export class ActionsClientGeminiChatModel extends ChatGoogleGenerativeAI { yield chunk; await runManager?.handleLLMNewToken(chunk.text ?? ''); } - } else if (parsedStreamChunk) { - // handle bad finish reason - const errorMessage = convertResponseBadFinishReasonToErrorMsg(parsedStreamChunk); - if (errorMessage != null) { - throw new Error(errorMessage); - } } } } diff --git a/x-pack/packages/kbn-langchain/server/utils/bedrock.ts b/x-pack/packages/kbn-langchain/server/utils/bedrock.ts index 7c8c069e5eb5..b61144a5f9ad 100644 --- a/x-pack/packages/kbn-langchain/server/utils/bedrock.ts +++ b/x-pack/packages/kbn-langchain/server/utils/bedrock.ts @@ -10,6 +10,7 @@ import { finished } from 'stream/promises'; import { Logger } from '@kbn/core/server'; import { EventStreamCodec } from '@smithy/eventstream-codec'; import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; +import { Message } from '@aws-sdk/client-bedrock-runtime'; import { StreamParser } from './types'; export const parseBedrockStreamAsAsyncIterator = async function* ( @@ -227,7 +228,7 @@ function parseContent(content: Array<{ text?: string; type: string }>): string { * Prepare messages for the bedrock API by combining messages from the same role * @param messages */ -export const prepareMessages = (messages: Array<{ role: string; content: string[] }>) => +export const prepareMessages = (messages: Message[]) => messages.reduce((acc, { role, content }) => { const lastMessage = acc[acc.length - 1]; @@ -236,13 +237,13 @@ export const prepareMessages = (messages: Array<{ role: string; content: string[ return acc; } - if (lastMessage.role === role) { - acc[acc.length - 1].content = lastMessage.content.concat(content); + if (lastMessage.role === role && lastMessage.content) { + acc[acc.length - 1].content = lastMessage.content.concat(content || []); return acc; } return acc; - }, [] as Array<{ role: string; content: string[] }>); + }, [] as Message[]); export const DEFAULT_BEDROCK_MODEL = 'anthropic.claude-3-5-sonnet-20240620-v1:0'; export const DEFAULT_BEDROCK_REGION = 'us-east-1'; diff --git a/x-pack/packages/kbn-random-sampling/kibana.jsonc b/x-pack/packages/kbn-random-sampling/kibana.jsonc index 963ef9add20b..5f63a3a67b27 100644 --- a/x-pack/packages/kbn-random-sampling/kibana.jsonc +++ b/x-pack/packages/kbn-random-sampling/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/random-sampling", - "owner": "@elastic/kibana-visualizations", -} + "owner": [ + "@elastic/kibana-visualizations" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-slo-schema/kibana.jsonc b/x-pack/packages/kbn-slo-schema/kibana.jsonc index b4ca324fc112..7e4c7cab070b 100644 --- a/x-pack/packages/kbn-slo-schema/kibana.jsonc +++ b/x-pack/packages/kbn-slo-schema/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/slo-schema", - "owner": "@elastic/obs-ux-management-team" -} + "owner": [ + "@elastic/obs-ux-management-team" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/maps/vector_tile_utils/kibana.jsonc b/x-pack/packages/maps/vector_tile_utils/kibana.jsonc index 5e1e9957ecdf..1ec30b45e522 100644 --- a/x-pack/packages/maps/vector_tile_utils/kibana.jsonc +++ b/x-pack/packages/maps/vector_tile_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/maps-vector-tile-utils", - "owner": "@elastic/kibana-presentation" -} + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/agg_utils/kibana.jsonc b/x-pack/packages/ml/agg_utils/kibana.jsonc index 3c29356a24ad..8b39e5ee5678 100644 --- a/x-pack/packages/ml/agg_utils/kibana.jsonc +++ b/x-pack/packages/ml/agg_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-agg-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/aiops_change_point_detection/kibana.jsonc b/x-pack/packages/ml/aiops_change_point_detection/kibana.jsonc index 280a686665c6..29e42822d728 100644 --- a/x-pack/packages/ml/aiops_change_point_detection/kibana.jsonc +++ b/x-pack/packages/ml/aiops_change_point_detection/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/aiops-change-point-detection", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/aiops_common/kibana.jsonc b/x-pack/packages/ml/aiops_common/kibana.jsonc index d675cd402227..88dfda8bd28c 100644 --- a/x-pack/packages/ml/aiops_common/kibana.jsonc +++ b/x-pack/packages/ml/aiops_common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/aiops-common", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/aiops_components/kibana.jsonc b/x-pack/packages/ml/aiops_components/kibana.jsonc index 6df0d201312a..70a974dfba49 100644 --- a/x-pack/packages/ml/aiops_components/kibana.jsonc +++ b/x-pack/packages/ml/aiops_components/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/aiops-components", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/aiops_log_pattern_analysis/kibana.jsonc b/x-pack/packages/ml/aiops_log_pattern_analysis/kibana.jsonc index da2c590b49dc..042910e2f8a8 100644 --- a/x-pack/packages/ml/aiops_log_pattern_analysis/kibana.jsonc +++ b/x-pack/packages/ml/aiops_log_pattern_analysis/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/aiops-log-pattern-analysis", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/aiops_log_pattern_analysis/schema.ts b/x-pack/packages/ml/aiops_log_pattern_analysis/schema.ts index 72b1dbca3803..05254e72a651 100644 --- a/x-pack/packages/ml/aiops_log_pattern_analysis/schema.ts +++ b/x-pack/packages/ml/aiops_log_pattern_analysis/schema.ts @@ -38,7 +38,6 @@ export const indicesOptionsSchema = schema.object({ ), ignore_unavailable: schema.maybe(schema.boolean()), allow_no_indices: schema.maybe(schema.boolean()), - ignore_throttled: schema.maybe(schema.boolean()), }); /** diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/api/schema_v2.ts b/x-pack/packages/ml/aiops_log_rate_analysis/api/schema_v2.ts index 1e132f6f00e7..95c778a11845 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/api/schema_v2.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/api/schema_v2.ts @@ -45,6 +45,7 @@ export const aiopsLogRateAnalysisBase = schema.object({ end: schema.number(), searchQuery: schema.string(), timeFieldName: schema.string(), + // when v2 is removed, includeFrozen should not carry over to v3+ includeFrozen: schema.maybe(schema.boolean()), grouping: schema.maybe(schema.boolean()), /** Analysis selection time ranges */ diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/kibana.jsonc b/x-pack/packages/ml/aiops_log_rate_analysis/kibana.jsonc index 210878e009a2..8b9f6a222583 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/kibana.jsonc +++ b/x-pack/packages/ml/aiops_log_rate_analysis/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/aiops-log-rate-analysis", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/params_match_all.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/params_match_all.ts index a81ba523caa4..36d63c883ecd 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/params_match_all.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/params_match_all.ts @@ -14,6 +14,5 @@ export const paramsMock = { baselineMax: 20, deviationMin: 30, deviationMax: 40, - includeFrozen: false, searchQuery: '{ "match_all": {} }', }; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/get_request_base.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/get_request_base.test.ts deleted file mode 100644 index 33797e219fd3..000000000000 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/get_request_base.test.ts +++ /dev/null @@ -1,25 +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 { paramsMock } from './__mocks__/params_match_all'; - -import { getRequestBase } from './get_request_base'; - -describe('getRequestBase', () => { - it('defaults to not setting `ignore_throttled`', () => { - const requestBase = getRequestBase(paramsMock); - expect(requestBase.ignore_throttled).toEqual(undefined); - }); - - it('adds `ignore_throttled=false` when `includeFrozen=true`', () => { - const requestBase = getRequestBase({ - ...paramsMock, - includeFrozen: true, - }); - expect(requestBase.ignore_throttled).toEqual(false); - }); -}); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/get_request_base.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/get_request_base.ts index 8083cb25c12b..8b16b5c05059 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/get_request_base.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/get_request_base.ts @@ -7,8 +7,7 @@ import type { AiopsLogRateAnalysisSchema } from '../api/schema'; -export const getRequestBase = ({ index, includeFrozen }: AiopsLogRateAnalysisSchema) => ({ +export const getRequestBase = ({ index }: AiopsLogRateAnalysisSchema) => ({ index, - ...(includeFrozen ? { ignore_throttled: false } : {}), ignore_unavailable: true, }); diff --git a/x-pack/packages/ml/aiops_test_utils/kibana.jsonc b/x-pack/packages/ml/aiops_test_utils/kibana.jsonc index 1c79f5adf34a..3af237ddc2c0 100644 --- a/x-pack/packages/ml/aiops_test_utils/kibana.jsonc +++ b/x-pack/packages/ml/aiops_test_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/aiops-test-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/anomaly_utils/kibana.jsonc b/x-pack/packages/ml/anomaly_utils/kibana.jsonc index 92fa54e6104d..2685dfbcaa2e 100644 --- a/x-pack/packages/ml/anomaly_utils/kibana.jsonc +++ b/x-pack/packages/ml/anomaly_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-anomaly-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/cancellable_search/kibana.jsonc b/x-pack/packages/ml/cancellable_search/kibana.jsonc index 2006bfd74671..8e7c129fd613 100644 --- a/x-pack/packages/ml/cancellable_search/kibana.jsonc +++ b/x-pack/packages/ml/cancellable_search/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-cancellable-search", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/category_validator/kibana.jsonc b/x-pack/packages/ml/category_validator/kibana.jsonc index de1fea187f3c..08e1dd96edd2 100644 --- a/x-pack/packages/ml/category_validator/kibana.jsonc +++ b/x-pack/packages/ml/category_validator/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-category-validator", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/chi2test/kibana.jsonc b/x-pack/packages/ml/chi2test/kibana.jsonc index 073ffe15d429..29d73318e1d4 100644 --- a/x-pack/packages/ml/chi2test/kibana.jsonc +++ b/x-pack/packages/ml/chi2test/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-chi2test", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/creation_wizard_utils/kibana.jsonc b/x-pack/packages/ml/creation_wizard_utils/kibana.jsonc index 158d81188350..01481fbf943d 100644 --- a/x-pack/packages/ml/creation_wizard_utils/kibana.jsonc +++ b/x-pack/packages/ml/creation_wizard_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-creation-wizard-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/data_frame_analytics_utils/kibana.jsonc b/x-pack/packages/ml/data_frame_analytics_utils/kibana.jsonc index 9a25068100ab..78d1168b0495 100644 --- a/x-pack/packages/ml/data_frame_analytics_utils/kibana.jsonc +++ b/x-pack/packages/ml/data_frame_analytics_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-data-frame-analytics-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/data_grid/hooks/use_column_chart.test.tsx b/x-pack/packages/ml/data_grid/hooks/use_column_chart.test.tsx index 7a5493eedcfd..4b8191de1d87 100644 --- a/x-pack/packages/ml/data_grid/hooks/use_column_chart.test.tsx +++ b/x-pack/packages/ml/data_grid/hooks/use_column_chart.test.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, renderHook } from '@testing-library/react'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; diff --git a/x-pack/packages/ml/data_grid/kibana.jsonc b/x-pack/packages/ml/data_grid/kibana.jsonc index d13bf0f37aec..517acb32d025 100644 --- a/x-pack/packages/ml/data_grid/kibana.jsonc +++ b/x-pack/packages/ml/data_grid/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-data-grid", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/data_view_utils/kibana.jsonc b/x-pack/packages/ml/data_view_utils/kibana.jsonc index 41251d1f7cbc..a97ecab13383 100644 --- a/x-pack/packages/ml/data_view_utils/kibana.jsonc +++ b/x-pack/packages/ml/data_view_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-data-view-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/date_picker/kibana.jsonc b/x-pack/packages/ml/date_picker/kibana.jsonc index d5ba9fb472f9..655a77472525 100644 --- a/x-pack/packages/ml/date_picker/kibana.jsonc +++ b/x-pack/packages/ml/date_picker/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/ml-date-picker", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/date_picker/src/hooks/use_timefilter.test.ts b/x-pack/packages/ml/date_picker/src/hooks/use_timefilter.test.ts index 4b54b74d900f..86a2c127939c 100644 --- a/x-pack/packages/ml/date_picker/src/hooks/use_timefilter.test.ts +++ b/x-pack/packages/ml/date_picker/src/hooks/use_timefilter.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useDatePickerContext } from './use_date_picker_context'; import { useTimefilter } from './use_timefilter'; diff --git a/x-pack/packages/ml/date_utils/kibana.jsonc b/x-pack/packages/ml/date_utils/kibana.jsonc index dd8f2187bacb..ccf73f9d922b 100644 --- a/x-pack/packages/ml/date_utils/kibana.jsonc +++ b/x-pack/packages/ml/date_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-date-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/error_utils/kibana.jsonc b/x-pack/packages/ml/error_utils/kibana.jsonc index 7629766aca7a..fa13892db876 100644 --- a/x-pack/packages/ml/error_utils/kibana.jsonc +++ b/x-pack/packages/ml/error_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-error-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/field_stats_flyout/kibana.jsonc b/x-pack/packages/ml/field_stats_flyout/kibana.jsonc index 4c362fcc84e0..d6c4fc392f88 100644 --- a/x-pack/packages/ml/field_stats_flyout/kibana.jsonc +++ b/x-pack/packages/ml/field_stats_flyout/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/ml-field-stats-flyout", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/in_memory_table/kibana.jsonc b/x-pack/packages/ml/in_memory_table/kibana.jsonc index cb50985bc615..b1dd95dc080d 100644 --- a/x-pack/packages/ml/in_memory_table/kibana.jsonc +++ b/x-pack/packages/ml/in_memory_table/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-in-memory-table", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/inference_integration_flyout/kibana.jsonc b/x-pack/packages/ml/inference_integration_flyout/kibana.jsonc index f7657078bb78..6cf8955917b5 100644 --- a/x-pack/packages/ml/inference_integration_flyout/kibana.jsonc +++ b/x-pack/packages/ml/inference_integration_flyout/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/inference_integration_flyout", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/is_defined/kibana.jsonc b/x-pack/packages/ml/is_defined/kibana.jsonc index b25718598901..43b4d3a5fdae 100644 --- a/x-pack/packages/ml/is_defined/kibana.jsonc +++ b/x-pack/packages/ml/is_defined/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-is-defined", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/is_populated_object/kibana.jsonc b/x-pack/packages/ml/is_populated_object/kibana.jsonc index 1ef21fe110b1..719c85fa2434 100644 --- a/x-pack/packages/ml/is_populated_object/kibana.jsonc +++ b/x-pack/packages/ml/is_populated_object/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-is-populated-object", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/json_schemas/kibana.jsonc b/x-pack/packages/ml/json_schemas/kibana.jsonc index 4233a2938eca..cc9c6e710c31 100644 --- a/x-pack/packages/ml/json_schemas/kibana.jsonc +++ b/x-pack/packages/ml/json_schemas/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/json-schemas", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/json_schemas/src/put___ml_anomaly_detectors__job_id__schema.json b/x-pack/packages/ml/json_schemas/src/put___ml_anomaly_detectors__job_id__schema.json index 79a871f1ef3f..81e1d22bdad9 100644 --- a/x-pack/packages/ml/json_schemas/src/put___ml_anomaly_detectors__job_id__schema.json +++ b/x-pack/packages/ml/json_schemas/src/put___ml_anomaly_detectors__job_id__schema.json @@ -10765,10 +10765,6 @@ "ignore_unavailable": { "description": "If true, missing or closed indices are not included in the response.", "type": "boolean" - }, - "ignore_throttled": { - "description": "If true, concrete, expanded or aliased indices are ignored when frozen.", - "type": "boolean" } } }, @@ -10884,4 +10880,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/packages/ml/json_schemas/src/put___ml_datafeeds__datafeed_id__schema.json b/x-pack/packages/ml/json_schemas/src/put___ml_datafeeds__datafeed_id__schema.json index 575411eb3a8c..8bf2dbfc1f52 100644 --- a/x-pack/packages/ml/json_schemas/src/put___ml_datafeeds__datafeed_id__schema.json +++ b/x-pack/packages/ml/json_schemas/src/put___ml_datafeeds__datafeed_id__schema.json @@ -7932,10 +7932,6 @@ "ignore_unavailable": { "description": "If true, missing or closed indices are not included in the response.", "type": "boolean" - }, - "ignore_throttled": { - "description": "If true, concrete, expanded or aliased indices are ignored when frozen.", - "type": "boolean" } } }, @@ -8051,4 +8047,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/packages/ml/kibana_theme/kibana.jsonc b/x-pack/packages/ml/kibana_theme/kibana.jsonc index e9f16a153779..cbd58ef1489a 100644 --- a/x-pack/packages/ml/kibana_theme/kibana.jsonc +++ b/x-pack/packages/ml/kibana_theme/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-kibana-theme", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/local_storage/kibana.jsonc b/x-pack/packages/ml/local_storage/kibana.jsonc index 8afac70248f4..6b891cccb959 100644 --- a/x-pack/packages/ml/local_storage/kibana.jsonc +++ b/x-pack/packages/ml/local_storage/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-local-storage", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/nested_property/kibana.jsonc b/x-pack/packages/ml/nested_property/kibana.jsonc index 8256bcfc7d72..0d3c27d83334 100644 --- a/x-pack/packages/ml/nested_property/kibana.jsonc +++ b/x-pack/packages/ml/nested_property/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/ml-nested-property", - "owner": "@elastic/ml-ui" + "owner": "@elastic/ml-ui", + "group": "platform", + "visibility": "private" } diff --git a/x-pack/packages/ml/number_utils/kibana.jsonc b/x-pack/packages/ml/number_utils/kibana.jsonc index ca46d8896907..604ca1648739 100644 --- a/x-pack/packages/ml/number_utils/kibana.jsonc +++ b/x-pack/packages/ml/number_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-number-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/parse_interval/kibana.jsonc b/x-pack/packages/ml/parse_interval/kibana.jsonc index 6d8398c29b1c..bb83b183b6fb 100644 --- a/x-pack/packages/ml/parse_interval/kibana.jsonc +++ b/x-pack/packages/ml/parse_interval/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-parse-interval", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/query_utils/kibana.jsonc b/x-pack/packages/ml/query_utils/kibana.jsonc index a37eb31cb1da..85476d1de652 100644 --- a/x-pack/packages/ml/query_utils/kibana.jsonc +++ b/x-pack/packages/ml/query_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-query-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/random_sampler_utils/kibana.jsonc b/x-pack/packages/ml/random_sampler_utils/kibana.jsonc index 87b8e2ec0ca0..fc1185a36d95 100644 --- a/x-pack/packages/ml/random_sampler_utils/kibana.jsonc +++ b/x-pack/packages/ml/random_sampler_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-random-sampler-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/response_stream/kibana.jsonc b/x-pack/packages/ml/response_stream/kibana.jsonc index 7e8b96cc9d12..b1fd2969348d 100644 --- a/x-pack/packages/ml/response_stream/kibana.jsonc +++ b/x-pack/packages/ml/response_stream/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-response-stream", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/route_utils/kibana.jsonc b/x-pack/packages/ml/route_utils/kibana.jsonc index 8494cda1924d..91565f4296b6 100644 --- a/x-pack/packages/ml/route_utils/kibana.jsonc +++ b/x-pack/packages/ml/route_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-route-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/runtime_field_utils/kibana.jsonc b/x-pack/packages/ml/runtime_field_utils/kibana.jsonc index 5422abd1ac94..0790fca4ed7c 100644 --- a/x-pack/packages/ml/runtime_field_utils/kibana.jsonc +++ b/x-pack/packages/ml/runtime_field_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-runtime-field-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/string_hash/kibana.jsonc b/x-pack/packages/ml/string_hash/kibana.jsonc index 1573e9a97048..f454a38ac781 100644 --- a/x-pack/packages/ml/string_hash/kibana.jsonc +++ b/x-pack/packages/ml/string_hash/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-string-hash", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/time_buckets/kibana.jsonc b/x-pack/packages/ml/time_buckets/kibana.jsonc index 850c794948d2..53f61afe112f 100644 --- a/x-pack/packages/ml/time_buckets/kibana.jsonc +++ b/x-pack/packages/ml/time_buckets/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-time-buckets", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/trained_models_utils/kibana.jsonc b/x-pack/packages/ml/trained_models_utils/kibana.jsonc index 7481396bee40..9a09f09e77ba 100644 --- a/x-pack/packages/ml/trained_models_utils/kibana.jsonc +++ b/x-pack/packages/ml/trained_models_utils/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-trained-models-utils", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/ml/ui_actions/kibana.jsonc b/x-pack/packages/ml/ui_actions/kibana.jsonc index 999f955bc2e4..b9324eb47457 100644 --- a/x-pack/packages/ml/ui_actions/kibana.jsonc +++ b/x-pack/packages/ml/ui_actions/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-ui-actions", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/url_state/kibana.jsonc b/x-pack/packages/ml/url_state/kibana.jsonc index 8850913bfa8c..3579ef325730 100644 --- a/x-pack/packages/ml/url_state/kibana.jsonc +++ b/x-pack/packages/ml/url_state/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-url-state", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/ml/validators/kibana.jsonc b/x-pack/packages/ml/validators/kibana.jsonc index e747549d8e33..d1cae5ea7515 100644 --- a/x-pack/packages/ml/validators/kibana.jsonc +++ b/x-pack/packages/ml/validators/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/ml-validators", - "owner": "@elastic/ml-ui" -} + "owner": [ + "@elastic/ml-ui" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/observability/alert_details/kibana.jsonc b/x-pack/packages/observability/alert_details/kibana.jsonc index f63d6cf11995..a53cdcd28b20 100644 --- a/x-pack/packages/observability/alert_details/kibana.jsonc +++ b/x-pack/packages/observability/alert_details/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/observability-alert-details", - "owner": "@elastic/obs-ux-management-team" -} + "owner": [ + "@elastic/obs-ux-management-team" + ], + "group": "observability", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/observability/alerting_rule_utils/kibana.jsonc b/x-pack/packages/observability/alerting_rule_utils/kibana.jsonc index b1b3646302d1..ecaebb0582b2 100644 --- a/x-pack/packages/observability/alerting_rule_utils/kibana.jsonc +++ b/x-pack/packages/observability/alerting_rule_utils/kibana.jsonc @@ -1,5 +1,8 @@ { "type": "shared-common", "id": "@kbn/observability-alerting-rule-utils", - "owner": "@elastic/obs-ux-management-team" + "owner": "@elastic/obs-ux-management-team", + // TODO refactor and transfer owner / contents to response-ops / alerting + "group": "platform", + "visibility": "shared" } diff --git a/x-pack/packages/observability/get_padded_alert_time_range_util/kibana.jsonc b/x-pack/packages/observability/get_padded_alert_time_range_util/kibana.jsonc index 30797d6915c4..394dd91e0f3a 100644 --- a/x-pack/packages/observability/get_padded_alert_time_range_util/kibana.jsonc +++ b/x-pack/packages/observability/get_padded_alert_time_range_util/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/observability-get-padded-alert-time-range-util", - "owner": "@elastic/obs-ux-management-team" -} + "owner": [ + "@elastic/obs-ux-management-team" + ], + "group": "observability", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/observability/logs_overview/kibana.jsonc b/x-pack/packages/observability/logs_overview/kibana.jsonc index 90b337508672..34d8ac98a525 100644 --- a/x-pack/packages/observability/logs_overview/kibana.jsonc +++ b/x-pack/packages/observability/logs_overview/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/observability-logs-overview", - "owner": "@elastic/obs-ux-logs-team" + "owner": [ + "@elastic/obs-ux-logs-team" + ], + "group": "observability", + "visibility": "private" } diff --git a/x-pack/packages/observability/observability_utils/observability_utils_browser/kibana.jsonc b/x-pack/packages/observability/observability_utils/observability_utils_browser/kibana.jsonc index dbee36828d08..3786f797da39 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_browser/kibana.jsonc +++ b/x-pack/packages/observability/observability_utils/observability_utils_browser/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/observability-utils-browser", - "owner": "@elastic/observability-ui" + "owner": [ + "@elastic/observability-ui" + ], + "group": "observability", + "visibility": "private" } diff --git a/x-pack/packages/observability/observability_utils/observability_utils_common/kibana.jsonc b/x-pack/packages/observability/observability_utils/observability_utils_common/kibana.jsonc index eb120052e5b0..35916e1ec4ce 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_common/kibana.jsonc +++ b/x-pack/packages/observability/observability_utils/observability_utils_common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/observability-utils-common", - "owner": "@elastic/observability-ui" + "owner": [ + "@elastic/observability-ui" + ], + "group": "observability", + "visibility": "private" } diff --git a/x-pack/packages/observability/observability_utils/observability_utils_common/object/unflatten_object.test.ts b/x-pack/packages/observability/observability_utils/observability_utils_common/object/unflatten_object.test.ts index 22cee17bb1a6..aa25821b4ac1 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_common/object/unflatten_object.test.ts +++ b/x-pack/packages/observability/observability_utils/observability_utils_common/object/unflatten_object.test.ts @@ -37,4 +37,16 @@ describe('unflattenObject', () => { }, }); }); + + it('handles null values correctly', () => { + expect( + unflattenObject({ + 'agent.name': null, + }) + ).toEqual({ + agent: { + name: null, + }, + }); + }); }); diff --git a/x-pack/packages/observability/observability_utils/observability_utils_common/object/unflatten_object.ts b/x-pack/packages/observability/observability_utils/observability_utils_common/object/unflatten_object.ts index 83508d5d2dbf..8a4493905f1d 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_common/object/unflatten_object.ts +++ b/x-pack/packages/observability/observability_utils/observability_utils_common/object/unflatten_object.ts @@ -6,13 +6,17 @@ */ import { set } from '@kbn/safer-lodash-set'; +import { DedotObject } from '@kbn/utility-types'; -export function unflattenObject(source: Record, target: Record = {}) { +export function unflattenObject>( + source: T, + target: Record = {} +): DedotObject { // eslint-disable-next-line guard-for-in for (const key in source) { const val = source[key as keyof typeof source]; if (Array.isArray(val)) { - const unflattenedArray = val.map((item) => { + const unflattenedArray = val.map((item: unknown) => { if (item && typeof item === 'object' && !Array.isArray(item)) { return unflattenObject(item); } @@ -23,5 +27,6 @@ export function unflattenObject(source: Record, target: Record; } diff --git a/x-pack/packages/observability/observability_utils/observability_utils_common/tsconfig.json b/x-pack/packages/observability/observability_utils/observability_utils_common/tsconfig.json index 7954cdc946e9..e2226268918a 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_common/tsconfig.json +++ b/x-pack/packages/observability/observability_utils/observability_utils_common/tsconfig.json @@ -19,5 +19,6 @@ "@kbn/es-query", "@kbn/safer-lodash-set", "@kbn/inference-common", + "@kbn/utility-types", ] } diff --git a/x-pack/packages/observability/observability_utils/observability_utils_server/es/client/create_observability_es_client.ts b/x-pack/packages/observability/observability_utils/observability_utils_server/es/client/create_observability_es_client.ts index 78ed20a582bc..92c7b8d19e53 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_server/es/client/create_observability_es_client.ts +++ b/x-pack/packages/observability/observability_utils/observability_utils_server/es/client/create_observability_es_client.ts @@ -10,12 +10,15 @@ import type { FieldCapsRequest, FieldCapsResponse, MsearchRequest, + ScalarValue, SearchResponse, } from '@elastic/elasticsearch/lib/api/types'; import { withSpan } from '@kbn/apm-utils'; import type { ElasticsearchClient, Logger } from '@kbn/core/server'; -import type { ESQLSearchResponse, ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; -import { Required } from 'utility-types'; +import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; +import { Required, ValuesType } from 'utility-types'; +import { DedotObject } from '@kbn/utility-types'; +import { unflattenObject } from '@kbn/task-manager-plugin/server/metrics/lib'; import { esqlResultToPlainObjects } from '../esql_result_to_plain_objects'; type SearchRequest = ESSearchRequest & { @@ -24,19 +27,52 @@ type SearchRequest = ESSearchRequest & { size: number | boolean; }; -type EsqlQueryParameters = EsqlQueryRequest & { parseOutput?: boolean }; -type EsqlOutputParameters = Omit & { - parseOutput?: true; - format?: 'json'; - columnar?: false; -}; +export interface EsqlOptions { + transform?: 'none' | 'plain' | 'unflatten'; +} + +export type EsqlValue = ScalarValue | ScalarValue[]; + +export type EsqlOutput = Record; + +type MaybeUnflatten, TApply> = TApply extends true + ? DedotObject + : T; + +interface UnparsedEsqlResponseOf { + columns: Array<{ name: keyof TOutput; type: string }>; + values: Array>>; +} -type EsqlParameters = EsqlOutputParameters | EsqlQueryParameters; +interface ParsedEsqlResponseOf< + TOutput extends EsqlOutput, + TOptions extends EsqlOptions | undefined = { transform: 'none' } +> { + hits: Array< + MaybeUnflatten< + { + [key in keyof TOutput]: TOutput[key]; + }, + TOptions extends { transform: 'unflatten' } ? true : false + > + >; +} export type InferEsqlResponseOf< - TOutput = unknown, - TParameters extends EsqlParameters = EsqlParameters -> = TParameters extends EsqlOutputParameters ? TOutput[] : ESQLSearchResponse; + TOutput extends EsqlOutput, + TOptions extends EsqlOptions | undefined = { transform: 'none' } +> = TOptions extends { transform: 'plain' | 'unflatten' } + ? ParsedEsqlResponseOf + : UnparsedEsqlResponseOf; + +export type ObservabilityESSearchRequest = SearchRequest; + +export type ObservabilityEsQueryRequest = Omit; + +export type ParsedEsqlResponse = ParsedEsqlResponseOf; +export type UnparsedEsqlResponse = UnparsedEsqlResponseOf; + +export type EsqlQueryResponse = UnparsedEsqlResponse | ParsedEsqlResponse; /** * An Elasticsearch Client with a fully typed `search` method and built-in @@ -57,14 +93,18 @@ export interface ObservabilityElasticsearchClient { operationName: string, request: Required ): Promise; - esql( + esql( operationName: string, - parameters: TQueryParams - ): Promise>; - esql( + parameters: ObservabilityEsQueryRequest + ): Promise>; + esql< + TOutput extends EsqlOutput = EsqlOutput, + TEsqlOptions extends EsqlOptions = { transform: 'none' } + >( operationName: string, - parameters: TQueryParams - ): Promise>; + parameters: ObservabilityEsQueryRequest, + options: TEsqlOptions + ): Promise>; client: ElasticsearchClient; } @@ -109,32 +149,41 @@ export function createObservabilityEsClient({ }); }); }, - esql( + esql( operationName: string, - { parseOutput = true, format = 'json', columnar = false, ...parameters }: TSearchRequest - ) { - logger.trace(() => `Request (${operationName}):\n${JSON.stringify(parameters, null, 2)}`); - return withSpan({ name: operationName, labels: { plugin } }, () => { - return client.esql.query( - { ...parameters, format, columnar }, - { - querystring: { - drop_null_columns: true, - }, - } - ); - }) - .then((response) => { - logger.trace(() => `Response (${operationName}):\n${JSON.stringify(response, null, 2)}`); - - const esqlResponse = response as unknown as ESQLSearchResponse; - - const shouldParseOutput = parseOutput && !columnar && format === 'json'; - return shouldParseOutput ? esqlResultToPlainObjects(esqlResponse) : esqlResponse; - }) - .catch((error) => { - throw error; - }); + parameters: ObservabilityEsQueryRequest, + options?: EsqlOptions + ): Promise> { + return callWithLogger(operationName, parameters, () => { + return client.esql + .query( + { ...parameters }, + { + querystring: { + drop_null_columns: true, + }, + } + ) + .then((response) => { + const esqlResponse = response as unknown as UnparsedEsqlResponseOf; + + const transform = options?.transform ?? 'none'; + + if (transform === 'none') { + return esqlResponse; + } + + const parsedResponse = { hits: esqlResultToPlainObjects(esqlResponse) }; + + if (transform === 'plain') { + return parsedResponse; + } + + return { + hits: parsedResponse.hits.map((hit) => unflattenObject(hit)), + }; + }) as Promise>; + }); }, search( operationName: string, diff --git a/x-pack/packages/observability/observability_utils/observability_utils_server/es/esql_result_to_plain_objects.test.ts b/x-pack/packages/observability/observability_utils/observability_utils_server/es/esql_result_to_plain_objects.test.ts index 4557d0ba0bdd..55d77368cfdf 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_server/es/esql_result_to_plain_objects.test.ts +++ b/x-pack/packages/observability/observability_utils/observability_utils_server/es/esql_result_to_plain_objects.test.ts @@ -27,40 +27,15 @@ describe('esqlResultToPlainObjects', () => { expect(output).toEqual([{ name: 'Foo Bar' }]); }); - it('should return columns without "text" or "keyword" in their names', () => { + it('should not unflatten objects', () => { const result: ESQLSearchResponse = { columns: [ - { name: 'name.text', type: 'text' }, - { name: 'age', type: 'keyword' }, - ], - values: [ - ['Foo Bar', 30], - ['Foo Qux', 25], - ], - }; - const output = esqlResultToPlainObjects(result); - expect(output).toEqual([ - { name: 'Foo Bar', age: 30 }, - { name: 'Foo Qux', age: 25 }, - ]); - }); - - it('should handle mixed columns correctly', () => { - const result: ESQLSearchResponse = { - columns: [ - { name: 'name', type: 'text' }, - { name: 'name.text', type: 'text' }, - { name: 'age', type: 'keyword' }, - ], - values: [ - ['Foo Bar', 'Foo Bar', 30], - ['Foo Qux', 'Foo Qux', 25], + { name: 'name', type: 'keyword' }, + { name: 'name.nested', type: 'keyword' }, ], + values: [['Foo Bar', 'Bar Foo']], }; const output = esqlResultToPlainObjects(result); - expect(output).toEqual([ - { name: 'Foo Bar', age: 30 }, - { name: 'Foo Qux', age: 25 }, - ]); + expect(output).toEqual([{ name: 'Foo Bar', 'name.nested': 'Bar Foo' }]); }); }); diff --git a/x-pack/packages/observability/observability_utils/observability_utils_server/es/esql_result_to_plain_objects.ts b/x-pack/packages/observability/observability_utils/observability_utils_server/es/esql_result_to_plain_objects.ts index 34781153532c..53f54b608ca3 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_server/es/esql_result_to_plain_objects.ts +++ b/x-pack/packages/observability/observability_utils/observability_utils_server/es/esql_result_to_plain_objects.ts @@ -6,28 +6,24 @@ */ import type { ESQLSearchResponse } from '@kbn/es-types'; -import { unflattenObject } from '@kbn/observability-utils-common/object/unflatten_object'; -export function esqlResultToPlainObjects( - result: ESQLSearchResponse -): TDocument[] { - return result.values.map((row) => { - return unflattenObject( - row.reduce>((acc, value, index) => { - const column = result.columns[index]; +export function esqlResultToPlainObjects< + TDocument extends Record = Record +>(result: ESQLSearchResponse): TDocument[] { + return result.values.map((row): TDocument => { + return row.reduce>((acc, value, index) => { + const column = result.columns[index]; - if (!column) { - return acc; - } + if (!column) { + return acc; + } - // Removes the type suffix from the column name - const name = column.name.replace(/\.(text|keyword)$/, ''); - if (!acc[name]) { - acc[name] = value; - } + const name = column.name; + if (!acc[name]) { + acc[name] = value; + } - return acc; - }, {}) - ) as TDocument; + return acc; + }, {}) as TDocument; }); } diff --git a/x-pack/packages/observability/observability_utils/observability_utils_server/kibana.jsonc b/x-pack/packages/observability/observability_utils/observability_utils_server/kibana.jsonc index 4c2f20ef1491..7374ebddff22 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_server/kibana.jsonc +++ b/x-pack/packages/observability/observability_utils/observability_utils_server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/observability-utils-server", - "owner": "@elastic/observability-ui" + "owner": [ + "@elastic/observability-ui" + ], + "group": "observability", + "visibility": "private" } diff --git a/x-pack/packages/observability/observability_utils/observability_utils_server/tsconfig.json b/x-pack/packages/observability/observability_utils/observability_utils_server/tsconfig.json index f51d93089c62..f6dd781184b8 100644 --- a/x-pack/packages/observability/observability_utils/observability_utils_server/tsconfig.json +++ b/x-pack/packages/observability/observability_utils/observability_utils_server/tsconfig.json @@ -24,5 +24,7 @@ "@kbn/alerting-plugin", "@kbn/rule-registry-plugin", "@kbn/rule-data-utils", + "@kbn/utility-types", + "@kbn/task-manager-plugin", ] } diff --git a/x-pack/packages/observability/synthetics_test_data/kibana.jsonc b/x-pack/packages/observability/synthetics_test_data/kibana.jsonc index 94f80d9b59ca..74e5bd7a76c1 100644 --- a/x-pack/packages/observability/synthetics_test_data/kibana.jsonc +++ b/x-pack/packages/observability/synthetics_test_data/kibana.jsonc @@ -2,4 +2,6 @@ "type": "shared-common", "id": "@kbn/observability-synthetics-test-data", "owner": "@elastic/obs-ux-management-team", + "group": "observability", + "visibility": "private" } diff --git a/x-pack/packages/rollup/kibana.jsonc b/x-pack/packages/rollup/kibana.jsonc index 3961e7c7468e..160c2b999ce5 100644 --- a/x-pack/packages/rollup/kibana.jsonc +++ b/x-pack/packages/rollup/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/rollup", - "owner": "@elastic/kibana-management" -} + "owner": [ + "@elastic/kibana-management" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/search/shared_ui/kibana.jsonc b/x-pack/packages/search/shared_ui/kibana.jsonc index aedc015c1d6f..94c698a4cded 100644 --- a/x-pack/packages/search/shared_ui/kibana.jsonc +++ b/x-pack/packages/search/shared_ui/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/search-shared-ui", - "owner": "@elastic/search-kibana" + "owner": [ + "@elastic/search-kibana" + ], + "group": "search", + "visibility": "private" } \ No newline at end of file diff --git a/x-pack/packages/security-solution/data_table/kibana.jsonc b/x-pack/packages/security-solution/data_table/kibana.jsonc index 9695411a6530..027d2a426bf2 100644 --- a/x-pack/packages/security-solution/data_table/kibana.jsonc +++ b/x-pack/packages/security-solution/data_table/kibana.jsonc @@ -1,7 +1,9 @@ { "type": "shared-common", "id": "@kbn/securitysolution-data-table", - "owner": "@elastic/security-threat-hunting-investigations", + "owner": [ + "@elastic/security-threat-hunting-investigations" + ], "group": "security", "visibility": "private" -} +} \ No newline at end of file diff --git a/x-pack/packages/security-solution/distribution_bar/kibana.jsonc b/x-pack/packages/security-solution/distribution_bar/kibana.jsonc index 5c984aadba9c..49d0c41ae103 100644 --- a/x-pack/packages/security-solution/distribution_bar/kibana.jsonc +++ b/x-pack/packages/security-solution/distribution_bar/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/security-solution-distribution-bar", - "owner": "@elastic/kibana-cloud-security-posture" -} + "owner": [ + "@elastic/kibana-cloud-security-posture" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc b/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc index 30cc9cf24982..f5686c6d5cf9 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/ecs-data-quality-dashboard", - "owner": "@elastic/security-threat-hunting-explore" -} + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/security-solution/features/kibana.jsonc b/x-pack/packages/security-solution/features/kibana.jsonc index 0e5a360ea992..ce6c88cbc2f7 100644 --- a/x-pack/packages/security-solution/features/kibana.jsonc +++ b/x-pack/packages/security-solution/features/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/security-solution-features", - "owner": "@elastic/security-threat-hunting-explore" -} + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/security-solution/navigation/kibana.jsonc b/x-pack/packages/security-solution/navigation/kibana.jsonc index cec911937400..9b2d406eb08e 100644 --- a/x-pack/packages/security-solution/navigation/kibana.jsonc +++ b/x-pack/packages/security-solution/navigation/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/security-solution-navigation", - "owner": "@elastic/security-threat-hunting-explore" + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "security", + "visibility": "private" } diff --git a/x-pack/packages/security-solution/side_nav/kibana.jsonc b/x-pack/packages/security-solution/side_nav/kibana.jsonc index 88e34019f44b..0b83d7c4d44b 100644 --- a/x-pack/packages/security-solution/side_nav/kibana.jsonc +++ b/x-pack/packages/security-solution/side_nav/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/security-solution-side-nav", - "owner": "@elastic/security-threat-hunting-explore" -} + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/security-solution/upselling/kibana.jsonc b/x-pack/packages/security-solution/upselling/kibana.jsonc index 7cf9a3026f18..b28572062b89 100644 --- a/x-pack/packages/security-solution/upselling/kibana.jsonc +++ b/x-pack/packages/security-solution/upselling/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/security-solution-upselling", - "owner": "@elastic/security-threat-hunting-explore" -} + "owner": [ + "@elastic/security-threat-hunting-explore" + ], + "group": "security", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/security/api_key_management/kibana.jsonc b/x-pack/packages/security/api_key_management/kibana.jsonc index 16e9244e4927..9f467a2c5369 100644 --- a/x-pack/packages/security/api_key_management/kibana.jsonc +++ b/x-pack/packages/security/api_key_management/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/security-api-key-management", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/security/authorization_core/kibana.jsonc b/x-pack/packages/security/authorization_core/kibana.jsonc index f2e33db5c8a8..513d8ddc84d8 100644 --- a/x-pack/packages/security/authorization_core/kibana.jsonc +++ b/x-pack/packages/security/authorization_core/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/security-authorization-core", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/security/authorization_core_common/kibana.jsonc b/x-pack/packages/security/authorization_core_common/kibana.jsonc index 1ddb58d87582..9ff7f5d0cae8 100644 --- a/x-pack/packages/security/authorization_core_common/kibana.jsonc +++ b/x-pack/packages/security/authorization_core_common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/security-authorization-core-common", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/security/form_components/kibana.jsonc b/x-pack/packages/security/form_components/kibana.jsonc index 44f54ee5fe4c..b78de9f2f9c3 100644 --- a/x-pack/packages/security/form_components/kibana.jsonc +++ b/x-pack/packages/security/form_components/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/security-form-components", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/security/plugin_types_common/kibana.jsonc b/x-pack/packages/security/plugin_types_common/kibana.jsonc index 714eb0f564cd..37202115cacf 100644 --- a/x-pack/packages/security/plugin_types_common/kibana.jsonc +++ b/x-pack/packages/security/plugin_types_common/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/security-plugin-types-common", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/security/plugin_types_public/kibana.jsonc b/x-pack/packages/security/plugin_types_public/kibana.jsonc index f4fbe8fe8ea7..a9e235915ded 100644 --- a/x-pack/packages/security/plugin_types_public/kibana.jsonc +++ b/x-pack/packages/security/plugin_types_public/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-browser", "id": "@kbn/security-plugin-types-public", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/security/plugin_types_server/kibana.jsonc b/x-pack/packages/security/plugin_types_server/kibana.jsonc index e4f4a074f6e7..40ada7902e5e 100644 --- a/x-pack/packages/security/plugin_types_server/kibana.jsonc +++ b/x-pack/packages/security/plugin_types_server/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-server", "id": "@kbn/security-plugin-types-server", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/x-pack/packages/security/role_management_model/kibana.jsonc b/x-pack/packages/security/role_management_model/kibana.jsonc index 9ba793649416..95a1116fd718 100644 --- a/x-pack/packages/security/role_management_model/kibana.jsonc +++ b/x-pack/packages/security/role_management_model/kibana.jsonc @@ -1,5 +1,9 @@ { "type": "shared-common", "id": "@kbn/security-role-management-model", - "owner": "@elastic/kibana-security" -} + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private" +} \ No newline at end of file diff --git a/x-pack/packages/security/ui_components/kibana.jsonc b/x-pack/packages/security/ui_components/kibana.jsonc index 1aad2d80ed7f..40aaaf007acd 100644 --- a/x-pack/packages/security/ui_components/kibana.jsonc +++ b/x-pack/packages/security/ui_components/kibana.jsonc @@ -1,5 +1,9 @@ { - "type": "shared-common", - "id": "@kbn/security-ui-components", - "owner": "@elastic/kibana-security" + "type": "shared-browser", + "id": "@kbn/security-ui-components", + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private" } diff --git a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx index 2ed172a49ad8..6e55fa7ed7bb 100644 --- a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx +++ b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.test.tsx @@ -54,6 +54,7 @@ const setup = (config: TestConfig) => { kibanaPrivileges={kibanaPrivileges} onChange={onChange} onChangeAll={onChangeAll} + showAdditionalPermissionsMessage={true} canCustomizeSubFeaturePrivileges={config.canCustomizeSubFeaturePrivileges} privilegeIndex={config.privilegeIndex} allSpacesSelected={true} diff --git a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx index 2f77b55ce5ba..9ee1ea3517da 100644 --- a/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx +++ b/x-pack/packages/security/ui_components/src/kibana_privilege_table/feature_table.tsx @@ -45,13 +45,10 @@ interface Props { privilegeIndex: number; onChange: (featureId: string, privileges: string[]) => void; onChangeAll: (privileges: string[]) => void; + showAdditionalPermissionsMessage: boolean; canCustomizeSubFeaturePrivileges: boolean; allSpacesSelected: boolean; disabled?: boolean; - /** - * default is true, to remain backwards compatible - */ - showTitle?: boolean; } interface State { @@ -62,7 +59,6 @@ export class FeatureTable extends Component { public static defaultProps = { privilegeIndex: -1, showLocks: true, - showTitle: true, }; private featureCategories: Map = new Map(); @@ -189,20 +185,7 @@ export class FeatureTable extends Component { return (
- - {this.props.showTitle && ( - - - {i18n.translate( - 'xpack.security.management.editRole.featureTable.featureVisibilityTitle', - { - defaultMessage: 'Customize feature privileges', - } - )} - - - )} - + {!this.props.disabled && ( { }; private getCategoryHelpText = (category: AppCategory) => { - if (category.id === 'management') { + if (category.id === 'management' && this.props.showAdditionalPermissionsMessage) { return i18n.translate( 'xpack.security.management.editRole.featureTable.managementCategoryHelpText', { diff --git a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap index 86f9124702fb..9367ecb221b5 100644 --- a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap +++ b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap @@ -10,45 +10,6 @@ Object { "presence": "optional", }, "keys": Object { - "apiType": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "matches": Array [ - Object { - "schema": Object { - "allow": Array [ - "converse", - ], - "flags": Object { - "error": [Function], - "only": true, - }, - "type": "any", - }, - }, - Object { - "schema": Object { - "allow": Array [ - "invoke", - ], - "flags": Object { - "error": [Function], - "only": true, - }, - "type": "any", - }, - }, - ], - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "alternatives", - }, "body": Object { "flags": Object { "error": [Function], @@ -170,45 +131,6 @@ Object { "presence": "optional", }, "keys": Object { - "apiType": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "matches": Array [ - Object { - "schema": Object { - "allow": Array [ - "converse", - ], - "flags": Object { - "error": [Function], - "only": true, - }, - "type": "any", - }, - }, - Object { - "schema": Object { - "allow": Array [ - "invoke", - ], - "flags": Object { - "error": [Function], - "only": true, - }, - "type": "any", - }, - }, - ], - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "alternatives", - }, "body": Object { "flags": Object { "error": [Function], @@ -1471,23 +1393,18 @@ Object { "presence": "optional", }, "keys": Object { - "additionalModelRequestFields": Object { + "command": Object { "flags": Object { - "default": [Function], "error": [Function], - "presence": "optional", }, "metas": Array [ Object { "x-oas-any-type": true, }, - Object { - "x-oas-optional": true, - }, ], "type": "any", }, - "additionalModelResponseFieldPaths": Object { + "signal": Object { "flags": Object { "default": [Function], "error": [Function], @@ -1503,927 +1420,83 @@ Object { ], "type": "any", }, - "guardrailConfig": Object { + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .bedrock 8`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "apiUrl": Object { "flags": Object { - "default": [Function], "error": [Function], - "presence": "optional", }, - "metas": Array [ - Object { - "x-oas-any-type": true, - }, + "rules": Array [ Object { - "x-oas-optional": true, + "args": Object { + "method": [Function], + }, + "name": "custom", }, ], - "type": "any", + "type": "string", }, - "inferenceConfig": Object { + "defaultModel": Object { "flags": Object { - "default": Object { - "special": "deep", - }, + "default": "anthropic.claude-3-5-sonnet-20240620-v1:0", "error": [Function], "presence": "optional", }, - "keys": Object { - "maxTokens": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "number", - }, - "stopSequences": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "items": Array [ - Object { - "flags": Object { - "error": [Function], - "presence": "optional", - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - ], - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "array", - }, - "temperature": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "number", - }, - "topP": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", + "rules": Array [ + Object { + "args": Object { + "method": [Function], }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "number", + "name": "custom", }, - }, - "type": "object", + ], + "type": "string", }, - "messages": Object { + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .bedrock 9`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "accessKey": Object { "flags": Object { "error": [Function], }, - "items": Array [ + "rules": Array [ Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "content": Object { - "flags": Object { - "error": [Function], - }, - "metas": Array [ - Object { - "x-oas-any-type": true, - }, - ], - "type": "any", - }, - "role": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, + "args": Object { + "method": [Function], }, - "type": "object", + "name": "custom", }, ], - "type": "array", + "type": "string", }, - "modelId": Object { + "secret": Object { "flags": Object { - "default": [Function], "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - "signal": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-any-type": true, - }, - Object { - "x-oas-optional": true, - }, - ], - "type": "any", - }, - "system": Object { - "flags": Object { - "error": [Function], - }, - "items": Array [ - Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "text": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - }, - "type": "object", - }, - ], - "type": "array", - }, - "toolConfig": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "toolChoice": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - "unknown": true, - }, - "keys": Object {}, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, - "type": "object", - }, - "tools": Object { - "flags": Object { - "error": [Function], - }, - "items": Array [ - Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "toolSpec": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "description": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - "inputSchema": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "json": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "$schema": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - "additionalProperties": Object { - "flags": Object { - "error": [Function], - }, - "type": "boolean", - }, - "properties": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - "unknown": true, - }, - "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, - "type": "object", - }, - "required": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "items": Array [ - Object { - "flags": Object { - "error": [Function], - "presence": "optional", - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - ], - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "array", - }, - "type": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - }, - "type": "object", - }, - }, - "type": "object", - }, - "name": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - }, - "type": "object", - }, - }, - "type": "object", - }, - ], - "type": "array", - }, - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "object", - }, - }, - "type": "object", -} -`; - -exports[`Connector type config checks detect connector type changes for: .bedrock 8`] = ` -Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "additionalModelRequestFields": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-any-type": true, - }, - Object { - "x-oas-optional": true, - }, - ], - "type": "any", - }, - "additionalModelResponseFieldPaths": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-any-type": true, - }, - Object { - "x-oas-optional": true, - }, - ], - "type": "any", - }, - "guardrailConfig": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-any-type": true, - }, - Object { - "x-oas-optional": true, - }, - ], - "type": "any", - }, - "inferenceConfig": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "maxTokens": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "number", - }, - "stopSequences": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "items": Array [ - Object { - "flags": Object { - "error": [Function], - "presence": "optional", - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - ], - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "array", - }, - "temperature": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "number", - }, - "topP": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "number", - }, - }, - "type": "object", - }, - "messages": Object { - "flags": Object { - "error": [Function], - }, - "items": Array [ - Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "content": Object { - "flags": Object { - "error": [Function], - }, - "metas": Array [ - Object { - "x-oas-any-type": true, - }, - ], - "type": "any", - }, - "role": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - }, - "type": "object", - }, - ], - "type": "array", - }, - "modelId": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - "signal": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-any-type": true, - }, - Object { - "x-oas-optional": true, - }, - ], - "type": "any", - }, - "system": Object { - "flags": Object { - "error": [Function], - }, - "items": Array [ - Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "text": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - }, - "type": "object", - }, - ], - "type": "array", - }, - "toolConfig": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "toolChoice": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - "unknown": true, - }, - "keys": Object {}, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, - "type": "object", - }, - "tools": Object { - "flags": Object { - "error": [Function], - }, - "items": Array [ - Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "toolSpec": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "description": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - "inputSchema": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "json": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "$schema": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - "additionalProperties": Object { - "flags": Object { - "error": [Function], - }, - "type": "boolean", - }, - "properties": Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - "unknown": true, - }, - "keys": Object {}, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, - "type": "object", - }, - "required": Object { - "flags": Object { - "default": [Function], - "error": [Function], - "presence": "optional", - }, - "items": Array [ - Object { - "flags": Object { - "error": [Function], - "presence": "optional", - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - ], - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "array", - }, - "type": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - }, - "type": "object", - }, - }, - "type": "object", - }, - "name": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - }, - "type": "object", - }, - }, - "type": "object", - }, - ], - "type": "array", - }, - }, - "metas": Array [ - Object { - "x-oas-optional": true, - }, - ], - "type": "object", - }, - }, - "type": "object", -} -`; - -exports[`Connector type config checks detect connector type changes for: .bedrock 9`] = ` -Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "apiUrl": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - "defaultModel": Object { - "flags": Object { - "default": "anthropic.claude-3-5-sonnet-20240620-v1:0", - "error": [Function], - "presence": "optional", }, "rules": Array [ Object { @@ -2441,49 +1514,6 @@ Object { `; exports[`Connector type config checks detect connector type changes for: .bedrock 10`] = ` -Object { - "flags": Object { - "default": Object { - "special": "deep", - }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "accessKey": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - "secret": Object { - "flags": Object { - "error": [Function], - }, - "rules": Array [ - Object { - "args": Object { - "method": [Function], - }, - "name": "custom", - }, - ], - "type": "string", - }, - }, - "type": "object", -} -`; - -exports[`Connector type config checks detect connector type changes for: .bedrock 11`] = ` Object { "flags": Object { "default": Object { diff --git a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts index d6c89a35ba7f..f9cc6ff5a5ba 100644 --- a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts +++ b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts @@ -24,6 +24,7 @@ describe('getGenAiTokenTracking', () => { let mockGetTokenCountFromInvokeStream: jest.Mock; let mockGetTokenCountFromInvokeAsyncIterator: jest.Mock; beforeEach(() => { + jest.clearAllMocks(); mockGetTokenCountFromBedrockInvoke = ( getTokenCountFromBedrockInvoke as jest.Mock ).mockResolvedValueOnce({ @@ -163,6 +164,103 @@ describe('getGenAiTokenTracking', () => { }); }); + it('should return the total, prompt, and completion token counts when given a valid ConverseResponse for bedrockClientSend subaction', async () => { + const actionTypeId = '.bedrock'; + + const result = { + actionId: '123', + status: 'ok' as const, + data: { + usage: { + inputTokens: 50, + outputTokens: 50, + totalTokens: 100, + }, + }, + }; + const validatedParams = { + subAction: 'bedrockClientSend', + }; + + const tokenTracking = await getGenAiTokenTracking({ + actionTypeId, + logger, + result, + validatedParams, + }); + + expect(tokenTracking).toEqual({ + total_tokens: 100, + prompt_tokens: 50, + completion_tokens: 50, + }); + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should return the total, prompt, and completion token counts when given a valid ConverseStreamResponse for bedrockClientSend subaction', async () => { + const chunkIterable = { + async *[Symbol.asyncIterator]() { + await new Promise((resolve) => setTimeout(resolve, 100)); + yield { + metadata: { + usage: { + totalTokens: 100, + inputTokens: 40, + outputTokens: 60, + }, + }, + }; + }, + }; + const actionTypeId = '.bedrock'; + + const result = { + actionId: '123', + status: 'ok' as const, + data: { + tokenStream: chunkIterable, + }, + }; + const validatedParams = { + subAction: 'bedrockClientSend', + }; + + const tokenTracking = await getGenAiTokenTracking({ + actionTypeId, + logger, + result, + validatedParams, + }); + + expect(tokenTracking).toEqual({ + total_tokens: 100, + prompt_tokens: 40, + completion_tokens: 60, + }); + expect(logger.error).not.toHaveBeenCalled(); + }); + + it('should return null when given an invalid Bedrock response for bedrockClientSend subaction', async () => { + const actionTypeId = '.bedrock'; + const result = { + actionId: '123', + status: 'ok' as const, + data: {}, + }; + const validatedParams = { + subAction: 'bedrockClientSend', + }; + + const tokenTracking = await getGenAiTokenTracking({ + actionTypeId, + logger, + result, + validatedParams, + }); + + expect(tokenTracking).toBeNull(); + expect(logger.error).toHaveBeenCalled(); + }); it('should return the total, prompt, and completion token counts when given a valid OpenAI streamed response', async () => { const mockReader = new IncomingMessage(new Socket()); const actionTypeId = '.gen-ai'; diff --git a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts index 41bfa28605f4..d73610892098 100644 --- a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts +++ b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts @@ -9,6 +9,10 @@ import { PassThrough, Readable } from 'stream'; import { Logger } from '@kbn/logging'; import { Stream } from 'openai/streaming'; import { ChatCompletionChunk } from 'openai/resources/chat/completions'; +import { + getTokensFromBedrockConverseStream, + SmithyStream, +} from './get_token_count_from_bedrock_converse'; import { InvokeAsyncIteratorBody, getTokenCountFromInvokeAsyncIterator, @@ -264,6 +268,29 @@ export const getGenAiTokenTracking = async ({ // silently fail and null is returned at bottom of function } } + + // BedrockRuntimeClient.send response used by chat model ActionsClientChatBedrockConverse + if (actionTypeId === '.bedrock' && validatedParams.subAction === 'bedrockClientSend') { + const { tokenStream, usage } = result.data as unknown as { + tokenStream?: SmithyStream; + usage?: { inputTokens: number; outputTokens: number; totalTokens: number }; + }; + if (tokenStream) { + const res = await getTokensFromBedrockConverseStream(tokenStream, logger); + return res; + } + if (usage) { + return { + total_tokens: usage.totalTokens, + prompt_tokens: usage.inputTokens, + completion_tokens: usage.outputTokens, + }; + } else { + logger.error('Response from Bedrock converse API did not contain usage object'); + return null; + } + } + return null; }; diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_bedrock_converse.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_bedrock_converse.ts new file mode 100644 index 000000000000..55bd9e6582e0 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/get_token_count_from_bedrock_converse.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SmithyMessageDecoderStream } from '@smithy/eventstream-codec'; +import { Logger } from '@kbn/logging'; + +export type SmithyStream = SmithyMessageDecoderStream<{ + metadata?: { + usage: { inputTokens: number; outputTokens: number; totalTokens: number }; + }; +}>; + +export const getTokensFromBedrockConverseStream = async function ( + responseStream: SmithyStream, + logger: Logger +): Promise<{ total_tokens: number; prompt_tokens: number; completion_tokens: number } | null> { + try { + for await (const { metadata } of responseStream) { + if (metadata) { + return { + total_tokens: metadata.usage.totalTokens, + prompt_tokens: metadata.usage.inputTokens, + completion_tokens: metadata.usage.outputTokens, + }; + } + } + return null; // Return the final tokens once the generator finishes + } catch (e) { + logger.error('Response from Bedrock converse API did not contain usage object'); + return null; + } +}; diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.test.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.test.ts index cc81706fc257..a1bc118066b9 100644 --- a/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.test.ts +++ b/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.test.ts @@ -61,6 +61,16 @@ describe('getTokenCountFromOpenAIStream', () => { ], }; + const usageChunk = { + object: 'chat.completion.chunk', + choices: [], + usage: { + prompt_tokens: 50, + completion_tokens: 100, + total_tokens: 150, + }, + }; + const PROMPT_TOKEN_COUNT = 36; const COMPLETION_TOKEN_COUNT = 5; @@ -70,55 +80,79 @@ describe('getTokenCountFromOpenAIStream', () => { }); describe('when a stream completes', () => { - beforeEach(async () => { - stream.write('data: [DONE]'); - stream.complete(); - }); + describe('with usage chunk', () => { + it('returns the counts from the usage chunk', async () => { + stream = createStreamMock(); + stream.write(`data: ${JSON.stringify(chunk)}`); + stream.write(`data: ${JSON.stringify(usageChunk)}`); + stream.write('data: [DONE]'); + stream.complete(); - describe('without function tokens', () => { - beforeEach(async () => { tokens = await getTokenCountFromOpenAIStream({ responseStream: stream.transform, logger, body: JSON.stringify(body), }); - }); - it('counts the prompt tokens', () => { - expect(tokens.prompt).toBe(PROMPT_TOKEN_COUNT); - expect(tokens.completion).toBe(COMPLETION_TOKEN_COUNT); - expect(tokens.total).toBe(PROMPT_TOKEN_COUNT + COMPLETION_TOKEN_COUNT); + expect(tokens).toEqual({ + prompt: usageChunk.usage.prompt_tokens, + completion: usageChunk.usage.completion_tokens, + total: usageChunk.usage.total_tokens, + }); }); }); - describe('with function tokens', () => { + describe('without usage chunk', () => { beforeEach(async () => { - tokens = await getTokenCountFromOpenAIStream({ - responseStream: stream.transform, - logger, - body: JSON.stringify({ - ...body, - functions: [ - { - name: 'my_function', - description: 'My function description', - parameters: { - type: 'object', - properties: { - my_property: { - type: 'boolean', - description: 'My function property', + stream.write('data: [DONE]'); + stream.complete(); + }); + + describe('without function tokens', () => { + beforeEach(async () => { + tokens = await getTokenCountFromOpenAIStream({ + responseStream: stream.transform, + logger, + body: JSON.stringify(body), + }); + }); + + it('counts the prompt tokens', () => { + expect(tokens.prompt).toBe(PROMPT_TOKEN_COUNT); + expect(tokens.completion).toBe(COMPLETION_TOKEN_COUNT); + expect(tokens.total).toBe(PROMPT_TOKEN_COUNT + COMPLETION_TOKEN_COUNT); + }); + }); + + describe('with function tokens', () => { + beforeEach(async () => { + tokens = await getTokenCountFromOpenAIStream({ + responseStream: stream.transform, + logger, + body: JSON.stringify({ + ...body, + functions: [ + { + name: 'my_function', + description: 'My function description', + parameters: { + type: 'object', + properties: { + my_property: { + type: 'boolean', + description: 'My function property', + }, }, }, }, - }, - ], - }), + ], + }), + }); }); - }); - it('counts the function tokens', () => { - expect(tokens.prompt).toBeGreaterThan(PROMPT_TOKEN_COUNT); + it('counts the function tokens', () => { + expect(tokens.prompt).toBeGreaterThan(PROMPT_TOKEN_COUNT); + }); }); }); }); diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.ts index 790a59fe6097..5c19a23e6d23 100644 --- a/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.ts +++ b/x-pack/plugins/actions/server/lib/get_token_count_from_openai_stream.ts @@ -25,44 +25,7 @@ export async function getTokenCountFromOpenAIStream({ prompt: number; completion: number; }> { - const chatCompletionRequest = JSON.parse( - body - ) as OpenAI.ChatCompletionCreateParams.ChatCompletionCreateParamsStreaming; - - // per https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb - const tokensFromMessages = encode( - chatCompletionRequest.messages - .map( - (msg) => - `<|start|>${msg.role}\n${msg.content}\n${ - 'name' in msg - ? msg.name - : 'function_call' in msg && msg.function_call - ? msg.function_call.name + '\n' + msg.function_call.arguments - : '' - }<|end|>` - ) - .join('\n') - ).length; - - // this is an approximation. OpenAI cuts off a function schema - // at a certain level of nesting, so their token count might - // be lower than what we are calculating here. - - const tokensFromFunctions = chatCompletionRequest.functions - ? encode( - chatCompletionRequest.functions - ?.map( - (fn) => - `<|start|>${fn.name}\n${fn.description}\n${JSON.stringify(fn.parameters)}<|end|>` - ) - .join('\n') - ).length - : 0; - - const promptTokens = tokensFromMessages + tokensFromFunctions; - - let responseBody: string = ''; + let responseBody = ''; responseStream.on('data', (chunk: string) => { responseBody += chunk.toString(); @@ -74,7 +37,9 @@ export async function getTokenCountFromOpenAIStream({ logger.error('An error occurred while calculating streaming response tokens'); } - const response = responseBody + let completionUsage: OpenAI.CompletionUsage | undefined; + + const response: ParsedResponse = responseBody .split('\n') .filter((line) => { return line.startsWith('data: ') && !line.endsWith('[DONE]'); @@ -82,31 +47,54 @@ export async function getTokenCountFromOpenAIStream({ .map((line) => { return JSON.parse(line.replace('data: ', '')); }) - .filter( - ( - line - ): line is { - choices: Array<{ - delta: { content?: string; function_call?: { name?: string; arguments: string } }; - }>; - } => { - return ( - 'object' in line && line.object === 'chat.completion.chunk' && line.choices.length > 0 - ); - } - ) + .filter((line): line is OpenAI.ChatCompletionChunk => { + return 'object' in line && line.object === 'chat.completion.chunk'; + }) .reduce( (prev, line) => { - const msg = line.choices[0].delta!; - prev.content += msg.content || ''; - prev.function_call.name += msg.function_call?.name || ''; - prev.function_call.arguments += msg.function_call?.arguments || ''; + if (line.usage) { + completionUsage = line.usage; + } + if (line.choices?.length) { + const msg = line.choices[0].delta!; + prev.content += msg.content || ''; + prev.function_call.name += msg.function_call?.name || ''; + prev.function_call.arguments += msg.function_call?.arguments || ''; + } return prev; }, { content: '', function_call: { name: '', arguments: '' } } ); - const completionTokens = encode( + // not all openAI compatible providers emit completion chunk, so we still have to support + // manually counting the tokens + if (completionUsage) { + return { + prompt: completionUsage.prompt_tokens, + completion: completionUsage.completion_tokens, + total: completionUsage.total_tokens, + }; + } else { + const promptTokens = manuallyCountPromptTokens(body); + const completionTokens = manuallyCountCompletionTokens(response); + return { + prompt: promptTokens, + completion: completionTokens, + total: promptTokens + completionTokens, + }; + } +} + +interface ParsedResponse { + content: string; + function_call: { + name: string; + arguments: string; + }; +} + +const manuallyCountCompletionTokens = (response: ParsedResponse) => { + return encode( JSON.stringify( omitBy( { @@ -117,10 +105,42 @@ export async function getTokenCountFromOpenAIStream({ ) ) ).length; +}; - return { - prompt: promptTokens, - completion: completionTokens, - total: promptTokens + completionTokens, - }; -} +const manuallyCountPromptTokens = (requestBody: string) => { + const chatCompletionRequest: OpenAI.ChatCompletionCreateParams.ChatCompletionCreateParamsStreaming = + JSON.parse(requestBody); + + // per https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb + const tokensFromMessages = encode( + chatCompletionRequest.messages + .map( + (msg) => + `<|start|>${msg.role}\n${msg.content}\n${ + 'name' in msg + ? msg.name + : 'function_call' in msg && msg.function_call + ? msg.function_call.name + '\n' + msg.function_call.arguments + : '' + }<|end|>` + ) + .join('\n') + ).length; + + // this is an approximation. OpenAI cuts off a function schema + // at a certain level of nesting, so their token count might + // be lower than what we are calculating here. + + const tokensFromFunctions = chatCompletionRequest.functions + ? encode( + chatCompletionRequest.functions + ?.map( + (fn) => + `<|start|>${fn.name}\n${fn.description}\n${JSON.stringify(fn.parameters)}<|end|>` + ) + .join('\n') + ).length + : 0; + + return tokensFromMessages + tokensFromFunctions; +}; diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index d067ddaaae7a..cf1f9eed87c3 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -8,17 +8,6 @@ import { v4 as uuidv4 } from 'uuid'; import { pick } from 'lodash'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/server'; -import { - CoreKibanaRequest, - FakeRawRequest, - Headers, - IBasePath, - ISavedObjectsRepository, - Logger, - SavedObject, - SavedObjectReference, - SavedObjectsErrorHelpers, -} from '@kbn/core/server'; import { createTaskRunError, RunContext, @@ -28,6 +17,15 @@ import { } from '@kbn/task-manager-plugin/server'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { createRetryableError, getErrorSource } from '@kbn/task-manager-plugin/server/task_running'; +import { type IBasePath, type Headers, type FakeRawRequest } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; +import type { Logger } from '@kbn/logging'; +import type { + ISavedObjectsRepository, + SavedObject, + SavedObjectReference, +} from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; import { ActionExecutorContract } from './action_executor'; import { ActionTaskExecutorParams, @@ -243,7 +241,7 @@ function getFakeRequest(apiKey?: string) { // Since we're using API keys and accessing elasticsearch can only be done // via a request, we're faking one with the proper authorization headers. - return CoreKibanaRequest.from(fakeRawRequest); + return kibanaRequestFactory(fakeRawRequest); } async function getActionTaskParams( diff --git a/x-pack/plugins/actions/tsconfig.json b/x-pack/plugins/actions/tsconfig.json index 8a3c56a47206..709826d33e89 100644 --- a/x-pack/plugins/actions/tsconfig.json +++ b/x-pack/plugins/actions/tsconfig.json @@ -47,7 +47,8 @@ "@kbn/core-test-helpers-kbn-server", "@kbn/security-plugin-types-server", "@kbn/core-application-common", - "@kbn/cloud-plugin" + "@kbn/cloud-plugin", + "@kbn/core-http-server-utils" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ai_infra/llm_tasks/kibana.jsonc b/x-pack/plugins/ai_infra/llm_tasks/kibana.jsonc index 1ef211d01210..2a3da90e3e3d 100644 --- a/x-pack/plugins/ai_infra/llm_tasks/kibana.jsonc +++ b/x-pack/plugins/ai_infra/llm_tasks/kibana.jsonc @@ -2,6 +2,9 @@ "type": "plugin", "id": "@kbn/llm-tasks-plugin", "owner": "@elastic/appex-ai-infra", + // all packages under 'ai_infra' will be used across solutions + "group": "platform", + "visibility": "shared", "plugin": { "id": "llmTasks", "server": true, diff --git a/x-pack/plugins/ai_infra/product_doc_base/kibana.jsonc b/x-pack/plugins/ai_infra/product_doc_base/kibana.jsonc index 268b4a70c992..c157105026b1 100644 --- a/x-pack/plugins/ai_infra/product_doc_base/kibana.jsonc +++ b/x-pack/plugins/ai_infra/product_doc_base/kibana.jsonc @@ -2,6 +2,9 @@ "type": "plugin", "id": "@kbn/product-doc-base-plugin", "owner": "@elastic/appex-ai-infra", + // all packages under 'ai_infra' will be used across solutions + "group": "platform", + "visibility": "shared", "plugin": { "id": "productDocBase", "server": true, diff --git a/x-pack/plugins/ai_infra/product_doc_base/server/routes/installation.ts b/x-pack/plugins/ai_infra/product_doc_base/server/routes/installation.ts index dbede9f7d94d..1e6b5545ebb4 100644 --- a/x-pack/plugins/ai_infra/product_doc_base/server/routes/installation.ts +++ b/x-pack/plugins/ai_infra/product_doc_base/server/routes/installation.ts @@ -29,10 +29,10 @@ export const registerInstallationRoutes = ({ validate: false, options: { access: 'internal', - security: { - authz: { - requiredPrivileges: ['manage_llm_product_doc'], - }, + }, + security: { + authz: { + requiredPrivileges: ['manage_llm_product_doc'], }, }, }, @@ -56,13 +56,13 @@ export const registerInstallationRoutes = ({ validate: false, options: { access: 'internal', - security: { - authz: { - requiredPrivileges: ['manage_llm_product_doc'], - }, - }, timeout: { idleSocket: 20 * 60 * 1000 }, // install can take time. }, + security: { + authz: { + requiredPrivileges: ['manage_llm_product_doc'], + }, + }, }, async (ctx, req, res) => { const { documentationManager } = getServices(); @@ -90,10 +90,10 @@ export const registerInstallationRoutes = ({ validate: false, options: { access: 'internal', - security: { - authz: { - requiredPrivileges: ['manage_llm_product_doc'], - }, + }, + security: { + authz: { + requiredPrivileges: ['manage_llm_product_doc'], }, }, }, diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx index 420e2b510c62..1ca4d0449a6c 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_root.tsx @@ -10,7 +10,6 @@ import React, { useMemo } from 'react'; import type { Observable } from 'rxjs'; import { map } from 'rxjs'; import { pick } from 'lodash'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { EuiSpacer } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/common'; @@ -86,30 +85,28 @@ export const ChangePointDetectionAppState: FC const casesPermissions = appContextValue.cases?.helpers.canUseCases(); return ( - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/format_category.test.tsx b/x-pack/plugins/aiops/public/components/log_categorization/format_category.test.tsx index 9ed7de75999a..c93a2de54862 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/format_category.test.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/format_category.test.tsx @@ -7,7 +7,7 @@ import type { Category } from '@kbn/aiops-log-pattern-analysis/types'; import { useCreateFormattedExample } from './format_category'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; jest.mock('../../hooks/use_eui_theme', () => ({ useIsDarkTheme: () => false, diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx index 506a92abce55..1d5dbd0dffb1 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_copy_to_clipboard_action.test.tsx @@ -7,8 +7,7 @@ import type { ReactElement } from 'react'; import userEvent from '@testing-library/user-event'; -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, renderHook } from '@testing-library/react'; import type { SignificantItem } from '@kbn/ml-agg-utils'; diff --git a/x-pack/plugins/aiops/public/hooks/use_filters_query.test.tsx b/x-pack/plugins/aiops/public/hooks/use_filters_query.test.tsx index bfea21f9e8bb..4ff9aaec9c27 100644 --- a/x-pack/plugins/aiops/public/hooks/use_filters_query.test.tsx +++ b/x-pack/plugins/aiops/public/hooks/use_filters_query.test.tsx @@ -6,7 +6,7 @@ */ import { FilterQueryContextProvider, useFilterQueryUpdates } from './use_filters_query'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { dataPluginMock as mockDataPlugin } from '@kbn/data-plugin/public/mocks'; import type { TimefilterConfig } from '@kbn/data-plugin/public/query'; import { Timefilter } from '@kbn/data-plugin/public/query'; diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index e8b4f4f3ed97..234420f01c52 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -63,7 +63,6 @@ "@kbn/presentation-containers", "@kbn/presentation-publishing", "@kbn/presentation-util-plugin", - "@kbn/react-kibana-context-theme", "@kbn/react-kibana-mount", "@kbn/rison", "@kbn/saved-search-plugin", diff --git a/x-pack/plugins/alerting/public/hooks/use_archive_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_archive_maintenance_window.test.tsx index e6f58df8d3a7..367da7e65811 100644 --- a/x-pack/plugins/alerting/public/hooks/use_archive_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_archive_maintenance_window.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; + +import { waitFor, renderHook, act } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useArchiveMaintenanceWindow } from './use_archive_maintenance_window'; diff --git a/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx index 9eb5970e86a9..f06fd2be6799 100644 --- a/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_breadcrumbs.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useBreadcrumbs } from './use_breadcrumbs'; import { MAINTENANCE_WINDOW_DEEP_LINK_IDS } from '../../common'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; diff --git a/x-pack/plugins/alerting/public/hooks/use_create_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_create_maintenance_window.test.tsx index 26d70f2d4e9a..12564df1bf1b 100644 --- a/x-pack/plugins/alerting/public/hooks/use_create_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_create_maintenance_window.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; + +import { waitFor, renderHook, act } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useCreateMaintenanceWindow } from './use_create_maintenance_window'; diff --git a/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.test.tsx b/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.test.tsx index d21b145aea93..b543d7940cd9 100644 --- a/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_find_maintenance_windows.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; + +import { waitFor, renderHook } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useFindMaintenanceWindows } from './use_find_maintenance_windows'; diff --git a/x-pack/plugins/alerting/public/hooks/use_finish_and_archive_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_finish_and_archive_maintenance_window.test.tsx index 8b55812bd030..7e453d5d78d5 100644 --- a/x-pack/plugins/alerting/public/hooks/use_finish_and_archive_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_finish_and_archive_maintenance_window.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; + +import { waitFor, renderHook, act } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useFinishAndArchiveMaintenanceWindow } from './use_finish_and_archive_maintenance_window'; diff --git a/x-pack/plugins/alerting/public/hooks/use_finish_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_finish_maintenance_window.test.tsx index 6041796fcc00..fc972eddeafe 100644 --- a/x-pack/plugins/alerting/public/hooks/use_finish_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_finish_maintenance_window.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; + +import { waitFor, renderHook, act } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useFinishMaintenanceWindow } from './use_finish_maintenance_window'; diff --git a/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx index 3003f1003ce1..d58aebe0a157 100644 --- a/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_get_maintenance_window.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; + +import { waitFor, renderHook } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useGetMaintenanceWindow } from './use_get_maintenance_window'; diff --git a/x-pack/plugins/alerting/public/hooks/use_license.test.tsx b/x-pack/plugins/alerting/public/hooks/use_license.test.tsx index 0611a6ba86ae..f9d439607257 100644 --- a/x-pack/plugins/alerting/public/hooks/use_license.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_license.test.tsx @@ -6,7 +6,7 @@ */ import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useLicense } from './use_license'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; diff --git a/x-pack/plugins/alerting/public/hooks/use_navigation.test.tsx b/x-pack/plugins/alerting/public/hooks/use_navigation.test.tsx index 2ea981db2a4d..1b7c48ee684e 100644 --- a/x-pack/plugins/alerting/public/hooks/use_navigation.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_navigation.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useCreateMaintenanceWindowNavigation, diff --git a/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx b/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx index 6ba19c27c362..a1da94422c89 100644 --- a/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx +++ b/x-pack/plugins/alerting/public/hooks/use_update_maintenance_window.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; + +import { waitFor, renderHook, act } from '@testing-library/react'; import { AppMockRenderer, createAppMockRenderer } from '../lib/test_utils'; import { useUpdateMaintenanceWindow } from './use_update_maintenance_window'; diff --git a/x-pack/plugins/alerting/server/task_runner/maintenance_windows/get_maintenance_windows.test.ts b/x-pack/plugins/alerting/server/task_runner/maintenance_windows/get_maintenance_windows.test.ts index a6479fba828f..9f44194dbb95 100644 --- a/x-pack/plugins/alerting/server/task_runner/maintenance_windows/get_maintenance_windows.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/maintenance_windows/get_maintenance_windows.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { maintenanceWindowCategoryIdTypes } from '../../application/maintenance_window/constants'; import { getMockMaintenanceWindow } from '../../data/maintenance_window/test_helpers'; @@ -21,6 +20,7 @@ import { import { getFakeKibanaRequest } from '../rule_loader'; import { TaskRunnerContext } from '../types'; import { FilterStateStore } from '@kbn/es-query'; +import { KibanaRequest } from '@kbn/core-http-server'; const logger = loggingSystemMock.create().get(); const mockBasePathService = { set: jest.fn() }; @@ -32,7 +32,7 @@ const ruleTypeId = mockedRule.alertTypeId; describe('getMaintenanceWindows', () => { let context: TaskRunnerContext; - let fakeRequest: CoreKibanaRequest; + let fakeRequest: KibanaRequest; let contextMock: ReturnType; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/task_runner/rule_loader.test.ts b/x-pack/plugins/alerting/server/task_runner/rule_loader.test.ts index 1d4741f84749..4690ccc653a3 100644 --- a/x-pack/plugins/alerting/server/task_runner/rule_loader.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/rule_loader.test.ts @@ -6,10 +6,11 @@ */ import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; -import { CoreKibanaRequest, SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; +import { isCoreKibanaRequest } from '@kbn/core-http-server-utils'; import { schema } from '@kbn/config-schema'; -import { Logger } from '@kbn/core/server'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; +import type { Logger } from '@kbn/logging'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { getDecryptedRule, @@ -244,59 +245,48 @@ describe('rule_loader', () => { describe('getFakeKibanaRequest()', () => { test('has API key, in default space', async () => { - const kibanaRequestFromMock = jest.spyOn(CoreKibanaRequest, 'from'); const fakeRequest = getFakeKibanaRequest(context, 'default', apiKey); - const bpsSetParams = mockBasePathService.set.mock.calls[0]; expect(bpsSetParams).toEqual([fakeRequest, '/']); - expect(fakeRequest).toEqual(expect.any(CoreKibanaRequest)); - expect(kibanaRequestFromMock.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "headers": Object { - "authorization": "ApiKey MTIzOmFiYw==", - }, - "path": "/", - }, - ] - `); + expect(isCoreKibanaRequest(fakeRequest)).toEqual(true); + expect(fakeRequest.auth.isAuthenticated).toEqual(false); + expect(fakeRequest.headers.authorization).toEqual('ApiKey MTIzOmFiYw=='); + expect(fakeRequest.isFakeRequest).toEqual(true); + expect(fakeRequest.isInternalApiRequest).toEqual(false); + expect(fakeRequest.isSystemRequest).toEqual(false); + expect(fakeRequest.route.path).toEqual('/'); + expect(fakeRequest.url.toString()).toEqual('https://fake-request/url'); + expect(fakeRequest.uuid).toEqual(expect.any(String)); }); test('has API key, in non-default space', async () => { - const kibanaRequestFromMock = jest.spyOn(CoreKibanaRequest, 'from'); const fakeRequest = getFakeKibanaRequest(context, spaceId, apiKey); - const bpsSetParams = mockBasePathService.set.mock.calls[0]; expect(bpsSetParams).toEqual([fakeRequest, '/s/rule-spaceId']); - expect(fakeRequest).toEqual(expect.any(CoreKibanaRequest)); - expect(kibanaRequestFromMock.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "headers": Object { - "authorization": "ApiKey MTIzOmFiYw==", - }, - "path": "/", - }, - ] - `); + expect(isCoreKibanaRequest(fakeRequest)).toEqual(true); + expect(fakeRequest.auth.isAuthenticated).toEqual(false); + expect(fakeRequest.headers.authorization).toEqual('ApiKey MTIzOmFiYw=='); + expect(fakeRequest.isFakeRequest).toEqual(true); + expect(fakeRequest.isInternalApiRequest).toEqual(false); + expect(fakeRequest.isSystemRequest).toEqual(false); + expect(fakeRequest.route.path).toEqual('/'); + expect(fakeRequest.url.toString()).toEqual('https://fake-request/url'); + expect(fakeRequest.uuid).toEqual(expect.any(String)); }); test('does not have API key, in default space', async () => { - const kibanaRequestFromMock = jest.spyOn(CoreKibanaRequest, 'from'); const fakeRequest = getFakeKibanaRequest(context, 'default', null); - const bpsSetParams = mockBasePathService.set.mock.calls[0]; expect(bpsSetParams).toEqual([fakeRequest, '/']); - expect(fakeRequest).toEqual(expect.any(CoreKibanaRequest)); - expect(kibanaRequestFromMock.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "headers": Object {}, - "path": "/", - }, - ] - `); + expect(fakeRequest.auth.isAuthenticated).toEqual(false); + expect(fakeRequest.headers).toEqual({}); + expect(fakeRequest.isFakeRequest).toEqual(true); + expect(fakeRequest.isInternalApiRequest).toEqual(false); + expect(fakeRequest.isSystemRequest).toEqual(false); + expect(fakeRequest.route.path).toEqual('/'); + expect(fakeRequest.url.toString()).toEqual('https://fake-request/url'); + expect(fakeRequest.uuid).toEqual(expect.any(String)); }); }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/rule_loader.ts b/x-pack/plugins/alerting/server/task_runner/rule_loader.ts index 3dd0dc8c53a5..b917ba334b93 100644 --- a/x-pack/plugins/alerting/server/task_runner/rule_loader.ts +++ b/x-pack/plugins/alerting/server/task_runner/rule_loader.ts @@ -6,16 +6,12 @@ */ import { addSpaceIdToPath } from '@kbn/spaces-plugin/server'; -import { - CoreKibanaRequest, - FakeRawRequest, - Headers, - Logger, - SavedObject, - SavedObjectReference, - SavedObjectsErrorHelpers, -} from '@kbn/core/server'; import { createTaskRunError, TaskErrorSource } from '@kbn/task-manager-plugin/server'; +import { type FakeRawRequest, type Headers } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; +import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-api-server'; +import type { Logger } from '@kbn/logging'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; import { RunRuleParams, TaskRunnerContext } from './types'; import { ErrorWithReason, validateRuleTypeParams } from '../lib'; import { @@ -174,7 +170,7 @@ export function getFakeKibanaRequest( path: '/', }; - const fakeRequest = CoreKibanaRequest.from(fakeRawRequest); + const fakeRequest = kibanaRequestFactory(fakeRawRequest); context.basePathService.set(fakeRequest, path); return fakeRequest; diff --git a/x-pack/plugins/alerting/tsconfig.json b/x-pack/plugins/alerting/tsconfig.json index eefc1999b26d..d261a00db74c 100644 --- a/x-pack/plugins/alerting/tsconfig.json +++ b/x-pack/plugins/alerting/tsconfig.json @@ -65,7 +65,6 @@ "@kbn/core-saved-objects-api-server-mocks", "@kbn/core-ui-settings-server-mocks", "@kbn/core-test-helpers-kbn-server", - "@kbn/core-http-router-server-internal", "@kbn/core-execution-context-server-mocks", "@kbn/react-kibana-context-render", "@kbn/search-types", @@ -74,7 +73,8 @@ "@kbn/core-http-server", "@kbn/zod", "@kbn/core-saved-objects-base-server-internal", - "@kbn/response-ops-rule-params" + "@kbn/response-ops-rule-params", + "@kbn/core-http-server-utils" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/canvas/public/components/hooks/use_canvas_api.tsx b/x-pack/plugins/canvas/public/components/hooks/use_canvas_api.tsx index fa302c57ead8..d815864fb4a6 100644 --- a/x-pack/plugins/canvas/public/components/hooks/use_canvas_api.tsx +++ b/x-pack/plugins/canvas/public/components/hooks/use_canvas_api.tsx @@ -49,6 +49,8 @@ export const useCanvasApi: () => CanvasContainerApi = () => { createNewEmbeddable(panelType, initialState); }, disableTriggers: true, + // this is required to disable inline editing now enabled by default + canEditInline: false, type: 'canvas', /** * getSerializedStateForChild is left out here because we cannot access the state here. That method diff --git a/x-pack/plugins/canvas/public/feature_catalogue_entry.ts b/x-pack/plugins/canvas/public/feature_catalogue_entry.ts deleted file mode 100644 index be9661eb891f..000000000000 --- a/x-pack/plugins/canvas/public/feature_catalogue_entry.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import type { FeatureCatalogueCategory } from '@kbn/home-plugin/public'; - -export const featureCatalogueEntry = { - id: 'canvas', - title: 'Canvas', - subtitle: i18n.translate('xpack.canvas.featureCatalogue.canvasSubtitle', { - defaultMessage: 'Design pixel-perfect presentations.', - }), - description: i18n.translate('xpack.canvas.appDescription', { - defaultMessage: 'Showcase your data in a pixel-perfect way.', - }), - icon: 'canvasApp', - path: '/app/canvas', - showOnHomePage: false, - category: 'data' as FeatureCatalogueCategory, - solutionId: 'kibana', - order: 300, -}; diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index bd4e920a56f7..37a0ae138889 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -18,6 +18,7 @@ import { AppUpdater, DEFAULT_APP_CATEGORIES, PluginInitializerContext, + AppStatus, } from '@kbn/core/public'; import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; @@ -30,7 +31,6 @@ import { Start as InspectorStart } from '@kbn/inspector-plugin/public'; import { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; -import { featureCatalogueEntry } from './feature_catalogue_entry'; import { CanvasAppLocatorDefinition } from '../common/locator'; import { SESSIONSTORAGE_LASTPATH, CANVAS_APP } from '../common/lib/constants'; import { getSessionStorage } from './lib/storage'; @@ -39,6 +39,7 @@ import { getPluginApi, CanvasApi } from './plugin_api'; import { setupExpressions } from './setup_expressions'; import { addCanvasElementTrigger } from './state/triggers/add_canvas_element_trigger'; import { setKibanaServices, untilPluginStartServicesReady } from './services/kibana_services'; +import { getHasWorkpads } from './services/get_has_workpads'; export type { CoreStart, CoreSetup }; @@ -161,9 +162,11 @@ export class CanvasPlugin }, }); - if (setupPlugins.home) { - setupPlugins.home.featureCatalogue.register(featureCatalogueEntry); - } + getHasWorkpads(coreSetup.http).then((hasWorkpads) => { + this.appUpdater.next(() => ({ + status: hasWorkpads ? AppStatus.accessible : AppStatus.inaccessible, + })); + }); if (setupPlugins.share) { setupPlugins.share.url.locators.create(new CanvasAppLocatorDefinition()); diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx index d293d8deade4..23823c54a26d 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx @@ -6,7 +6,7 @@ */ import React, { FC, PropsWithChildren } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useAutoplayHelper } from './use_autoplay_helper'; import { WorkpadRoutingContext, WorkpadRoutingContextType } from '../workpad_routing_context'; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_page_sync.test.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_page_sync.test.ts index 6d4c99cf618f..ba3fd671c5e2 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_page_sync.test.ts +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_page_sync.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { usePageSync } from './use_page_sync'; const mockDispatch = jest.fn(); diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx index d7dbbcf43c12..bc23831c2b65 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx @@ -6,7 +6,7 @@ */ import React, { PropsWithChildren } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useRefreshHelper } from './use_refresh_helper'; import { WorkpadRoutingContext, WorkpadRoutingContextType } from '../workpad_routing_context'; @@ -31,7 +31,7 @@ const getMockedContext = (context: any) => const getContextWrapper = (context: WorkpadRoutingContextType) => - ({ children }: PropsWithChildren) => + ({ children }: PropsWithChildren) => {children}; describe('useRefreshHelper', () => { @@ -77,7 +77,7 @@ describe('useRefreshHelper', () => { expect(mockDispatch).not.toHaveBeenCalledWith(refreshAction); state.transient.inFlight = true; - // @ts-expect-error @types/react@18 - Type '() => void' has no properties in common with type '{ children?: ReactNode; }'. + rerender(useRefreshHelper); jest.runAllTimers(); diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_restore_history.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_restore_history.test.tsx index 0504368be05a..5db80771dea2 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_restore_history.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_restore_history.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useRestoreHistory } from './use_restore_history'; import { encode } from '../route_state'; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.test.tsx index 1ba0dacd8c14..c8c106bb2bca 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useWorkpad } from './use_workpad'; import { spacesService } from '../../../services/kibana_services'; @@ -62,7 +62,7 @@ describe('useWorkpad', () => { workpad: workpadResponse, }); - const { waitFor, unmount } = renderHook(() => useWorkpad(workpadId, true, getRedirectPath)); + const { unmount } = renderHook(() => useWorkpad(workpadId, true, getRedirectPath)); try { await waitFor(() => expect(mockDispatch).toHaveBeenCalledTimes(3)); @@ -88,7 +88,7 @@ describe('useWorkpad', () => { aliasId, }); - const { waitFor, unmount } = renderHook(() => useWorkpad(workpadId, true, getRedirectPath)); + const { unmount } = renderHook(() => useWorkpad(workpadId, true, getRedirectPath)); try { await waitFor(() => expect(mockDispatch).toHaveBeenCalledTimes(3)); @@ -118,7 +118,7 @@ describe('useWorkpad', () => { aliasPurpose: 'savedObjectConversion', }); - const { waitFor, unmount } = renderHook(() => useWorkpad(workpadId, true, getRedirectPath)); + const { unmount } = renderHook(() => useWorkpad(workpadId, true, getRedirectPath)); try { await waitFor(() => expect(mockRedirectLegacyUrl).toHaveBeenCalled()); expect(mockRedirectLegacyUrl).toBeCalledWith({ diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_history.test.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_history.test.ts index 93c750a3e13f..bb0430660890 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_history.test.ts +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_history.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useWorkpadHistory } from './use_workpad_history'; import { encode } from '../route_state'; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx index 3193ad3dd79e..f53a5a080f3d 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_workpad_persist.test.tsx @@ -4,11 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; + +import crypto from 'crypto'; +import { renderHook } from '@testing-library/react'; import { useWorkpadPersist } from './use_workpad_persist'; const mockGetState = jest.fn(); -const mockUpdateWorkpad = jest.fn(); +const mockUpdateWorkpad = jest.fn(() => Promise.resolve(null)); const mockUpdateAssets = jest.fn(); const mockUpdate = jest.fn(); @@ -36,53 +38,41 @@ jest.mock('../../../services', () => ({ })); describe('useWorkpadPersist', () => { + const initialState = { + persistent: { + workpad: { id: crypto.randomUUID(), some: 'workpad' }, + }, + assets: { + asset1: 'some asset', + asset2: 'other asset', + }, + }; + beforeEach(() => { - jest.resetAllMocks(); + // create a default state for each test + mockGetState.mockReturnValue(initialState); }); - afterAll(() => { + afterEach(() => { jest.clearAllMocks(); }); test('initial render does not persist state', () => { - const state = { - persistent: { - workpad: { some: 'workpad' }, - }, - assets: { - asset1: 'some asset', - asset2: 'other asset', - }, - }; - - mockGetState.mockReturnValue(state); - renderHook(useWorkpadPersist); expect(mockUpdateWorkpad).not.toBeCalled(); }); test('changes to workpad cause a workpad update', () => { - const state = { - persistent: { - workpad: { some: 'workpad' }, - }, - assets: { - asset1: 'some asset', - asset2: 'other asset', - }, - }; - - mockGetState.mockReturnValue(state); - const { rerender } = renderHook(useWorkpadPersist); const newState = { - ...state, + ...initialState, persistent: { - workpad: { new: 'workpad' }, + workpad: { id: crypto.randomUUID(), new: 'workpad' }, }, }; + mockGetState.mockReturnValue(newState); rerender(); @@ -91,13 +81,6 @@ describe('useWorkpadPersist', () => { }); test('non changes causes no updated', () => { - const state = { - persistent: { - workpad: { some: 'workpad' }, - }, - }; - mockGetState.mockReturnValue(state); - const { rerender } = renderHook(useWorkpadPersist); rerender(); @@ -106,26 +89,17 @@ describe('useWorkpadPersist', () => { }); test('non write permissions causes no updates', () => { - const state = { - persistent: { - workpad: { some: 'workpad' }, - }, - transient: { - canUserWrite: false, - }, - }; - mockGetState.mockReturnValue(state); - const { rerender } = renderHook(useWorkpadPersist); const newState = { persistent: { - workpad: { new: 'workpad value' }, + workpad: { id: crypto.randomUUID(), new: 'workpad value' }, }, transient: { canUserWrite: false, }, }; + mockGetState.mockReturnValue(newState); rerender(); diff --git a/x-pack/plugins/canvas/public/services/get_has_workpads.ts b/x-pack/plugins/canvas/public/services/get_has_workpads.ts new file mode 100644 index 000000000000..84cfdfc8a29f --- /dev/null +++ b/x-pack/plugins/canvas/public/services/get_has_workpads.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { API_ROUTE_WORKPAD } from '../../common/lib/constants'; + +export async function getHasWorkpads(http: HttpSetup): Promise { + try { + const response = await http.get(`${API_ROUTE_WORKPAD}/hasWorkpads`, { + version: '1', + }); + return (response as { hasWorkpads: boolean })?.hasWorkpads ?? false; + } catch (error) { + return false; + } +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts b/x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts new file mode 100644 index 000000000000..e42c8fe6fb7c --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/has_workpads.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectAttributes } from '@kbn/core/server'; +import { RouteInitializerDeps } from '..'; +import { CANVAS_TYPE, API_ROUTE_WORKPAD } from '../../../common/lib/constants'; + +export function initializeHasWorkpadsRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.versioned + .get({ + path: `${API_ROUTE_WORKPAD}/hasWorkpads`, + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: {}, + }, + }, + async (context, request, response) => { + const savedObjectsClient = (await context.core).savedObjects.client; + + try { + const workpads = await savedObjectsClient.find({ + type: CANVAS_TYPE, + fields: ['id'], + perPage: 1, + // search across all spaces + namespaces: ['*'], + }); + + return response.ok({ + body: { + hasWorkpads: workpads.total > 0, + }, + }); + } catch (error) { + return response.ok({ + body: { + hasWorkpads: false, + }, + }); + } + } + ); +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/index.ts b/x-pack/plugins/canvas/server/routes/workpad/index.ts index 067b54e7cbeb..fefd1b84fd8a 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/index.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/index.ts @@ -13,8 +13,10 @@ import { initializeImportWorkpadRoute } from './import'; import { initializeUpdateWorkpadRoute, initializeUpdateWorkpadAssetsRoute } from './update'; import { initializeDeleteWorkpadRoute } from './delete'; import { initializeResolveWorkpadRoute } from './resolve'; +import { initializeHasWorkpadsRoute } from './has_workpads'; export function initWorkpadRoutes(deps: RouteInitializerDeps) { + initializeHasWorkpadsRoute(deps); initializeFindWorkpadsRoute(deps); initializeResolveWorkpadRoute(deps); initializeGetWorkpadRoute(deps); diff --git a/x-pack/plugins/cases/public/common/apm/use_cases_transactions.test.ts b/x-pack/plugins/cases/public/common/apm/use_cases_transactions.test.ts index c1cf6b305d48..9a5709c911fb 100644 --- a/x-pack/plugins/cases/public/common/apm/use_cases_transactions.test.ts +++ b/x-pack/plugins/cases/public/common/apm/use_cases_transactions.test.ts @@ -5,12 +5,8 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { CaseAttachmentsWithoutOwner } from '../../types'; -import type { - StartAddAttachmentToExistingCaseTransaction, - StartCreateCaseWithAttachmentsTransaction, -} from './use_cases_transactions'; import { useAddAttachmentToExistingCaseTransaction, useCreateCaseWithAttachmentsTransaction, @@ -37,14 +33,10 @@ const bulkAttachments = [ ] as CaseAttachmentsWithoutOwner; const renderUseCreateCaseWithAttachmentsTransaction = () => - renderHook( - useCreateCaseWithAttachmentsTransaction - ); + renderHook(useCreateCaseWithAttachmentsTransaction); const renderUseAddAttachmentToExistingCaseTransaction = () => - renderHook( - useAddAttachmentToExistingCaseTransaction - ); + renderHook(useAddAttachmentToExistingCaseTransaction); describe('cases transactions', () => { beforeEach(() => { diff --git a/x-pack/plugins/cases/public/common/hooks.test.tsx b/x-pack/plugins/cases/public/common/hooks.test.tsx index d2cea878504b..85dcfe11aaf5 100644 --- a/x-pack/plugins/cases/public/common/hooks.test.tsx +++ b/x-pack/plugins/cases/public/common/hooks.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { TestProviders } from './mock'; import { useIsMainApplication } from './hooks'; diff --git a/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx b/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx index 60b798d37822..73d1822c6249 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx +++ b/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useApplicationCapabilities } from './hooks'; import { allCasesPermissions, TestProviders } from '../../mock'; @@ -14,10 +14,7 @@ import { allCasesPermissions, TestProviders } from '../../mock'; describe('hooks', () => { describe('useApplicationCapabilities', () => { it('should return the correct capabilities', async () => { - const { result } = renderHook< - React.PropsWithChildren<{}>, - ReturnType - >(() => useApplicationCapabilities(), { + const { result } = renderHook(() => useApplicationCapabilities(), { wrapper: ({ children }) => {children}, }); diff --git a/x-pack/plugins/cases/public/common/lib/kibana/use_application.test.tsx b/x-pack/plugins/cases/public/common/lib/kibana/use_application.test.tsx index 81152d3d3c5a..69c5fc4f8db2 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/use_application.test.tsx +++ b/x-pack/plugins/cases/public/common/lib/kibana/use_application.test.tsx @@ -7,7 +7,7 @@ import type { PublicAppInfo } from '@kbn/core-application-browser'; import { AppStatus } from '@kbn/core-application-browser'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { BehaviorSubject, Subject } from 'rxjs'; import type { AppMockRenderer } from '../../mock'; import { createAppMockRenderer } from '../../mock'; diff --git a/x-pack/plugins/cases/public/common/navigation/hooks.test.tsx b/x-pack/plugins/cases/public/common/navigation/hooks.test.tsx index 2170ed2d0e58..867e4d682695 100644 --- a/x-pack/plugins/cases/public/common/navigation/hooks.test.tsx +++ b/x-pack/plugins/cases/public/common/navigation/hooks.test.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { act, renderHook } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; import { APP_ID } from '../../../common/constants'; import { useNavigation } from '../lib/kibana'; diff --git a/x-pack/plugins/cases/public/common/use_cases_features.test.tsx b/x-pack/plugins/cases/public/common/use_cases_features.test.tsx index eeabf3fb0cab..c4c54af0b1c4 100644 --- a/x-pack/plugins/cases/public/common/use_cases_features.test.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_features.test.tsx @@ -6,10 +6,9 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import type { CasesContextFeatures } from '../../common/ui'; -import type { UseCasesFeatures } from './use_cases_features'; import { useCasesFeatures } from './use_cases_features'; import { TestProviders } from './mock/test_providers'; import type { LicenseType } from '@kbn/licensing-plugin/common/types'; @@ -37,14 +36,9 @@ describe('useCasesFeatures', () => { it.each(tests)( 'returns isAlertsEnabled=%s and isSyncAlertsEnabled=%s if feature.alerts=%s', async (isAlertsEnabled, isSyncAlertsEnabled, alerts) => { - const { result } = renderHook, UseCasesFeatures>( - () => useCasesFeatures(), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); + const { result } = renderHook(() => useCasesFeatures(), { + wrapper: ({ children }) => {children}, + }); expect(result.current).toEqual({ isAlertsEnabled, @@ -57,16 +51,13 @@ describe('useCasesFeatures', () => { ); it('returns the metrics correctly', async () => { - const { result } = renderHook, UseCasesFeatures>( - () => useCasesFeatures(), - { - wrapper: ({ children }) => ( - - {children} - - ), - } - ); + const { result } = renderHook(() => useCasesFeatures(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); expect(result.current).toEqual({ isAlertsEnabled: true, @@ -91,12 +82,9 @@ describe('useCasesFeatures', () => { license: { type }, }); - const { result } = renderHook, UseCasesFeatures>( - () => useCasesFeatures(), - { - wrapper: ({ children }) => {children}, - } - ); + const { result } = renderHook(() => useCasesFeatures(), { + wrapper: ({ children }) => {children}, + }); expect(result.current).toEqual({ isAlertsEnabled: true, diff --git a/x-pack/plugins/cases/public/common/use_cases_local_storage.test.tsx b/x-pack/plugins/cases/public/common/use_cases_local_storage.test.tsx index 740fa78dc3c0..92dd5808b6b7 100644 --- a/x-pack/plugins/cases/public/common/use_cases_local_storage.test.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_local_storage.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { Subject } from 'rxjs'; import type { AppMockRenderer } from './mock/test_providers'; import { createAppMockRenderer } from './mock/test_providers'; diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx index bb0c0b3a9f53..c1f7d67a8b8b 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; import { useKibana, useToasts } from './lib/kibana'; import type { AppMockRenderer } from './mock'; import { createAppMockRenderer, TestProviders } from './mock'; @@ -14,7 +13,7 @@ import { alertComment, basicComment, mockCase } from '../containers/mock'; import React from 'react'; import userEvent from '@testing-library/user-event'; import type { SupportedCaseAttachment } from '../types'; -import { getByTestId, queryByTestId, screen } from '@testing-library/react'; +import { getByTestId, queryByTestId, screen, renderHook } from '@testing-library/react'; import { OWNER_INFO } from '../../common/constants'; import { useApplication } from './lib/kibana/use_application'; diff --git a/x-pack/plugins/cases/public/common/use_is_user_typing.test.tsx b/x-pack/plugins/cases/public/common/use_is_user_typing.test.tsx index 229ecc13bba0..d4ec97346996 100644 --- a/x-pack/plugins/cases/public/common/use_is_user_typing.test.tsx +++ b/x-pack/plugins/cases/public/common/use_is_user_typing.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import type { AppMockRenderer } from './mock'; import { createAppMockRenderer } from './mock'; import { useIsUserTyping } from './use_is_user_typing'; diff --git a/x-pack/plugins/cases/public/common/use_license.test.tsx b/x-pack/plugins/cases/public/common/use_license.test.tsx index 0c28be2ca746..8a5c29394cc6 100644 --- a/x-pack/plugins/cases/public/common/use_license.test.tsx +++ b/x-pack/plugins/cases/public/common/use_license.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { TestProviders } from './mock'; import { useLicense } from './use_license'; diff --git a/x-pack/plugins/cases/public/components/actions/assignees/use_assignees_action.test.tsx b/x-pack/plugins/cases/public/components/actions/assignees/use_assignees_action.test.tsx index 98cac1dfaf46..78b7801b699f 100644 --- a/x-pack/plugins/cases/public/components/actions/assignees/use_assignees_action.test.tsx +++ b/x-pack/plugins/cases/public/components/actions/assignees/use_assignees_action.test.tsx @@ -7,7 +7,7 @@ import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useAssigneesAction } from './use_assignees_action'; import * as api from '../../../containers/api'; @@ -56,7 +56,7 @@ describe('useAssigneesAction', () => { it('update the assignees correctly', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useAssigneesAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -92,7 +92,7 @@ describe('useAssigneesAction', () => { }); it('shows the success toaster correctly when updating one case', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useAssigneesAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -118,7 +118,7 @@ describe('useAssigneesAction', () => { }); it('shows the success toaster correctly when updating multiple cases', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useAssigneesAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, diff --git a/x-pack/plugins/cases/public/components/actions/copy_id/use_copy_id_action.test.tsx b/x-pack/plugins/cases/public/components/actions/copy_id/use_copy_id_action.test.tsx index 388b3de940ec..2be5f4b83a23 100644 --- a/x-pack/plugins/cases/public/components/actions/copy_id/use_copy_id_action.test.tsx +++ b/x-pack/plugins/cases/public/components/actions/copy_id/use_copy_id_action.test.tsx @@ -7,7 +7,7 @@ import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useCopyIDAction } from './use_copy_id_action'; import { basicCase } from '../../../containers/mock'; @@ -58,7 +58,7 @@ describe('useCopyIDAction', () => { }); it('copies the id of the selected case to the clipboard', async () => { - const { result, waitFor } = renderHook(() => useCopyIDAction({ onActionSuccess }), { + const { result } = renderHook(() => useCopyIDAction({ onActionSuccess }), { wrapper: appMockRender.AppWrapper, }); @@ -73,7 +73,7 @@ describe('useCopyIDAction', () => { }); it('shows the success toaster correctly when copying the case id', async () => { - const { result, waitFor } = renderHook(() => useCopyIDAction({ onActionSuccess }), { + const { result } = renderHook(() => useCopyIDAction({ onActionSuccess }), { wrapper: appMockRender.AppWrapper, }); diff --git a/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.test.tsx b/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.test.tsx index 9730783f39af..fee612cbf04f 100644 --- a/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.test.tsx +++ b/x-pack/plugins/cases/public/components/actions/delete/use_delete_action.test.tsx @@ -7,7 +7,7 @@ import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useDeleteAction } from './use_delete_action'; import * as api from '../../../containers/api'; @@ -84,7 +84,7 @@ describe('useDeleteAction', () => { it('deletes the selected cases', async () => { const deleteSpy = jest.spyOn(api, 'deleteCases'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -112,7 +112,7 @@ describe('useDeleteAction', () => { }); it('closes the modal', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -137,7 +137,7 @@ describe('useDeleteAction', () => { }); it('shows the success toaster correctly when delete one case', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -163,7 +163,7 @@ describe('useDeleteAction', () => { }); it('shows the success toaster correctly when delete multiple case', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useDeleteAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, diff --git a/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.test.tsx b/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.test.tsx index 79ae67610d90..93982ff334c2 100644 --- a/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.test.tsx +++ b/x-pack/plugins/cases/public/components/actions/severity/use_severity_action.test.tsx @@ -7,7 +7,7 @@ import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useSeverityAction } from './use_severity_action'; import * as api from '../../../containers/api'; @@ -80,7 +80,7 @@ describe('useSeverityAction', () => { it('update the severity cases', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useSeverityAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -120,7 +120,7 @@ describe('useSeverityAction', () => { it.each(singleCaseTests)( 'shows the success toaster correctly when updating the severity of the case: %s', async (_, index, expectedMessage) => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useSeverityAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -153,7 +153,7 @@ describe('useSeverityAction', () => { it.each(multipleCasesTests)( 'shows the success toaster correctly when updating the severity of the case: %s', async (_, index, expectedMessage) => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useSeverityAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, diff --git a/x-pack/plugins/cases/public/components/actions/status/use_status_action.test.tsx b/x-pack/plugins/cases/public/components/actions/status/use_status_action.test.tsx index 5ad7f9803dd6..9a007e5ea28a 100644 --- a/x-pack/plugins/cases/public/components/actions/status/use_status_action.test.tsx +++ b/x-pack/plugins/cases/public/components/actions/status/use_status_action.test.tsx @@ -7,7 +7,7 @@ import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useStatusAction } from './use_status_action'; import * as api from '../../../containers/api'; @@ -82,7 +82,7 @@ describe('useStatusAction', () => { it('update the status cases', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useStatusAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -120,7 +120,7 @@ describe('useStatusAction', () => { it.each(singleCaseTests)( 'shows the success toaster correctly when updating the status of the case: %s', async (_, index, expectedMessage) => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useStatusAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -152,7 +152,7 @@ describe('useStatusAction', () => { it.each(multipleCasesTests)( 'shows the success toaster correctly when updating the status of the case: %s', async (_, index, expectedMessage) => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useStatusAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, diff --git a/x-pack/plugins/cases/public/components/actions/tags/use_tags_action.test.tsx b/x-pack/plugins/cases/public/components/actions/tags/use_tags_action.test.tsx index 14973cc59be7..dbe2a1cc17aa 100644 --- a/x-pack/plugins/cases/public/components/actions/tags/use_tags_action.test.tsx +++ b/x-pack/plugins/cases/public/components/actions/tags/use_tags_action.test.tsx @@ -7,7 +7,7 @@ import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useTagsAction } from './use_tags_action'; import * as api from '../../../containers/api'; @@ -56,7 +56,7 @@ describe('useTagsAction', () => { it('update the tags correctly', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useTagsAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -86,7 +86,7 @@ describe('useTagsAction', () => { }); it('shows the success toaster correctly when updating one case', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useTagsAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, @@ -112,7 +112,7 @@ describe('useTagsAction', () => { }); it('shows the success toaster correctly when updating multiple cases', async () => { - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useTagsAction({ onAction, onActionSuccess, isDisabled: false }), { wrapper: appMockRender.AppWrapper, diff --git a/x-pack/plugins/cases/public/components/actions/use_items_action.test.tsx b/x-pack/plugins/cases/public/components/actions/use_items_action.test.tsx index 25a08007ac31..b1f24562f89b 100644 --- a/x-pack/plugins/cases/public/components/actions/use_items_action.test.tsx +++ b/x-pack/plugins/cases/public/components/actions/use_items_action.test.tsx @@ -7,7 +7,7 @@ import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useItemsAction } from './use_items_action'; import * as api from '../../containers/api'; @@ -54,7 +54,7 @@ describe('useItemsAction', () => { }); it('closes the flyout', async () => { - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -81,7 +81,7 @@ describe('useItemsAction', () => { it('update the items correctly', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -117,7 +117,7 @@ describe('useItemsAction', () => { }); it('calls fieldSelector correctly', async () => { - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -142,7 +142,7 @@ describe('useItemsAction', () => { }); it('calls itemsTransformer correctly', async () => { - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -169,7 +169,7 @@ describe('useItemsAction', () => { it('removes duplicates', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -203,7 +203,7 @@ describe('useItemsAction', () => { }); it('shows the success toaster correctly when updating a case', async () => { - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -229,7 +229,7 @@ describe('useItemsAction', () => { it('do not update cases with no changes', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -254,7 +254,7 @@ describe('useItemsAction', () => { it('do not update if the selected items are the same but with different order', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -279,7 +279,7 @@ describe('useItemsAction', () => { it('do not update if the selected items are the same', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -304,7 +304,7 @@ describe('useItemsAction', () => { it('do not update if selecting and unselecting the same item', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); @@ -329,7 +329,7 @@ describe('useItemsAction', () => { it('do not update with empty items and no selection', async () => { const updateSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor } = renderHook(() => useItemsAction(props), { + const { result } = renderHook(() => useItemsAction(props), { wrapper: appMockRender.AppWrapper, }); diff --git a/x-pack/plugins/cases/public/components/actions/use_items_state.test.tsx b/x-pack/plugins/cases/public/components/actions/use_items_state.test.tsx index a680ec655652..e0f07eaf1a5c 100644 --- a/x-pack/plugins/cases/public/components/actions/use_items_state.test.tsx +++ b/x-pack/plugins/cases/public/components/actions/use_items_state.test.tsx @@ -7,7 +7,7 @@ import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useItemsState } from './use_items_state'; import { basicCase } from '../../containers/mock'; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index bc540040cce5..5c79aadbcfee 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -7,8 +7,7 @@ import React from 'react'; import moment from 'moment-timezone'; -import { render, waitFor, screen, within } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, waitFor, screen, within, renderHook } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -27,7 +26,6 @@ import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { getEmptyCellValue } from '../empty_value'; import { useKibana } from '../../common/lib/kibana'; import { AllCasesList } from './all_cases_list'; -import type { GetCasesColumn, UseCasesColumnsReturnValue } from './use_cases_columns'; import { useCasesColumns } from './use_cases_columns'; import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; import { registerConnectorsToMockActionRegistry } from '../../common/mock/register_connectors'; @@ -267,10 +265,7 @@ describe.skip('AllCasesListGeneric', () => { expect(column[key].querySelector('span')).toHaveTextContent(emptyTag); }; - const { result } = renderHook< - React.PropsWithChildren, - UseCasesColumnsReturnValue - >(() => useCasesColumns(defaultColumnArgs), { + const { result } = renderHook(() => useCasesColumns(defaultColumnArgs), { wrapper: ({ children }) => {children}, }); diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx index 70635a2f8c36..226064204bc2 100644 --- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx @@ -91,7 +91,8 @@ describe('AllCases', () => { jest.clearAllMocks(); }); - describe('empty table', () => { + // FLAKY: https://github.com/elastic/kibana/issues/162852 + describe.skip('empty table', () => { beforeEach(() => { useGetCasesMock.mockReturnValue({ ...defaultGetCases, diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx index 644c67b632df..dbe7412a5d7b 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { waitFor } from '@testing-library/react'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { FC, PropsWithChildren } from 'react'; import React from 'react'; @@ -96,12 +95,11 @@ describe('use cases add to existing case modal hook', () => { }); it('should throw if called outside of a cases context', () => { - const { result } = renderHook(() => { - useCasesAddToExistingCaseModal(defaultParams()); - }); - expect(result.error?.message).toContain( - 'useCasesContext must be used within a CasesProvider and have a defined value' - ); + expect(() => + renderHook(() => { + useCasesAddToExistingCaseModal(defaultParams()); + }) + ).toThrow(/useCasesContext must be used within a CasesProvider and have a defined value/); }); it('should dispatch the open action when invoked', () => { diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filter_config/use_filter_config.test.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filter_config/use_filter_config.test.tsx index 89419d587237..c38486dfb7ea 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table_filter_config/use_filter_config.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table_filter_config/use_filter_config.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; import type { FilterConfig, FilterConfigRenderParams } from './types'; @@ -64,7 +64,7 @@ describe('useFilterConfig', () => { it('should remove a selected option if the filter is deleted', async () => { const { rerender } = renderHook(useFilterConfig, { - wrapper: ({ children }: React.PropsWithChildren[0]>) => ( + wrapper: ({ children }: React.PropsWithChildren) => ( {children} ), initialProps: { @@ -106,7 +106,7 @@ describe('useFilterConfig', () => { ); const { result } = renderHook(useFilterConfig, { - wrapper: ({ children }: React.PropsWithChildren[0]>) => ( + wrapper: ({ children }: React.PropsWithChildren) => ( {children} ), initialProps: { diff --git a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx index bb0ba6ed009e..e98926bcd0b4 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx @@ -6,8 +6,7 @@ */ import userEvent, { type UserEvent } from '@testing-library/user-event'; -import { waitFor } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor, renderHook } from '@testing-library/react'; import { waitForEuiPopoverOpen, waitForEuiContextMenuPanelTransition, diff --git a/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.test.tsx index 1e257c8fbcef..511edce760a4 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_all_cases_state.test.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook, act } from '@testing-library/react'; import { CaseStatuses } from '@kbn/cases-components'; import { TestProviders } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.test.tsx index 1838ee3b14f5..2bbd7da38545 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_bulk_actions.test.tsx @@ -8,8 +8,7 @@ import React from 'react'; import { EuiContextMenu } from '@elastic/eui'; import userEvent from '@testing-library/user-event'; -import { waitFor } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor, renderHook } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { @@ -190,7 +189,7 @@ describe('useBulkActions', () => { it('change the status of cases', async () => { const updateCasesSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), { wrapper: appMockRender.AppWrapper, @@ -219,7 +218,7 @@ describe('useBulkActions', () => { pointerEventsCheck: 0, }); - await waitForHook(() => { + await waitFor(() => { expect(updateCasesSpy).toHaveBeenCalled(); }); }); @@ -227,7 +226,7 @@ describe('useBulkActions', () => { it('change the severity of cases', async () => { const updateCasesSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), { wrapper: appMockRender.AppWrapper, @@ -257,7 +256,7 @@ describe('useBulkActions', () => { pointerEventsCheck: 0, }); - await waitForHook(() => { + await waitFor(() => { expect(updateCasesSpy).toHaveBeenCalled(); }); }); @@ -266,7 +265,7 @@ describe('useBulkActions', () => { it('delete a case', async () => { const deleteSpy = jest.spyOn(api, 'deleteCases'); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), { wrapper: appMockRender.AppWrapper, @@ -299,7 +298,7 @@ describe('useBulkActions', () => { await userEvent.click(res.getByTestId('confirmModalConfirmButton')); - await waitForHook(() => { + await waitFor(() => { expect(deleteSpy).toHaveBeenCalled(); }); }); @@ -355,7 +354,7 @@ describe('useBulkActions', () => { it('change the tags of the case', async () => { const updateCasesSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), { wrapper: appMockRender.AppWrapper, @@ -394,7 +393,7 @@ describe('useBulkActions', () => { await userEvent.click(res.getByText('coke')); await userEvent.click(res.getByTestId('cases-edit-tags-flyout-submit')); - await waitForHook(() => { + await waitFor(() => { expect(updateCasesSpy).toHaveBeenCalled(); }); }); @@ -402,7 +401,7 @@ describe('useBulkActions', () => { it('change the assignees of the case', async () => { const updateCasesSpy = jest.spyOn(api, 'updateCases'); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), { wrapper: appMockRender.AppWrapper, @@ -441,7 +440,7 @@ describe('useBulkActions', () => { await userEvent.click(res.getByText('Damaged Raccoon')); await userEvent.click(res.getByTestId('cases-edit-assignees-flyout-submit')); - await waitForHook(() => { + await waitFor(() => { expect(updateCasesSpy).toHaveBeenCalled(); }); }); @@ -450,7 +449,7 @@ describe('useBulkActions', () => { describe('Permissions', () => { it('shows the correct actions with all permissions', async () => { appMockRender = createAppMockRenderer({ permissions: allCasesPermissions() }); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), { wrapper: appMockRender.AppWrapper, @@ -467,7 +466,7 @@ describe('useBulkActions', () => { ); - await waitForHook(() => { + await waitFor(() => { expect(res.getByTestId('case-bulk-action-status')).toBeInTheDocument(); expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); expect(res.getByTestId('bulk-actions-separator')).toBeInTheDocument(); @@ -476,7 +475,7 @@ describe('useBulkActions', () => { it('shows the correct actions with no delete permissions', async () => { appMockRender = createAppMockRenderer({ permissions: noDeleteCasesPermissions() }); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), { wrapper: appMockRender.AppWrapper, @@ -493,7 +492,7 @@ describe('useBulkActions', () => { ); - await waitForHook(() => { + await waitFor(() => { expect(res.getByTestId('case-bulk-action-status')).toBeInTheDocument(); expect(res.queryByTestId('cases-bulk-action-delete')).toBeFalsy(); expect(res.queryByTestId('bulk-actions-separator')).toBeFalsy(); @@ -502,7 +501,7 @@ describe('useBulkActions', () => { it('shows the correct actions with only delete permissions', async () => { appMockRender = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCase] }), { wrapper: appMockRender.AppWrapper, @@ -519,7 +518,7 @@ describe('useBulkActions', () => { ); - await waitForHook(() => { + await waitFor(() => { expect(res.queryByTestId('case-bulk-action-status')).toBeFalsy(); expect(res.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); expect(res.queryByTestId('bulk-actions-separator')).toBeFalsy(); @@ -528,7 +527,7 @@ describe('useBulkActions', () => { it('shows the correct actions with no reopen permissions', async () => { appMockRender = createAppMockRenderer({ permissions: noReopenCasesPermissions() }); - const { result, waitFor: waitForHook } = renderHook( + const { result } = renderHook( () => useBulkActions({ onAction, onActionSuccess, selectedCases: [basicCaseClosed] }), { wrapper: appMockRender.AppWrapper, @@ -545,12 +544,12 @@ describe('useBulkActions', () => { ); - await waitForHook(() => { + await waitFor(() => { expect(res.queryByTestId('case-bulk-action-status')).toBeInTheDocument(); res.queryByTestId('case-bulk-action-status')?.click(); }); - await waitForHook(() => { + await waitFor(() => { expect(res.queryByTestId('cases-bulk-action-status-open')).toBeDisabled(); expect(res.queryByTestId('cases-bulk-action-status-in-progress')).toBeDisabled(); expect(res.queryByTestId('cases-bulk-action-status-closed')).toBeDisabled(); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx index 22783cf05cfc..550240060ddf 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx @@ -15,7 +15,7 @@ import { useGetCasesMockState } from '../../containers/mock'; import { connectors, useCaseConfigureResponse } from '../configure_cases/__mock__'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer, readCasesPermissions, TestProviders } from '../../common/mock'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { CaseStatuses, CustomFieldTypes } from '../../../common/types/domain'; import { userProfilesMap } from '../../containers/user_profiles/api.mock'; import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration'; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns_configuration.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns_configuration.test.tsx index 761da1d6316e..4a4a139445c5 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns_configuration.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns_configuration.test.tsx @@ -6,7 +6,7 @@ */ import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns_selection.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns_selection.test.tsx index 26f0f8c2fb85..e6603a35a1a0 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns_selection.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns_selection.test.tsx @@ -6,7 +6,7 @@ */ import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.test.tsx index 484fabca00c3..338055e5da0c 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_on_refresh_cases.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import { casesQueriesKeys } from '../../containers/constants'; diff --git a/x-pack/plugins/cases/public/components/app/use_available_owners.test.ts b/x-pack/plugins/cases/public/components/app/use_available_owners.test.ts index 4cd015de0c92..5a19e9a0f995 100644 --- a/x-pack/plugins/cases/public/components/app/use_available_owners.test.ts +++ b/x-pack/plugins/cases/public/components/app/use_available_owners.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { APP_ID, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { useKibana } from '../../common/lib/kibana'; diff --git a/x-pack/plugins/cases/public/components/app/use_readonly_header.test.tsx b/x-pack/plugins/cases/public/components/app/use_readonly_header.test.tsx index 9be5a6336b3c..69d7a9a8b65a 100644 --- a/x-pack/plugins/cases/public/components/app/use_readonly_header.test.tsx +++ b/x-pack/plugins/cases/public/components/app/use_readonly_header.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useKibana } from '../../common/lib/kibana'; import { readCasesPermissions, TestProviders } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx index b1c893d020e4..2034d3c4099a 100644 --- a/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/severity.test.tsx @@ -6,9 +6,7 @@ */ import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; +import { render, screen, waitFor } from '@testing-library/react'; import { Severity } from './severity'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -17,18 +15,8 @@ import { FormTestComponent } from '../../common/test_utils'; const onSubmit = jest.fn(); describe('Severity form field', () => { - let appMockRender: AppMockRenderer; - - beforeEach(() => { - appMockRender = createAppMockRenderer(); - }); - - afterEach(async () => { - await appMockRender.clearQueryCache(); - }); - it('renders', async () => { - appMockRender.render( + render( @@ -40,7 +28,7 @@ describe('Severity form field', () => { // default to LOW in this test configuration it('defaults to the correct value', async () => { - appMockRender.render( + render( @@ -51,7 +39,7 @@ describe('Severity form field', () => { }); it('selects the correct value when changed', async () => { - appMockRender.render( + render( @@ -73,7 +61,7 @@ describe('Severity form field', () => { }); it('disables when loading data', async () => { - appMockRender.render( + render( diff --git a/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.test.tsx index e783e81800f3..238abfefeb47 100644 --- a/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/sync_alerts_toggle.test.tsx @@ -6,17 +6,13 @@ */ import React from 'react'; -import { screen, within, waitFor } from '@testing-library/react'; +import { screen, within, waitFor, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SyncAlertsToggle } from './sync_alerts_toggle'; import { schema } from '../create/schema'; import { FormTestComponent } from '../../common/test_utils'; -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; -// Failing: https://github.com/elastic/kibana/issues/190270 -describe.skip('SyncAlertsToggle', () => { - let appMockRender: AppMockRenderer; +describe('SyncAlertsToggle', () => { const onSubmit = jest.fn(); const defaultFormProps = { onSubmit, @@ -28,52 +24,48 @@ describe.skip('SyncAlertsToggle', () => { beforeEach(() => { jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); - }); - - afterEach(async () => { - await appMockRender.clearQueryCache(); }); it('it renders', async () => { - appMockRender.render( + render( ); - expect(await screen.findByTestId('caseSyncAlerts')).toBeInTheDocument(); - expect(await screen.findByRole('switch')).toHaveAttribute('aria-checked', 'true'); - expect(await screen.findByText('On')).toBeInTheDocument(); + const syncAlerts = await screen.findByTestId('caseSyncAlerts'); + expect(syncAlerts).toBeInTheDocument(); + expect(within(syncAlerts).getByRole('switch')).toHaveAttribute('aria-checked', 'true'); + expect(within(syncAlerts).getByText('On')).toBeInTheDocument(); }); it('it toggles the switch', async () => { - appMockRender.render( + render( ); - const synAlerts = await screen.findByTestId('caseSyncAlerts'); + const syncAlerts = await screen.findByTestId('caseSyncAlerts'); - await userEvent.click(within(synAlerts).getByRole('switch')); + await userEvent.click(within(syncAlerts).getByRole('switch')); expect(await screen.findByRole('switch')).toHaveAttribute('aria-checked', 'false'); expect(await screen.findByText('Off')).toBeInTheDocument(); }); it('calls onSubmit with correct data', async () => { - appMockRender.render( + render( ); - const synAlerts = await screen.findByTestId('caseSyncAlerts'); + const syncAlerts = await screen.findByTestId('caseSyncAlerts'); - await userEvent.click(within(synAlerts).getByRole('switch')); + await userEvent.click(within(syncAlerts).getByRole('switch')); - await userEvent.click(screen.getByText('Submit')); + await userEvent.click(await screen.findByText('Submit')); await waitFor(() => { expect(onSubmit).toBeCalledWith( diff --git a/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx index e277a992c6bd..e861b4a3babe 100644 --- a/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx @@ -7,7 +7,7 @@ import type { FC, PropsWithChildren } from 'react'; import React from 'react'; -import { screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import type { CaseFormFieldsSchemaProps } from './schema'; @@ -17,11 +17,9 @@ import userEvent from '@testing-library/user-event'; import { Title } from './title'; import { schema } from '../create/schema'; -import { createAppMockRenderer, type AppMockRenderer } from '../../common/mock'; describe('Title', () => { let globalForm: FormHook; - let appMockRender: AppMockRenderer; const MockHookWrapperComponent: FC> = ({ children }) => { const { form } = useForm({ @@ -38,11 +36,10 @@ describe('Title', () => { beforeEach(() => { jest.resetAllMocks(); - appMockRender = createAppMockRenderer(); }); it('it renders', async () => { - appMockRender.render( + render( </MockHookWrapperComponent> @@ -52,7 +49,7 @@ describe('Title', () => { }); it('it disables the input when loading', async () => { - appMockRender.render( + render( <MockHookWrapperComponent> <Title isLoading={true} /> </MockHookWrapperComponent> @@ -61,7 +58,7 @@ describe('Title', () => { }); it('it changes the title', async () => { - appMockRender.render( + render( <MockHookWrapperComponent> <Title isLoading={false} /> </MockHookWrapperComponent> diff --git a/x-pack/plugins/cases/public/components/cases_context/state/use_is_add_to_case_open.test.tsx b/x-pack/plugins/cases/public/components/cases_context/state/use_is_add_to_case_open.test.tsx index 9974d0cb530d..61cf35522966 100644 --- a/x-pack/plugins/cases/public/components/cases_context/state/use_is_add_to_case_open.test.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/state/use_is_add_to_case_open.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useCasesAddToExistingCaseModal } from '../../all_cases/selector_modal/use_cases_add_to_existing_case_modal'; import { createAppMockRenderer } from '../../../common/mock'; import { useIsAddToCaseOpen } from './use_is_add_to_case_open'; @@ -26,9 +26,8 @@ describe('use is add to existing case modal open hook', () => { }); it('should throw if called outside of a cases context', () => { - const { result } = renderHook(useIsAddToCaseOpen); - expect(result.error?.message).toContain( - 'useCasesStateContext must be used within a CasesProvider and have a defined value' + expect(() => renderHook(useIsAddToCaseOpen)).toThrow( + /useCasesStateContext must be used within a CasesProvider and have a defined value/ ); }); diff --git a/x-pack/plugins/cases/public/components/category/category_form_field.test.tsx b/x-pack/plugins/cases/public/components/category/category_form_field.test.tsx index 8380276e3e10..e2aed2c39783 100644 --- a/x-pack/plugins/cases/public/components/category/category_form_field.test.tsx +++ b/x-pack/plugins/cases/public/components/category/category_form_field.test.tsx @@ -6,28 +6,19 @@ */ import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; import { CategoryFormField } from './category_form_field'; import { categories } from '../../containers/mock'; import { MAX_CATEGORY_LENGTH } from '../../../common/constants'; import { FormTestComponent } from '../../common/test_utils'; -// FLAKY: https://github.com/elastic/kibana/issues/189739 -describe.skip('Category', () => { - let appMockRender: AppMockRenderer; +describe('Category', () => { const onSubmit = jest.fn(); - beforeEach(() => { - jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); - }); - it('renders the category field correctly', async () => { - appMockRender.render( + render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> @@ -37,7 +28,7 @@ describe.skip('Category', () => { }); it('can submit without setting a category', async () => { - appMockRender.render( + render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> @@ -53,7 +44,7 @@ describe.skip('Category', () => { }); it('can submit with category a string as default value', async () => { - appMockRender.render( + render( <FormTestComponent formDefaultValue={{ category: categories[0] }} onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> @@ -69,7 +60,7 @@ describe.skip('Category', () => { }); it('can submit with category with null as default value', async () => { - appMockRender.render( + render( <FormTestComponent formDefaultValue={{ category: null }} onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> @@ -85,7 +76,7 @@ describe.skip('Category', () => { }); it('cannot submit if the category is an empty string', async () => { - appMockRender.render( + render( <FormTestComponent formDefaultValue={{ category: '' }} onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> @@ -100,13 +91,13 @@ describe.skip('Category', () => { expect(onSubmit).toBeCalledWith({}, false); }); - expect(screen.getByText('Empty category is not allowed')); + expect(await screen.findByText('Empty category is not allowed')); }); it(`cannot submit if the category is more than ${MAX_CATEGORY_LENGTH}`, async () => { const category = 'a'.repeat(MAX_CATEGORY_LENGTH + 1); - appMockRender.render( + render( <FormTestComponent formDefaultValue={{ category }} onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> @@ -122,20 +113,20 @@ describe.skip('Category', () => { }); expect( - screen.getByText( + await screen.findByText( 'The length of the category is too long. The maximum length is 50 characters.' ) ); }); it('can set a category from existing ones', async () => { - appMockRender.render( + render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> ); - await userEvent.type(screen.getByRole('combobox'), `${categories[1]}{enter}`); + await userEvent.type(await screen.findByRole('combobox'), `${categories[1]}{enter}`); await userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { @@ -145,13 +136,13 @@ describe.skip('Category', () => { }); it('can set a new category', async () => { - appMockRender.render( + render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> ); - await userEvent.type(screen.getByRole('combobox'), 'my new category{enter}'); + await userEvent.type(await screen.findByRole('combobox'), 'my new category{enter}'); await userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { @@ -161,30 +152,30 @@ describe.skip('Category', () => { }); it('cannot set an empty category', async () => { - appMockRender.render( + render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> ); - await userEvent.type(screen.getByRole('combobox'), ' {enter}'); + await userEvent.type(await screen.findByRole('combobox'), ' {enter}'); await userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid expect(onSubmit).toBeCalledWith({}, false); - expect(screen.getByText('Empty category is not allowed')); }); + expect(await screen.findByText('Empty category is not allowed')); }); it('setting an empty category and clear it do not produce an error', async () => { - appMockRender.render( + render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> ); - await userEvent.type(screen.getByRole('combobox'), ' {enter}'); + await userEvent.type(await screen.findByRole('combobox'), ' {enter}'); await userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { @@ -202,7 +193,7 @@ describe.skip('Category', () => { }); it('disables the component correctly when it is loading', async () => { - appMockRender.render( + render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={true} availableCategories={categories} /> </FormTestComponent> diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx index f1cb277f1a24..b529139644d5 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana, useToasts } from '../../../common/lib/kibana'; import { connector } from '../mock'; @@ -30,7 +30,7 @@ describe('useGetFieldsByIssueType', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getFieldsByIssueType'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetFieldsByIssueType({ http, @@ -88,7 +88,7 @@ describe('useGetFieldsByIssueType', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetFieldsByIssueType({ http, @@ -114,7 +114,7 @@ describe('useGetFieldsByIssueType', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetFieldsByIssueType({ http, diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.test.tsx index 876738025e6a..04f1995f6cc8 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana, useToasts } from '../../../common/lib/kibana'; import { connector as actionConnector } from '../mock'; @@ -30,7 +30,7 @@ describe('useGetIssue', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getIssue'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetIssue({ http, @@ -40,7 +40,7 @@ describe('useGetIssue', () => { { wrapper: appMockRender.AppWrapper } ); - await waitFor(() => result.current.isSuccess); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(spy).toHaveBeenCalledWith({ http, @@ -88,7 +88,7 @@ describe('useGetIssue', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetIssue({ http, @@ -98,9 +98,10 @@ describe('useGetIssue', () => { { wrapper: appMockRender.AppWrapper } ); - await waitFor(() => result.current.isError); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(result.current.isError).toBe(true); + expect(addError).toHaveBeenCalled(); + }); }); it('calls addError when the getIssue api returns successfully but contains an error', async () => { @@ -114,7 +115,7 @@ describe('useGetIssue', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetIssue({ http, @@ -124,8 +125,9 @@ describe('useGetIssue', () => { { wrapper: appMockRender.AppWrapper } ); - await waitFor(() => result.current.isSuccess); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx index 0d7e3127dd9f..dde59c2dd64b 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana, useToasts } from '../../../common/lib/kibana'; import { connector } from '../mock'; @@ -30,7 +30,7 @@ describe('useGetIssueTypes', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getIssueTypes'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetIssueTypes({ http, @@ -70,7 +70,7 @@ describe('useGetIssueTypes', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetIssueTypes({ http, @@ -95,7 +95,7 @@ describe('useGetIssueTypes', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetIssueTypes({ http, diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.test.tsx index a06cd4391f76..b43a231e4eb0 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issues.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana, useToasts } from '../../../common/lib/kibana'; import { connector as actionConnector } from '../mock'; @@ -30,7 +30,7 @@ describe('useGetIssues', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getIssues'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetIssues({ http, @@ -40,13 +40,14 @@ describe('useGetIssues', () => { { wrapper: appMockRender.AppWrapper } ); - await waitFor(() => result.current.isSuccess); - - expect(spy).toHaveBeenCalledWith({ - http, - signal: expect.anything(), - connectorId: actionConnector.id, - title: 'Task', + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + expect(spy).toHaveBeenCalledWith({ + http, + signal: expect.anything(), + connectorId: actionConnector.id, + title: 'Task', + }); }); }); @@ -74,7 +75,7 @@ describe('useGetIssues', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetIssues({ http, @@ -84,9 +85,10 @@ describe('useGetIssues', () => { { wrapper: appMockRender.AppWrapper } ); - await waitFor(() => result.current.isError); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(result.current.isError).toBe(true); + expect(addError).toHaveBeenCalled(); + }); }); it('calls addError when the getIssues api returns successfully but contains an error', async () => { @@ -100,7 +102,7 @@ describe('useGetIssues', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetIssues({ http, @@ -110,8 +112,9 @@ describe('useGetIssues', () => { { wrapper: appMockRender.AppWrapper } ); - await waitFor(() => result.current.isSuccess); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx index 7bd0c16a6a4d..bfe20b4dc4de 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana, useToasts } from '../../../common/lib/kibana'; import { connector } from '../mock'; @@ -30,7 +30,7 @@ describe('useGetIncidentTypes', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getIncidentTypes'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetIncidentTypes({ http, @@ -70,7 +70,7 @@ describe('useGetIncidentTypes', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetIncidentTypes({ http, @@ -95,7 +95,7 @@ describe('useGetIncidentTypes', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetIncidentTypes({ http, diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx index 6f59b4d50c31..71d09a0cc68e 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana, useToasts } from '../../../common/lib/kibana'; import { connector } from '../mock'; @@ -30,7 +30,7 @@ describe('useGetSeverity', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getSeverity'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useGetSeverity({ http, @@ -70,7 +70,7 @@ describe('useGetSeverity', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetSeverity({ http, @@ -95,7 +95,7 @@ describe('useGetSeverity', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetSeverity({ http, diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx index 150881761950..3f44f4c30f7c 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana, useToasts } from '../../../common/lib/kibana'; import type { ActionConnector } from '../../../../common/types/domain'; @@ -47,7 +47,7 @@ describe('useGetChoices', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getChoices'); - const { waitFor } = renderHook( + renderHook( () => useGetChoices({ http, @@ -92,7 +92,7 @@ describe('useGetChoices', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetChoices({ http, @@ -118,7 +118,7 @@ describe('useGetChoices', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitFor } = renderHook( + renderHook( () => useGetChoices({ http, diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx index 168cae0e478f..0d1ad5b8b65b 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx @@ -6,7 +6,7 @@ */ import { alertComment } from '../../../containers/mock'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { FC, PropsWithChildren } from 'react'; import React from 'react'; import { CasesContext } from '../../cases_context'; @@ -47,12 +47,11 @@ describe('use cases add to new case flyout hook', () => { }); it('should throw if called outside of a cases context', () => { - const { result } = renderHook(() => { - useCasesAddToNewCaseFlyout(); - }); - expect(result.error?.message).toContain( - 'useCasesContext must be used within a CasesProvider and have a defined value' - ); + expect(() => + renderHook(() => { + useCasesAddToNewCaseFlyout(); + }) + ).toThrow(/useCasesContext must be used within a CasesProvider and have a defined value/); }); it('should dispatch the open action when invoked without attachments', () => { diff --git a/x-pack/plugins/cases/public/components/create/owner_selector.test.tsx b/x-pack/plugins/cases/public/components/create/owner_selector.test.tsx index 7f11214a1576..8ba88a4d8a3c 100644 --- a/x-pack/plugins/cases/public/components/create/owner_selector.test.tsx +++ b/x-pack/plugins/cases/public/components/create/owner_selector.test.tsx @@ -6,29 +6,19 @@ */ import React from 'react'; -import { waitFor, screen } from '@testing-library/react'; +import { waitFor, screen, render } from '@testing-library/react'; import { SECURITY_SOLUTION_OWNER } from '../../../common'; import { OBSERVABILITY_OWNER, OWNER_INFO } from '../../../common/constants'; import { CreateCaseOwnerSelector } from './owner_selector'; -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; import userEvent from '@testing-library/user-event'; -// FLAKY: https://github.com/elastic/kibana/issues/188488 -describe.skip('Case Owner Selection', () => { +describe('Case Owner Selection', () => { const onOwnerChange = jest.fn(); const selectedOwner = SECURITY_SOLUTION_OWNER; - let appMockRender: AppMockRenderer; - - beforeEach(() => { - jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); - }); - it('renders all options', async () => { - appMockRender.render( + render( <CreateCaseOwnerSelector availableOwners={[SECURITY_SOLUTION_OWNER, OBSERVABILITY_OWNER]} isLoading={false} @@ -49,7 +39,7 @@ describe.skip('Case Owner Selection', () => { it.each([[SECURITY_SOLUTION_OWNER], [OBSERVABILITY_OWNER]])( 'only displays %s option if available', async (available) => { - appMockRender.render( + render( <CreateCaseOwnerSelector availableOwners={[available]} isLoading={false} @@ -67,7 +57,7 @@ describe.skip('Case Owner Selection', () => { ); it('changes the selection', async () => { - appMockRender.render( + render( <CreateCaseOwnerSelector availableOwners={[OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER]} isLoading={false} diff --git a/x-pack/plugins/cases/public/components/create/template.test.tsx b/x-pack/plugins/cases/public/components/create/template.test.tsx index 156caf6341e0..22f1f4d1ee90 100644 --- a/x-pack/plugins/cases/public/components/create/template.test.tsx +++ b/x-pack/plugins/cases/public/components/create/template.test.tsx @@ -6,29 +6,16 @@ */ import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; import { templatesConfigurationMock } from '../../containers/mock'; import { TemplateSelector } from './templates'; -// FLAKY: https://github.com/elastic/kibana/issues/193482 -describe.skip('TemplateSelector', () => { - let appMockRender: AppMockRenderer; +describe('TemplateSelector', () => { const onTemplateChange = jest.fn(); - beforeEach(() => { - jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); - }); - - afterEach(async () => { - await appMockRender.clearQueryCache(); - }); - it('renders correctly', async () => { - appMockRender.render( + render( <TemplateSelector isLoading={false} templates={templatesConfigurationMock} @@ -43,7 +30,7 @@ describe.skip('TemplateSelector', () => { it('selects a template correctly', async () => { const selectedTemplate = templatesConfigurationMock[2]; - appMockRender.render( + render( <TemplateSelector isLoading={false} templates={templatesConfigurationMock} @@ -69,7 +56,7 @@ describe.skip('TemplateSelector', () => { it('shows selected template as default', async () => { const templateToSelect = templatesConfigurationMock[1]; - appMockRender.render( + render( <TemplateSelector isLoading={false} templates={templatesConfigurationMock} @@ -85,7 +72,7 @@ describe.skip('TemplateSelector', () => { const templateToSelect = templatesConfigurationMock[1]; const newTemplate = templatesConfigurationMock[2]; - appMockRender.render( + render( <TemplateSelector isLoading={false} templates={templatesConfigurationMock} @@ -112,7 +99,7 @@ describe.skip('TemplateSelector', () => { it('shows the selected option correctly', async () => { const selectedTemplate = templatesConfigurationMock[2]; - appMockRender.render( + render( <TemplateSelector isLoading={false} templates={templatesConfigurationMock} diff --git a/x-pack/plugins/cases/public/components/create/use_cancel_creation_action.test.tsx b/x-pack/plugins/cases/public/components/create/use_cancel_creation_action.test.tsx index 4174d33c44d2..080009feb184 100644 --- a/x-pack/plugins/cases/public/components/create/use_cancel_creation_action.test.tsx +++ b/x-pack/plugins/cases/public/components/create/use_cancel_creation_action.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import { useCancelCreationAction } from './use_cancel_creation_action'; diff --git a/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx b/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx index efededf3fba8..82944c772b17 100644 --- a/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx +++ b/x-pack/plugins/cases/public/components/files/file_name_link.test.tsx @@ -6,19 +6,13 @@ */ import React from 'react'; -import { screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import type { AppMockRenderer } from '../../common/mock'; - -import { createAppMockRenderer } from '../../common/mock'; import { basicFileMock } from '../../containers/mock'; import { FileNameLink } from './file_name_link'; -// Failing: See https://github.com/elastic/kibana/issues/192944 -describe.skip('FileNameLink', () => { - let appMockRender: AppMockRenderer; - +describe('FileNameLink', () => { const defaultProps = { file: basicFileMock, showPreview: jest.fn(), @@ -26,11 +20,10 @@ describe.skip('FileNameLink', () => { beforeEach(() => { jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); }); it('renders clickable name if file is image', async () => { - appMockRender.render(<FileNameLink {...defaultProps} />); + render(<FileNameLink {...defaultProps} />); const nameLink = await screen.findByTestId('cases-files-name-link'); @@ -42,7 +35,7 @@ describe.skip('FileNameLink', () => { }); it('renders simple text name if file is not image', async () => { - appMockRender.render( + render( <FileNameLink showPreview={defaultProps.showPreview} file={{ ...basicFileMock, mimeType: 'text/csv' }} diff --git a/x-pack/plugins/cases/public/components/files/use_file_preview.test.tsx b/x-pack/plugins/cases/public/components/files/use_file_preview.test.tsx index 49e18fb818cd..f5a502f49077 100644 --- a/x-pack/plugins/cases/public/components/files/use_file_preview.test.tsx +++ b/x-pack/plugins/cases/public/components/files/use_file_preview.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useFilePreview } from './use_file_preview'; diff --git a/x-pack/plugins/cases/public/components/files/use_files_table_columns.test.tsx b/x-pack/plugins/cases/public/components/files/use_files_table_columns.test.tsx index 0467bb7a2efe..a064667f93c0 100644 --- a/x-pack/plugins/cases/public/components/files/use_files_table_columns.test.tsx +++ b/x-pack/plugins/cases/public/components/files/use_files_table_columns.test.tsx @@ -9,7 +9,7 @@ import type { FilesTableColumnsProps } from './use_files_table_columns'; import { useFilesTableColumns } from './use_files_table_columns'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { basicCase } from '../../containers/mock'; describe('useFilesTableColumns', () => { diff --git a/x-pack/plugins/cases/public/components/filter_popover/index.test.tsx b/x-pack/plugins/cases/public/components/filter_popover/index.test.tsx index 77e5593671c7..c45599860a56 100644 --- a/x-pack/plugins/cases/public/components/filter_popover/index.test.tsx +++ b/x-pack/plugins/cases/public/components/filter_popover/index.test.tsx @@ -6,31 +6,21 @@ */ import React from 'react'; -import { waitForEuiPopoverOpen, screen } from '@elastic/eui/lib/test/rtl'; -import { waitFor } from '@testing-library/react'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import { render, waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; - -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; - import { FilterPopover } from '.'; describe('FilterPopover ', () => { - let appMockRender: AppMockRenderer; const onSelectedOptionsChanged = jest.fn(); const tags: string[] = ['coke', 'pepsi']; beforeEach(() => { - appMockRender = createAppMockRenderer(); jest.clearAllMocks(); }); - afterEach(async () => { - await appMockRender.clearQueryCache(); - }); - it('renders button label correctly', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} @@ -43,7 +33,7 @@ describe('FilterPopover ', () => { }); it('renders empty label correctly', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} @@ -61,7 +51,7 @@ describe('FilterPopover ', () => { }); it('renders string type options correctly', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} @@ -79,7 +69,7 @@ describe('FilterPopover ', () => { }); it('should call onSelectionChange with selected option', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} @@ -100,7 +90,7 @@ describe('FilterPopover ', () => { }); it('should call onSelectionChange with empty array when option is deselected', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} @@ -126,7 +116,7 @@ describe('FilterPopover ', () => { const maxLengthLabel = `You have selected maximum number of ${maxLength} tags to filter`; it('should show message when maximum options are selected', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} @@ -152,7 +142,7 @@ describe('FilterPopover ', () => { }); it('should not show message when maximum length label is missing', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} @@ -176,7 +166,7 @@ describe('FilterPopover ', () => { }); it('should not show message and disable options when maximum length property is missing', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} @@ -198,7 +188,7 @@ describe('FilterPopover ', () => { }); it('should allow to select more options when maximum length property is missing', async () => { - appMockRender.render( + render( <FilterPopover buttonLabel={'Tags'} onSelectedOptionsChanged={onSelectedOptionsChanged} diff --git a/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.test.tsx b/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.test.tsx index e4ce68ed4523..06a92712f63d 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.test.tsx +++ b/x-pack/plugins/cases/public/components/markdown_editor/use_markdown_session_storage.test.tsx @@ -5,11 +5,9 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { waitFor, renderHook, act } from '@testing-library/react'; import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import type { SessionStorageType } from './use_markdown_session_storage'; import { useMarkdownSessionStorage } from './use_markdown_session_storage'; -import { waitForComponentToUpdate } from '../../common/test_utils'; describe('useMarkdownSessionStorage', () => { const field = { @@ -45,7 +43,7 @@ describe('useMarkdownSessionStorage', () => { }); it('should return hasConflicts as false', async () => { - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useMarkdownSessionStorage({ field, sessionKey, initialValue }) ); @@ -55,7 +53,7 @@ describe('useMarkdownSessionStorage', () => { }); it('should return hasConflicts as false when sessionKey is empty', async () => { - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useMarkdownSessionStorage({ field, sessionKey: '', initialValue }) ); @@ -66,7 +64,7 @@ describe('useMarkdownSessionStorage', () => { }); it('should update the session value with field value when it is first render', async () => { - const { waitFor } = renderHook<SessionStorageType, { hasConflicts: boolean }>( + renderHook( (props) => { return useMarkdownSessionStorage(props); }, @@ -86,7 +84,7 @@ describe('useMarkdownSessionStorage', () => { it('should set session storage when field has value and session key is not created yet', async () => { const specialCharsValue = '!{tooltip[Hello again](This is tooltip!)}'; - const { waitFor, result } = renderHook<SessionStorageType, { hasConflicts: boolean }>( + const { result } = renderHook( (props) => { return useMarkdownSessionStorage(props); }, @@ -101,8 +99,6 @@ describe('useMarkdownSessionStorage', () => { jest.advanceTimersByTime(1000); }); - await waitForComponentToUpdate(); - await waitFor(() => { expect(result.current.hasConflicts).toBe(false); expect(sessionStorage.getItem(sessionKey)).toBe(specialCharsValue); @@ -110,7 +106,7 @@ describe('useMarkdownSessionStorage', () => { }); it('should update session value ', async () => { - const { result, rerender, waitFor } = renderHook<SessionStorageType, { hasConflicts: boolean }>( + const { result, rerender } = renderHook( (props) => { return useMarkdownSessionStorage(props); }, @@ -129,8 +125,6 @@ describe('useMarkdownSessionStorage', () => { jest.advanceTimersByTime(1000); }); - await waitForComponentToUpdate(); - await waitFor(() => { expect(result.current.hasConflicts).toBe(false); expect(sessionStorage.getItem(sessionKey)).toBe('new value'); @@ -138,7 +132,7 @@ describe('useMarkdownSessionStorage', () => { }); it('should return has conflict true', async () => { - const { result, rerender, waitFor } = renderHook<SessionStorageType, { hasConflicts: boolean }>( + const { result, rerender } = renderHook( (props) => { return useMarkdownSessionStorage(props); }, @@ -162,7 +156,7 @@ describe('useMarkdownSessionStorage', () => { }); it('should set field value if session already exists and it is a first render', async () => { - const { waitFor, result } = renderHook<SessionStorageType, { hasConflicts: boolean }>( + const { result } = renderHook( (props) => { return useMarkdownSessionStorage(props); }, @@ -171,8 +165,6 @@ describe('useMarkdownSessionStorage', () => { } ); - await waitForComponentToUpdate(); - await waitFor(() => { expect(field.setValue).toHaveBeenCalled(); }); @@ -181,8 +173,6 @@ describe('useMarkdownSessionStorage', () => { jest.advanceTimersByTime(1000); }); - await waitForComponentToUpdate(); - await waitFor(() => { expect(result.current.hasConflicts).toBe(false); expect(field.value).toBe(sessionStorage.getItem(sessionKey)); @@ -190,10 +180,7 @@ describe('useMarkdownSessionStorage', () => { }); it('should update existing session key if field value changed', async () => { - const { waitFor, rerender, result } = renderHook< - SessionStorageType, - { hasConflicts: boolean } - >( + const { rerender, result } = renderHook( (props) => { return useMarkdownSessionStorage(props); }, @@ -202,8 +189,6 @@ describe('useMarkdownSessionStorage', () => { } ); - await waitForComponentToUpdate(); - await waitFor(() => { expect(field.setValue).toHaveBeenCalled(); }); @@ -218,8 +203,6 @@ describe('useMarkdownSessionStorage', () => { jest.advanceTimersByTime(1000); }); - await waitForComponentToUpdate(); - await waitFor(() => { expect(result.current.hasConflicts).toBe(false); expect(sessionStorage.getItem(sessionKey)).toBe('new value'); diff --git a/x-pack/plugins/cases/public/components/use_breadcrumbs/index.test.tsx b/x-pack/plugins/cases/public/components/use_breadcrumbs/index.test.tsx index 9f48783fde24..b494bd1d8183 100644 --- a/x-pack/plugins/cases/public/components/use_breadcrumbs/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_breadcrumbs/index.test.tsx @@ -7,7 +7,7 @@ import type { ReactNode } from 'react'; import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { TestProviders } from '../../common/mock'; import { useCasesBreadcrumbs, useCasesTitleBreadcrumbs } from '.'; import { CasesDeepLinkId } from '../../common/navigation'; diff --git a/x-pack/plugins/cases/public/components/use_create_case_modal/index.test.tsx b/x-pack/plugins/cases/public/components/use_create_case_modal/index.test.tsx index dcef6d26393a..56599299fd0a 100644 --- a/x-pack/plugins/cases/public/components/use_create_case_modal/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_create_case_modal/index.test.tsx @@ -6,10 +6,10 @@ */ import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; import { useKibana } from '../../common/lib/kibana'; -import type { UseCreateCaseModalProps, UseCreateCaseModalReturnedValues } from '.'; import { useCreateCaseModal } from '.'; import { TestProviders } from '../../common/mock'; @@ -27,10 +27,7 @@ describe('useCreateCaseModal', () => { }); it('init', async () => { - const { result } = renderHook< - React.PropsWithChildren<UseCreateCaseModalProps>, - UseCreateCaseModalReturnedValues - >(() => useCreateCaseModal({ onCaseCreated }), { + const { result } = renderHook(() => useCreateCaseModal({ onCaseCreated }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, }); @@ -38,10 +35,7 @@ describe('useCreateCaseModal', () => { }); it('opens the modal', async () => { - const { result } = renderHook< - React.PropsWithChildren<UseCreateCaseModalProps>, - UseCreateCaseModalReturnedValues - >(() => useCreateCaseModal({ onCaseCreated }), { + const { result } = renderHook(() => useCreateCaseModal({ onCaseCreated }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, }); @@ -53,10 +47,7 @@ describe('useCreateCaseModal', () => { }); it('closes the modal', async () => { - const { result } = renderHook< - React.PropsWithChildren<UseCreateCaseModalProps>, - UseCreateCaseModalReturnedValues - >(() => useCreateCaseModal({ onCaseCreated }), { + const { result } = renderHook(() => useCreateCaseModal({ onCaseCreated }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, }); @@ -69,10 +60,7 @@ describe('useCreateCaseModal', () => { }); it('returns a memoized value', async () => { - const { result, rerender } = renderHook< - React.PropsWithChildren<UseCreateCaseModalProps>, - UseCreateCaseModalReturnedValues - >(() => useCreateCaseModal({ onCaseCreated }), { + const { result, rerender } = renderHook(() => useCreateCaseModal({ onCaseCreated }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, }); @@ -84,10 +72,7 @@ describe('useCreateCaseModal', () => { }); it('closes the modal when creating a case', async () => { - const { result } = renderHook< - React.PropsWithChildren<UseCreateCaseModalProps>, - UseCreateCaseModalReturnedValues - >(() => useCreateCaseModal({ onCaseCreated }), { + const { result } = renderHook(() => useCreateCaseModal({ onCaseCreated }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, }); diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx index 75c2694f8947..02e1a99fd063 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx @@ -6,9 +6,8 @@ */ import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; -import type { ReturnUsePushToService, UsePushToService } from '.'; import { usePushToService } from '.'; import { noPushCasesPermissions, readCasesPermissions, TestProviders } from '../../common/mock'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; @@ -67,10 +66,7 @@ describe('usePushToService', () => { }); it('calls pushCaseToExternalService with correct arguments', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); @@ -93,10 +89,7 @@ describe('usePushToService', () => { }, })); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); @@ -115,10 +108,7 @@ describe('usePushToService', () => { }, })); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); @@ -129,10 +119,7 @@ describe('usePushToService', () => { }); it('Displays message when user has select none as connector', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -155,10 +142,7 @@ describe('usePushToService', () => { }); it('Displays message when connector is deleted', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -182,10 +166,7 @@ describe('usePushToService', () => { }); it('should not call pushCaseToExternalService when the selected connector is none', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -209,10 +190,7 @@ describe('usePushToService', () => { }); it('refresh case view page after push', async () => { - const { result, waitFor } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); @@ -227,10 +205,7 @@ describe('usePushToService', () => { describe('user does not have write or push permissions', () => { it('returns correct information about push permissions', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => ( <TestProviders permissions={noPushCasesPermissions()}> {children}</TestProviders> ), @@ -248,10 +223,7 @@ describe('usePushToService', () => { }, })); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => ( <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> ), @@ -270,10 +242,7 @@ describe('usePushToService', () => { }, })); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => ( <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> ), @@ -284,10 +253,7 @@ describe('usePushToService', () => { }); it('does not display a message when user does not have any connector configured', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -310,10 +276,7 @@ describe('usePushToService', () => { }); it('does not display a message when user does have a connector but is configured to none', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -336,10 +299,7 @@ describe('usePushToService', () => { }); it('does not display a message when connector is deleted', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -363,10 +323,7 @@ describe('usePushToService', () => { }); it('does not display a message when case is closed', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -386,10 +343,7 @@ describe('usePushToService', () => { describe('returned values', () => { it('initial', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); @@ -408,10 +362,7 @@ describe('usePushToService', () => { it('isLoading is true when usePostPushToService is loading', async () => { usePostPushToServiceMock.mockReturnValue({ ...mockPostPush, isLoading: true }); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); @@ -424,10 +375,7 @@ describe('usePushToService', () => { data: actionLicense, }); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); @@ -435,21 +383,18 @@ describe('usePushToService', () => { }); it('hasErrorMessages=true if there are error messages', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService({ ...defaultArgs, isValidConnector: false }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, - }); + const { result } = renderHook( + () => usePushToService({ ...defaultArgs, isValidConnector: false }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); expect(result.current.hasErrorMessages).toBe(true); }); it('needsToBePushed=true if the connector needs to be pushed', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -473,10 +418,7 @@ describe('usePushToService', () => { }); it('needsToBePushed=false if the connector does not exist', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -497,10 +439,7 @@ describe('usePushToService', () => { }); it('hasBeenPushed=false if the connector has been pushed', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -524,10 +463,7 @@ describe('usePushToService', () => { }); it('hasBeenPushed=false if the connector does not exist', async () => { - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >( + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -553,10 +489,7 @@ describe('usePushToService', () => { data: actionLicense, }); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => ( <TestProviders permissions={noPushCasesPermissions()}> {children}</TestProviders> ), @@ -574,10 +507,7 @@ describe('usePushToService', () => { }, })); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); @@ -590,10 +520,7 @@ describe('usePushToService', () => { data: undefined, })); - const { result } = renderHook< - React.PropsWithChildren<UsePushToService>, - ReturnUsePushToService - >(() => usePushToService(defaultArgs), { + const { result } = renderHook(() => usePushToService(defaultArgs), { wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, }); diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.test.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.test.tsx index 0a31b0cb875a..0f485845fcd3 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import type { AttachmentType, @@ -16,8 +16,6 @@ import { AttachmentActionType } from '../../../client/attachment_framework/types import { AttachmentTypeRegistry } from '../../../../common/registry'; import { getMockBuilderArgs } from '../mock'; import { createRegisteredAttachmentUserActionBuilder } from './registered_attachments'; -import type { AppMockRenderer } from '../../../common/mock'; -import { createAppMockRenderer } from '../../../common/mock'; const getLazyComponent = () => React.lazy(() => { @@ -32,8 +30,6 @@ const getLazyComponent = () => }); describe('createRegisteredAttachmentUserActionBuilder', () => { - let appMockRender: AppMockRenderer; - const attachmentTypeId = 'test'; const builderArgs = getMockBuilderArgs(); const registry = new AttachmentTypeRegistry<AttachmentType<CommonAttachmentViewProps>>( @@ -77,7 +73,6 @@ describe('createRegisteredAttachmentUserActionBuilder', () => { }; beforeEach(() => { - appMockRender = createAppMockRenderer(); jest.clearAllMocks(); }); @@ -161,8 +156,7 @@ describe('createRegisteredAttachmentUserActionBuilder', () => { const userAction = createRegisteredAttachmentUserActionBuilder(userActionBuilderArgs).build()[0]; - // @ts-expect-error: children is a proper React element - appMockRender.render(userAction.children); + render(userAction.children); expect(await screen.findByText('My component')).toBeInTheDocument(); }); diff --git a/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx b/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx index 0d9d90058eaf..fb58ed73b7ba 100644 --- a/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx @@ -7,13 +7,10 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; import { DeleteAttachmentConfirmationModal } from './delete_attachment_confirmation_modal'; +import { render, screen } from '@testing-library/react'; -// FLAKY: https://github.com/elastic/kibana/issues/195672 -describe.skip('DeleteAttachmentConfirmationModal', () => { - let appMock: AppMockRenderer; +describe('DeleteAttachmentConfirmationModal', () => { const props = { title: 'My title', confirmButtonText: 'My button text', @@ -21,34 +18,29 @@ describe.skip('DeleteAttachmentConfirmationModal', () => { onConfirm: jest.fn(), }; - beforeEach(() => { - appMock = createAppMockRenderer(); - jest.clearAllMocks(); - }); - it('renders correctly', async () => { - const result = appMock.render(<DeleteAttachmentConfirmationModal {...props} />); + render(<DeleteAttachmentConfirmationModal {...props} />); - expect(result.getByTestId('property-actions-confirm-modal')).toBeInTheDocument(); - expect(result.getByText('My title')).toBeInTheDocument(); - expect(result.getByText('My button text')).toBeInTheDocument(); - expect(result.getByText('Cancel')).toBeInTheDocument(); + expect(await screen.findByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + expect(await screen.findByText('My title')).toBeInTheDocument(); + expect(await screen.findByText('My button text')).toBeInTheDocument(); + expect(await screen.findByText('Cancel')).toBeInTheDocument(); }); it('calls onConfirm', async () => { - const result = appMock.render(<DeleteAttachmentConfirmationModal {...props} />); + const result = render(<DeleteAttachmentConfirmationModal {...props} />); - expect(result.getByText('My button text')).toBeInTheDocument(); - await userEvent.click(result.getByText('My button text')); + expect(await result.findByText('My button text')).toBeInTheDocument(); + await userEvent.click(await result.findByText('My button text')); expect(props.onConfirm).toHaveBeenCalled(); }); it('calls onCancel', async () => { - const result = appMock.render(<DeleteAttachmentConfirmationModal {...props} />); + render(<DeleteAttachmentConfirmationModal {...props} />); - expect(result.getByText('Cancel')).toBeInTheDocument(); - await userEvent.click(result.getByText('Cancel')); + expect(await screen.findByText('Cancel')).toBeInTheDocument(); + await userEvent.click(await screen.findByText('Cancel')); expect(props.onCancel).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.test.tsx index 2db865ee3b22..dba35699e2d8 100644 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer } from '../../../common/mock'; import { useDeletePropertyAction } from './use_delete_property_action'; diff --git a/x-pack/plugins/cases/public/components/user_actions/show_more_button.test.tsx b/x-pack/plugins/cases/public/components/user_actions/show_more_button.test.tsx index 58c152f6b0b3..515cc5085f28 100644 --- a/x-pack/plugins/cases/public/components/user_actions/show_more_button.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/show_more_button.test.tsx @@ -6,30 +6,25 @@ */ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ShowMoreButton } from './show_more_button'; -import type { AppMockRenderer } from '../../common/mock'; -import { createAppMockRenderer } from '../../common/mock'; const showMoreClickMock = jest.fn(); describe('ShowMoreButton', () => { - let appMockRender: AppMockRenderer; - beforeEach(() => { jest.clearAllMocks(); - appMockRender = createAppMockRenderer(); }); it('renders correctly', () => { - appMockRender.render(<ShowMoreButton onShowMoreClick={showMoreClickMock} />); + render(<ShowMoreButton onShowMoreClick={showMoreClickMock} />); expect(screen.getByTestId('cases-show-more-user-actions')).toBeInTheDocument(); }); it('shows loading state and is disabled when isLoading is true', () => { - appMockRender.render(<ShowMoreButton onShowMoreClick={showMoreClickMock} isLoading={true} />); + render(<ShowMoreButton onShowMoreClick={showMoreClickMock} isLoading={true} />); const btn = screen.getByTestId('cases-show-more-user-actions'); @@ -39,7 +34,7 @@ describe('ShowMoreButton', () => { }); it('calls onShowMoreClick on button click', async () => { - appMockRender.render(<ShowMoreButton onShowMoreClick={showMoreClickMock} />); + render(<ShowMoreButton onShowMoreClick={showMoreClickMock} />); await userEvent.click(screen.getByTestId('cases-show-more-user-actions')); expect(showMoreClickMock).toHaveBeenCalled(); diff --git a/x-pack/plugins/cases/public/components/user_actions/use_last_page.test.tsx b/x-pack/plugins/cases/public/components/user_actions/use_last_page.test.tsx index 525fe19771e4..a05097e7f7b2 100644 --- a/x-pack/plugins/cases/public/components/user_actions/use_last_page.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/use_last_page.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useLastPage } from './use_last_page'; import type { UserActivityParams } from '../user_actions_activity_bar/types'; diff --git a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx index 3600a247540f..4acd8ce0ee10 100644 --- a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx @@ -7,7 +7,7 @@ import type { FC, PropsWithChildren } from 'react'; import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { basicCase } from '../../containers/mock'; import { useUpdateComment } from '../../containers/use_update_comment'; diff --git a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_last_page.test.tsx b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_last_page.test.tsx index 3207e4ffb13f..ec50c60bb559 100644 --- a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_last_page.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_last_page.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useLastPageUserActions } from './use_user_actions_last_page'; import type { UserActivityParams } from '../user_actions_activity_bar/types'; @@ -32,7 +32,7 @@ describe('useLastPageUserActions', () => { }); it('renders correctly', async () => { - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useLastPageUserActions({ lastPage: 5, userActivityQueryParams, @@ -79,7 +79,7 @@ describe('useLastPageUserActions', () => { it('returns loading state correctly', async () => { useFindCaseUserActionsMock.mockReturnValue({ isLoading: true }); - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useLastPageUserActions({ lastPage: 2, userActivityQueryParams, @@ -108,7 +108,7 @@ describe('useLastPageUserActions', () => { it('returns empty array when data is undefined', async () => { useFindCaseUserActionsMock.mockReturnValue({ isLoading: false, data: undefined }); - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useLastPageUserActions({ lastPage: 2, userActivityQueryParams, diff --git a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_pagination.test.tsx b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_pagination.test.tsx index 0d005a8b404f..21702583e029 100644 --- a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_pagination.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_pagination.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useUserActionsPagination } from './use_user_actions_pagination'; import type { UserActivityParams } from '../user_actions_activity_bar/types'; @@ -32,7 +32,7 @@ describe('useUserActionsPagination', () => { }); it('renders expandable option correctly when user actions are more than 10', async () => { - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useUserActionsPagination({ userActivityQueryParams, caseId: basicCase.id, @@ -62,7 +62,7 @@ describe('useUserActionsPagination', () => { }); it('renders less than 10 user actions correctly', async () => { - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useUserActionsPagination({ userActivityQueryParams, caseId: basicCase.id, @@ -92,7 +92,7 @@ describe('useUserActionsPagination', () => { it('returns loading state correctly', async () => { useInfiniteFindCaseUserActionsMock.mockReturnValue({ isLoading: true }); - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useUserActionsPagination({ userActivityQueryParams, caseId: basicCase.id, @@ -124,7 +124,7 @@ describe('useUserActionsPagination', () => { it('returns empty array when data is undefined', async () => { useInfiniteFindCaseUserActionsMock.mockReturnValue({ isLoading: false, data: undefined }); - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useUserActionsPagination({ userActivityQueryParams, caseId: basicCase.id, @@ -161,7 +161,7 @@ describe('useUserActionsPagination', () => { }, }); - const { result, waitFor } = renderHook(() => + const { result } = renderHook(() => useUserActionsPagination({ userActivityQueryParams, caseId: basicCase.id, diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/is_compatible.ts b/x-pack/plugins/cases/public/components/visualizations/actions/is_compatible.ts index 64becf44e266..90347cb8d406 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/is_compatible.ts +++ b/x-pack/plugins/cases/public/components/visualizations/actions/is_compatible.ts @@ -7,7 +7,7 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { isLensApi } from '@kbn/lens-plugin/public'; -import { hasBlockingError } from '@kbn/presentation-publishing'; +import { apiPublishesTimeRange, hasBlockingError } from '@kbn/presentation-publishing'; import { canUseCases } from '../../../client/helpers/can_use_cases'; import { getCaseOwnerByAppId } from '../../../../common/utils/owner'; @@ -20,7 +20,11 @@ export function isCompatible( if (!embeddable.getFullAttributes()) { return false; } - const timeRange = embeddable.timeRange$?.value ?? embeddable.parentApi?.timeRange$?.value; + const timeRange = + embeddable.timeRange$?.value ?? + (embeddable.parentApi && apiPublishesTimeRange(embeddable.parentApi) + ? embeddable.parentApi?.timeRange$?.value + : undefined); if (!timeRange) { return false; } diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/mocks.ts b/x-pack/plugins/cases/public/components/visualizations/actions/mocks.ts index dea0c1ace09a..94c7e5a1c939 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/mocks.ts +++ b/x-pack/plugins/cases/public/components/visualizations/actions/mocks.ts @@ -7,11 +7,11 @@ import { createBrowserHistory } from 'history'; import { BehaviorSubject } from 'rxjs'; - +import { getLensApiMock } from '@kbn/lens-plugin/public/react_embeddable/mocks'; import type { PublicAppInfo } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; import type { LensApi, LensSavedObjectAttributes } from '@kbn/lens-plugin/public'; -import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import type { TimeRange } from '@kbn/es-query'; import type { Services } from './types'; const coreStart = coreMock.createStart(); @@ -39,24 +39,16 @@ export const mockLensAttributes = { export const getMockLensApi = ( { from, to = 'now' }: { from: string; to: string } = { from: 'now-24h', to: 'now' } ): LensApi => - ({ - type: 'lens', - getSavedVis: () => {}, - canViewUnderlyingData$: new BehaviorSubject(true), - getViewUnderlyingDataArgs: () => {}, + getLensApiMock({ getFullAttributes: () => { return mockLensAttributes; }, - panelTitle: new BehaviorSubject('myPanel'), - hidePanelTitle: new BehaviorSubject(false), - timeslice$: new BehaviorSubject<[number, number] | undefined>(undefined), + panelTitle: new BehaviorSubject<string | undefined>('myPanel'), timeRange$: new BehaviorSubject<TimeRange | undefined>({ from, to, }), - filters$: new BehaviorSubject<Filter[] | undefined>(undefined), - query$: new BehaviorSubject<Query | AggregateQuery | undefined>(undefined), - } as unknown as LensApi); + }); export const getMockCurrentAppId$ = () => new BehaviorSubject<string>('securitySolutionUI'); export const getMockApplications$ = () => diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/open_modal.tsx b/x-pack/plugins/cases/public/components/visualizations/actions/open_modal.tsx index 0542a13b1ef2..c781098b44b5 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/open_modal.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/actions/open_modal.tsx @@ -9,7 +9,7 @@ import React, { useEffect, useMemo } from 'react'; import { unmountComponentAtNode } from 'react-dom'; import type { LensApi } from '@kbn/lens-plugin/public'; import { toMountPoint } from '@kbn/react-kibana-mount'; -import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; +import { apiPublishesTimeRange, useStateFromPublishingSubject } from '@kbn/presentation-publishing'; import { ActionWrapper } from './action_wrapper'; import type { CasesActionContextProps, Services } from './types'; import type { CaseUI } from '../../../../common'; @@ -30,7 +30,9 @@ const AddExistingCaseModalWrapper: React.FC<Props> = ({ lensApi, onClose, onSucc }); const timeRange = useStateFromPublishingSubject(lensApi.timeRange$); - const parentTimeRange = useStateFromPublishingSubject(lensApi.parentApi?.timeRange$); + const parentTimeRange = useStateFromPublishingSubject( + apiPublishesTimeRange(lensApi.parentApi) ? lensApi.parentApi?.timeRange$ : undefined + ); const absoluteTimeRange = convertToAbsoluteTimeRange(timeRange); const absoluteParentTimeRange = convertToAbsoluteTimeRange(parentTimeRange); diff --git a/x-pack/plugins/cases/public/containers/configure/use_action_types.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_action_types.test.tsx index 5718b07438a9..99d79484b17c 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_action_types.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_action_types.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import * as api from './api'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; @@ -43,10 +43,10 @@ describe('useActionTypes', () => { (useToasts as jest.Mock).mockReturnValue({ addError: addErrorMock }); - const { waitForNextUpdate } = renderHook(() => useGetActionTypes(), { + renderHook(() => useGetActionTypes(), { wrapper: appMockRenderer.AppWrapper, }); - await waitForNextUpdate({ timeout: 2000 }); - expect(addErrorMock).toHaveBeenCalled(); + + await waitFor(() => expect(addErrorMock).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/configure/use_get_all_case_configurations.test.ts b/x-pack/plugins/cases/public/containers/configure/use_get_all_case_configurations.test.ts index cd9e44d1bdaa..52d4df20e540 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_get_all_case_configurations.test.ts +++ b/x-pack/plugins/cases/public/containers/configure/use_get_all_case_configurations.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useGetAllCaseConfigurations } from './use_get_all_case_configurations'; import * as api from './api'; import type { AppMockRenderer } from '../../common/mock'; @@ -32,18 +32,11 @@ describe('Use get all case configurations hook', () => { { id: 'my-configuration-3', owner: '3' }, ]); - const { result, waitForNextUpdate } = renderHook(() => useGetAllCaseConfigurations(), { + const { result } = renderHook(() => useGetAllCaseConfigurations(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - /** - * Ensures that the initial data is returned≠ - * before fetching - */ - // @ts-expect-error: data is defined - expect(result.all[0].data).toEqual([ + expect(result.current.data).toEqual([ { closureType: 'close-by-user', connector: { fields: null, id: 'none', name: 'none', type: '.none' }, @@ -56,43 +49,36 @@ describe('Use get all case configurations hook', () => { }, ]); - /** - * The response after fetching - */ - // @ts-expect-error: data is defined - expect(result.all[1].data).toEqual([ - { id: 'my-configuration-1', owner: '1' }, - { id: 'my-configuration-2', owner: '2' }, - { id: 'my-configuration-3', owner: '3' }, - ]); + await waitFor(() => + expect(result.current.data).toEqual([ + { id: 'my-configuration-1', owner: '1' }, + { id: 'my-configuration-2', owner: '2' }, + { id: 'my-configuration-3', owner: '3' }, + ]) + ); }); it('returns the initial configuration if none is available', async () => { const spy = jest.spyOn(api, 'getCaseConfigure'); spy.mockResolvedValue([]); - const { result, waitForNextUpdate } = renderHook(() => useGetAllCaseConfigurations(), { + const { result } = renderHook(() => useGetAllCaseConfigurations(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - /** - * Ensures that the initial data is returned≠ - * before fetching - */ - // @ts-expect-error: data is defined - expect(result.all[0].data).toEqual([ - { - closureType: 'close-by-user', - connector: { fields: null, id: 'none', name: 'none', type: '.none' }, - customFields: [], - templates: [], - id: '', - mappings: [], - version: '', - owner: '', - }, - ]); + await waitFor(() => + expect(result.current.data).toEqual([ + { + closureType: 'close-by-user', + connector: { fields: null, id: 'none', name: 'none', type: '.none' }, + customFields: [], + templates: [], + id: '', + mappings: [], + version: '', + owner: '', + }, + ]) + ); }); }); diff --git a/x-pack/plugins/cases/public/containers/configure/use_get_case_configuration.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_get_case_configuration.test.tsx index e504bd22e9cc..35faebd12a78 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_get_case_configuration.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_get_case_configuration.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useGetCaseConfiguration } from './use_get_case_configuration'; import * as api from './api'; import type { AppMockRenderer } from '../../common/mock'; @@ -35,16 +35,14 @@ describe('Use get case configuration hook', () => { targetConfiguration, ]); - const { result, waitForNextUpdate } = renderHook(() => useGetCaseConfiguration(), { + const { result } = renderHook(() => useGetCaseConfiguration(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - /** * The response after fetching */ - expect(result.current.data).toEqual(targetConfiguration); + await waitFor(() => expect(result.current.data).toEqual(targetConfiguration)); }); it('returns the initial configuration if none matches the owner', async () => { @@ -59,16 +57,14 @@ describe('Use get case configuration hook', () => { { ...initialConfiguration, id: 'my-new-configuration-2', owner: 'bar' }, ]); - const { result, waitForNextUpdate } = renderHook(() => useGetCaseConfiguration(), { + const { result } = renderHook(() => useGetCaseConfiguration(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - /** * The response after fetching */ - expect(result.current.data).toEqual(initialConfiguration); + await waitFor(() => expect(result.current.data).toEqual(initialConfiguration)); }); it('returns the initial configuration if none exists', async () => { @@ -76,16 +72,14 @@ describe('Use get case configuration hook', () => { spy.mockResolvedValue([]); - const { result, waitForNextUpdate } = renderHook(() => useGetCaseConfiguration(), { + const { result } = renderHook(() => useGetCaseConfiguration(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - /** * The response after fetching */ - expect(result.current.data).toEqual(initialConfiguration); + await waitFor(() => expect(result.current.data).toEqual(initialConfiguration)); }); it('returns the initial configuration if the owner is undefined', async () => { @@ -94,15 +88,13 @@ describe('Use get case configuration hook', () => { spy.mockResolvedValue([]); - const { result, waitForNextUpdate } = renderHook(() => useGetCaseConfiguration(), { + const { result } = renderHook(() => useGetCaseConfiguration(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - /** * The response after fetching */ - expect(result.current.data).toEqual(initialConfiguration); + await waitFor(() => expect(result.current.data).toEqual(initialConfiguration)); }); }); diff --git a/x-pack/plugins/cases/public/containers/configure/use_get_case_configurations_query.test.ts b/x-pack/plugins/cases/public/containers/configure/use_get_case_configurations_query.test.ts index adc06ad840d9..02cb83442144 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_get_case_configurations_query.test.ts +++ b/x-pack/plugins/cases/public/containers/configure/use_get_case_configurations_query.test.ts @@ -5,10 +5,9 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; import { useGetCaseConfigurationsQuery } from './use_get_case_configurations_query'; import * as api from './api'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { useToasts } from '../../common/lib/kibana'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; diff --git a/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx.test.tsx index a81d6ac46d18..c9dad2340f2b 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import * as api from './api'; import { noConnectorsCasePermission, TestProviders } from '../../common/mock'; import { useApplicationCapabilities, useToasts } from '../../common/lib/kibana'; @@ -26,15 +26,13 @@ describe('useConnectors', () => { it('fetches connectors', async () => { const spy = jest.spyOn(api, 'getSupportedActionConnectors'); - const { waitForNextUpdate } = renderHook(() => useGetSupportedActionConnectors(), { + renderHook(() => useGetSupportedActionConnectors(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( <TestProviders>{children}</TestProviders> ), }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ signal: expect.any(AbortSignal) }); + await waitFor(() => expect(spy).toHaveBeenCalledWith({ signal: expect.any(AbortSignal) })); }); it('shows a toast error when the API returns error', async () => { @@ -46,45 +44,44 @@ describe('useConnectors', () => { throw new Error('Something went wrong'); }); - const { waitForNextUpdate } = renderHook(() => useGetSupportedActionConnectors(), { + renderHook(() => useGetSupportedActionConnectors(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( <TestProviders>{children}</TestProviders> ), }); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); it('does not fetch connectors when the user does not has access to actions', async () => { const spyOnFetchConnectors = jest.spyOn(api, 'getSupportedActionConnectors'); useApplicationCapabilitiesMock().actions = { crud: false, read: false }; - const { result, waitForNextUpdate } = renderHook(() => useGetSupportedActionConnectors(), { + const { result } = renderHook(() => useGetSupportedActionConnectors(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( <TestProviders>{children}</TestProviders> ), }); - await waitForNextUpdate(); - - expect(spyOnFetchConnectors).not.toHaveBeenCalled(); - expect(result.current.data).toEqual([]); + await waitFor(() => { + expect(spyOnFetchConnectors).not.toHaveBeenCalled(); + expect(result.current.data).toEqual([]); + }); }); it('does not fetch connectors when the user does not has access to connectors', async () => { const spyOnFetchConnectors = jest.spyOn(api, 'getSupportedActionConnectors'); useApplicationCapabilitiesMock().actions = { crud: true, read: true }; - const { result, waitForNextUpdate } = renderHook(() => useGetSupportedActionConnectors(), { + const { result } = renderHook(() => useGetSupportedActionConnectors(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( <TestProviders permissions={noConnectorsCasePermission()}>{children}</TestProviders> ), }); - await waitForNextUpdate(); - - expect(spyOnFetchConnectors).not.toHaveBeenCalled(); - expect(result.current.data).toEqual([]); + await waitFor(() => { + expect(spyOnFetchConnectors).not.toHaveBeenCalled(); + expect(result.current.data).toEqual([]); + }); }); }); diff --git a/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.test.tsx index 4fab35fd5ce5..04b266478f66 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_persist_configuration.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { usePersistConfiguration } from './use_persist_configuration'; import * as api from './api'; @@ -55,7 +55,7 @@ describe('usePersistConfiguration', () => { const spyPost = jest.spyOn(api, 'postCaseConfigure'); const spyPatch = jest.spyOn(api, 'patchCaseConfigure'); - const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + const { result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -80,7 +80,7 @@ describe('usePersistConfiguration', () => { const spyPost = jest.spyOn(api, 'postCaseConfigure'); const spyPatch = jest.spyOn(api, 'patchCaseConfigure'); - const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + const { result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -104,7 +104,7 @@ describe('usePersistConfiguration', () => { it('calls postCaseConfigure with correct data', async () => { const spyPost = jest.spyOn(api, 'postCaseConfigure'); - const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + const { result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -133,7 +133,7 @@ describe('usePersistConfiguration', () => { const spyPost = jest.spyOn(api, 'postCaseConfigure'); const spyPatch = jest.spyOn(api, 'patchCaseConfigure'); - const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + const { result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -157,7 +157,7 @@ describe('usePersistConfiguration', () => { it('calls patchCaseConfigure with correct data', async () => { const spyPatch = jest.spyOn(api, 'patchCaseConfigure'); - const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + const { result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -184,7 +184,7 @@ describe('usePersistConfiguration', () => { it('invalidates the queries correctly', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + const { result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -198,7 +198,7 @@ describe('usePersistConfiguration', () => { }); it('shows the success toaster', async () => { - const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + const { result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); @@ -216,7 +216,7 @@ describe('usePersistConfiguration', () => { .spyOn(api, 'postCaseConfigure') .mockRejectedValue(new Error('useCreateAttachments: Test error')); - const { waitFor, result } = renderHook(() => usePersistConfiguration(), { + const { result } = renderHook(() => usePersistConfiguration(), { wrapper: appMockRender.AppWrapper, }); diff --git a/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx index 17b4568cc9de..50d28921e1aa 100644 --- a/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_bulk_update_case.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useUpdateCases } from './use_bulk_update_case'; import { allCases } from './mock'; import { useToasts } from '../common/lib/kibana'; @@ -32,7 +32,7 @@ describe('useUpdateCases', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'updateCases'); - const { waitForNextUpdate, result } = renderHook(() => useUpdateCases(), { + const { result } = renderHook(() => useUpdateCases(), { wrapper: appMockRender.AppWrapper, }); @@ -40,14 +40,12 @@ describe('useUpdateCases', () => { result.current.mutate({ cases: allCases.cases, successToasterTitle: 'Success title' }); }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ cases: allCases.cases }); + await waitFor(() => expect(spy).toHaveBeenCalledWith({ cases: allCases.cases })); }); it('invalidates the queries correctly', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitForNextUpdate, result } = renderHook(() => useUpdateCases(), { + const { result } = renderHook(() => useUpdateCases(), { wrapper: appMockRender.AppWrapper, }); @@ -55,15 +53,15 @@ describe('useUpdateCases', () => { result.current.mutate({ cases: allCases.cases, successToasterTitle: 'Success title' }); }); - await waitForNextUpdate(); - - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); + await waitFor(() => { + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); + }); }); it('shows a success toaster', async () => { - const { waitForNextUpdate, result } = renderHook(() => useUpdateCases(), { + const { result } = renderHook(() => useUpdateCases(), { wrapper: appMockRender.AppWrapper, }); @@ -71,18 +69,18 @@ describe('useUpdateCases', () => { result.current.mutate({ cases: allCases.cases, successToasterTitle: 'Success title' }); }); - await waitForNextUpdate(); - - expect(addSuccess).toHaveBeenCalledWith({ - title: 'Success title', - className: 'eui-textBreakWord', - }); + await waitFor(() => + expect(addSuccess).toHaveBeenCalledWith({ + title: 'Success title', + className: 'eui-textBreakWord', + }) + ); }); it('shows a toast error when the api return an error', async () => { jest.spyOn(api, 'updateCases').mockRejectedValue(new Error('useUpdateCases: Test error')); - const { waitForNextUpdate, result } = renderHook(() => useUpdateCases(), { + const { result } = renderHook(() => useUpdateCases(), { wrapper: appMockRender.AppWrapper, }); @@ -90,8 +88,6 @@ describe('useUpdateCases', () => { result.current.mutate({ cases: allCases.cases, successToasterTitle: 'Success title' }); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_create_attachments.test.tsx b/x-pack/plugins/cases/public/containers/use_create_attachments.test.tsx index 14d4477df62d..c7aa4b521bcd 100644 --- a/x-pack/plugins/cases/public/containers/use_create_attachments.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_create_attachments.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { AttachmentType } from '../../common/types/domain'; import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; @@ -58,7 +58,7 @@ describe('useCreateAttachments', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'createAttachments'); - const { waitForNextUpdate, result } = renderHook(() => useCreateAttachments(), { + const { result } = renderHook(() => useCreateAttachments(), { wrapper: appMockRender.AppWrapper, }); @@ -66,13 +66,16 @@ describe('useCreateAttachments', () => { result.current.mutate(request); }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ attachments: attachmentsWithOwner, caseId: request.caseId }); + await waitFor(() => + expect(spy).toHaveBeenCalledWith({ + attachments: attachmentsWithOwner, + caseId: request.caseId, + }) + ); }); it('does not show a success toaster', async () => { - const { waitForNextUpdate, result } = renderHook(() => useCreateAttachments(), { + const { result } = renderHook(() => useCreateAttachments(), { wrapper: appMockRender.AppWrapper, }); @@ -80,9 +83,7 @@ describe('useCreateAttachments', () => { result.current.mutate(request); }); - await waitForNextUpdate(); - - expect(addSuccess).not.toHaveBeenCalled(); + await waitFor(() => expect(addSuccess).not.toHaveBeenCalled()); }); it('shows a toast error when the api return an error', async () => { @@ -90,7 +91,7 @@ describe('useCreateAttachments', () => { .spyOn(api, 'createAttachments') .mockRejectedValue(new Error('useCreateAttachments: Test error')); - const { waitForNextUpdate, result } = renderHook(() => useCreateAttachments(), { + const { result } = renderHook(() => useCreateAttachments(), { wrapper: appMockRender.AppWrapper, }); @@ -98,8 +99,6 @@ describe('useCreateAttachments', () => { result.current.mutate(request); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx index f0ec4e9a43d8..405351c3f068 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_cases.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useDeleteCases } from './use_delete_cases'; import * as api from './api'; @@ -32,7 +32,7 @@ describe('useDeleteCases', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'deleteCases'); - const { waitForNextUpdate, result } = renderHook(() => useDeleteCases(), { + const { result } = renderHook(() => useDeleteCases(), { wrapper: appMockRender.AppWrapper, }); @@ -40,14 +40,12 @@ describe('useDeleteCases', () => { result.current.mutate({ caseIds: ['1', '2'], successToasterTitle: 'Success title' }); }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ caseIds: ['1', '2'] }); + await waitFor(() => expect(spy).toHaveBeenCalledWith({ caseIds: ['1', '2'] })); }); it('invalidates the queries correctly', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitForNextUpdate, result } = renderHook(() => useDeleteCases(), { + const { result } = renderHook(() => useDeleteCases(), { wrapper: appMockRender.AppWrapper, }); @@ -55,15 +53,15 @@ describe('useDeleteCases', () => { result.current.mutate({ caseIds: ['1', '2'], successToasterTitle: 'Success title' }); }); - await waitForNextUpdate(); - - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); + await waitFor(() => { + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); + }); }); it('shows a success toaster', async () => { - const { waitForNextUpdate, result } = renderHook(() => useDeleteCases(), { + const { result } = renderHook(() => useDeleteCases(), { wrapper: appMockRender.AppWrapper, }); @@ -71,18 +69,18 @@ describe('useDeleteCases', () => { result.current.mutate({ caseIds: ['1', '2'], successToasterTitle: 'Success title' }); }); - await waitForNextUpdate(); - - expect(addSuccess).toHaveBeenCalledWith({ - title: 'Success title', - className: 'eui-textBreakWord', - }); + await waitFor(() => + expect(addSuccess).toHaveBeenCalledWith({ + title: 'Success title', + className: 'eui-textBreakWord', + }) + ); }); it('shows a toast error when the api return an error', async () => { jest.spyOn(api, 'deleteCases').mockRejectedValue(new Error('useDeleteCases: Test error')); - const { waitForNextUpdate, result } = renderHook(() => useDeleteCases(), { + const { result } = renderHook(() => useDeleteCases(), { wrapper: appMockRender.AppWrapper, }); @@ -90,8 +88,6 @@ describe('useDeleteCases', () => { result.current.mutate({ caseIds: ['1', '2'], successToasterTitle: 'Success title' }); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_delete_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_comment.test.tsx index c185a7394c88..c3d0739cec37 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_comment.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_comment.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useDeleteComment } from './use_delete_comment'; import * as api from './api'; import { basicCaseId } from './mock'; @@ -45,7 +45,7 @@ describe('useDeleteComment', () => { it('calls deleteComment with correct arguments - case', async () => { const spyOnDeleteComment = jest.spyOn(api, 'deleteComment'); - const { waitForNextUpdate, result } = renderHook(() => useDeleteComment(), { + const { result } = renderHook(() => useDeleteComment(), { wrapper: appMockRender.AppWrapper, }); @@ -57,32 +57,32 @@ describe('useDeleteComment', () => { }); }); - await waitForNextUpdate(); - - expect(spyOnDeleteComment).toBeCalledWith({ - caseId: basicCaseId, - commentId, - }); + await waitFor(() => + expect(spyOnDeleteComment).toBeCalledWith({ + caseId: basicCaseId, + commentId, + }) + ); }); it('refreshes the case page view after delete', async () => { - const { waitForNextUpdate, result } = renderHook(() => useDeleteComment(), { + const { result } = renderHook(() => useDeleteComment(), { wrapper: appMockRender.AppWrapper, }); - result.current.mutate({ - caseId: basicCaseId, - commentId, - successToasterTitle, + act(() => { + result.current.mutate({ + caseId: basicCaseId, + commentId, + successToasterTitle, + }); }); - await waitForNextUpdate(); - - expect(useRefreshCaseViewPage()).toBeCalled(); + await waitFor(() => expect(useRefreshCaseViewPage()).toBeCalled()); }); it('shows a success toaster correctly', async () => { - const { waitForNextUpdate, result } = renderHook(() => useDeleteComment(), { + const { result } = renderHook(() => useDeleteComment(), { wrapper: appMockRender.AppWrapper, }); @@ -94,36 +94,38 @@ describe('useDeleteComment', () => { }); }); - await waitForNextUpdate(); - - expect(addSuccess).toHaveBeenCalledWith({ - title: 'Deleted', - className: 'eui-textBreakWord', - }); + await waitFor(() => + expect(addSuccess).toHaveBeenCalledWith({ + title: 'Deleted', + className: 'eui-textBreakWord', + }) + ); }); it('sets isError when fails to delete a case', async () => { const spyOnDeleteComment = jest.spyOn(api, 'deleteComment'); spyOnDeleteComment.mockRejectedValue(new Error('Error')); - const { waitForNextUpdate, result } = renderHook(() => useDeleteComment(), { + const { result } = renderHook(() => useDeleteComment(), { wrapper: appMockRender.AppWrapper, }); - result.current.mutate({ - caseId: basicCaseId, - commentId, - successToasterTitle, + act(() => { + result.current.mutate({ + caseId: basicCaseId, + commentId, + successToasterTitle, + }); }); - await waitForNextUpdate(); + await waitFor(() => { + expect(spyOnDeleteComment).toBeCalledWith({ + caseId: basicCaseId, + commentId, + }); - expect(spyOnDeleteComment).toBeCalledWith({ - caseId: basicCaseId, - commentId, + expect(addError).toHaveBeenCalled(); + expect(result.current.isError).toBe(true); }); - - expect(addError).toHaveBeenCalled(); - expect(result.current.isError).toBe(true); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_delete_file_attachment.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_file_attachment.test.tsx index 51382a0f548d..329423e41dfa 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_file_attachment.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_file_attachment.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import * as api from './api'; import { basicCaseId, basicFileMock } from './mock'; import { useRefreshCaseViewPage } from '../components/case_view/use_on_refresh_case_view_page'; @@ -34,7 +34,7 @@ describe('useDeleteFileAttachment', () => { it('calls deleteFileAttachment with correct arguments - case', async () => { const spyOnDeleteFileAttachments = jest.spyOn(api, 'deleteFileAttachments'); - const { waitForNextUpdate, result } = renderHook(() => useDeleteFileAttachment(), { + const { result } = renderHook(() => useDeleteFileAttachment(), { wrapper: appMockRender.AppWrapper, }); @@ -45,16 +45,16 @@ describe('useDeleteFileAttachment', () => { }); }); - await waitForNextUpdate(); - - expect(spyOnDeleteFileAttachments).toHaveBeenCalledWith({ - caseId: basicCaseId, - fileIds: [basicFileMock.id], - }); + await waitFor(() => + expect(spyOnDeleteFileAttachments).toHaveBeenCalledWith({ + caseId: basicCaseId, + fileIds: [basicFileMock.id], + }) + ); }); it('refreshes the case page view', async () => { - const { waitForNextUpdate, result } = renderHook(() => useDeleteFileAttachment(), { + const { result } = renderHook(() => useDeleteFileAttachment(), { wrapper: appMockRender.AppWrapper, }); @@ -65,13 +65,11 @@ describe('useDeleteFileAttachment', () => { }) ); - await waitForNextUpdate(); - - expect(useRefreshCaseViewPage()).toBeCalled(); + await waitFor(() => expect(useRefreshCaseViewPage()).toBeCalled()); }); it('shows a success toaster correctly', async () => { - const { waitForNextUpdate, result } = renderHook(() => useDeleteFileAttachment(), { + const { result } = renderHook(() => useDeleteFileAttachment(), { wrapper: appMockRender.AppWrapper, }); @@ -82,19 +80,19 @@ describe('useDeleteFileAttachment', () => { }) ); - await waitForNextUpdate(); - - expect(addSuccess).toHaveBeenCalledWith({ - title: 'File deleted successfully', - className: 'eui-textBreakWord', - }); + await waitFor(() => + expect(addSuccess).toHaveBeenCalledWith({ + title: 'File deleted successfully', + className: 'eui-textBreakWord', + }) + ); }); it('sets isError when fails to delete a file attachment', async () => { const spyOnDeleteFileAttachments = jest.spyOn(api, 'deleteFileAttachments'); spyOnDeleteFileAttachments.mockRejectedValue(new Error('Error')); - const { waitForNextUpdate, result } = renderHook(() => useDeleteFileAttachment(), { + const { result } = renderHook(() => useDeleteFileAttachment(), { wrapper: appMockRender.AppWrapper, }); @@ -105,7 +103,7 @@ describe('useDeleteFileAttachment', () => { }) ); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.isError).toBe(true)); expect(spyOnDeleteFileAttachments).toBeCalledWith({ caseId: basicCaseId, @@ -113,6 +111,5 @@ describe('useDeleteFileAttachment', () => { }); expect(addError).toHaveBeenCalled(); - expect(result.current.isError).toBe(true); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_find_case_user_actions.test.tsx b/x-pack/plugins/cases/public/containers/use_find_case_user_actions.test.tsx index 67110c8ebc0b..7dfaa1ff146e 100644 --- a/x-pack/plugins/cases/public/containers/use_find_case_user_actions.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_find_case_user_actions.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useFindCaseUserActions } from './use_find_case_user_actions'; import type { CaseUserActionTypeWithAll } from '../../common/ui/types'; import { basicCase, findCaseUserActionsResponse } from './mock'; @@ -43,33 +43,32 @@ describe('UseFindCaseUserActions', () => { }); it('returns proper state on findCaseUserActions', async () => { - const { result, waitForNextUpdate } = renderHook( - () => useFindCaseUserActions(basicCase.id, params, isEnabled), - { wrapper: appMockRender.AppWrapper } - ); - - await waitForNextUpdate(); - - expect(result.current).toEqual( - expect.objectContaining({ - ...initialData, - data: { - userActions: [...findCaseUserActionsResponse.userActions], - total: 30, - perPage: 10, - page: 1, - }, - isError: false, - isLoading: false, - isFetching: false, - }) + const { result } = renderHook(() => useFindCaseUserActions(basicCase.id, params, isEnabled), { + wrapper: appMockRender.AppWrapper, + }); + + await waitFor(() => + expect(result.current).toEqual( + expect.objectContaining({ + ...initialData, + data: { + userActions: [...findCaseUserActionsResponse.userActions], + total: 30, + perPage: 10, + page: 1, + }, + isError: false, + isLoading: false, + isFetching: false, + }) + ) ); }); it('calls the API with correct parameters', async () => { const spy = jest.spyOn(api, 'findCaseUserActions').mockRejectedValue(initialData); - const { waitForNextUpdate } = renderHook( + renderHook( () => useFindCaseUserActions( basicCase.id, @@ -84,12 +83,12 @@ describe('UseFindCaseUserActions', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith( - basicCase.id, - { type: 'user', sortOrder: 'desc', page: 1, perPage: 5 }, - expect.any(AbortSignal) + await waitFor(() => + expect(spy).toHaveBeenCalledWith( + basicCase.id, + { type: 'user', sortOrder: 'desc', page: 1, perPage: 5 }, + expect.any(AbortSignal) + ) ); }); @@ -120,20 +119,17 @@ describe('UseFindCaseUserActions', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addError }); - const { waitForNextUpdate } = renderHook( - () => useFindCaseUserActions(basicCase.id, params, isEnabled), - { - wrapper: appMockRender.AppWrapper, - } - ); - - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith( - basicCase.id, - { type: filterActionType, sortOrder, page: 1, perPage: 10 }, - expect.any(AbortSignal) - ); - expect(addError).toHaveBeenCalled(); + renderHook(() => useFindCaseUserActions(basicCase.id, params, isEnabled), { + wrapper: appMockRender.AppWrapper, + }); + + await waitFor(() => { + expect(spy).toHaveBeenCalledWith( + basicCase.id, + { type: filterActionType, sortOrder, page: 1, perPage: 10 }, + expect.any(AbortSignal) + ); + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_action_license.test.tsx b/x-pack/plugins/cases/public/containers/use_get_action_license.test.tsx index 43e445fa39aa..33cba9ef7123 100644 --- a/x-pack/plugins/cases/public/containers/use_get_action_license.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_action_license.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import * as api from './api'; import { useGetActionLicense } from './use_get_action_license'; import type { AppMockRenderer } from '../common/mock'; @@ -25,12 +25,11 @@ describe('useGetActionLicense', () => { it('calls getActionLicense with correct arguments', async () => { const spyOnGetActionLicense = jest.spyOn(api, 'getActionLicense'); - const { waitForNextUpdate } = renderHook(() => useGetActionLicense(), { + renderHook(() => useGetActionLicense(), { wrapper: appMockRenderer.AppWrapper, }); - await waitForNextUpdate(); - expect(spyOnGetActionLicense).toBeCalledWith(abortCtrl.signal); + await waitFor(() => expect(spyOnGetActionLicense).toBeCalledWith(abortCtrl.signal)); }); it('unhappy path', async () => { @@ -42,11 +41,9 @@ describe('useGetActionLicense', () => { throw new Error('Something went wrong'); }); - const { waitForNextUpdate } = renderHook(() => useGetActionLicense(), { + renderHook(() => useGetActionLicense(), { wrapper: appMockRenderer.AppWrapper, }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_case.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case.test.tsx index 1187c7de07d2..6f693e5d4dc1 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case.test.tsx @@ -5,10 +5,9 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; import { useGetCase } from './use_get_case'; import * as api from './api'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import type { FC, PropsWithChildren } from 'react'; import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -32,21 +31,21 @@ const wrapper: FC<PropsWithChildren<unknown>> = ({ children }) => { describe.skip('Use get case hook', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'resolveCase'); - const { waitForNextUpdate } = renderHook(() => useGetCase('case-1'), { wrapper }); - await waitForNextUpdate(); - expect(spy).toHaveBeenCalledWith({ - caseId: 'case-1', - includeComments: true, - signal: expect.any(AbortSignal), - }); + renderHook(() => useGetCase('case-1'), { wrapper }); + await waitFor(() => + expect(spy).toHaveBeenCalledWith({ + caseId: 'case-1', + includeComments: true, + signal: expect.any(AbortSignal), + }) + ); }); it('shows a toast error when the api return an error', async () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addError }); const spy = jest.spyOn(api, 'resolveCase').mockRejectedValue(new Error("C'est la vie")); - const { waitForNextUpdate } = renderHook(() => useGetCase('case-1'), { wrapper }); - await waitForNextUpdate(); + renderHook(() => useGetCase('case-1'), { wrapper }); await waitFor(() => { expect(spy).toHaveBeenCalledWith({ caseId: 'case-1', diff --git a/x-pack/plugins/cases/public/containers/use_get_case_connectors.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_connectors.test.tsx index e73012f83f72..713f8865cd02 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_connectors.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_connectors.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import * as api from './api'; import type { AppMockRenderer } from '../common/mock'; import { createAppMockRenderer } from '../common/mock'; @@ -30,7 +30,7 @@ describe('useGetCaseConnectors', () => { it('calls getCaseConnectors with correct arguments', async () => { const spyOnGetCases = jest.spyOn(api, 'getCaseConnectors'); - const { waitFor } = renderHook(() => useGetCaseConnectors(caseId), { + renderHook(() => useGetCaseConnectors(caseId), { wrapper: appMockRender.AppWrapper, }); @@ -50,7 +50,7 @@ describe('useGetCaseConnectors', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); - const { waitFor } = renderHook(() => useGetCaseConnectors(caseId), { + renderHook(() => useGetCaseConnectors(caseId), { wrapper: appMockRender.AppWrapper, }); diff --git a/x-pack/plugins/cases/public/containers/use_get_case_file_stats.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_file_stats.test.tsx index e7c55cd1fc0c..dbf800577e85 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_file_stats.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_file_stats.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { basicCase } from './mock'; @@ -37,12 +37,12 @@ describe('useGetCaseFileStats', () => { }); it('calls filesClient.list with correct arguments', async () => { - const { waitForNextUpdate } = renderHook(() => useGetCaseFileStats(hookParams), { + renderHook(() => useGetCaseFileStats(hookParams), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(appMockRender.getFilesClient().list).toHaveBeenCalledWith(expectedCallParams); + await waitFor(() => + expect(appMockRender.getFilesClient().list).toHaveBeenCalledWith(expectedCallParams) + ); }); it('shows an error toast when filesClient.list throws', async () => { @@ -53,12 +53,12 @@ describe('useGetCaseFileStats', () => { throw new Error('Something went wrong'); }); - const { waitForNextUpdate } = renderHook(() => useGetCaseFileStats(hookParams), { + renderHook(() => useGetCaseFileStats(hookParams), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(appMockRender.getFilesClient().list).toHaveBeenCalledWith(expectedCallParams); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(appMockRender.getFilesClient().list).toHaveBeenCalledWith(expectedCallParams); + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_case_files.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_files.test.tsx index 2be8dd605240..c95eaf15aefe 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_files.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_files.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { basicCase } from './mock'; @@ -48,22 +48,22 @@ describe('useGetCaseFiles', () => { throw new Error('Something went wrong'); }); - const { waitForNextUpdate } = renderHook(() => useGetCaseFiles(hookParams), { + renderHook(() => useGetCaseFiles(hookParams), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(appMockRender.getFilesClient().list).toBeCalledWith(expectedCallParams); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(appMockRender.getFilesClient().list).toBeCalledWith(expectedCallParams); + expect(addError).toHaveBeenCalled(); + }); }); it('calls filesClient.list with correct arguments', async () => { - const { waitForNextUpdate } = renderHook(() => useGetCaseFiles(hookParams), { + renderHook(() => useGetCaseFiles(hookParams), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(appMockRender.getFilesClient().list).toBeCalledWith(expectedCallParams); + await waitFor(() => + expect(appMockRender.getFilesClient().list).toBeCalledWith(expectedCallParams) + ); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_case_metrics.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_metrics.test.tsx index 3cda384f1c63..0feb032649a6 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_metrics.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_metrics.test.tsx @@ -7,7 +7,7 @@ import type { FC, PropsWithChildren } from 'react'; import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import type { SingleCaseMetricsFeature } from '../../common/ui'; import { useGetCaseMetrics } from './use_get_case_metrics'; import { basicCase } from './mock'; @@ -34,12 +34,13 @@ describe('useGetCaseMetrics', () => { it('calls getSingleCaseMetrics with correct arguments', async () => { const spyOnGetCaseMetrics = jest.spyOn(api, 'getSingleCaseMetrics'); - const { waitForNextUpdate } = renderHook(() => useGetCaseMetrics(basicCase.id, features), { + renderHook(() => useGetCaseMetrics(basicCase.id, features), { wrapper, }); - await waitForNextUpdate(); - expect(spyOnGetCaseMetrics).toBeCalledWith(basicCase.id, features, abortCtrl.signal); + await waitFor(() => + expect(spyOnGetCaseMetrics).toBeCalledWith(basicCase.id, features, abortCtrl.signal) + ); }); it('shows an error toast when getSingleCaseMetrics throws', async () => { @@ -51,13 +52,13 @@ describe('useGetCaseMetrics', () => { throw new Error('Something went wrong'); }); - const { waitForNextUpdate } = renderHook(() => useGetCaseMetrics(basicCase.id, features), { + renderHook(() => useGetCaseMetrics(basicCase.id, features), { wrapper, }); - await waitForNextUpdate(); - - expect(spyOnGetCaseMetrics).toBeCalledWith(basicCase.id, features, abortCtrl.signal); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(spyOnGetCaseMetrics).toBeCalledWith(basicCase.id, features, abortCtrl.signal); + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_case_user_actions_stats.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_user_actions_stats.test.tsx index b81f9d1448aa..bd8ecf7393b2 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_user_actions_stats.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_user_actions_stats.test.tsx @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; + +import { waitFor, renderHook } from '@testing-library/react'; import { useToasts } from '../common/lib/kibana'; import type { AppMockRenderer } from '../common/mock'; @@ -31,12 +32,11 @@ describe('useGetCaseUserActionsStats', () => { }); it('returns proper state on getCaseUserActionsStats', async () => { - const { result, waitForNextUpdate } = renderHook( - () => useGetCaseUserActionsStats(basicCase.id), - { wrapper: appMockRender.AppWrapper } - ); + const { result } = renderHook(() => useGetCaseUserActionsStats(basicCase.id), { + wrapper: appMockRender.AppWrapper, + }); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current).toEqual( expect.objectContaining({ @@ -61,25 +61,23 @@ describe('useGetCaseUserActionsStats', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addError }); - const { waitForNextUpdate } = renderHook(() => useGetCaseUserActionsStats(basicCase.id), { + renderHook(() => useGetCaseUserActionsStats(basicCase.id), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith(basicCase.id, expect.any(AbortSignal)); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(spy).toHaveBeenCalledWith(basicCase.id, expect.any(AbortSignal)); + expect(addError).toHaveBeenCalled(); + }); }); it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getCaseUserActionsStats'); - const { waitForNextUpdate } = renderHook(() => useGetCaseUserActionsStats(basicCase.id), { + renderHook(() => useGetCaseUserActionsStats(basicCase.id), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith(basicCase.id, expect.any(AbortSignal)); + await waitFor(() => expect(spy).toHaveBeenCalledWith(basicCase.id, expect.any(AbortSignal))); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_case_users.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_users.test.tsx index e35e41c3d49f..7f425081989b 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case_users.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case_users.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useGetCaseUsers } from './use_get_case_users'; import * as api from './api'; import { useToasts } from '../common/lib/kibana'; @@ -26,13 +26,13 @@ describe('useGetCaseUsers', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getCaseUsers'); - const { waitForNextUpdate } = renderHook(() => useGetCaseUsers('case-1'), { + renderHook(() => useGetCaseUsers('case-1'), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ caseId: 'case-1', signal: expect.any(AbortSignal) }); + await waitFor(() => + expect(spy).toHaveBeenCalledWith({ caseId: 'case-1', signal: expect.any(AbortSignal) }) + ); }); it('shows a toast error when the api return an error', async () => { @@ -40,13 +40,13 @@ describe('useGetCaseUsers', () => { (useToasts as jest.Mock).mockReturnValue({ addError }); const spy = jest.spyOn(api, 'getCaseUsers').mockRejectedValue(new Error("C'est la vie")); - const { waitForNextUpdate } = renderHook(() => useGetCaseUsers('case-1'), { + renderHook(() => useGetCaseUsers('case-1'), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ caseId: 'case-1', signal: expect.any(AbortSignal) }); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(spy).toHaveBeenCalledWith({ caseId: 'case-1', signal: expect.any(AbortSignal) }); + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx index 92d7abde2f9d..0719c14e9fc4 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './constants'; import { useGetCases } from './use_get_cases'; import * as api from './api'; @@ -31,7 +31,7 @@ describe('useGetCases', () => { it('calls getCases with correct arguments', async () => { const spyOnGetCases = jest.spyOn(api, 'getCases'); - const { waitFor } = renderHook(() => useGetCases(), { + renderHook(() => useGetCases(), { wrapper: appMockRender.AppWrapper, }); @@ -55,7 +55,7 @@ describe('useGetCases', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); - const { waitFor } = renderHook(() => useGetCases(), { + renderHook(() => useGetCases(), { wrapper: appMockRender.AppWrapper, }); @@ -90,7 +90,7 @@ describe('useGetCases', () => { }; const spyOnGetCases = jest.spyOn(api, 'getCases'); - const { waitFor } = renderHook(() => useGetCases(), { + renderHook(() => useGetCases(), { wrapper: appMockRender.AppWrapper, }); @@ -109,7 +109,7 @@ describe('useGetCases', () => { appMockRender = createAppMockRenderer({ owner: [] }); const spyOnGetCases = jest.spyOn(api, 'getCases'); - const { waitFor } = renderHook(() => useGetCases(), { + renderHook(() => useGetCases(), { wrapper: appMockRender.AppWrapper, }); @@ -128,7 +128,7 @@ describe('useGetCases', () => { appMockRender = createAppMockRenderer({ owner: ['observability'] }); const spyOnGetCases = jest.spyOn(api, 'getCases'); - const { waitFor } = renderHook(() => useGetCases(), { + renderHook(() => useGetCases(), { wrapper: appMockRender.AppWrapper, }); @@ -147,7 +147,7 @@ describe('useGetCases', () => { appMockRender = createAppMockRenderer({ owner: ['observability'] }); const spyOnGetCases = jest.spyOn(api, 'getCases'); - const { waitFor } = renderHook(() => useGetCases({ filterOptions: { owner: ['my-owner'] } }), { + renderHook(() => useGetCases({ filterOptions: { owner: ['my-owner'] } }), { wrapper: appMockRender.AppWrapper, }); diff --git a/x-pack/plugins/cases/public/containers/use_get_cases_metrics.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_metrics.test.tsx index 619e1ea4e1eb..895d41bbfa4c 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases_metrics.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_metrics.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import * as api from '../api'; import type { AppMockRenderer } from '../common/mock'; import { createAppMockRenderer } from '../common/mock'; @@ -33,17 +33,17 @@ describe('useGetCasesMetrics', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getCasesMetrics'); - const { waitForNextUpdate } = renderHook(() => useGetCasesMetrics(), { + renderHook(() => useGetCasesMetrics(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ - http: expect.anything(), - signal: abortCtrl.signal, - query: { owner: [SECURITY_SOLUTION_OWNER], features: [CaseMetricsFeature.MTTR] }, - }); + await waitFor(() => + expect(spy).toHaveBeenCalledWith({ + http: expect.anything(), + signal: abortCtrl.signal, + query: { owner: [SECURITY_SOLUTION_OWNER], features: [CaseMetricsFeature.MTTR] }, + }) + ); }); it('shows a toast error when the api return an error', async () => { @@ -51,12 +51,12 @@ describe('useGetCasesMetrics', () => { .spyOn(api, 'getCasesMetrics') .mockRejectedValue(new Error('useGetCasesMetrics: Test error')); - const { waitForNextUpdate } = renderHook(() => useGetCasesMetrics(), { + renderHook(() => useGetCasesMetrics(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx index fed7159ac15e..e24ac9d1fec4 100644 --- a/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_cases_status.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useGetCasesStatus } from './use_get_cases_status'; import * as api from '../api'; import type { AppMockRenderer } from '../common/mock'; @@ -32,17 +32,17 @@ describe('useGetCasesMetrics', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getCasesStatus'); - const { waitForNextUpdate } = renderHook(() => useGetCasesStatus(), { + renderHook(() => useGetCasesStatus(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ - http: expect.anything(), - signal: abortCtrl.signal, - query: { owner: [SECURITY_SOLUTION_OWNER] }, - }); + await waitFor(() => + expect(spy).toHaveBeenCalledWith({ + http: expect.anything(), + signal: abortCtrl.signal, + query: { owner: [SECURITY_SOLUTION_OWNER] }, + }) + ); }); it('shows a toast error when the api return an error', async () => { @@ -50,12 +50,10 @@ describe('useGetCasesMetrics', () => { .spyOn(api, 'getCasesStatus') .mockRejectedValue(new Error('useGetCasesMetrics: Test error')); - const { waitForNextUpdate } = renderHook(() => useGetCasesStatus(), { + renderHook(() => useGetCasesStatus(), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_categories.test.tsx b/x-pack/plugins/cases/public/containers/use_get_categories.test.tsx index d80f03822ec7..11b242a3072d 100644 --- a/x-pack/plugins/cases/public/containers/use_get_categories.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_categories.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import * as api from './api'; import { TestProviders } from '../common/mock'; import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; @@ -24,18 +24,18 @@ describe('useGetCategories', () => { it('calls getCategories api', async () => { const spyOnGetCategories = jest.spyOn(api, 'getCategories'); - const { waitForNextUpdate } = renderHook(() => useGetCategories(), { + renderHook(() => useGetCategories(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( <TestProviders>{children}</TestProviders> ), }); - await waitForNextUpdate(); - - expect(spyOnGetCategories).toBeCalledWith({ - signal: abortCtrl.signal, - owner: [SECURITY_SOLUTION_OWNER], - }); + await waitFor(() => + expect(spyOnGetCategories).toBeCalledWith({ + signal: abortCtrl.signal, + owner: [SECURITY_SOLUTION_OWNER], + }) + ); }); it('displays an error toast when an error occurs', async () => { @@ -47,14 +47,12 @@ describe('useGetCategories', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addError }); - const { waitForNextUpdate } = renderHook(() => useGetCategories(), { + renderHook(() => useGetCategories(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( <TestProviders>{children}</TestProviders> ), }); - await waitForNextUpdate(); - - expect(addError).toBeCalled(); + await waitFor(() => expect(addError).toBeCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx b/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx index 9446007e367d..dee68dff0337 100644 --- a/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { useToasts } from '../common/lib/kibana'; import type { AppMockRenderer } from '../common/mock'; import { createAppMockRenderer } from '../common/mock'; @@ -32,12 +31,10 @@ describe('useGetFeaturesIds', () => { it('returns the features ids correctly', async () => { const spy = jest.spyOn(api, 'getFeatureIds').mockRejectedValue([]); - const { waitForNextUpdate } = renderHook(() => useGetFeatureIds(['alert-id-1'], true), { + renderHook(() => useGetFeatureIds(['alert-id-1'], true), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - await waitFor(() => { expect(spy).toHaveBeenCalledWith({ query: { @@ -67,12 +64,10 @@ describe('useGetFeaturesIds', () => { .spyOn(api, 'getFeatureIds') .mockRejectedValue(new Error('Something went wrong')); - const { waitForNextUpdate } = renderHook(() => useGetFeatureIds(['alert-id-1'], true), { + renderHook(() => useGetFeatureIds(['alert-id-1'], true), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - await waitFor(() => { expect(spy).toHaveBeenCalledWith({ query: { diff --git a/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx b/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx index a19074ff279e..315757f065d8 100644 --- a/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_tags.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import * as api from './api'; import { TestProviders } from '../common/mock'; import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; @@ -24,16 +24,17 @@ describe('useGetTags', () => { it('calls getTags api', async () => { const spyOnGetTags = jest.spyOn(api, 'getTags'); - const { waitForNextUpdate } = renderHook(() => useGetTags(), { + renderHook(() => useGetTags(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( <TestProviders>{children}</TestProviders> ), }); - await waitForNextUpdate(); - expect(spyOnGetTags).toBeCalledWith({ - owner: [SECURITY_SOLUTION_OWNER], - signal: abortCtrl.signal, - }); + await waitFor(() => + expect(spyOnGetTags).toBeCalledWith({ + owner: [SECURITY_SOLUTION_OWNER], + signal: abortCtrl.signal, + }) + ); }); it('displays and error toast when an error occurs', async () => { @@ -43,12 +44,11 @@ describe('useGetTags', () => { spyOnGetTags.mockImplementation(() => { throw new Error('Something went wrong'); }); - const { waitForNextUpdate } = renderHook(() => useGetTags(), { + renderHook(() => useGetTags(), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( <TestProviders>{children}</TestProviders> ), }); - await waitForNextUpdate(); - expect(addError).toBeCalled(); + await waitFor(() => expect(addError).toBeCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_infinite_find_case_user_actions.test.tsx b/x-pack/plugins/cases/public/containers/use_infinite_find_case_user_actions.test.tsx index b3df805033ba..19a7f2b9edc3 100644 --- a/x-pack/plugins/cases/public/containers/use_infinite_find_case_user_actions.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_infinite_find_case_user_actions.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useInfiniteFindCaseUserActions } from './use_infinite_find_case_user_actions'; import type { CaseUserActionTypeWithAll } from '../../common/ui/types'; @@ -42,12 +42,12 @@ describe('UseInfiniteFindCaseUserActions', () => { }); it('returns proper state on findCaseUserActions', async () => { - const { result, waitForNextUpdate } = renderHook( + const { result } = renderHook( () => useInfiniteFindCaseUserActions(basicCase.id, params, isEnabled), { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current).toEqual( expect.objectContaining({ @@ -73,7 +73,7 @@ describe('UseInfiniteFindCaseUserActions', () => { it('calls the API with correct parameters', async () => { const spy = jest.spyOn(api, 'findCaseUserActions').mockRejectedValue(initialData); - const { waitForNextUpdate } = renderHook( + renderHook( () => useInfiniteFindCaseUserActions( basicCase.id, @@ -87,12 +87,12 @@ describe('UseInfiniteFindCaseUserActions', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith( - basicCase.id, - { type: 'user', sortOrder: 'desc', page: 1, perPage: 5 }, - expect.any(AbortSignal) + await waitFor(() => + expect(spy).toHaveBeenCalledWith( + basicCase.id, + { type: 'user', sortOrder: 'desc', page: 1, perPage: 5 }, + expect.any(AbortSignal) + ) ); }); @@ -122,33 +122,31 @@ describe('UseInfiniteFindCaseUserActions', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addError }); - const { waitForNextUpdate } = renderHook( - () => useInfiniteFindCaseUserActions(basicCase.id, params, isEnabled), - { - wrapper: appMockRender.AppWrapper, - } - ); + renderHook(() => useInfiniteFindCaseUserActions(basicCase.id, params, isEnabled), { + wrapper: appMockRender.AppWrapper, + }); - await waitForNextUpdate(); + await waitFor(() => { + expect(spy).toHaveBeenCalledWith( + basicCase.id, + { type: filterActionType, sortOrder, page: 1, perPage: 10 }, + expect.any(AbortSignal) + ); + expect(addError).toHaveBeenCalled(); + }); - expect(spy).toHaveBeenCalledWith( - basicCase.id, - { type: filterActionType, sortOrder, page: 1, perPage: 10 }, - expect.any(AbortSignal) - ); - expect(addError).toHaveBeenCalled(); spy.mockRestore(); }); it('fetches next page with correct params', async () => { const spy = jest.spyOn(api, 'findCaseUserActions'); - const { result, waitFor } = renderHook( + const { result } = renderHook( () => useInfiniteFindCaseUserActions(basicCase.id, params, isEnabled), { wrapper: appMockRender.AppWrapper } ); - await waitFor(() => result.current.isSuccess); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data?.pages).toStrictEqual([findCaseUserActionsResponse]); @@ -165,7 +163,7 @@ describe('UseInfiniteFindCaseUserActions', () => { expect.any(AbortSignal) ); }); - await waitFor(() => result.current.data?.pages.length === 2); + await waitFor(() => expect(result.current.data?.pages).toHaveLength(2)); }); it('returns hasNextPage correctly', async () => { diff --git a/x-pack/plugins/cases/public/containers/use_messages_storage.test.tsx b/x-pack/plugins/cases/public/containers/use_messages_storage.test.tsx index ef4a164e759b..30254f02412f 100644 --- a/x-pack/plugins/cases/public/containers/use_messages_storage.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_messages_storage.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; -import type { UseMessagesStorage } from './use_messages_storage'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useMessagesStorage } from './use_messages_storage'; describe('useMessagesStorage', () => { @@ -18,35 +17,29 @@ describe('useMessagesStorage', () => { }); it('should return an empty array when there is no messages', async () => { + const { result } = renderHook(() => useMessagesStorage()); + await waitFor(() => new Promise((resolve) => resolve(null))); + const { getMessages } = result.current; await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => - useMessagesStorage() - ); - await waitForNextUpdate(); - const { getMessages } = result.current; expect(getMessages('case')).toEqual([]); }); }); it('should add a message', async () => { + const { result } = renderHook(() => useMessagesStorage()); + await waitFor(() => new Promise((resolve) => resolve(null))); + const { getMessages, addMessage } = result.current; await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => - useMessagesStorage() - ); - await waitForNextUpdate(); - const { getMessages, addMessage } = result.current; addMessage('case', 'id-1'); expect(getMessages('case')).toEqual(['id-1']); }); }); it('should add multiple messages', async () => { + const { result } = renderHook(() => useMessagesStorage()); + await waitFor(() => new Promise((resolve) => resolve(null))); + const { getMessages, addMessage } = result.current; await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => - useMessagesStorage() - ); - await waitForNextUpdate(); - const { getMessages, addMessage } = result.current; addMessage('case', 'id-1'); addMessage('case', 'id-2'); expect(getMessages('case')).toEqual(['id-1', 'id-2']); @@ -54,12 +47,10 @@ describe('useMessagesStorage', () => { }); it('should remove a message', async () => { + const { result } = renderHook(() => useMessagesStorage()); + await waitFor(() => new Promise((resolve) => resolve(null))); + const { getMessages, addMessage, removeMessage } = result.current; await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => - useMessagesStorage() - ); - await waitForNextUpdate(); - const { getMessages, addMessage, removeMessage } = result.current; addMessage('case', 'id-1'); addMessage('case', 'id-2'); removeMessage('case', 'id-2'); @@ -68,12 +59,10 @@ describe('useMessagesStorage', () => { }); it('should return presence of a message', async () => { + const { result } = renderHook(() => useMessagesStorage()); + await waitFor(() => new Promise((resolve) => resolve(null))); + const { hasMessage, addMessage, removeMessage } = result.current; await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => - useMessagesStorage() - ); - await waitForNextUpdate(); - const { hasMessage, addMessage, removeMessage } = result.current; addMessage('case', 'id-1'); addMessage('case', 'id-2'); removeMessage('case', 'id-2'); @@ -83,16 +72,14 @@ describe('useMessagesStorage', () => { }); it('should clear all messages', async () => { + const { result } = renderHook(() => useMessagesStorage()); + await waitFor(() => new Promise((resolve) => resolve(null))); + const { getMessages, addMessage, clearAllMessages } = result.current; await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseMessagesStorage>(() => - useMessagesStorage() - ); - await waitForNextUpdate(); - const { getMessages, addMessage, clearAllMessages } = result.current; addMessage('case', 'id-1'); addMessage('case', 'id-2'); clearAllMessages('case'); - expect(getMessages('case')).toEqual([]); }); + expect(getMessages('case')).toEqual([]); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_post_case.test.tsx b/x-pack/plugins/cases/public/containers/use_post_case.test.tsx index 58a878b32e57..d2c4cb65435c 100644 --- a/x-pack/plugins/cases/public/containers/use_post_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_case.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import * as api from './api'; import { ConnectorTypes } from '../../common/types/domain'; import { SECURITY_SOLUTION_OWNER } from '../../common/constants'; @@ -49,7 +49,7 @@ describe('usePostCase', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'postCase'); - const { waitForNextUpdate, result } = renderHook(() => usePostCase(), { + const { result } = renderHook(() => usePostCase(), { wrapper: appMockRender.AppWrapper, }); @@ -57,14 +57,12 @@ describe('usePostCase', () => { result.current.mutate({ request: samplePost }); }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ newCase: samplePost }); + await waitFor(() => expect(spy).toHaveBeenCalledWith({ newCase: samplePost })); }); it('invalidates the queries correctly', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitForNextUpdate, result } = renderHook(() => usePostCase(), { + const { result } = renderHook(() => usePostCase(), { wrapper: appMockRender.AppWrapper, }); @@ -72,15 +70,15 @@ describe('usePostCase', () => { result.current.mutate({ request: samplePost }); }); - await waitForNextUpdate(); - - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); + await waitFor(() => { + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.casesList()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.userProfiles()); + }); }); it('does not show a success toaster', async () => { - const { waitForNextUpdate, result } = renderHook(() => usePostCase(), { + const { result } = renderHook(() => usePostCase(), { wrapper: appMockRender.AppWrapper, }); @@ -88,15 +86,13 @@ describe('usePostCase', () => { result.current.mutate({ request: samplePost }); }); - await waitForNextUpdate(); - - expect(addSuccess).not.toHaveBeenCalled(); + await waitFor(() => expect(addSuccess).not.toHaveBeenCalled()); }); it('shows a toast error when the api return an error', async () => { jest.spyOn(api, 'postCase').mockRejectedValue(new Error('usePostCase: Test error')); - const { waitForNextUpdate, result } = renderHook(() => usePostCase(), { + const { result } = renderHook(() => usePostCase(), { wrapper: appMockRender.AppWrapper, }); @@ -104,8 +100,6 @@ describe('usePostCase', () => { result.current.mutate({ request: samplePost }); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx b/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx index 1d802b3737c9..0c7a76bc6ff3 100644 --- a/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_post_push_to_service.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useToasts } from '../common/lib/kibana'; import { usePostPushToService } from './use_post_push_to_service'; import { pushedCase } from './mock'; @@ -42,7 +42,7 @@ describe('usePostPushToService', () => { it('refresh the case after pushing', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitForNextUpdate, result } = renderHook(() => usePostPushToService(), { + const { result } = renderHook(() => usePostPushToService(), { wrapper: appMockRender.AppWrapper, }); @@ -50,15 +50,15 @@ describe('usePostPushToService', () => { result.current.mutate({ caseId, connector }); }); - await waitForNextUpdate(); - - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + await waitFor(() => { + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + }); }); it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'pushCase'); - const { waitForNextUpdate, result } = renderHook(() => usePostPushToService(), { + const { result } = renderHook(() => usePostPushToService(), { wrapper: appMockRender.AppWrapper, }); @@ -66,13 +66,11 @@ describe('usePostPushToService', () => { result.current.mutate({ caseId, connector }); }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith({ caseId, connectorId: connector.id }); + await waitFor(() => expect(spy).toHaveBeenCalledWith({ caseId, connectorId: connector.id })); }); it('shows a success toaster', async () => { - const { waitForNextUpdate, result } = renderHook(() => usePostPushToService(), { + const { result } = renderHook(() => usePostPushToService(), { wrapper: appMockRender.AppWrapper, }); @@ -80,18 +78,18 @@ describe('usePostPushToService', () => { result.current.mutate({ caseId, connector }); }); - await waitForNextUpdate(); - - expect(addSuccess).toHaveBeenCalledWith({ - title: 'Successfully sent to My connector', - className: 'eui-textBreakWord', - }); + await waitFor(() => + expect(addSuccess).toHaveBeenCalledWith({ + title: 'Successfully sent to My connector', + className: 'eui-textBreakWord', + }) + ); }); it('shows a toast error when the api return an error', async () => { jest.spyOn(api, 'pushCase').mockRejectedValue(new Error('usePostPushToService: Test error')); - const { waitForNextUpdate, result } = renderHook(() => usePostPushToService(), { + const { result } = renderHook(() => usePostPushToService(), { wrapper: appMockRender.AppWrapper, }); @@ -99,8 +97,6 @@ describe('usePostPushToService', () => { result.current.mutate({ caseId, connector }); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_replace_custom_field.test.tsx b/x-pack/plugins/cases/public/containers/use_replace_custom_field.test.tsx index 366d946af1d9..4b17c874339d 100644 --- a/x-pack/plugins/cases/public/containers/use_replace_custom_field.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_replace_custom_field.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { basicCase } from './mock'; import * as api from './api'; import type { AppMockRenderer } from '../common/mock'; @@ -39,7 +39,7 @@ describe('useReplaceCustomField', () => { it('replace a customField and refresh the case page', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitForNextUpdate, result } = renderHook(() => useReplaceCustomField(), { + const { result } = renderHook(() => useReplaceCustomField(), { wrapper: appMockRender.AppWrapper, }); @@ -47,15 +47,15 @@ describe('useReplaceCustomField', () => { result.current.mutate(sampleData); }); - await waitForNextUpdate(); - - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + await waitFor(() => { + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + }); }); it('calls the api when invoked with the correct parameters', async () => { const patchCustomFieldSpy = jest.spyOn(api, 'replaceCustomField'); - const { waitForNextUpdate, result } = renderHook(() => useReplaceCustomField(), { + const { result } = renderHook(() => useReplaceCustomField(), { wrapper: appMockRender.AppWrapper, }); @@ -63,16 +63,16 @@ describe('useReplaceCustomField', () => { result.current.mutate(sampleData); }); - await waitForNextUpdate(); - - expect(patchCustomFieldSpy).toHaveBeenCalledWith({ - caseId: sampleData.caseId, - customFieldId: sampleData.customFieldId, - request: { - value: sampleData.customFieldValue, - caseVersion: sampleData.caseVersion, - }, - }); + await waitFor(() => + expect(patchCustomFieldSpy).toHaveBeenCalledWith({ + caseId: sampleData.caseId, + customFieldId: sampleData.customFieldId, + request: { + value: sampleData.customFieldValue, + caseVersion: sampleData.caseVersion, + }, + }) + ); }); it('calls the api when invoked with the correct parameters of toggle field', async () => { @@ -83,7 +83,7 @@ describe('useReplaceCustomField', () => { caseVersion: basicCase.version, }; const patchCustomFieldSpy = jest.spyOn(api, 'replaceCustomField'); - const { waitForNextUpdate, result } = renderHook(() => useReplaceCustomField(), { + const { result } = renderHook(() => useReplaceCustomField(), { wrapper: appMockRender.AppWrapper, }); @@ -91,16 +91,16 @@ describe('useReplaceCustomField', () => { result.current.mutate(newData); }); - await waitForNextUpdate(); - - expect(patchCustomFieldSpy).toHaveBeenCalledWith({ - caseId: newData.caseId, - customFieldId: newData.customFieldId, - request: { - value: newData.customFieldValue, - caseVersion: newData.caseVersion, - }, - }); + await waitFor(() => + expect(patchCustomFieldSpy).toHaveBeenCalledWith({ + caseId: newData.caseId, + customFieldId: newData.customFieldId, + request: { + value: newData.customFieldValue, + caseVersion: newData.caseVersion, + }, + }) + ); }); it('calls the api when invoked with the correct parameters with null value', async () => { @@ -111,7 +111,7 @@ describe('useReplaceCustomField', () => { caseVersion: basicCase.version, }; const patchCustomFieldSpy = jest.spyOn(api, 'replaceCustomField'); - const { waitForNextUpdate, result } = renderHook(() => useReplaceCustomField(), { + const { result } = renderHook(() => useReplaceCustomField(), { wrapper: appMockRender.AppWrapper, }); @@ -119,16 +119,16 @@ describe('useReplaceCustomField', () => { result.current.mutate(newData); }); - await waitForNextUpdate(); - - expect(patchCustomFieldSpy).toHaveBeenCalledWith({ - caseId: newData.caseId, - customFieldId: newData.customFieldId, - request: { - value: newData.customFieldValue, - caseVersion: newData.caseVersion, - }, - }); + await waitFor(() => + expect(patchCustomFieldSpy).toHaveBeenCalledWith({ + caseId: newData.caseId, + customFieldId: newData.customFieldId, + request: { + value: newData.customFieldValue, + caseVersion: newData.caseVersion, + }, + }) + ); }); it('shows a toast error when the api return an error', async () => { @@ -136,7 +136,7 @@ describe('useReplaceCustomField', () => { .spyOn(api, 'replaceCustomField') .mockRejectedValue(new Error('useUpdateComment: Test error')); - const { waitForNextUpdate, result } = renderHook(() => useReplaceCustomField(), { + const { result } = renderHook(() => useReplaceCustomField(), { wrapper: appMockRender.AppWrapper, }); @@ -144,8 +144,6 @@ describe('useReplaceCustomField', () => { result.current.mutate(sampleData); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_update_case.test.tsx b/x-pack/plugins/cases/public/containers/use_update_case.test.tsx index f8d4ff82078c..4b1454446079 100644 --- a/x-pack/plugins/cases/public/containers/use_update_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_case.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useUpdateCase } from './use_update_case'; import { basicCase } from './mock'; import * as api from './api'; @@ -41,7 +41,7 @@ describe('useUpdateCase', () => { it('patch case and refresh the case page', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitForNextUpdate, result } = renderHook(() => useUpdateCase(), { + const { result } = renderHook(() => useUpdateCase(), { wrapper: appMockRender.AppWrapper, }); @@ -49,15 +49,15 @@ describe('useUpdateCase', () => { result.current.mutate(sampleUpdate); }); - await waitForNextUpdate(); - - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + await waitFor(() => { + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + }); }); it('calls the api when invoked with the correct parameters', async () => { const patchCaseSpy = jest.spyOn(api, 'patchCase'); - const { waitForNextUpdate, result } = renderHook(() => useUpdateCase(), { + const { result } = renderHook(() => useUpdateCase(), { wrapper: appMockRender.AppWrapper, }); @@ -65,17 +65,17 @@ describe('useUpdateCase', () => { result.current.mutate(sampleUpdate); }); - await waitForNextUpdate(); - - expect(patchCaseSpy).toHaveBeenCalledWith({ - caseId: basicCase.id, - updatedCase: { description: 'updated description' }, - version: basicCase.version, - }); + await waitFor(() => + expect(patchCaseSpy).toHaveBeenCalledWith({ + caseId: basicCase.id, + updatedCase: { description: 'updated description' }, + version: basicCase.version, + }) + ); }); it('shows a success toaster', async () => { - const { waitForNextUpdate, result } = renderHook(() => useUpdateCase(), { + const { result } = renderHook(() => useUpdateCase(), { wrapper: appMockRender.AppWrapper, }); @@ -83,18 +83,18 @@ describe('useUpdateCase', () => { result.current.mutate(sampleUpdate); }); - await waitForNextUpdate(); - - expect(addSuccess).toHaveBeenCalledWith({ - title: 'Updated "Another horrible breach!!"', - className: 'eui-textBreakWord', - }); + await waitFor(() => + expect(addSuccess).toHaveBeenCalledWith({ + title: 'Updated "Another horrible breach!!"', + className: 'eui-textBreakWord', + }) + ); }); it('shows a toast error when the api return an error', async () => { jest.spyOn(api, 'patchCase').mockRejectedValue(new Error('useUpdateCase: Test error')); - const { waitForNextUpdate, result } = renderHook(() => useUpdateCase(), { + const { result } = renderHook(() => useUpdateCase(), { wrapper: appMockRender.AppWrapper, }); @@ -102,8 +102,6 @@ describe('useUpdateCase', () => { result.current.mutate(sampleUpdate); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx index 59c06bd545c8..7a2522d6ac8c 100644 --- a/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_update_comment.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { basicCase } from './mock'; import * as api from './api'; import type { AppMockRenderer } from '../common/mock'; @@ -39,7 +39,7 @@ describe('useUpdateComment', () => { it('patch case and refresh the case page', async () => { const queryClientSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { waitForNextUpdate, result } = renderHook(() => useUpdateComment(), { + const { result } = renderHook(() => useUpdateComment(), { wrapper: appMockRender.AppWrapper, }); @@ -47,15 +47,15 @@ describe('useUpdateComment', () => { result.current.mutate(sampleUpdate); }); - await waitForNextUpdate(); - - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); - expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + await waitFor(() => { + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.caseView()); + expect(queryClientSpy).toHaveBeenCalledWith(casesQueriesKeys.tags()); + }); }); it('calls the api when invoked with the correct parameters', async () => { const patchCommentSpy = jest.spyOn(api, 'patchComment'); - const { waitForNextUpdate, result } = renderHook(() => useUpdateComment(), { + const { result } = renderHook(() => useUpdateComment(), { wrapper: appMockRender.AppWrapper, }); @@ -63,18 +63,18 @@ describe('useUpdateComment', () => { result.current.mutate(sampleUpdate); }); - await waitForNextUpdate(); - - expect(patchCommentSpy).toHaveBeenCalledWith({ - ...sampleUpdate, - owner: 'securitySolution', - }); + await waitFor(() => + expect(patchCommentSpy).toHaveBeenCalledWith({ + ...sampleUpdate, + owner: 'securitySolution', + }) + ); }); it('shows a toast error when the api return an error', async () => { jest.spyOn(api, 'patchComment').mockRejectedValue(new Error('useUpdateComment: Test error')); - const { waitForNextUpdate, result } = renderHook(() => useUpdateComment(), { + const { result } = renderHook(() => useUpdateComment(), { wrapper: appMockRender.AppWrapper, }); @@ -82,8 +82,6 @@ describe('useUpdateComment', () => { result.current.mutate(sampleUpdate); }); - await waitForNextUpdate(); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); }); diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_assignees.test.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_assignees.test.ts index 3622b66aef00..f808d8d7dd93 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_assignees.test.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_assignees.test.ts @@ -6,7 +6,7 @@ */ import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { userProfiles, userProfilesMap } from './api.mock'; import { useAssignees } from './use_assignees'; diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts index 4edc105b8d34..4558c7cdd2d3 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { useToasts, useKibana } from '../../common/lib/kibana'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; @@ -41,26 +41,25 @@ describe.skip('useBulkGetUserProfiles', () => { it('calls bulkGetUserProfiles with correct arguments', async () => { const spyOnBulkGetUserProfiles = jest.spyOn(api, 'bulkGetUserProfiles'); - const { result, waitFor } = renderHook(() => useBulkGetUserProfiles(props), { + renderHook(() => useBulkGetUserProfiles(props), { wrapper: appMockRender.AppWrapper, }); - await waitFor(() => result.current.isSuccess); - - expect(spyOnBulkGetUserProfiles).toBeCalledWith({ - ...props, - security: expect.anything(), - }); + await waitFor(() => + expect(spyOnBulkGetUserProfiles).toBeCalledWith({ + ...props, + security: expect.anything(), + }) + ); }); it('returns a mapping with user profiles', async () => { - const { result, waitFor } = renderHook(() => useBulkGetUserProfiles(props), { + const { result } = renderHook(() => useBulkGetUserProfiles(props), { wrapper: appMockRender.AppWrapper, }); - await waitFor(() => result.current.isSuccess); - - expect(result.current.data).toMatchInlineSnapshot(` + await waitFor(() => + expect(result.current.data).toMatchInlineSnapshot(` Map { "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0" => Object { "data": Object {}, @@ -93,7 +92,8 @@ describe.skip('useBulkGetUserProfiles', () => { }, }, } - `); + `) + ); }); it('shows a toast error message when an error occurs in the response', async () => { @@ -106,13 +106,11 @@ describe.skip('useBulkGetUserProfiles', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); - const { result, waitFor } = renderHook(() => useBulkGetUserProfiles(props), { + renderHook(() => useBulkGetUserProfiles(props), { wrapper: appMockRender.AppWrapper, }); - await waitFor(() => result.current.isError); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => expect(addError).toHaveBeenCalled()); }); it('does not call the bulkGetUserProfiles if the array of uids is empty', async () => { diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_get_current_user_profile.test.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_get_current_user_profile.test.ts index c26a6af82654..918771d5b218 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_get_current_user_profile.test.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_get_current_user_profile.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useToasts, useKibana } from '../../common/lib/kibana'; import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock'; import type { AppMockRenderer } from '../../common/mock'; @@ -36,7 +36,7 @@ describe('useGetCurrentUserProfile', () => { it('calls getCurrentUserProfile with correct arguments', async () => { const spyOnGetCurrentUserProfile = jest.spyOn(api, 'getCurrentUserProfile'); - const { waitFor } = renderHook(() => useGetCurrentUserProfile(), { + renderHook(() => useGetCurrentUserProfile(), { wrapper: appMockRender.AppWrapper, }); @@ -59,7 +59,7 @@ describe('useGetCurrentUserProfile', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); - const { waitFor } = renderHook(() => useGetCurrentUserProfile(), { + renderHook(() => useGetCurrentUserProfile(), { wrapper: appMockRender.AppWrapper, }); @@ -78,7 +78,7 @@ describe('useGetCurrentUserProfile', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); - const { waitFor } = renderHook(() => useGetCurrentUserProfile(), { + renderHook(() => useGetCurrentUserProfile(), { wrapper: appMockRender.AppWrapper, }); diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_suggest_user_profiles.test.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_suggest_user_profiles.test.ts index 7daf1d1d5cf6..67907d649464 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_suggest_user_profiles.test.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_suggest_user_profiles.test.ts @@ -6,7 +6,7 @@ */ import { GENERAL_CASES_OWNER } from '../../../common/constants'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useToasts } from '../../common/lib/kibana'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; @@ -34,17 +34,18 @@ describe('useSuggestUserProfiles', () => { it('calls suggestUserProfiles with correct arguments', async () => { const spyOnSuggestUserProfiles = jest.spyOn(api, 'suggestUserProfiles'); - const { result, waitFor } = renderHook(() => useSuggestUserProfiles(props), { + const { result } = renderHook(() => useSuggestUserProfiles(props), { wrapper: appMockRender.AppWrapper, }); - await waitFor(() => result.current.isSuccess); - - expect(spyOnSuggestUserProfiles).toBeCalledWith({ - ...props, - size: 10, - http: expect.anything(), - signal: expect.anything(), + await waitFor(() => { + expect(result.current.isSuccess).toBeDefined(); + expect(spyOnSuggestUserProfiles).toBeCalledWith({ + ...props, + size: 10, + http: expect.anything(), + signal: expect.anything(), + }); }); }); @@ -58,12 +59,13 @@ describe('useSuggestUserProfiles', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); - const { result, waitFor } = renderHook(() => useSuggestUserProfiles(props), { + const { result } = renderHook(() => useSuggestUserProfiles(props), { wrapper: appMockRender.AppWrapper, }); - await waitFor(() => result.current.isError); - - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(result.current.isError).toBeDefined(); + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts b/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts index 755084d624b9..5cdd4c943b94 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_update.test.ts @@ -1512,6 +1512,59 @@ describe('update', () => { ); }); + it('throws an error if the case is not found', async () => { + clientArgsMock.services.caseService.getCases.mockResolvedValue({ saved_objects: [] }); + + await expect( + bulkUpdate( + { + cases: [ + { + id: mockCases[0].id, + version: mockCases[0].version ?? '', + status: CaseStatuses.open, + }, + ], + }, + clientArgsMock, + casesClientMock + ) + ).rejects.toThrow( + 'Failed to update case, ids: [{"id":"mock-id-1","version":"WzAsMV0="}]: Error: These cases mock-id-1 do not exist. Please check you have the correct ids.' + ); + }); + + it('throws an error if the case is not found and the SO clients returns an SO object', async () => { + clientArgsMock.services.caseService.getCases.mockResolvedValue({ + saved_objects: [ + { + type: 'cases', + id: 'mock-id-1', + references: [], + error: { error: 'Non found', message: 'Non found', statusCode: 404 }, + }, + ], + }); + + await expect( + bulkUpdate( + { + cases: [ + { + id: mockCases[0].id, + version: mockCases[0].version ?? '', + status: CaseStatuses.open, + }, + ], + }, + clientArgsMock, + casesClientMock + ) + ).rejects.toThrow( + 'Failed to update case, ids: [{"id":"mock-id-1","version":"WzAsMV0="}]: Error: These cases mock-id-1 do not exist. Please check you have the correct ids.' + ); + }); + describe('Validate max user actions per page', () => { beforeEach(() => { jest.clearAllMocks(); @@ -1681,6 +1734,7 @@ describe('update', () => { status: CaseStatuses.closed, }, }; + clientArgs.services.caseService.getCases.mockResolvedValue({ saved_objects: [closedCase] }); clientArgs.services.caseService.patchCases.mockResolvedValue({ @@ -1701,7 +1755,10 @@ describe('update', () => { casesClientMock ); - expect(clientArgs.authorization.ensureAuthorized).not.toThrow(); + expect(clientArgs.authorization.ensureAuthorized).toHaveBeenCalledWith({ + entities: [{ id: closedCase.id, owner: closedCase.attributes.owner }], + operation: [Operations.reopenCase, Operations.updateCase], + }); }); it('throws when user is not authorized to update case', async () => { @@ -1726,38 +1783,6 @@ describe('update', () => { `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: Unauthorized"` ); }); - - it('throws when user is not authorized to reopen case', async () => { - const closedCase = { - ...mockCases[0], - attributes: { - ...mockCases[0].attributes, - status: CaseStatuses.closed, - }, - }; - clientArgs.services.caseService.getCases.mockResolvedValue({ saved_objects: [closedCase] }); - - const error = new Error('Unauthorized to reopen case'); - clientArgs.authorization.ensureAuthorized.mockRejectedValueOnce(error); // Reject reopenCase - - await expect( - bulkUpdate( - { - cases: [ - { - id: closedCase.id, - version: closedCase.version ?? '', - status: CaseStatuses.open, - }, - ], - }, - clientArgs, - casesClientMock - ) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: Unauthorized to reopen case"` - ); - }); }); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/bulk_update.ts b/x-pack/plugins/cases/server/client/cases/bulk_update.ts index 9a90168b858d..1f9dd5a7cb28 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_update.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_update.ts @@ -295,6 +295,7 @@ function partitionPatchRequest( ) { // Track cases that are closed and a user is attempting to reopen reopenedCases.push(reqCase); + casesToAuthorize.set(foundCase.id, { id: foundCase.id, owner: foundCase.attributes.owner }); } else { casesToAuthorize.set(foundCase.id, { id: foundCase.id, owner: foundCase.attributes.owner }); } diff --git a/x-pack/plugins/cases/server/client/factory.test.ts b/x-pack/plugins/cases/server/client/factory.test.ts index f73e93afd680..e9f60b45ddba 100644 --- a/x-pack/plugins/cases/server/client/factory.test.ts +++ b/x-pack/plugins/cases/server/client/factory.test.ts @@ -5,12 +5,14 @@ * 2.0. */ -import { coreMock, httpServerMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { coreMock } from '@kbn/core/server/mocks'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { CasesClientFactory } from './factory'; import { createCasesClientFactoryMockArgs } from './mocks'; import { createCasesClient } from './client'; import type { FakeRawRequest } from '@kbn/core-http-server'; -import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; jest.mock('./client'); @@ -23,7 +25,7 @@ describe('CasesClientFactory', () => { path: '/', }; - const fakeRequest = CoreKibanaRequest.from(rawRequest); + const fakeRequest = kibanaRequestFactory(rawRequest); const createCasesClientMocked = createCasesClient as jest.Mock; const logger = loggingSystemMock.createLogger(); const args = createCasesClientFactoryMockArgs(); diff --git a/x-pack/plugins/cases/server/connectors/cases/cases_connector.test.ts b/x-pack/plugins/cases/server/connectors/cases/cases_connector.test.ts index c0a2a8567fd7..8eebaba88d76 100644 --- a/x-pack/plugins/cases/server/connectors/cases/cases_connector.test.ts +++ b/x-pack/plugins/cases/server/connectors/cases/cases_connector.test.ts @@ -6,6 +6,7 @@ */ import Boom from '@hapi/boom'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; @@ -17,7 +18,6 @@ import { CasesService } from './cases_service'; import { CasesConnectorError } from './cases_connector_error'; import { CaseError } from '../../common/error'; import { fullJitterBackoffFactory } from './full_jitter_backoff'; -import { CoreKibanaRequest } from '@kbn/core/server'; jest.mock('./cases_connector_executor'); jest.mock('./full_jitter_backoff'); @@ -28,7 +28,7 @@ const fullJitterBackoffFactoryMock = fullJitterBackoffFactory as jest.Mock; describe('CasesConnector', () => { const services = actionsMock.createServices(); const logger = loggingSystemMock.createLogger(); - const kibanaRequest = CoreKibanaRequest.from({ path: '/', headers: {} }); + const kibanaRequest = kibanaRequestFactory({ path: '/', headers: {} }); const groupingBy = ['host.name', 'dest.ip']; const rule = { diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index 1d126d78f954..48ca36a02b2b 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -73,10 +73,11 @@ "@kbn/datemath", "@kbn/core-logging-server-mocks", "@kbn/core-logging-browser-mocks", - "@kbn/core-http-router-server-internal", "@kbn/presentation-publishing", "@kbn/alerts-ui-shared", "@kbn/cloud-plugin", + "@kbn/core-http-server-mocks", + "@kbn/core-http-server-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts index ea3866cbe125..ff4f51fc0b47 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -23,8 +23,6 @@ import { } from '../../common/constants'; import eksLogo from '../assets/icons/cis_eks_logo.svg'; -import aksLogo from '../assets/icons/cis_aks_logo.svg'; -import gkeLogo from '../assets/icons/cis_gke_logo.svg'; import googleCloudLogo from '../assets/icons/google_cloud_logo.svg'; export const CSP_MOMENT_FORMAT = 'MMMM D, YYYY @ HH:mm:ss.SSS'; @@ -145,34 +143,6 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = { }), testId: 'cisEksTestId', }, - { - type: CLOUDBEAT_AKS, - name: i18n.translate('xpack.csp.kspmIntegration.aksOption.nameTitle', { - defaultMessage: 'AKS', - }), - benchmark: i18n.translate('xpack.csp.kspmIntegration.aksOption.benchmarkTitle', { - defaultMessage: 'CIS AKS', - }), - disabled: true, - icon: aksLogo, - tooltip: i18n.translate('xpack.csp.kspmIntegration.aksOption.tooltipContent', { - defaultMessage: 'Azure Kubernetes Service - Coming soon', - }), - }, - { - type: CLOUDBEAT_GKE, - name: i18n.translate('xpack.csp.kspmIntegration.gkeOption.nameTitle', { - defaultMessage: 'GKE', - }), - benchmark: i18n.translate('xpack.csp.kspmIntegration.gkeOption.benchmarkTitle', { - defaultMessage: 'CIS GKE', - }), - disabled: true, - icon: gkeLogo, - tooltip: i18n.translate('xpack.csp.kspmIntegration.gkeOption.tooltipContent', { - defaultMessage: 'Google Kubernetes Engine - Coming soon', - }), - }, ], }, vuln_mgmt: { diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts index af488c4bb81b..7dd8072ca278 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_benchmark_dynamic_values.test.ts @@ -6,7 +6,7 @@ */ import { useBenchmarkDynamicValues } from './use_benchmark_dynamic_values'; -import { renderHook } from '@testing-library/react-hooks/dom'; +import { renderHook } from '@testing-library/react'; import type { BenchmarksCisId } from '@kbn/cloud-security-posture-common'; import { useCspIntegrationLink } from '../navigation/use_csp_integration_link'; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_url_query.test.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_url_query.test.ts index bc87cf2d9bb1..e8bbf1b044a7 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_url_query.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_url_query.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks/dom'; +import { renderHook, act } from '@testing-library/react'; import { useUrlQuery } from './use_url_query'; import { useLocation, useHistory } from 'react-router-dom'; import { encodeQuery } from '@kbn/cloud-security-posture'; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts index 8bf3984f62fa..58ecb20aed04 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { SetupTechnology } from '@kbn/fleet-plugin/public'; import { AgentPolicy, NewPackagePolicyInput } from '@kbn/fleet-plugin/common'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_change_csp_rule_state.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_change_csp_rule_state.test.tsx index 6c28df502e5f..5b2bdbf98968 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_change_csp_rule_state.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_change_csp_rule_state.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { RuleStateAttributes } from '@kbn/cloud-security-posture-common/schema/rules/v4'; @@ -87,7 +87,7 @@ describe('use_change_csp_rule_state', () => { const appMockRender = testWrapper(); const httpPostSpy = jest.spyOn(useKibana().services.http!, 'post'); - const { result, waitForNextUpdate } = await renderHook(() => useChangeCspRuleState(), { + const { result } = await renderHook(() => useChangeCspRuleState(), { wrapper: appMockRender.wrapper, }); @@ -107,22 +107,22 @@ describe('use_change_csp_rule_state', () => { result.current.mutate(mockRuleStateUpdateRequest); }); - await waitForNextUpdate(); - - expect(httpPostSpy).toHaveBeenCalledWith(CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH, { - version: '1', - body: JSON.stringify({ - action: 'mute', - rules: [ - { - benchmark_id: 'benchmark_id', - benchmark_version: 'benchmark_version', - rule_number: '1', - rule_id: 'rule_1', - }, - ], - }), - }); + await waitFor(() => + expect(httpPostSpy).toHaveBeenCalledWith(CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH, { + version: '1', + body: JSON.stringify({ + action: 'mute', + rules: [ + { + benchmark_id: 'benchmark_id', + benchmark_version: 'benchmark_version', + rule_number: '1', + rule_id: 'rule_1', + }, + ], + }), + }) + ); }); it('should cancel queries and update query data onMutate', async () => { @@ -131,7 +131,7 @@ describe('use_change_csp_rule_state', () => { const queryClientGetSpy = jest.spyOn(appMockRender.queryClient, 'getQueryData'); const mockSetQueryDataSpy = jest.spyOn(appMockRender.queryClient, 'setQueryData'); - const { result, waitForNextUpdate } = await renderHook(() => useChangeCspRuleState(), { + const { result } = await renderHook(() => useChangeCspRuleState(), { wrapper: appMockRender.wrapper, }); @@ -151,24 +151,24 @@ describe('use_change_csp_rule_state', () => { result.current.mutate(mockRuleStateUpdateRequest); }); - await waitForNextUpdate(); - - const expectedMutatedRules = { - ...initialRules, - rule_1: { ...initialRules.rule_1, muted: true }, - }; + await waitFor(() => { + const expectedMutatedRules = { + ...initialRules, + rule_1: { ...initialRules.rule_1, muted: true }, + }; - expect(queryClientSpy).toHaveBeenCalled(); - expect(queryClientGetSpy).toHaveBeenCalled(); - expect(mockSetQueryDataSpy).toHaveBeenCalled(); - expect(mockSetQueryDataSpy).toHaveReturnedWith(expectedMutatedRules); + expect(queryClientSpy).toHaveBeenCalled(); + expect(queryClientGetSpy).toHaveBeenCalled(); + expect(mockSetQueryDataSpy).toHaveBeenCalled(); + expect(mockSetQueryDataSpy).toHaveReturnedWith(expectedMutatedRules); + }); }); it('should invalidate queries onSettled', async () => { const appMockRender = testWrapper(); const mockInvalidateQueriesSpy = jest.spyOn(appMockRender.queryClient, 'invalidateQueries'); - const { result, waitForNextUpdate } = await renderHook(() => useChangeCspRuleState(), { + const { result } = await renderHook(() => useChangeCspRuleState(), { wrapper: appMockRender.wrapper, }); @@ -188,19 +188,19 @@ describe('use_change_csp_rule_state', () => { result.current.mutate(mockRuleStateUpdateRequest); }); - await waitForNextUpdate(); - - expect(mockInvalidateQueriesSpy).toHaveBeenCalledWith(BENCHMARK_INTEGRATION_QUERY_KEY_V2); - expect(mockInvalidateQueriesSpy).toHaveBeenCalledWith(CSPM_STATS_QUERY_KEY); - expect(mockInvalidateQueriesSpy).toHaveBeenCalledWith(KSPM_STATS_QUERY_KEY); - expect(mockInvalidateQueriesSpy).toHaveBeenCalledWith(CSP_RULES_STATES_QUERY_KEY); + await waitFor(() => { + expect(mockInvalidateQueriesSpy).toHaveBeenCalledWith(BENCHMARK_INTEGRATION_QUERY_KEY_V2); + expect(mockInvalidateQueriesSpy).toHaveBeenCalledWith(CSPM_STATS_QUERY_KEY); + expect(mockInvalidateQueriesSpy).toHaveBeenCalledWith(KSPM_STATS_QUERY_KEY); + expect(mockInvalidateQueriesSpy).toHaveBeenCalledWith(CSP_RULES_STATES_QUERY_KEY); + }); }); it('should restore previous query data onError', async () => { const appMockRender = testWrapper(); const mockSetQueryDataSpy = jest.spyOn(appMockRender.queryClient, 'setQueryData'); - const { result, waitForNextUpdate } = await renderHook(() => useChangeCspRuleState(), { + const { result } = await renderHook(() => useChangeCspRuleState(), { wrapper: appMockRender.wrapper, }); @@ -221,13 +221,13 @@ describe('use_change_csp_rule_state', () => { result.current.mutate(mockRuleStateUpdateRequest); }); - await waitForNextUpdate(); - - expect(mockSetQueryDataSpy).toHaveBeenCalled(); - expect(mockSetQueryDataSpy).toHaveReturnedWith(initialRules); + await waitFor(() => { + expect(mockSetQueryDataSpy).toHaveBeenCalled(); + expect(mockSetQueryDataSpy).toHaveReturnedWith(initialRules); + }); }); - it('creates the new set of cache rules in a muted state when calling createRulesWithUpdatedState', async () => { + it('creates the new set of cache rules in a muted state when calling createRulesWithUpdatedState', () => { const request: RuleStateUpdateRequest = { newState: 'mute', ruleIds: [ @@ -267,7 +267,7 @@ describe('use_change_csp_rule_state', () => { expect(newRulesState).toEqual({ ...initialRules, ...updateRules }); }); - it('creates the new cache with rules in a unmute state', async () => { + it('creates the new cache with rules in a unmute state', () => { const initialMutedRules: Record<string, RuleStateAttributes> = { rule_1: { benchmark_id: 'benchmark_id', diff --git a/x-pack/plugins/data_usage/common/constants.ts b/x-pack/plugins/data_usage/common/constants.ts new file mode 100644 index 000000000000..bf8b801cbf92 --- /dev/null +++ b/x-pack/plugins/data_usage/common/constants.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const PLUGIN_ID = 'data_usage'; + +export const DEFAULT_SELECTED_OPTIONS = 50 as const; + +export const DATA_USAGE_API_ROUTE_PREFIX = '/api/data_usage/'; +export const DATA_USAGE_METRICS_API_ROUTE = `/internal${DATA_USAGE_API_ROUTE_PREFIX}metrics`; +export const DATA_USAGE_DATA_STREAMS_API_ROUTE = `/internal${DATA_USAGE_API_ROUTE_PREFIX}data_streams`; diff --git a/x-pack/plugins/data_usage/common/experimental_features.ts b/x-pack/plugins/data_usage/common/experimental_features.ts new file mode 100644 index 000000000000..bf01916bfff3 --- /dev/null +++ b/x-pack/plugins/data_usage/common/experimental_features.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. + */ + +export type ServerlessExperimentalFeatures = Record< + keyof typeof allowedExperimentalValues, + boolean +>; + +/** + * A list of allowed values that can be used in `xpack.dataUsage.enableExperimental`. + * This object is then used to validate and parse the value entered. + */ +export const allowedExperimentalValues = Object.freeze({ + /** + * <Add a description of the feature here> + * + * [This is a fake feature key to showcase how to add a new serverless-specific experimental flag. + * It also prevents `allowedExperimentalValues` from being empty. It should be removed once a real feature is added.] + */ + dataUsageDisabled: false, +}); + +type ServerlessExperimentalConfigKeys = Array<keyof ServerlessExperimentalFeatures>; +type Mutable<T> = { -readonly [P in keyof T]: T[P] }; + +const allowedKeys = Object.keys( + allowedExperimentalValues +) as Readonly<ServerlessExperimentalConfigKeys>; + +export type ExperimentalFeatures = ServerlessExperimentalFeatures; +/** + * Parses the string value used in `xpack.dataUsage.enableExperimental` kibana configuration, + * which should be a string of values delimited by a comma (`,`) + * The generic experimental features are merged with the serverless values to ensure they are available + * + * @param configValue + * @throws DataUsagenvalidExperimentalValue + */ +export const parseExperimentalConfigValue = ( + configValue: string[] +): { features: ExperimentalFeatures; invalid: string[] } => { + const enabledFeatures: Mutable<Partial<ExperimentalFeatures>> = {}; + const invalidKeys: string[] = []; + + for (const value of configValue) { + if (!allowedKeys.includes(value as keyof ServerlessExperimentalFeatures)) { + invalidKeys.push(value); + } else { + enabledFeatures[value as keyof ServerlessExperimentalFeatures] = true; + } + } + + return { + features: { + ...allowedExperimentalValues, + ...enabledFeatures, + }, + invalid: invalidKeys, + }; +}; + +export const getExperimentalAllowedValues = (): string[] => [...allowedKeys]; diff --git a/x-pack/plugins/data_usage/common/index.ts b/x-pack/plugins/data_usage/common/index.ts index 8b952b13d4cc..63e34f872108 100644 --- a/x-pack/plugins/data_usage/common/index.ts +++ b/x-pack/plugins/data_usage/common/index.ts @@ -5,15 +5,10 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; - -export const PLUGIN_ID = 'data_usage'; -export const PLUGIN_NAME = i18n.translate('xpack.dataUsage.name', { - defaultMessage: 'Data Usage', -}); - -export const DEFAULT_SELECTED_OPTIONS = 50 as const; - -export const DATA_USAGE_API_ROUTE_PREFIX = '/api/data_usage/'; -export const DATA_USAGE_METRICS_API_ROUTE = `/internal${DATA_USAGE_API_ROUTE_PREFIX}metrics`; -export const DATA_USAGE_DATA_STREAMS_API_ROUTE = `/internal${DATA_USAGE_API_ROUTE_PREFIX}data_streams`; +export { + PLUGIN_ID, + DEFAULT_SELECTED_OPTIONS, + DATA_USAGE_API_ROUTE_PREFIX, + DATA_USAGE_METRICS_API_ROUTE, + DATA_USAGE_DATA_STREAMS_API_ROUTE, +} from './constants'; diff --git a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts index 09a6481f2445..ccc8158ecace 100644 --- a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts +++ b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts @@ -82,56 +82,45 @@ export type UsageMetricsRequestBody = TypeOf<typeof UsageMetricsRequestSchema>; export const UsageMetricsResponseSchema = { body: () => - schema.object({ - metrics: schema.recordOf( - metricTypesSchema, - schema.arrayOf( - schema.object({ - name: schema.string(), - error: schema.nullable(schema.string()), - data: schema.arrayOf( - schema.object({ - x: schema.number(), - y: schema.number(), - }) - ), - }) - ) - ), - }), + schema.recordOf( + metricTypesSchema, + schema.arrayOf( + schema.object({ + name: schema.string(), + error: schema.nullable(schema.string()), + data: schema.arrayOf( + schema.object({ + x: schema.number(), + y: schema.number(), + }) + ), + }) + ) + ), }; -export type UsageMetricsResponseSchemaBody = Omit< - TypeOf<typeof UsageMetricsResponseSchema.body>, - 'metrics' -> & { - metrics: Partial<Record<MetricTypes, MetricSeries[]>>; -}; -export type MetricSeries = TypeOf< - typeof UsageMetricsResponseSchema.body ->['metrics'][MetricTypes][number]; + +export type UsageMetricsResponseSchemaBody = Partial<Record<MetricTypes, MetricSeries[]>>; + +export type MetricSeries = TypeOf<typeof UsageMetricsResponseSchema.body>[MetricTypes][number]; export const UsageMetricsAutoOpsResponseSchema = { body: () => - schema.object({ - metrics: schema.recordOf( - metricTypesSchema, - schema.arrayOf( - schema.object({ - name: schema.string(), - error: schema.nullable(schema.string()), - data: schema.arrayOf(schema.arrayOf(schema.number(), { minSize: 2, maxSize: 2 })), - }) - ) - ), - }), + schema.recordOf( + metricTypesSchema, + schema.arrayOf( + schema.object({ + name: schema.string(), + error: schema.nullable(schema.string()), + data: schema.arrayOf(schema.arrayOf(schema.number(), { minSize: 2, maxSize: 2 })), + }) + ) + ), }; + export type UsageMetricsAutoOpsResponseMetricSeries = TypeOf< typeof UsageMetricsAutoOpsResponseSchema.body ->['metrics'][MetricTypes][number]; +>[MetricTypes][number]; -export type UsageMetricsAutoOpsResponseSchemaBody = Omit< - TypeOf<typeof UsageMetricsAutoOpsResponseSchema.body>, - 'metrics' -> & { - metrics: Partial<Record<MetricTypes, UsageMetricsAutoOpsResponseMetricSeries[]>>; -}; +export type UsageMetricsAutoOpsResponseSchemaBody = Partial< + Record<MetricTypes, UsageMetricsAutoOpsResponseMetricSeries[]> +>; diff --git a/x-pack/plugins/data_usage/common/test_utils/index.ts b/x-pack/plugins/data_usage/common/test_utils/index.ts new file mode 100644 index 000000000000..c3c8e75b2945 --- /dev/null +++ b/x-pack/plugins/data_usage/common/test_utils/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { TestProvider } from './test_provider'; +export { dataUsageTestQueryClientOptions } from './test_query_client_options'; +export { timeXMinutesAgo } from './time_ago'; diff --git a/x-pack/plugins/data_usage/common/test_utils/test_provider.tsx b/x-pack/plugins/data_usage/common/test_utils/test_provider.tsx new file mode 100644 index 000000000000..a3d154ba911e --- /dev/null +++ b/x-pack/plugins/data_usage/common/test_utils/test_provider.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { I18nProvider } from '@kbn/i18n-react'; + +export const TestProvider = memo(({ children }: { children?: React.ReactNode }) => { + return <I18nProvider>{children}</I18nProvider>; +}); diff --git a/x-pack/plugins/data_usage/common/test_utils/time_ago.ts b/x-pack/plugins/data_usage/common/test_utils/time_ago.ts new file mode 100644 index 000000000000..7fe74e232bda --- /dev/null +++ b/x-pack/plugins/data_usage/common/test_utils/time_ago.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 const timeXMinutesAgo = (x: number) => + new Date(new Date().getTime() - x * 60 * 1000).toISOString(); diff --git a/x-pack/plugins/data_usage/common/utils.ts b/x-pack/plugins/data_usage/common/utils.ts new file mode 100644 index 000000000000..ddd707b1134f --- /dev/null +++ b/x-pack/plugins/data_usage/common/utils.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import dateMath from '@kbn/datemath'; +export const dateParser = (date: string) => dateMath.parse(date)?.toISOString(); +export const momentDateParser = (date: string) => dateMath.parse(date); diff --git a/x-pack/plugins/data_usage/public/app/components/assets/illustration_product_no_results_magnifying_glass.svg b/x-pack/plugins/data_usage/public/app/components/assets/illustration_product_no_results_magnifying_glass.svg new file mode 100644 index 000000000000..4329041f84a9 --- /dev/null +++ b/x-pack/plugins/data_usage/public/app/components/assets/illustration_product_no_results_magnifying_glass.svg @@ -0,0 +1,32 @@ +<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 200 148"> + <g fill="#e6ebf2"> + <path d="M66.493 121.253a.664.664 0 0 0 .373-.78.945.945 0 0 0-1.1-.386.686.686 0 0 0 .208 1.253c.178.041.364.01.519-.087zM46.666 68.12c.12-.113.373-.44.287-.6-.087-.16-.514-.046-.667 0-.153.047-.22.267-.073.454.146.186.3.293.453.146zm-1.26 13.006a.427.427 0 0 0-.58-.153c-.247.107-.46.253-.46.573 0 .947.173 1.16 1.1 1.334h.067a1.205 1.205 0 0 0-.367 0 .286.286 0 0 0-.2.426.34.34 0 0 0 .487.22.907.907 0 0 0 .273-.36.4.4 0 0 0 .54.147.474.474 0 0 0 .073-.767c-.226-.22-.146-.426-.12-.666.707.046 1-.267.88-.867a1.716 1.716 0 0 0-.053-.173c-.147-.394-.38-.487-.753-.3-.374.186-.6.326-.487.873-.2-.133-.333-.186-.4-.286zM45.42 80a.386.386 0 0 0 .166-.52.486.486 0 0 0-.507-.346.552.552 0 0 0-.46.666.58.58 0 0 0 .8.2zm59.546 54.133a.7.7 0 0 0 .2.914.71.71 0 0 0 .887-1.107 1.035 1.035 0 0 0-1.087.193zM47.08 69.04a.449.449 0 0 0-.22.666.5.5 0 0 0 .626.18.54.54 0 0 0 .167-.666.427.427 0 0 0-.574-.18zM46.987 64c.12-.086.346-.4.28-.507-.067-.106-.427-.126-.58-.1-.154.027-.247.26-.14.487a.266.266 0 0 0 .44.12zm4.486 44a.772.772 0 0 0-.107 1.033.892.892 0 0 0 1.2-.087.6.6 0 0 0 .203-.453.602.602 0 0 0-.203-.453.73.73 0 0 0-1.093-.04zm1.54-7.846a.177.177 0 0 0 .063-.048.17.17 0 0 0 .03-.15.168.168 0 0 0-.04-.069.16.16 0 0 0-.266-.053.166.166 0 0 0-.047.273.16.16 0 0 0 .119.087.16.16 0 0 0 .141-.04zm-3.28-8.374c.072.19.16.373.267.546-.05.084-.094.171-.133.26a.92.92 0 0 0 .173 1 1.02 1.02 0 0 0 .9.174 1.22 1.22 0 0 0 0 .5l.313.146a58.42 58.42 0 0 1-1.106-3.42.775.775 0 0 0-.18.094.607.607 0 0 0-.234.7zm2.807 10.347a.234.234 0 0 0 .28-.114.319.319 0 0 0-.113-.34c-.094-.06-.24.06-.287.154-.047.093.007.266.12.3zm.193 4.286a.664.664 0 0 0 .106-.713.354.354 0 0 0-.102-.104.34.34 0 0 0-.282-.043.35.35 0 0 0-.129.067c-.313.213-.453.447-.36.613a.776.776 0 0 0 .767.18zm.32-2.286a.795.795 0 0 0-.073-.947.801.801 0 0 0-.84.1.533.533 0 0 0 0 .667c.353.32.713.393.913.18zM44.5 73.374a.86.86 0 0 0-.394 1.08.873.873 0 0 0 1.033.5 1.12 1.12 0 0 0 .467-1.247.872.872 0 0 0-1.107-.333zm9.886 28.086c-.173-.326-.34-.666-.507-.986-.253.16-.353.393-.253.593a1.04 1.04 0 0 0 .76.393zm.794 4.713c-.12.26-.22.527.087.667.306.14.473-.034.6-.274a.51.51 0 0 0-.464-.427.512.512 0 0 0-.223.034zm-4.14-10.84c-.12.047-.167.12-.114.247.053.127.127.16.253.113a.174.174 0 0 0 .114-.24c-.047-.12-.12-.173-.254-.12zm4.147 12.867a.357.357 0 0 0-.052.42.357.357 0 0 0 .098.113.442.442 0 0 0 .516.11.435.435 0 0 0 .151-.11.4.4 0 0 0-.04-.587.463.463 0 0 0-.528-.065.462.462 0 0 0-.145.119zm.499 2.707a.482.482 0 0 0 .667 0 .442.442 0 0 0 0-.6.397.397 0 0 0-.444-.144.382.382 0 0 0-.143.084.434.434 0 0 0-.19.312.44.44 0 0 0 .11.348zm-1.353-6.241a.345.345 0 0 0-.058.282.34.34 0 0 0 .178.225c.313.213.586.253.666.107a.77.77 0 0 0-.12-.774.67.67 0 0 0-.666.16zM37.22 85.493c-.16-.3-.353-.293-.513-.207s-.3.26-.16.434c.14.173.34.22.446.166.107-.053.174-.286.227-.393zm1.087 7.047a.666.666 0 0 0-.347.747.74.74 0 0 0 .813.38c.26-.107.334-.527.16-.907a.52.52 0 0 0-.626-.22zm-.387-3.367c-.274-.106-.514-.273-.747 0a.4.4 0 0 0 0 .554.534.534 0 0 0 .406.18c.334-.087.38-.367.34-.734zM45 100.606c-.26 0-.48.56-.3.794.18.233.6.293.74.106.14-.186-.174-.84-.44-.9zm-1.786-5.046-.207.22a4.215 4.215 0 0 0-.407-.373.434.434 0 0 0-.666 0 .667.667 0 0 0-.174.78.6.6 0 0 0 .667.366c.201-.018.4-.059.593-.12.067.072.14.136.22.194a.913.913 0 0 0 1.013.073 1.061 1.061 0 0 0 .387-.96.534.534 0 0 0-.2-.347.833.833 0 0 0-1.227.167zm2.959 9.774a.352.352 0 0 0-.118.266.358.358 0 0 0 .118.267.32.32 0 0 0 .104.083.323.323 0 0 0 .261.018.311.311 0 0 0 .115-.068.35.35 0 0 0 .1-.513.398.398 0 0 0-.58-.053zm-5.84-19.148c0-.353-.213-.493-.567-.5-.193-.826-.666-.926-1.333-.313a.838.838 0 0 1-.167-.06.4.4 0 0 0-.533.113.433.433 0 0 0-.087.5c.154.467.36.567.82.38l1.04-.4c.287.62.367.64.827.28zm1 2.614a.787.787 0 1 0-1.334.747.874.874 0 0 0 1.187.147.614.614 0 0 0 .147-.894zm.614 3.2a.8.8 0 0 0-.94-.333.727.727 0 0 0-.26.9c.146.307.426.367.82.173.266-.133.473-.533.38-.74zm.893 5.94a.426.426 0 0 0-.467.273.387.387 0 0 0 .533.42.4.4 0 0 0-.067-.693zm1.5-31.093c.1-.047.093-.3.053-.367s-.187-.167-.287-.113a.26.26 0 0 0-.107.3c.047.073.247.233.34.18zm-1.294 8.893c.107.107.394.354.54.26.147-.093.04-.486 0-.6-.04-.113-.286-.086-.406-.04a.22.22 0 0 0-.134.38zm-.292-3.78a.76.76 0 0 0 .173-.98.713.713 0 0 0-1.18.78.726.726 0 0 0 1.007.2zM41.22 84.873c-.227.114-.307.3-.147.48s.313.46.56.327c.247-.133.113-.487.087-.667-.027-.18-.294-.246-.5-.14zm-.72-3.066a.926.926 0 0 0-.247 1.086.808.808 0 0 0 1.167.18.667.667 0 0 0 .293-.96.907.907 0 0 0-1.213-.306zm4.586-9.714a.76.76 0 0 0 .34-.88.52.52 0 0 0-.667-.246.667.667 0 0 0-.26.84.448.448 0 0 0 .587.286zm.5-2.367a.14.14 0 0 0 .033-.18c0-.04-.14-.046-.166 0a.123.123 0 0 0 .04.224.123.123 0 0 0 .093-.017zm-1.88 7.7a.667.667 0 0 0 .86.134c.387-.22.5-.467.34-.767a.774.774 0 0 0-.926-.333.847.847 0 0 0-.273.966zM40.8 74.787c.12-.113.386-.447.293-.587-.094-.14-.494-.086-.667-.046-.173.04-.213.346-.093.52.12.173.333.26.466.113zm-2.447-1.86c0 .04.18.133.22.106s.247-.14.2-.286c-.046-.147-.266-.12-.3-.087a.4.4 0 0 0-.12.267zm.247 3.553a.436.436 0 0 0 .8-.347.573.573 0 0 0-.627-.233.453.453 0 0 0-.173.58zm2.327-10.587c.073-.04.073-.207.053-.3s-.173-.193-.287-.133-.053.273 0 .36a.24.24 0 0 0 .234.073zM38 71.1a.466.466 0 0 0 .513-.333.346.346 0 0 0-.14-.467c-.293-.193-.52-.046-.767.16.06.3.047.607.394.64zm2.76 8.3a.7.7 0 0 0 .146-.827.667.667 0 0 0-.82.127.5.5 0 0 0 .667.666zm-2.387 1.266c.287-.16.367-.38.234-.666a.78.78 0 0 0-.954-.407.667.667 0 0 0-.126.873.627.627 0 0 0 .846.2zm7.233 27.268c-.133 0-.287.266-.413.433a.184.184 0 0 0-.047.061.184.184 0 0 0 0 .151.185.185 0 0 0 .047.061.56.56 0 0 0 .666-.1.404.404 0 0 0-.253-.606zm33.627 20.193c-.126-.254-.466-.32-.846-.154-.28.134-.487.507-.394.72a.847.847 0 0 0 .914.42.548.548 0 0 0 .12-.066.763.763 0 0 0 0 .386c.1.329.31.614.593.807.1.077.177.181.22.3a.584.584 0 0 0 .887.433.28.28 0 0 0 .033.167.523.523 0 0 0 .667.113.531.531 0 0 0 .313-.666.46.46 0 0 0-.277-.219.468.468 0 0 0-.35.045.86.86 0 0 0-.173-.42.832.832 0 0 1-.227-.586c0-.36-.253-.58-.5-.78a.827.827 0 0 0-.88.04l-.066.06a.786.786 0 0 0-.034-.6zm9.973 5.979c.1.2.414.14.554.114a.305.305 0 0 0 .225-.134.3.3 0 0 0 .035-.26c-.06-.213-.34-.4-.5-.28s-.414.36-.314.56zm4.247-1.332a.605.605 0 0 0-.287.866c.173.354.513.52.773.374a.893.893 0 0 0 .367-1.054.667.667 0 0 0-.853-.186zm.16 2.966c-.12.193.093.427.193.533a.292.292 0 0 0 .364.085.292.292 0 0 0 .103-.085c.146-.166.16-.5 0-.573s-.54-.147-.66.04zm-3.42-4.167a.447.447 0 0 0-.477-.299.459.459 0 0 0-.19.066.472.472 0 0 0 0 .833.269.269 0 0 0 .18.28c.142.02.285.02.427 0a.465.465 0 0 0 .033-.426.49.49 0 0 0 .027-.454zM76 127.926c.34-.167.486-.467.353-.72a.91.91 0 0 0-1.053-.373.668.668 0 0 0-.154.82.575.575 0 0 0 .853.273zm-9.22-3.706a.757.757 0 0 0 .32.14c.28-.146.373-.406.233-.546a.544.544 0 0 0-.406-.147c-.194.033-.26.4-.147.553zm4.733-.807c.3-.106.553-.446.48-.666-.147-.46-.667-.454-.92-.44-.254.013-.38.613-.14.96a.439.439 0 0 0 .58.146zm42.58 9.307c-.3.113-.22.366-.167.606.26.12.494.14.667-.126a.384.384 0 0 0-.053-.36.504.504 0 0 0-.447-.12zm-.533 1.873c.113.073.206.167.353.147s.2-.234.087-.4a.723.723 0 0 0-.474-.28.197.197 0 0 0-.096.012.211.211 0 0 0-.137.234.459.459 0 0 0 .267.287zm2.513-4.133c.353.047.493-.187.626-.487-.093-.113-.173-.233-.26-.32l-.666.147a.62.62 0 0 0-.054.3.355.355 0 0 0 .354.36zm5.14-1.587c.033 0 .14 0 .16-.053.02-.054 0-.127 0-.16a.14.14 0 0 0-.194 0c-.023.033-.033.073-.027.113s.028.076.061.1zm-6.633 3.247a.815.815 0 0 0 .754-.786.843.843 0 0 0-.898-.657.84.84 0 0 0-.762.81c-.034.347.44.6.906.633zm2.98 2.966a.256.256 0 0 0 0 .354c.133.126.28-.06.347-.127.066-.067 0-.227-.034-.24a.342.342 0 0 0-.313.013zm.473-4.626c0-.147 0-.433-.114-.473a.475.475 0 0 0-.407.146c-.06.087-.053.44.054.507.106.067.433-.113.467-.18zm-10.32 3.86a.421.421 0 0 0-.031.346.44.44 0 0 0 .237.254c.194.093.407.2.534 0 .206-.334.186-.62 0-.76a.483.483 0 0 0-.408-.093.498.498 0 0 0-.332.253zm-.38-2.113a.663.663 0 0 0-.007.433.66.66 0 0 0 .26.347.396.396 0 0 0 .318.035.393.393 0 0 0 .236-.215c.233-.354.253-.627.073-.767a.759.759 0 0 0-.88.167zm-3.44 4.06a.38.38 0 0 0 .14.56.517.517 0 0 0 .667-.134.502.502 0 0 0-.167-.633.452.452 0 0 0-.364-.036.464.464 0 0 0-.276.243zM112 131.2a.724.724 0 0 0 .967-.187.945.945 0 0 0 .046-.666l-1.4.213a.888.888 0 0 0 .387.64zm-1.747 1.993a.424.424 0 0 0 .506-.035.403.403 0 0 0 .107-.145c.267-.413.254-.7-.046-.893a.75.75 0 0 0-.854.213.857.857 0 0 0 .287.86zM50.88 97.454a.18.18 0 0 1 0 .113.899.899 0 0 0-.16.08c-.074-.1-.227-.12-.487.04a.466.466 0 0 0-.233.666.667.667 0 0 0 .666.367c.05.056.115.097.187.12.247.074.487.254.667.154a.447.447 0 0 0 .246-.314.666.666 0 0 0 .087-.426 1.094 1.094 0 0 0-.173-.38 1.134 1.134 0 0 0 0-.214c-.034-.266-.094-.666-.46-.613-.367.053-.367.287-.34.407zm-4.054-24.248c-.3 0-.613 0-.613.434a.48.48 0 0 0 .346.5.348.348 0 0 0 .287 0c.147-.167.267-.36.413-.56.235-.005.464-.07.667-.187v-.887a.92.92 0 0 0-.6-.26c-.4-.02-.5.18-.5.96zm1.08 11.214a.56.56 0 0 0 .667.174l.04-.047a35.31 35.31 0 0 1-.154-.84.667.667 0 0 0-.426 0 .489.489 0 0 0-.127.713zm-.866-7.933a.394.394 0 0 0 .306-.2c.094-.28-.066-.454-.346-.56-.2.126-.447.246-.334.546a.507.507 0 0 0 .374.214zm2.206 17.407c.087.153.207.3.427.213a.493.493 0 0 0 .186-.667 1.467 1.467 0 0 0-.486 0c-.2.074-.227.274-.127.454zm-.179-7.061a.587.587 0 0 0 .147.667c-.047-.24-.107-.454-.147-.667zm-.401 3.38a.833.833 0 0 0 1.047-.227.667.667 0 0 0 .053-.313c-.04-.14-.087-.273-.12-.413a.72.72 0 0 0-.4-.354.847.847 0 0 0-.993.267c-.22.267.026.747.413 1.04zm-1.14-12.593c-.406.093-.873.247-.973.74a1.14 1.14 0 0 0 1.14 1.267.386.386 0 0 1 .353.166 56.95 56.95 0 0 1-.166-3.213 6.074 6.074 0 0 0-.354 1.04zm3.407 24.806a.15.15 0 0 0 .055-.043.156.156 0 0 0 .034-.132.159.159 0 0 0-.029-.065c-.06-.073-.187-.213-.333-.153-.147.06-.06.287 0 .32s.153.147.273.073zm-.267-7.093a.42.42 0 0 0-.38-.38c-.233 0-.3.14-.28.62a.407.407 0 0 0 .66-.24zm-1.066 5.82a.242.242 0 0 0 .046.093.443.443 0 0 0 .36.174c.127 0 .254.066.434 0a.955.955 0 0 0 .52-.667.719.719 0 0 0-.287-.667.614.614 0 0 0-.62-.033.725.725 0 0 0-.427.533.28.28 0 0 0-.193 0c-.307.074-.667.067-.907.38l.04.454c-.313.28-.466.666-.326.86a.42.42 0 0 0 .5.16c.366-.12.446-.26.413-.72.18-.24.313-.387.447-.567zm-.76 3.514c.246-.24.22-.527-.08-.82a.454.454 0 0 0-.312-.195.456.456 0 0 0-.355.095.971.971 0 0 0-.12.987c.273.28.567.253.867-.067zm13.9 14.6c0-.134-.247-.174-.32-.154a.308.308 0 0 0-.2.24c0 .107.18.174.266.174s.267-.134.254-.26zM48.287 95.7a.16.16 0 0 0-.043.191.16.16 0 0 0 .043.055c.06.067.233.22.333.134s0-.28 0-.314c0-.033-.213-.16-.333-.066z"/> + <path d="M48.147 96.866c.113 0 .28.167.16.207s-.187.187-.094.493a.476.476 0 0 0 .587.387.7.7 0 0 0 .52-.833c-.08-.38-.306-.4-.333-.667s-.18-.313-.447-.353-.667-.074-.707.3c-.04.373.2.466.314.466zm.173-10.7c.266-.153.273-.553 0-.966a.606.606 0 0 0-.707-.14.872.872 0 0 0-.273.806.847.847 0 0 0 .98.3zm-1.327 2.187a1.333 1.333 0 0 0-1.333-.42.854.854 0 0 0-.267 1.12.993.993 0 0 0 1.167.373.833.833 0 0 0 .433-1.073zM42.96 78.92a1.494 1.494 0 0 0 0-.527c-.073-.247-.433-.293-.667-.14a.413.413 0 0 0-.093.587.553.553 0 0 0 .76.08zm2.62 17.88c-.06.227.066.327.533.42a.4.4 0 0 0-.067-.666.42.42 0 0 0-.467.246zm-3.513-16.387a.5.5 0 0 0 .666.14.48.48 0 0 0 .134-.626.507.507 0 0 0-.667-.134.46.46 0 0 0-.133.62zm3.966 12.541a.408.408 0 0 0-.52.3.533.533 0 0 0 .113.466c.26.207.52.074.747-.146-.007-.24-.04-.54-.34-.62zm-.7-1.887c.24-.207.12-.467 0-.747-.293 0-.6-.1-.726.247a.259.259 0 0 0 0 .1 1.067 1.067 0 0 0-.827.046.444.444 0 0 0-.113.627.667.667 0 0 0 .846.333.74.74 0 0 0 .36-.513.427.427 0 0 0 .46-.093zm2.141 2.306c.666.72 1.253.347 1.653-.16a.966.966 0 0 0 .213-.593 1 1 0 0 0-.38-.74c-.247-.227-.253-.567-.567-.667a1.373 1.373 0 0 0-1.593.467 1.106 1.106 0 0 0-.213 1.04c.127.36.62.347.887.653zm-3.947-10.6c.28-.16.32-.466.106-.846-.213-.38-.46-.434-.666-.294a1.172 1.172 0 0 0-.36.947.667.667 0 0 0 .92.193zm1.926 4.22a.666.666 0 0 0 .206-.666.46.46 0 0 0-.666-.2.512.512 0 0 0-.227.62.726.726 0 0 0 .687.246zm-1.62 5.674a.807.807 0 0 0-.38.98.82.82 0 0 0 1.067.273.567.567 0 0 0 .193-.78c-.153-.32-.633-.573-.88-.473zm-.693-6.634c.113.213.78.287 1.047.113a.667.667 0 0 0 .16-.706.667.667 0 0 0-.847-.327.76.76 0 0 0-.36.92zm1.026 3.014a.806.806 0 0 0 .373-1.22.9.9 0 0 0-1.213-.3 1.073 1.073 0 0 0-.207 1.273.732.732 0 0 0 1.047.247zm49.127 45.106c.033-.234-.38-.667-.667-.707-.287-.04-.527-.08-.667.253-.14.334-.226.474-.346.714a.392.392 0 0 0-.156.31.386.386 0 0 0 .156.31.497.497 0 0 0 .666-.047c.12-.118.26-.213.414-.28a.67.67 0 0 0 .6-.553zm5.013.147v.033c-.067.267.18.353.373.433.267-.113.414-.293.294-.56-.04-.093-.227-.206-.307-.18l-.153.08a.368.368 0 0 0 .04-.133.404.404 0 0 0-.374-.321.408.408 0 0 0-.173.028.387.387 0 0 0-.187.306.472.472 0 0 0 .22.36.31.31 0 0 0 .267-.046zm-2.026 2.88a.449.449 0 0 0 .042-.529.444.444 0 0 0-.129-.137.552.552 0 0 0-.666.073.456.456 0 0 0 .113.667.432.432 0 0 0 .337.109.427.427 0 0 0 .303-.183zm1.393-5.314a.406.406 0 0 0-.468-.029.407.407 0 0 0-.119.116.441.441 0 0 0 .04.667.468.468 0 0 0 .35.098.476.476 0 0 0 .317-.178.508.508 0 0 0-.12-.674zm-.713 6.801a.674.674 0 0 0-.94.126.672.672 0 0 0 .127.94.91.91 0 0 0 1.1-.24c.16-.206.006-.64-.287-.826zm-1.287-3.22a.452.452 0 0 0 .344.045.446.446 0 0 0 .27-.218.46.46 0 0 0 .086-.362.46.46 0 0 0-.214-.305.55.55 0 0 0-.666.213.493.493 0 0 0 .18.627zm-1.806 4.46c-.06.173-.14.606 0 .713s.493-.187.5-.253c0-.2.08-.46-.1-.607s-.367.06-.4.147z"/> + <path d="M95.72 132.54a.7.7 0 0 0 .48-.286.919.919 0 0 0 .153-.74c1.378.16 2.76.278 4.146.353-.033.113-.053.227-.08.347-.284.05-.549.177-.766.366l-.187-.1a.846.846 0 0 0-.947.5.853.853 0 0 0 .747.914.886.886 0 0 0 .667-.207l.093.08c.38.3.713.14 1.033-.127.194.18.32.474.667.287a.433.433 0 0 0 .22-.513c-.1-.367-.407-.3-.667-.267-.06-.16-.113-.307-.173-.453l.58-.254c.031-.162.047-.327.047-.493l1.62.04h.066a.67.67 0 0 0 .2 0h1.467a.667.667 0 0 0 .32.627.823.823 0 0 0 1.2-.287.663.663 0 0 0 .093-.387l1.147-.053a.505.505 0 0 0 .507-.507.507.507 0 0 0-.507-.506c-12.3.12-24.54-2.667-34.913-9.414a57.165 57.165 0 0 1-15.814-15.646 30.06 30.06 0 0 1-2.213-3.62.197.197 0 0 0-.074-.153c-.023-.019-.05-.032-.079-.039s-.058-.007-.087-.002l-.18-.313a.816.816 0 0 0-.6.087.59.59 0 0 0-.136.659.59.59 0 0 0 .136.194.441.441 0 0 0 .667.107l.06.14a.383.383 0 0 0 .086.587c.06.032.127.046.194.039a30.134 30.134 0 0 0 1.58 2.967.491.491 0 0 0 .126.413.406.406 0 0 0 .214.12c.133.22.273.447.413.667-.167.267-.133.507.153.787a.806.806 0 0 0 .54.233c.12.167.234.347.36.513a.872.872 0 0 0-.533.207c-.413.327-.353 1.013 0 1.127.353.113.533-.074.6.053.067.127.08.447.333.5s.46-.28.614-.513c.333.444.673.889 1.02 1.333a.6.6 0 0 0 .073.5.717.717 0 0 0 .527.227c.067.073.127.153.193.233.42.5.86 1 1.333 1.487a.579.579 0 0 0-.34.153.667.667 0 0 0 .12.827.58.58 0 0 0 .9 0 .592.592 0 0 0 .094-.134c.8.84 1.633 1.647 2.486 2.434a.477.477 0 0 0 0 .413.66.66 0 0 0 .487.247.327.327 0 0 0 .14-.06c.514.453 1.04.893 1.567 1.333a1.101 1.101 0 0 0 0 .947.763.763 0 0 0 .98.44.958.958 0 0 0 .4-.314c.36.28.733.554 1.1.82.055.07.13.123.213.154a41.756 41.756 0 0 0 3.74 2.446.5.5 0 0 0 0 .28.797.797 0 0 0 .967.407.21.21 0 0 0 .08-.073c.493.28.986.553 1.486.813a.513.513 0 0 0 .047.267c.213.473.753.78 1.113.626a.71.71 0 0 0 .234-.173 34.4 34.4 0 0 0 2.073.96.491.491 0 0 0 .567.247c.726.313 1.46.613 2.2.893 0 .253.273.3.52.26v-.053l.98.353a.62.62 0 0 0 0 .213.445.445 0 0 0 .587.254l.08-.04a.853.853 0 0 0 .727.52h.06a.785.785 0 0 0-.06.873c.052.12.124.23.213.327a.377.377 0 0 0-.267.186.602.602 0 0 0 .313.8.763.763 0 0 0 .814-.16.604.604 0 0 0 0-.726.86.86 0 0 0 .38-1.014.95.95 0 0 0-.394-.44c1.273.374 2.56.667 3.853.974a.277.277 0 0 0 0 .227.283.283 0 0 0 .167.153c.142.02.285.02.427 0a.598.598 0 0 0 .066-.247c.56.116 1.12.222 1.68.32a.435.435 0 0 0 .094.1.394.394 0 0 0 .546 0c.314.053.627.107.94.147a1.32 1.32 0 0 0-.16.34.369.369 0 0 0-.293-.134.586.586 0 0 0-.4.225.59.59 0 0 0-.12.442.754.754 0 0 0 .553.613.613.613 0 0 0 .634-.366.951.951 0 0 0 .433.086.145.145 0 0 0 .2.054.144.144 0 0 0 .054-.054zm7.167 5.613a.448.448 0 0 0-.387.127c-.073.093-.153.407-.04.507s.413-.047.513-.127a.319.319 0 0 0-.086-.507zm-2.22 4.093a.494.494 0 0 0 .153.667.46.46 0 0 0 .541-.002.488.488 0 0 0-.014-.778.467.467 0 0 0-.68.113zm1.08-7.066a.61.61 0 0 0-.88.22.718.718 0 0 0 .073.927.891.891 0 0 0 .98-.214.604.604 0 0 0 .134-.507.624.624 0 0 0-.307-.426zm-3.467 5.134a.153.153 0 0 0 .053.18c.034 0 .14 0 .16-.054.02-.053 0-.126 0-.16a.14.14 0 0 0-.213.034zm-51.867-38.808c.054-.073.16-.226.06-.353s-.293 0-.313.053a.255.255 0 0 0 0 .307.173.173 0 0 0 .058.042.169.169 0 0 0 .195-.049zm54.254 36.094a.669.669 0 0 0-.114-.22.582.582 0 0 0-.57-.252.58.58 0 0 0-.47.412 3.65 3.65 0 0 0-.206.94.907.907 0 0 0 .666.813c.207.04.667-.033.667-.46-.347-.62.153-.707.027-1.233zm-2.487 5.426a.5.5 0 0 0-.187.38c.067.294.44.314.58.24a.395.395 0 0 0 .127-.314.396.396 0 0 0-.16-.299.368.368 0 0 0-.36-.007zm1.313-7.313c-.113-.16-.667-.126-.72.12a.424.424 0 0 0 .273.473c.24.047.567-.433.447-.593zM84 135.147c-.187.106-.147.26-.08.406.066.147.313.3.473.16.095-.108.173-.229.233-.36a.452.452 0 0 0-.626-.206zm1.333 3.726a.24.24 0 0 0-.1.24c.04.08.207.1.3.087.094-.013.2-.153.154-.273s-.254-.08-.354-.054zm2.347-5.319a.9.9 0 0 0-.933.186c-.167.2-.347.407-.127.667s.286.44.433.667a.377.377 0 0 0 .338.355.498.498 0 0 0 .502-.635 1.158 1.158 0 0 1 0-.5.668.668 0 0 0-.213-.74zm4.24 4.692c-.054.047-.114.287 0 .36.113.074.28-.066.346-.126.067-.06.054-.194 0-.24a.333.333 0 0 0-.346.006z"/> + <path d="M85.466 131.287a.194.194 0 0 1-.271-.217.196.196 0 0 1 .078-.117.865.865 0 0 0-.287-1.166.955.955 0 0 0-.773-.06v-.04a.523.523 0 0 0-.544-.306.535.535 0 0 0-.21.072.582.582 0 0 0-.378.357.572.572 0 0 0 .065.517.772.772 0 0 0 .54.446c.03.169.123.319.26.42.209.096.443.122.667.074a.546.546 0 0 0 0 .353.43.43 0 0 0 .547.24.405.405 0 0 0 .306-.573zM103.767 134a.61.61 0 0 0-.054-.813.498.498 0 0 0-.666.053.713.713 0 0 0 .173.873.398.398 0 0 0 .547-.113zm-21.101 2.334c-.08 0-.26.2-.22.3s.294.12.367.086a.266.266 0 0 0 .14-.28.262.262 0 0 0-.287-.106zm1.427-3.001a.453.453 0 0 0-.446-.259.43.43 0 0 0-.175.053c-.186.106-.146.26-.08.406a.3.3 0 0 0 .474.16 1.25 1.25 0 0 0 .226-.36zm6.013 1.934a1.085 1.085 0 0 0-1.333.567c-.113.36.333.566.593.493s.187 0 .387.187a.477.477 0 0 0 .66.097.47.47 0 0 0 .153-.191.933.933 0 0 0-.46-1.153zm.507 1.326a.31.31 0 0 0-.14.26c0 .067.18.247.293.2s.087-.267.067-.36a.185.185 0 0 0-.092-.09.183.183 0 0 0-.128-.01zm1.207-4.019a.449.449 0 0 0-.413-.291.453.453 0 0 0-.174.031.541.541 0 0 0-.28.6.443.443 0 0 0 .421.274.446.446 0 0 0 .172-.041.426.426 0 0 0 .273-.573zm-3.874-1.28c-.067.093.047.313.113.353a.2.2 0 0 0 .073.037.203.203 0 0 0 .159-.018.209.209 0 0 0 .106-.206.207.207 0 0 0-.078-.14c-.06-.053-.32-.113-.373-.026zm-.973 5.559a1.1 1.1 0 0 0-.214 1.453c.247.28.667 0 .727-.253s.087-.167.36-.233a.473.473 0 0 0 .392-.268.467.467 0 0 0-.045-.472.947.947 0 0 0-1.22-.227zm26.62 4.327a.139.139 0 0 0-.009.098.145.145 0 0 0 .055.082c.04 0 .147 0 .16-.054.014-.053.034-.126 0-.16a.14.14 0 0 0-.052-.029.13.13 0 0 0-.114.018.123.123 0 0 0-.04.045zm3.773-1.747a.459.459 0 0 0 .114.667.356.356 0 0 0 .456.049.357.357 0 0 0 .11-.122.437.437 0 0 0 .041-.503.467.467 0 0 0-.428-.218.474.474 0 0 0-.293.127zm-2.1-5.807a.757.757 0 0 0-.048.872.743.743 0 0 0 .214.222.947.947 0 0 0 1.24-.2.994.994 0 0 0-.22-1.2.816.816 0 0 0-.661-.11.829.829 0 0 0-.525.416zm-.453 7.487c-.053.04-.08.307 0 .353.08.047.28-.06.347-.126.067-.067 0-.227 0-.24a.35.35 0 0 0-.347.013zm.98-4.193c-.093.133 0 .4.247.54a.247.247 0 0 0 .4-.08c.107-.18.153-.373-.047-.533a.477.477 0 0 0-.6.073zm-1.88-1.353a.386.386 0 0 0 .06.533c.133.14.62 0 .667-.153a.529.529 0 0 0-.147-.44.353.353 0 0 0-.305-.118c-.057.005-.111.025-.159.056s-.088.072-.116.122zm-.24 3.513a.302.302 0 0 0 .093.306c.087.047.267.134.32 0a.301.301 0 0 0-.04-.313c-.046-.02-.32-.093-.373.007zm9.874-8.354c-.067.094.046.314.113.354a.214.214 0 0 0 .154.044.211.211 0 0 0 .177-.15.22.22 0 0 0-.018-.159.227.227 0 0 0-.053-.062c-.06-.053-.307-.087-.373-.027zm-10.121 9.161a.735.735 0 0 0-.76.113.468.468 0 0 0 .18.62.442.442 0 0 0 .51-.037.428.428 0 0 0 .11-.143c.147-.22.14-.44-.04-.553zm-9.06-2.414c-.266-.186-.573-.086-.813.267a.5.5 0 0 0 .04.707.96.96 0 0 0 1-.1.584.584 0 0 0-.025-.717.584.584 0 0 0-.202-.157zm22.927-7.433a.226.226 0 0 0-.26.04c-.047.067 0 .22.087.293.086.074.233.094.313 0 .08-.093-.1-.28-.14-.333zm-4.867 4.4c-.066.134-.14.514 0 .58.14.067.447-.073.567-.166.12-.094.107-.34-.087-.5a.26.26 0 0 0-.268-.113.27.27 0 0 0-.212.199zm-2.393-5.153a.51.51 0 0 0-.666.12.533.533 0 0 0 .16.666.419.419 0 0 0 .58-.12.467.467 0 0 0-.074-.666zm1.3 1.246c-.18-.087-.38-.113-.46.087s-.133.56 0 .666c.134.107.474-.18.58-.306.107-.127.114-.314-.12-.447zm-1.093 8.607c-.054.107-.094.34 0 .413a.416.416 0 0 0 .413-.04c.073-.066.147-.413.047-.486-.1-.074-.42.04-.46.113zm-1.234-8.313a.8.8 0 0 0-.926.366c-.194.307-.08.607.306.84a.624.624 0 0 0 .754-.153 1.076 1.076 0 0 0-.134-1.053zm-11.6 5.733c-.4-.287-.84-.307-1.02-.047a.86.86 0 0 0 .234.967.782.782 0 0 0 .86-.213.57.57 0 0 0-.074-.707zm.947 3.326a.454.454 0 0 0 .098-.353.448.448 0 0 0-.191-.313.504.504 0 0 0-.667.113.53.53 0 0 0 .153.667c.047.033.1.057.157.07a.435.435 0 0 0 .45-.184zm-.507 4.248c-.093.093-.173.626 0 .766.174.14.627-.213.74-.373a.515.515 0 0 0-.106-.593c-.167-.134-.48.066-.634.2zm-2.9-3.754a.451.451 0 0 0-.348-.111.452.452 0 0 0-.319.177.352.352 0 0 0-.116.297.357.357 0 0 0 .17.27.43.43 0 0 0 .525.087.43.43 0 0 0 .141-.127.44.44 0 0 0-.053-.593zm3.647.76c-.18-.1-.373-.167-.533 0a.554.554 0 0 0 .187.666c.146 0 .3-.106.413-.226a.285.285 0 0 0 .069-.109.277.277 0 0 0-.136-.331zm-3.113-2.234a.57.57 0 0 0 .24.727.61.61 0 0 0 .9-.147.621.621 0 0 0-.054-.82.901.901 0 0 0-1.086.24zm-.62 4a.505.505 0 0 0 .087.667.49.49 0 0 0 .666-.14.532.532 0 0 0-.146-.667.413.413 0 0 0-.495.005.396.396 0 0 0-.112.135zm5.98-5.36a.544.544 0 0 0-.667.18.665.665 0 0 0 .107.82.81.81 0 0 0 .946-.12c.107-.193-.086-.673-.386-.88zm.533 5.454a.313.313 0 0 0 .1.347c.047.02.098.027.149.019a.277.277 0 0 0 .137-.059.292.292 0 0 0 0-.287c-.086-.053-.32-.14-.386-.02zm1.293-10.333a.67.67 0 0 0-.053-.873c-.227-.26-.507-.24-.787.18a7.979 7.979 0 0 0-.187.96.41.41 0 0 0 .387.46.429.429 0 0 0 .487-.354c.034-.13.085-.256.153-.373zm.361 4.206c.113-.093.053-.527-.147-.667s-.293 0-.393.147a.3.3 0 0 0 .113.487c.147.073.314.126.427.033zm-2.788-2.433c-.046-.533-.153-.72-.446-.747a.443.443 0 0 0-.48.274.515.515 0 0 0 .133.666c.313.16.553-.033.793-.193zm-.68-2.853a.716.716 0 0 0 .833.047.988.988 0 0 0 .074-.927c-.667.073-1.334.147-2 .2.046.167.16.313.313.3a.778.778 0 0 1 .78.38zm.741 4.013a.45.45 0 0 0-.393-.147.697.697 0 0 0-.367.407c-.067.353.26.38.527.5.246-.24.426-.474.233-.76zm-17.32.033c-.214.073-.14.347-.08.42s.446.433.606.373.1-.526.04-.566-.347-.3-.567-.227zM56.666 111.58c-.24.1-.153.667.18.947a.458.458 0 0 0 .607 0c.246-.2.38-.614.233-.807-.28-.387-.76-.247-1.02-.14zm-.086 4.04a.749.749 0 0 0-.134.98.806.806 0 0 0 1 .047c.2-.167.153-.667-.073-.933a.547.547 0 0 0-.598-.197.558.558 0 0 0-.196.103zm-.734-1.133c-.166-.12-.526 0-.666.1a.36.36 0 0 0 .046.54c.031.034.07.06.114.076a.282.282 0 0 0 .263-.034.28.28 0 0 0 .09-.102c.113-.12.307-.454.153-.58zm2.627 5.113c-.12-.167-.38 0-.454.053s-.173.24-.053.367a.29.29 0 0 0 .367 0c.08-.02.26-.247.14-.42zm-2.567 2.4a.27.27 0 0 0 0 .274.304.304 0 0 0 .3 0c.053-.054.166-.234 0-.36-.167-.127-.207.046-.3.086zm-.219-4.747a.321.321 0 0 0 0 .174c.04.015.086.015.126 0 0-.054.087-.127.047-.18-.04-.054-.113.006-.173.006zm4.866-.433a.569.569 0 0 0-.093.447c.053.12.466.26.613.12a.42.42 0 0 0 0-.593.32.32 0 0 0-.52.026zm1.34-.946a.404.404 0 0 0 .071-.536.403.403 0 0 0-.144-.131c-.127-.047-.36.16-.534.273a.184.184 0 0 0-.1.116.182.182 0 0 0 .02.151.551.551 0 0 0 .687.127zm-3.133 4.792c-.2.047-.447.507-.3.667.146.16.433.36.666 0 .234-.36-.166-.74-.366-.667zm2.873 2.754c-.087.093-.267.38-.067.573.2.194.427-.033.494-.113a.439.439 0 0 0 0-.407.286.286 0 0 0-.201-.126.291.291 0 0 0-.121.011.286.286 0 0 0-.105.062zm.167-1.807c.073 0 .18-.173.133-.28-.047-.107-.207-.133-.287-.107-.08.027-.253.2-.213.3s.293.12.366.087zm.74-1.113a.104.104 0 0 0-.053.054c-.207 0-.667.366-.534.6a.452.452 0 0 0 .554.22c.147-.074.187-.34.153-.547h.074c.053 0 .206-.16.16-.28-.047-.12-.267-.073-.354-.047zm-7.873-3.233c-.2.146-.054.513-.08.726l.126.074a.432.432 0 0 0 .474-.2.6.6 0 0 0 .16-.367c-.22-.067-.494-.373-.68-.233zm5.473.733c-.167.06-.16.44 0 .56.12.054.25.079.38.073.253-.22.3-.486.133-.573a.655.655 0 0 0-.513-.06zm-.047-2.667c-.16-.667-.786-.367-.733-.8s.427-.2.46-.82-.48-1.013-.86-.8-.353.607-.453.627-.567 0-.58.353c0 .733.666.587.666.807s-.1.666-.04.98c.087.466.42.84.9.666a.8.8 0 0 0 .546-.364.807.807 0 0 0 .094-.649zm-11.227-8a.339.339 0 0 0-.092.409.339.339 0 0 0 .092.118.622.622 0 0 0 .8.167.724.724 0 0 0 .107-.82c-.193-.2-.613-.134-.907.126zm6.52 5.827a.744.744 0 0 0-.06-.886.588.588 0 0 0-.667-.04c-.333.306-.433.733-.22.946a.81.81 0 0 0 .947-.02zm-7.646-7.513a.667.667 0 0 0 0 .773.453.453 0 0 0 .667 0 .518.518 0 0 0 .066-.666.662.662 0 0 0-.733-.107zm3.513-2.387a1.04 1.04 0 0 0-.593-.186c-.247-.087-.8.046-.887.26-.086.213.147.373.374.46a.867.867 0 0 0 .213.353c.26.253.573.213.887-.113a.669.669 0 0 0 .006-.774zm-3.22 9.44a.222.222 0 0 0 0 .254c.06.066.22 0 .307 0s.14-.214.06-.314-.293.027-.367.06zm-.393-13.113c-.154-.28-.234-.634-.594-.78l-.426.146a1.213 1.213 0 0 0-.354-.167.115.115 0 0 0 0-.046c-.053-.127-.266-.094-.306-.06l-.087.073a.32.32 0 0 0-.16.093.42.42 0 0 0 0 .527c.207.327.36.367.793.227.253.143.513.273.78.387a.3.3 0 0 0 .354-.4zm33.287 33.246a.63.63 0 0 0-.343.358.622.622 0 0 0 .03.495c.1.274.446.36.826.207a.437.437 0 0 0 .298-.23.45.45 0 0 0 .015-.377.967.967 0 0 0-.826-.453zM49.96 105.247c-.134-.147-.28-.12-.434.04 0 .06-.033.166 0 .213a.97.97 0 0 1 .167.607c0 .1.22.246.347.26a.575.575 0 0 0 .407-.194.456.456 0 0 0-.04-.38 4.377 4.377 0 0 0-.447-.546zm3.113 7.966c-.126-.166-.32-.073-.46.06-.14.134-.166.434 0 .514.167.08.427.253.574.04.146-.214-.014-.494-.114-.614zm.574 1.033a.563.563 0 0 0-.427 0c-.167.094-.14.467.047.567a.81.81 0 0 0 .346.033c.214-.233.214-.513.034-.6zm.679-4.579a1.026 1.026 0 0 0-1.273-.067c-.353.28-.32.833.067 1.287a.757.757 0 0 0 .513.272.755.755 0 0 0 .553-.179.994.994 0 0 0 .14-1.313zm-3.313 7.82a.415.415 0 0 0-.164.333.42.42 0 0 0 .164.333.387.387 0 0 0 .281.149.4.4 0 0 0 .3-.109c.206-.206.28-.62.132-.766a.662.662 0 0 0-.713.06zM51.16 114a.559.559 0 0 0-.067.713c.153.167.54.127.854-.153 0-.087 0-.274-.04-.387a.555.555 0 0 0-.747-.173zm-.213-2.754a.698.698 0 0 0-.148.504.686.686 0 0 0 1.214.356.624.624 0 0 0 .094-.853.92.92 0 0 0-1.16-.007zm-3.514 2.427c-.066.047-.18.28-.107.36.074.08.32 0 .374-.04a.206.206 0 0 0 .065-.222.206.206 0 0 0-.102-.122.195.195 0 0 0-.078-.023.2.2 0 0 0-.152.047zM74 131.293a.304.304 0 0 0-.267.16c-.04.1.107.227.194.253.086.027.293-.04.326-.166.034-.127-.166-.247-.253-.247zm2.113 1.127c-.066-.194-.513-.16-.753.1s.067.62.233.666c.167.047.587-.58.52-.766zm.04-2.8c-.1-.18-.246-.293-.44-.227a2.668 2.668 0 0 0-1.286.814.836.836 0 0 0-.087.226.573.573 0 0 0 .793.667c.308-.105.599-.25.867-.433a.916.916 0 0 0 .153-1.047zm-3.253 3.447a.468.468 0 0 0 .446.393c.273-.04.433-.667.273-.773-.16-.107-.746.113-.72.38zm-1.053 2.033c-.107.06-.374.273-.247.52s.413.107.507.053a.431.431 0 0 0 .16-.373.285.285 0 0 0-.29-.235.28.28 0 0 0-.13.035zm1.486-5.88a.43.43 0 0 0 .193-.56.326.326 0 0 0-.5-.154.57.57 0 0 0-.226.394c-.02.133.32.433.533.32zm-1.106.266c-.18 0-.3.367-.174.54.127.174.26.154.334.194.313-.127.446-.36.32-.5a.732.732 0 0 0-.48-.234zm3.513 8.887a.363.363 0 0 0 .066.34.41.41 0 0 0 .42 0c.087-.106-.073-.386-.153-.426a.358.358 0 0 0-.333.086zm3.766-3.746a.429.429 0 0 0 .32-.507.568.568 0 0 0-.267-.34c-.24-.113-.413 0-.666.447.146.213.246.513.613.4zm-7.359-6.907a.817.817 0 0 0 .337-.023.808.808 0 0 0 .583-.757c.06-.667-.627-.607-.434-1s.474-.047.707-.62c.233-.573-.12-1.113-.554-1.04-.433.073-.526.453-.626.447-.1-.007-.54-.187-.667.14-.266.666.407.76.394.986-.014.227-.314.587-.367.92-.074.467.126.927.626.947zm7.493 10.66a.968.968 0 0 0-.434 1.126.88.88 0 0 0 1.087.507 1.334 1.334 0 0 0 .493-1.273.879.879 0 0 0-1.147-.36zm-.307-5.906a.49.49 0 0 0 .21-.609.486.486 0 0 0-.577-.285.48.48 0 0 0-.273.587.525.525 0 0 0 .64.307zm-1.253-1.681a.461.461 0 0 0-.42.135.463.463 0 0 0-.12.425.622.622 0 0 0 .38.387.529.529 0 0 0 .453-.18.54.54 0 0 0 .033-.453.53.53 0 0 0-.327-.314zm-2.04 4.187a.678.678 0 0 0-.394.353.665.665 0 0 0-.013.527 1.06 1.06 0 0 0 1.2.56.773.773 0 0 0 .347-.986.835.835 0 0 0-.808-.533.83.83 0 0 0-.332.079zM63.773 126a.433.433 0 0 0-.236.255.421.421 0 0 0 .036.345.384.384 0 0 0 .216.234.393.393 0 0 0 .318-.007c.266-.12.473-.493.38-.667a.704.704 0 0 0-.714-.16zm3.127 5.9a.297.297 0 0 0-.067.267.316.316 0 0 0 .287.107c.066 0 .233-.167.14-.334-.094-.166-.28-.08-.36-.04zm.38-4.907c-.234.073-.22.46-.314.667.033.036.064.074.093.113a.431.431 0 0 0 .514 0 .578.578 0 0 0 .266-.3c-.16-.14-.32-.553-.56-.48zm-.333-1.627c.046-.126-.194-.333-.287-.346a.341.341 0 0 0-.28.206.366.366 0 0 0 .18.3.407.407 0 0 0 .386-.16zm-2.174-5.886c-.126-.153-.54 0-.666.347s.266.567.446.573c.18.007.347-.76.22-.92zm1.827 3.654c.12.113.28.246.44.133s.14-.467.086-.62a.195.195 0 0 0-.153-.14.777.777 0 0 0-.2-.527.848.848 0 0 0-.94-.238.847.847 0 0 0-.287.185.667.667 0 0 0-.14.887.424.424 0 0 0-.347-.047.559.559 0 0 0-.3.667c.094.2.467.293.86.133 0-.087.12-.253.087-.38a.46.46 0 0 0-.067-.14 1.153 1.153 0 0 0 .96.087zm-2.52-5.254a.912.912 0 0 0-.187-1.053.36.36 0 0 0-.493-.067c-.43.293-.76.709-.947 1.194a.743.743 0 0 0 0 .24.582.582 0 0 0 .69.536.574.574 0 0 0 .29-.15c.244-.205.461-.44.647-.7zm5.566 8.187a.766.766 0 0 0-.447.886.797.797 0 0 0 .927.374c.24-.094.367-.58.24-.907a.556.556 0 0 0-.5-.385.567.567 0 0 0-.22.032zm.407 5.459c-.207 0-.587.334-.514.56.074.227.294.487.667.254.373-.234.053-.787-.153-.814zm-1.86-4.052s.107.08.12.066c.014-.013.12-.093.1-.153-.02-.06-.093-.047-.146-.067a.445.445 0 0 0-.074.154zm1.654 3.346c.093 0 .333-.167.28-.367-.054-.2-.36-.14-.447-.1s-.24.167-.173.327a.282.282 0 0 0 .34.14zm1.419-6.907a.88.88 0 0 0-1.2.04.974.974 0 0 0-.04 1.207.884.884 0 0 0 .573.295.884.884 0 0 0 .62-.175 1.28 1.28 0 0 0 .047-1.367zm-1.933-.58a.739.739 0 0 0 .233-.86.575.575 0 0 0-.62-.26c-.413.18-.666.547-.513.827a.813.813 0 0 0 .9.293zm0 1.427c-.114-.173-.494-.167-.634-.113a.345.345 0 0 0-.192.38c.01.05.03.098.059.14.018.042.046.08.081.11a.287.287 0 0 0 .379-.01c.113-.087.413-.334.307-.507zm105.246-49.873h-8.253v8.253h8.253zm19.087 19.087h-8.253v8.253h8.253zm-19.087 0h-8.253v8.253h8.253zm-19.08-19.087h-8.253v8.253h8.253zm0 19.087h-8.253v8.253h8.253zm-21.106-19.087h-8.253v8.253h8.253z"/> + </g> + <path fill="#1ba9f5" d="M105.439 32.813a42.666 42.666 0 1 0 0 85.333 42.666 42.666 0 0 0 0-85.332zm0 69.394A26.751 26.751 0 0 1 79.2 70.232a26.753 26.753 0 1 1 50.956 15.465 26.744 26.744 0 0 1-24.717 16.51z"/> + <path fill="#0a89db" d="M133.935 93.105 123.47 103.57l27.143 27.144 10.465-10.465z"/> + <path fill="#0d90e0" d="M73.086 74.833a.48.48 0 0 0 .574-.393.58.58 0 0 0-.4-.6.754.754 0 0 0-.614.493c-.026.16.24.46.44.5zm10.287-15.347c.147.054.234-.18.227-.226-.007-.047-.033-.22-.173-.247a.153.153 0 0 0-.182.073.154.154 0 0 0-.018.067c-.007.094-.007.28.146.334zm1-.493c.154-.2.307-.407.474-.6a.433.433 0 0 0-.147-.107c-.28-.086-.667.087-.667.32-.033.12.147.274.34.387zM69.126 76.967a.465.465 0 0 0 .514-.427c.053-.28-.14-.666-.38-.666a.58.58 0 0 0-.667.433.549.549 0 0 0 .533.66zm.327-7.981.22-.226h-.707c.143.11.31.188.487.226zm-4.119 1.401a.426.426 0 0 0 .373-.053.9.9 0 0 0 .293-1 .433.433 0 0 0-.5-.327.74.74 0 0 0-.666.62c-.004.06-.004.12 0 .18v.04c.15.195.317.376.5.54zm1.852-1.627h-.407.054a.587.587 0 0 0 .353 0zm-3.633 8.44a1.114 1.114 0 0 0 .473.847c.28.08.613-.2.707-.6.093-.4-.06-.574-.5-.667a.559.559 0 0 0-.68.42zm5.294-3.427A.92.92 0 0 0 69 73.16c-.08-.207-.333-.34-.533-.533.106-.187.233-.42 0-.667-.62 0-.667.127-.387.667-.12.224-.2.468-.233.72.043.233.157.447.326.613.174.213.494.12.674-.187zm-4.474-1.347a1.273 1.273 0 0 0 1.294.44c.326-.146.253-.586.34-.666.086-.08.593.233.986 0a1.072 1.072 0 0 0 .38-1.187 1.04 1.04 0 0 0-.5-.6.866.866 0 0 0-.953.167c-.207.26-.213.766-.333.82-.12.053-.6-.287-.98-.054a.761.761 0 0 0-.234 1.08zm5.673 3.134a.592.592 0 0 0 .44-.213c.193-.34-.047-.553-.32-.753-.26.106-.553.206-.547.546a.406.406 0 0 0 .427.42zm7.2-19.266c.273.073.593-.22.667-.614a.347.347 0 0 0-.287-.44c-.32-.093-.707.073-.76.313a.728.728 0 0 0 .38.74zM75.24 58.62c-.08.16.1.547.306.667a.341.341 0 0 0 .473-.22c.14-.347.127-.62-.04-.707a.773.773 0 0 0-.74.26zm1.366 2.713h.52a.78.78 0 0 0 .36-.447.54.54 0 0 0-.386-.54c-.46-.073-.8.067-.847.354a.767.767 0 0 0 .353.633zm4.187-5.18a.407.407 0 0 0 .513-.28.367.367 0 0 0-.3-.44.334.334 0 0 0-.42.24.354.354 0 0 0 .207.48zm-.713 1.18c-.334-.073-.567.107-.667.52-.1.414.12.567.526.667a.473.473 0 0 0 .587-.273.974.974 0 0 0-.447-.914zm.22-3.953c.113-.074.086-.387.1-.594a.187.187 0 0 0-.167-.22.547.547 0 0 0-.52.467.406.406 0 0 0 .587.347zm-1.134 3.42a.666.666 0 0 0 .667-.34.666.666 0 0 0-.447-.666.46.46 0 0 0-.513.393.52.52 0 0 0 .293.613zm-1.673.36a.567.567 0 0 0-.226.38c0 .094.14.247.246.294.216.086.44.153.667.2.2.046.307-.054.34-.274-.033-.046-.06-.153-.12-.173a.974.974 0 0 1-.48-.413.466.466 0 0 0-.427-.014zm-4.16 3.02c-.047-.28-.114-.553-.454-.513s-.373.3-.34.567a.508.508 0 0 0 .794-.054zm3.613 14.947a.513.513 0 0 0 .58-.307.706.706 0 0 0-.326-.666.627.627 0 0 0-.614.373.467.467 0 0 0 .36.6zm.454 1.74a.767.767 0 0 0 .88-.447c.06-.233-.387-.733-.667-.787a.627.627 0 0 0-.18 1.234zm-2.8-7.713v.12c-.073.186-.187.26-.36.12-.06-.054-.1-.134-.167-.18a.806.806 0 0 0-.906 0 .666.666 0 0 0-.307.826 1.16 1.16 0 0 0 .78.827c-.055.18-.093.366-.113.553 0 .547.36.76.84.487a.613.613 0 0 0 .18-1.013 4.33 4.33 0 0 0-.42-.367c.186-.113.306-.253.426-.253a.58.58 0 0 0 .627-.594 1.09 1.09 0 0 1 .12-.273c.14-.307.107-.54-.093-.667h-.32a.606.606 0 0 0-.287.414zm-2.32 3.286c-.34 0-.714-.106-.987.26a.765.765 0 0 1-.594.347.726.726 0 0 0-.606.667 1.779 1.779 0 0 0 .986.986c.34-.153.794-.34 1.24-.546.054 0 .08-.187.067-.274a.86.86 0 0 1 .173-.666c.2-.294.027-.76-.28-.774zm2.233.42c-.293-.067-.573.227-.666.667a.593.593 0 0 0 .426.573.853.853 0 0 0 .74-.413.834.834 0 0 0-.5-.827zM78 71.593a.84.84 0 0 0-1.027.527 1.333 1.333 0 0 0 .667 1.186.853.853 0 0 0 .946-.666.987.987 0 0 0-.586-1.047zm-2.18 6.994c-.32-.067-.547.133-.667.56s.053.626.313.666a1.16 1.16 0 0 0 .9-.466.667.667 0 0 0-.546-.76zm2.886-3.64c0-.445.02-.89.06-1.334a.927.927 0 0 0-.327.54.84.84 0 0 0 .267.794zm.167 3.587c0-.214-.047-.434-.067-.667-.06.087-.106.187-.146.247-.04.06.006.34.213.42zM68.18 76a.667.667 0 0 0-.32-.44c-.374-.147-.488.207-.7.453.192.32.366.567.732.447a.374.374 0 0 0 .287-.46zm8.793-6.366a.813.813 0 0 0-.927-.534.847.847 0 0 0 .547 1.58c.326-.066.453-.586.38-1.046zm-11.94 6.626c-.06.22.093.58.293.593.314.071.643.026.927-.126.4-.287.426-.327.193-.84-.24 0-.433-.06-.627-.1a.727.727 0 0 0-.786.473zM79.16 59.893c.04-.3-.293-.667-.666-.727s-.594.147-.667.594a.667.667 0 0 0 .42.666c.266.034.88-.3.913-.533zM74.453 57.2a.74.74 0 0 0 .953-.594.786.786 0 0 0-.493-.92c-.354-.113-.827.26-.947.747a.606.606 0 0 0 .487.767zm.447 3.767a.347.347 0 0 0-.387-.353c-.373 0-.626.12-.666.306a.619.619 0 0 0 .246.407h.594a.666.666 0 0 0 .213-.36z"/> + <path fill="#0d90e0" d="M72.466 69.22a.62.62 0 0 0 .153-.46h-2.58c.094.373 0 .52-.42.393a.767.767 0 0 0-.933.373.78.78 0 0 0 0 .954.628.628 0 0 0 .9.213c.23-.135.425-.32.573-.54.127-.173.234-.3.454-.287.32 0 .426-.173.426-.466a.94.94 0 0 0 1.427-.18zm-1.92 2.614a.54.54 0 0 0 .6.293.467.467 0 0 0 .454-.407c.04-.34-.454-.873-.667-.84a.988.988 0 0 0-.387.954zm-.952-.048a.547.547 0 0 0-.614.354.706.706 0 0 0 .327.766.786.786 0 0 0 .9-.42c.06-.266-.207-.56-.613-.7zm2.619-13.272c0-.254-.12-.374-.34-.42a.447.447 0 0 0-.554.34.394.394 0 0 0 .36.46.473.473 0 0 0 .534-.38zm-4.46 18.399c-.32-.086-.54.227-.6.407s.287.533.667.433a.492.492 0 0 0 .287-.32.433.433 0 0 0-.354-.52zm-.42-2.539c-.446-1.147-1.12-1.247-1.88-.334a1.57 1.57 0 0 1-.233-.073.494.494 0 0 0-.667.2.613.613 0 0 0 0 .707c.314.666.614.766 1.187.466.433-.22.88-.426 1.333-.666.534.84.667.866 1.187.32a.74.74 0 0 0-.927-.62zm3.887-13.661a.38.38 0 0 0 .28.507.46.46 0 0 0 .58-.287.446.446 0 0 0-.36-.526.406.406 0 0 0-.5.306zm8.893.621h.76a.327.327 0 0 0-.186-.154.442.442 0 0 0-.394.067c-.053.027-.12.047-.18.087zm-12.587 24.16a.753.753 0 0 0-.847.426.52.52 0 0 0 .34.627.666.666 0 0 0 .747-.447.453.453 0 0 0-.24-.606zm3.734-2.514a.827.827 0 0 0 .846-.533c.067-.26-.213-.607-.546-.667-.434-.093-.667 0-.767.347a.78.78 0 0 0 .466.853zm.214 1.16c0-.147-.054-.527-.227-.554-.173-.026-.353.334-.38.467-.027.133.16.26.28.307s.347-.014.327-.22zm1.039 2.214c-.166 0-.586.073-.613.24-.027.166.313.393.46.473.147.08.393-.113.42-.327.027-.213-.053-.393-.267-.386zm-.98 4.52a.354.354 0 0 0-.207.44c.087.34.354.38.667.393.147-.266.36-.48.134-.74a.474.474 0 0 0-.594-.093zm-2.14-7.541a1.113 1.113 0 0 0-1.18.62.887.887 0 0 0 .6.987.86.86 0 0 0 .58-1.607zm1.219-5.186a.467.467 0 0 1-.133-.507.546.546 0 0 0-.513-.553c-.293 0-.373.153-.393.393.005.174-.03.348-.1.507-.2.32-.454.607-.667.927-.907-.254-1.18-.094-1.193.74a.62.62 0 0 0 .813.666 5.64 5.64 0 0 0 1-.546 1.1 1.1 0 0 0 1.22.1 1.147 1.147 0 0 0-.033-1.727zM67.56 82.82c-.225.033-.448.08-.667.14-.447-.347-.887-.407-1.093-.147a.92.92 0 0 0 .166 1.1c.3.247.507.173 1.02-.413.247.18.48.393.754.093a.48.48 0 0 0 .073-.607c-.067-.08-.18-.173-.253-.166zm1.606 4.327a.753.753 0 0 0-.78.62c-.073.386.16.666.607.74a.625.625 0 0 0 .793-.534.734.734 0 0 0-.62-.826zM73 89.14c-.087 0-.28-.054-.34.086s.113.26.16.26a.366.366 0 0 0 .267-.12s-.04-.213-.087-.226zm-3.86-7.806a.527.527 0 0 0-.14-.414.393.393 0 0 0-.354-.053c-.26.146-.253.38-.12.666.233.014.5.087.613-.2zm3.867-1.26a.587.587 0 0 0-.447-.667.386.386 0 0 0-.467.273.493.493 0 0 0 .147.6.547.547 0 0 0 .767-.206zm5.759 4.213a.666.666 0 0 0-.52-.667c-.32-.073-.533.04-.6.32a.787.787 0 0 0 .44.94.706.706 0 0 0 .68-.593zm-.433-3.62c.1-.414-.213-.794-.746-.914a.667.667 0 0 0-.86.52.9.9 0 0 0 .666 1.04.92.92 0 0 0 .94-.646zm-4.667 1.166c-.093.093-.293.207-.373.373-.08.167.126.514.406.554a.407.407 0 0 0 .46-.374.547.547 0 0 0-.493-.553zm-.613-27.573a.933.933 0 0 0 .967-.667.67.67 0 0 0-1.333-.113.626.626 0 0 0 .367.78zM76.82 88.7a.729.729 0 0 0-1.06 0 .667.667 0 0 0 .053.787.527.527 0 0 0 .707.16c.207-.1.667-.187.707-.527s-.287-.306-.407-.42zm.96-19.94a.48.48 0 0 0 .046.3 1.387 1.387 0 0 0 1.48.72c.075-.343.155-.683.24-1.02zm-2.994 12.906a.46.46 0 0 0 .547-.333.475.475 0 1 0-.934-.18.534.534 0 0 0 .387.513zM76 83.42a.44.44 0 0 0-.38-.473c-.367-.047-.667.373-.667.52a.667.667 0 0 0 .667.453.446.446 0 0 0 .38-.5zm-1.573-6.187-.04-.06c.083.093.183.17.293.227a.288.288 0 0 0 .433-.187.345.345 0 0 0-.22-.486.827.827 0 0 0-.44.086.413.413 0 0 0-.306-.473.473.473 0 0 0-.567.52c0 .32-.18.413-.373.593-.494-.506-.92-.473-1.227.06a.886.886 0 0 0-.08.16c-.153.394-.047.62.353.734.4.113.667.153.954-.327.055.157.091.32.106.487a.434.434 0 0 0 .327.506.667.667 0 0 0 .727-.12c.64-.713.653-.993.06-1.72zM74.98 86a.494.494 0 0 0-.528.306.566.566 0 0 0 .314.594.44.44 0 0 0 .513-.314.507.507 0 0 0-.3-.586zm3.84 1.06a.408.408 0 0 0-.52.273.46.46 0 0 0 .3.527.388.388 0 0 0 .52-.266.48.48 0 0 0-.3-.534zm-9.007-31.853a.56.56 0 0 0 .58-.34c.106-.44-.047-.847-.347-.907a.813.813 0 0 0-.78.547c-.06.24.26.653.547.7zm-3.2 37.873a.267.267 0 0 0-.073-.16zM66 60.3c.146 0 .413 0 .466-.173.053-.173-.127-.38-.253-.493s-.287 0-.387.12l-.067.18a.278.278 0 0 0 .122.327.28.28 0 0 0 .118.04zm1.166 1.033h.414c.093-.07.196-.127.306-.167a.587.587 0 0 0 .467-.62c0-.333-.227-.666-.18-.793.047-.127.153-.333.393-.24.24.093.627-.047.794-.547s-.274-1.04-.667-.926c-.393.113-.4.353-.533.293s-.314-.327-.554-.227-.253.874-.193.987c.06.113.347.347.153.533-.193.187-.526.234-.586.507-.06.273-.227.913.126 1.187zm1.007-4.313c.46.16.76-.254.906-.474.147-.22-.266-.667-.666-.667a.447.447 0 0 0-.487.36c-.107.287.013.7.247.78zm3.307-1.373a1 1 0 0 0 .62 1.167 1.033 1.033 0 0 0 1.1-.667c.127-.426-.2-.866-.78-1.026a.751.751 0 0 0-.94.526zm-1.2.9a.48.48 0 0 0-.554.346.446.446 0 0 0 .347.494.393.393 0 0 0 .52-.287.426.426 0 0 0-.313-.553zm.873-3.38c.207.073.313-.12.347-.307.033-.186-.1-.446-.294-.42-.193.027-.486 0-.493.287-.007.287.293.393.44.44zm-1.74 6.746a1 1 0 0 0 .727.973c.267.047.553-.226.613-.593.06-.367-.093-.667-.553-.753-.36-.067-.733.106-.787.373zm.687-7.247a.538.538 0 0 0 .36-.233c.086-.173-.147-.467-.354-.447a.74.74 0 0 0-.306.167c-.047.28.106.513.3.513zM66.413 80.38c.133-.048.262-.106.387-.174.413-.2.553-.46.44-.806a.72.72 0 0 0-.907-.467 7.66 7.66 0 0 1-1.333.26c-.367 0-.78.307-.754.56a.547.547 0 0 0 .534.367c.32-.107.413.12.606.233.194.09.407.13.62.12a.974.974 0 0 0 .407-.093zm-2.16 5.16a.433.433 0 0 0-.24 0l.22.874a.433.433 0 0 0 .366-.347.514.514 0 0 0-.346-.527zm.34-3.107a.993.993 0 0 0-.534-.727c-.253-.066-.44.094-.513.447-.073.353 0 .613.287.667.386.093.706-.074.76-.387zm2.467-1.98a.58.58 0 0 0-.667.3c-.134.447 0 .86.28.94a.773.773 0 0 0 .82-.48.667.667 0 0 0-.433-.76zm-1.487 6.467a.14.14 0 0 0-.146.114c0 .04.073.126.106.126a.134.134 0 0 0 .14-.08.132.132 0 0 0-.052-.138.134.134 0 0 0-.048-.022zm-2.106-11.1a.746.746 0 0 0-.214-.847.46.46 0 0 0-.446.054v1.293h.046a.8.8 0 0 0 .547-.107.294.294 0 0 0 .067-.393zm-.134-3.554a.726.726 0 0 0-.174-.973.771.771 0 0 0 .214-.227.532.532 0 0 0 0-.546c.068.006.138.006.206 0a1 1 0 0 0 .767-1.12.34.34 0 0 0 .2 0 .393.393 0 0 0 .127-.567h-.54a.447.447 0 0 0-.107.293.668.668 0 0 0-.727-.106 41.81 41.81 0 0 0-.4 3.806c.14-.1.294-.233.434-.56z"/> + <path fill="#294492" d="M33.566 123.433a1.635 1.635 0 0 1-1.433-.846 33.005 33.005 0 0 1-1.673-3.58 1.633 1.633 0 0 1 .92-2.114 1.633 1.633 0 0 1 1.77.385c.148.154.265.336.343.535a28.107 28.107 0 0 0 1.5 3.214 1.617 1.617 0 0 1-.619 2.196 1.61 1.61 0 0 1-.808.21zm-3.44-10.826a1.624 1.624 0 0 1-1.613-1.454 29.025 29.025 0 0 1-.18-3.233v-.733A1.633 1.633 0 0 1 30 105.6a1.632 1.632 0 0 1 1.136.509 1.62 1.62 0 0 1 .444 1.164v.667c0 .962.053 1.924.16 2.88a1.628 1.628 0 0 1-1.44 1.793zm1.127-11.274a1.622 1.622 0 0 1-1.623-1.503 1.64 1.64 0 0 1 .076-.63c.41-1.264.911-2.496 1.5-3.687a1.63 1.63 0 0 1 2.92 1.447 23.352 23.352 0 0 0-1.333 3.246 1.631 1.631 0 0 1-1.54 1.127zm5.8-9.733a1.627 1.627 0 0 1-1.173-2.753 28.727 28.727 0 0 1 2.933-2.667 1.626 1.626 0 1 1 2 2.547 25.898 25.898 0 0 0-2.6 2.373 1.605 1.605 0 0 1-1.16.48zm9.333-6.433a1.626 1.626 0 0 1-.666-3.12 34.99 34.99 0 0 1 3.686-1.334 1.628 1.628 0 1 1 .96 3.114 30.74 30.74 0 0 0-3.333 1.233 1.673 1.673 0 0 1-.627.086zm33.88-2.667a1.627 1.627 0 1 1-.033-3.254c1.233 0 2.48-.066 3.707-.12a1.629 1.629 0 0 1 .153 3.254c-1.26.053-2.533.1-3.793.12zm-7.68 0h-.046c-1.24-.031-2.5-.078-3.78-.14a1.628 1.628 0 1 1 .153-3.253c1.253.06 2.493.106 3.713.133a1.634 1.634 0 0 1-.04 3.26zm-15.173-.107a1.627 1.627 0 0 1-.173-3.247 48.211 48.211 0 0 1 3.866-.266 1.602 1.602 0 0 1 1.68 1.58 1.626 1.626 0 0 1-1.58 1.673c-1.226.04-2.446.12-3.613.247zm34.26-.553a1.627 1.627 0 0 1-.16-3.247c1.233-.127 2.473-.273 3.673-.433a1.628 1.628 0 0 1 .427 3.226c-1.233.167-2.5.314-3.773.447zm11.333-1.707a1.63 1.63 0 0 1-1.617-1.462 1.627 1.627 0 0 1 1.291-1.758 95.372 95.372 0 0 0 3.6-.813 1.63 1.63 0 0 1 .773 3.166c-1.213.294-2.467.574-3.72.834a1.816 1.816 0 0 1-.34-.027zM114 77.107a1.628 1.628 0 0 1-1.399-2.462c.198-.331.507-.581.872-.705a71.72 71.72 0 0 0 3.44-1.273 1.63 1.63 0 0 1 1.207 3.026c-1.167.46-2.38.907-3.594 1.333-.17.056-.348.083-.526.08zm10.473-4.547a1.634 1.634 0 0 1-.78-3.06 52.344 52.344 0 0 0 3.14-1.84 1.627 1.627 0 0 1 1.747 2.746 56.688 56.688 0 0 1-3.334 1.954 1.565 1.565 0 0 1-.746.2zm9.333-6.52a1.628 1.628 0 0 1-1.577-2.005 1.62 1.62 0 0 1 .491-.828c.9-.82 1.76-1.667 2.553-2.527a1.628 1.628 0 0 1 2.4 2.2c-.86.933-1.793 1.86-2.78 2.747a1.62 1.62 0 0 1-1.033.413zm7.167-8.834a1.628 1.628 0 0 1-1.427-2.413c.347-.667.667-1.28.967-1.92.193-.44.393-.88.593-1.333a1.638 1.638 0 0 1 1.536-.965 1.636 1.636 0 0 1 1.479 1.05 1.622 1.622 0 0 1-.035 1.248l-.606 1.333c-.334.727-.667 1.447-1.087 2.154a1.619 1.619 0 0 1-1.367.846zm-9.133-8.086a1.641 1.641 0 0 1-.607-.12 34.34 34.34 0 0 1-3.507-1.667 1.631 1.631 0 0 1-.895-1.61 1.632 1.632 0 0 1 1.182-1.413 1.627 1.627 0 0 1 1.26.163c1.048.57 2.127 1.08 3.233 1.527a1.628 1.628 0 0 1 .432 2.774 1.63 1.63 0 0 1-1.045.366zm13.586-2.453a1.621 1.621 0 0 1-1.619-1.509 1.628 1.628 0 0 1 .079-.632 53.13 53.13 0 0 0 1.027-3.493 1.629 1.629 0 0 1 2.977-.426c.22.371.283.815.176 1.233a52.294 52.294 0 0 1-1.093 3.713 1.63 1.63 0 0 1-1.547 1.114zm-23.086-3.694a1.612 1.612 0 0 1-1.167-.493 19.48 19.48 0 0 1-2.533-3.2 1.627 1.627 0 1 1 2.753-1.74 16.037 16.037 0 0 0 2.113 2.666 1.625 1.625 0 0 1-1.166 2.76zm25.333-7.487h-.04a1.63 1.63 0 0 1-1.478-1.04 1.63 1.63 0 0 1-.109-.626v-.627a19.053 19.053 0 0 0-.2-2.833 1.634 1.634 0 0 1 .772-1.636 1.638 1.638 0 0 1 1.233-.184 1.632 1.632 0 0 1 1.215 1.34c.165 1.103.245 2.218.24 3.333v.713a1.626 1.626 0 0 1-1.66 1.56zm-30-2.666A1.632 1.632 0 0 1 116 31.333c0-.42-.04-.853-.04-1.28v-.107c.006-.91.077-1.82.213-2.72a1.62 1.62 0 0 1 .651-1.064 1.63 1.63 0 0 1 2.569 1.571 15.184 15.184 0 0 0-.18 2.233V30c0 .36 0 .707.04 1.053a1.633 1.633 0 0 1-1.513 1.74zm26.893-8.093a1.62 1.62 0 0 1-1.286-.627 13.32 13.32 0 0 0-2.367-2.34 1.638 1.638 0 0 1-.29-2.29 1.63 1.63 0 0 1 2.29-.29 16.563 16.563 0 0 1 2.953 2.92 1.63 1.63 0 0 1 .18 1.712 1.631 1.631 0 0 1-1.46.915zm-23.533-2.5a1.628 1.628 0 0 1-1.2-2.727 13.671 13.671 0 0 1 3.333-2.667 1.629 1.629 0 0 1 1.614 2.827 10.443 10.443 0 0 0-2.527 2 1.605 1.605 0 0 1-1.24.567zm14-3.467c-.142 0-.283-.02-.42-.06a15.16 15.16 0 0 0-3.333-.513 1.627 1.627 0 0 1-1.56-1.694 1.653 1.653 0 0 1 1.693-1.56c1.37.057 2.73.267 4.053.627a1.627 1.627 0 0 1-.42 3.2z"/> + <path fill="#1ba9f5" d="M139.447 49.727c-.62.433-8.36 5.033-15.687 6.246a26.738 26.738 0 0 1-9.68 44.8c.42 5.374.98 10.74 1.58 16.1a42.667 42.667 0 0 0 23.787-67.146z"/> + <path fill="#0066b1" d="M133.333 107.814a.7.7 0 0 0 .047-.147c-.207.18-.4.36-.607.527a.605.605 0 0 0 .56-.38zm4.574-1.901a1.085 1.085 0 0 0 1-.353.667.667 0 0 0-.294-.667c0-.226-.18-.366-.52-.44a.574.574 0 0 0-.666.42.963.963 0 0 0 .166.52.667.667 0 0 0 .314.52zm1.28-1.393c.313.073.642.03.926-.12l.047-.04a.391.391 0 0 0 .38-.214.969.969 0 0 0-.307-.946.48.48 0 0 0-.39-.016.465.465 0 0 0-.167.109.465.465 0 0 0-.109.167.725.725 0 0 0-.667.473c-.073.213.087.573.287.587zm-.521-1.187c0-.053-.04-.167-.093-.187a.833.833 0 0 1-.353-.393c-.154.18-.3.367-.46.547.17.092.346.175.526.246.154.1.274.014.38-.213zm-1.293.38c-.067.08-.134.153-.207.227a.573.573 0 0 0 .107-.04.28.28 0 0 0 .07-.083.271.271 0 0 0 .03-.104zM137 107.7c.011.062.041.12.084.167a.334.334 0 0 0 .163.093c.106 0 .193-.154.186-.26-.006-.107-.113-.234-.226-.214a.221.221 0 0 0-.143.069.225.225 0 0 0-.064.145zm.653 4.34c.066-.167.106-.354-.1-.467a.482.482 0 0 0-.362.006.482.482 0 0 0-.258.254c.08.107.153.293.293.38s.353.02.427-.173zm-1.96-1.307c.16.105.35.154.54.14.111.032.225.05.34.053.06-.1.12-.193.167-.286.057-.039.11-.081.16-.127a2.78 2.78 0 0 0-.153-.56.666.666 0 0 0-.734-.387.738.738 0 0 0-.573.747.64.64 0 0 0 .083.236.636.636 0 0 0 .17.184zm5.093-10.786-.26-.254-.193.28a.868.868 0 0 0 .453-.026zm-10.653 10.28.106.113a.382.382 0 0 0 .074-.12.984.984 0 0 0 0-.133zm11.506-4.801a.497.497 0 0 0 .294-.326.421.421 0 0 0-.168-.445.428.428 0 0 0-.152-.069c-.32-.093-.54.22-.6.4s.28.534.626.44zm-11.285 4.994.32.32a.395.395 0 0 0-.32-.32zm8.099-.32a1.03 1.03 0 0 0-.54-.726.375.375 0 0 0-.194 0h-.04a.558.558 0 0 0-.273.426c-.087.394 0 .607.287.667.386.113.706-.053.76-.367zm-1.827 2.247c.147-.033.407-.16.393-.287a.47.47 0 0 0-.28-.32c-.106 0-.433.114-.453.24-.02.127.273.38.34.367zm7.034-1.014h-.08a.581.581 0 0 0-.327-.293 1.107 1.107 0 0 0-1.18.613.873.873 0 0 0 .6.987.858.858 0 0 0 1.013-.54.992.992 0 0 0 0-.447.138.138 0 0 0 .054-.053.169.169 0 0 0-.01-.225.169.169 0 0 0-.07-.042zm-.667-2.34a.477.477 0 0 0-.134-.407.386.386 0 0 0-.36-.06c-.253.147-.253.38-.113.667.233.02.493.093.607-.2zm-1.28 3.9a.416.416 0 0 0 .506.194c.207-.1.214-.254 0-.667a.393.393 0 0 0-.207-.007c-.068.015-.132.049-.183.097s-.089.109-.11.176a.404.404 0 0 0-.006.207zm2.953-8.007a.596.596 0 0 0-.5 0 .572.572 0 0 0-.313-.133c-.287 0-.367.153-.387.393a2.159 2.159 0 0 1-.06.374.164.164 0 0 0-.038-.082.17.17 0 0 0-.075-.052.169.169 0 0 0-.187.047.177.177 0 0 0-.033.06c0 .093-.053.28.087.353h.06c-.167.24-.36.48-.554.727-.9-.253-1.173-.093-1.186.747a.616.616 0 0 0 .222.558.617.617 0 0 0 .591.108c.161-.053.315-.124.46-.213.247.187.473.48.86.467l.327-.314c.42.067.806-.046.873-.28a.396.396 0 0 0-.067-.386 1.207 1.207 0 0 0-.24-1.427.568.568 0 0 1-.126-.18.48.48 0 0 0 .346.1c.22-.1.174-.733-.06-.867zm2.841 1.84h.08l-.4-.4a.4.4 0 0 0 .32.4zm-.661-.726-.94-.934-.066.094a1.234 1.234 0 0 0-.074.166c-.153.387-.046.614.354.727a.777.777 0 0 0 .726-.053zm.02 1.747a.589.589 0 0 0-.447-.7.374.374 0 0 0-.403.137.367.367 0 0 0-.063.136.48.48 0 0 0 .146.593c.061.041.13.069.202.082a.545.545 0 0 0 .565-.248zm-6.319 1.586a.766.766 0 0 0 .813-.48.667.667 0 0 0-.446-.76.554.554 0 0 0-.48.107c.048-.092.081-.191.1-.294l.12-.053c.42-.207.56-.467.446-.813a.72.72 0 0 0-.358-.419.72.72 0 0 0-.548-.048 8.425 8.425 0 0 1-1.334.267c-.36 0-.773.3-.746.553.026.253.42.407.533.367.287-.1.393.08.56.2a.482.482 0 0 0 0 .093.705.705 0 0 0 .553.507.593.593 0 0 0 .5-.147c-.126.467 0 .867.287.92zm1.173-6.08c-.374-.147-.494.213-.7.453.193.32.366.567.733.447a.361.361 0 0 0 .242-.169.38.38 0 0 0 .045-.291.72.72 0 0 0-.32-.44zm-.027-2.233c.043.233.157.447.327.613a.35.35 0 0 0 .48 0l-.794-.793a.421.421 0 0 0-.013.18zm1.42 2.547a.57.57 0 0 0-.626.433.558.558 0 0 0 .275.6.554.554 0 0 0 .225.067.482.482 0 0 0 .513-.434c.053-.306-.167-.66-.387-.666zm.786-.34a.547.547 0 0 0 .14 0l-.533-.534a.47.47 0 0 0 0 .14.405.405 0 0 0 .393.394z"/> + <path fill="#0066b1" d="M134.973 107.933c-.16.367-.086.667.18.78a.913.913 0 0 0 .514-.053.977.977 0 0 0 .4.6c.4.207.92.153.973-.247a1.561 1.561 0 0 0-.293-1.013c-.14-.26-.507-.167-.374-.487.114-.255.144-.54.087-.813a.828.828 0 0 0 .633-.44.541.541 0 0 0-.293-.593c-.44-.154-.8-.074-.893.206a.664.664 0 0 0 .08.467.793.793 0 0 0-.754.453.89.89 0 0 0-.446-.42c-.347.334-.7.66-1.06.98a.663.663 0 0 0 .506.5 1.139 1.139 0 0 0 .967-.526c.019.072.053.14.1.2a.66.66 0 0 0-.327.406zm3.42-5.393c.313.666.613.766 1.186.466.434-.22.874-.426 1.334-.633.533.84.666.867 1.18.327a.671.671 0 0 0-.494-.614.316.316 0 0 0 .107-.14.35.35 0 0 0-.005-.291.347.347 0 0 0-.222-.189.315.315 0 0 0-.128-.033.329.329 0 0 0-.318.2c-.44-.747-1.034-.72-1.687.067a1.397 1.397 0 0 1-.22-.067c-.233.3-.473.593-.713.887zm5.767 13.907a.61.61 0 0 0-.107-.727 2.728 2.728 0 0 0-.42-.353.52.52 0 0 0-.04-.094v-.233a.914.914 0 0 0-.58-.833 1.062 1.062 0 0 0-.534.019 1.063 1.063 0 0 0-.459.274.564.564 0 0 0-.14.38.814.814 0 0 0 .353.667c0 .326.2.573.607.633.047.003.093.003.14 0v.107c0 .346.127.473.46.513a.666.666 0 0 0 .72-.353zm2.213-2.447c-.167 0-.587.073-.614.24-.026.167.314.393.46.473.147.08.394-.113.414-.326.02-.214-.047-.387-.26-.387zm1.147-4.5c-.094.093-.294.207-.367.373-.073.167.127.514.407.554a.41.41 0 0 0 .308-.09.419.419 0 0 0 .152-.284.557.557 0 0 0-.5-.553zm-2.127 9.04c-.085.032-.154.096-.193.178s-.044.176-.013.262c.086.34.353.38.666.394.147-.26.36-.48.127-.74a.471.471 0 0 0-.587-.094zm1.467-1.733c-.087 0-.28-.053-.347.087-.066.14.12.26.167.26a.36.36 0 0 0 .267-.12s-.04-.214-.087-.227zM145.006 112c.153.06.347 0 .327-.22s-.06-.52-.227-.553c-.167-.034-.36.333-.38.466-.02.134.16.307.28.307zm.327 2.707a.386.386 0 0 0 0-.667c-.353-.12-.573.287-.5.487a.422.422 0 0 0 .5.18zm3.5-1.061a.485.485 0 0 0-.52.3.56.56 0 0 0 .307.594.436.436 0 0 0 .513-.314.486.486 0 0 0-.3-.58zm3.793-1.693a.508.508 0 0 0 0-.174l-.513-.52c-.32-.066-.527.04-.593.32-.1.4.119.88.433.94a.7.7 0 0 0 .673-.566zm.04 2.773a.413.413 0 0 0-.52.273.464.464 0 0 0 .456.555.404.404 0 0 0 .159-.042.385.385 0 0 0 .199-.253.476.476 0 0 0-.294-.533zm-1.999 1.641a.75.75 0 0 0-.53-.226.732.732 0 0 0-.53.226.672.672 0 0 0-.103.402c.01.142.065.276.156.385a.522.522 0 0 0 .7.16c.213-.1.667-.187.707-.527s-.274-.307-.4-.42zm-.801-5.28a.445.445 0 0 0-.387-.474.668.668 0 0 0-.667.52.67.67 0 0 0 .7.454.45.45 0 0 0 .274-.182.45.45 0 0 0 .08-.318zm-4.746-.42a.828.828 0 0 0 .847-.534c.066-.26-.214-.606-.547-.666-.433-.094-.667 0-.767.346a.783.783 0 0 0 .467.854zm3.546-1.334a.46.46 0 0 0 .54-.347.501.501 0 0 0-.413-.56.483.483 0 0 0-.52.38.52.52 0 0 0 .393.527zm-9.78 7.8a.75.75 0 0 0-.627.04.43.43 0 0 0-.353-.3c-.314-.06-.447.2-.594.453.06.087.121.187.181.267l.093.087a.359.359 0 0 0 .373.06l.087-.054a.804.804 0 0 0 .113.227.213.213 0 0 0 .107.147.73.73 0 0 0 .326.246.424.424 0 0 0 .177.019.422.422 0 0 0 .169-.052.427.427 0 0 0 .214-.273.665.665 0 0 0-.266-.867zm-1.093-1.679a.667.667 0 0 0 0-.167.999.999 0 0 0 .606-.433c.174-.267 0-.567-.04-.88a.56.56 0 0 0 .107-.207.478.478 0 0 0-.087-.353c-.033-.667-.613-.86-1.173-.86a.978.978 0 0 0-.593.206 1.261 1.261 0 0 0-.16.167.912.912 0 0 0-.487-.26c-.353-.067-.593.147-.667.593a.67.67 0 0 0 .427.667.916.916 0 0 0 .473-.107.785.785 0 0 0-.1.594 1.439 1.439 0 0 0 .874.886v.04c-.04.174.346.374.506.427s.3-.08.314-.313z"/> + <path fill="#0066b1" d="M135.86 112.5a1.065 1.065 0 0 0-.294-1.26.81.81 0 0 0-1.06.194.758.758 0 0 0 .12 1.173.894.894 0 0 0 1.234-.107zm-1.067-2.347a.842.842 0 0 0 .277-1.102.845.845 0 0 0-1.07-.384c-.313.113-.367.666-.227 1.093a.826.826 0 0 0 1.02.393zm-.467 3.98a.554.554 0 0 0-.2.093l.973.974c.035-.129.053-.261.054-.394a.822.822 0 0 0-.827-.673zm-1.306-4.993a.752.752 0 0 0-.582.024.735.735 0 0 0-.385.436c-.14.36.187.88.667 1.06a.657.657 0 0 0 .52-.038.658.658 0 0 0 .333-.402c.173-.64.013-.78-.553-1.08zM144 120.36c-.247-.14-.627.067-.813.44a.516.516 0 0 0 .22.627.668.668 0 0 0 .8-.2.748.748 0 0 0-.207-.867zm-11.334-9.4a.75.75 0 0 0-.833.277.755.755 0 0 0-.127.283.912.912 0 0 0 0 .266l1.054 1.047c.226-.033.32-.207.413-.387.066-.106.118-.22.153-.34a.705.705 0 0 0 .08-.166c.1-.427-.2-.84-.74-.98zm8.427 5.746a.832.832 0 0 0-.453-1 .58.58 0 0 0-.438.016.562.562 0 0 0-.295.324.847.847 0 0 0 .18.98.811.811 0 0 0 1.006-.32zm-9.807-7.326a.493.493 0 0 0-.166.354c0 .133-.074.253 0 .386.073.134.293.094.406-.073a.74.74 0 0 0 .087-.54.222.222 0 0 0-.13-.149.217.217 0 0 0-.197.022zm10.267 9.373a.7.7 0 0 0-.813.173.797.797 0 0 0 .326.94.724.724 0 0 0 .86-.36c.154-.306.027-.56-.373-.753zm-.173-5.593a.753.753 0 0 0-.84.433.512.512 0 0 0 .333.62.67.67 0 0 0 .754-.447.447.447 0 0 0 .006-.356.45.45 0 0 0-.253-.25zm-1.02-1.54a.418.418 0 0 0 .153-.054.426.426 0 0 0 .213-.286l.1-.114c.247.18.48.394.747.094a.475.475 0 0 0 .08-.607c-.053-.073-.16-.173-.233-.167l-.24.04a.475.475 0 0 0 .104-.348.455.455 0 0 0-.057-.177.443.443 0 0 0-.121-.141.68.68 0 0 0-.24-.135.674.674 0 0 0-.74.248.836.836 0 0 0-.173.373.44.44 0 0 0-.32.14.942.942 0 0 0 .167 1.1.398.398 0 0 0 .56.034zm-1.167 2.86c-.193.174-.393.367-.26.667a.405.405 0 0 0 .392.262.409.409 0 0 0 .161-.042.553.553 0 0 0 .294-.38c0-.32-.267-.467-.587-.507z"/> + <path fill="#294492" d="M175.172 25.74c1.935-2.095 2.189-5.01.566-6.509s-4.508-1.014-6.444 1.082c-1.936 2.097-2.189 5.011-.566 6.51 1.623 1.499 4.508 1.014 6.444-1.082z"/> + <path fill="#7de2d1" d="M155.779 18.4c.8 2.207 6.88 2.633 9.067 2.72a1.41 1.41 0 0 0 1.213-.607l.267-.387a1.395 1.395 0 0 0 .133-1.333c-.853-2-3.426-7.54-5.766-7.5-2.134.04-3.827 2.62-3.827 2.62s-1.813 2.487-1.087 4.487z"/> + <path fill="#7de2d1" d="M159.213 12.053c-.246 2.327 5.034 5.38 6.96 6.407a1.43 1.43 0 0 0 1.334 0l.406-.233a1.405 1.405 0 0 0 .754-1.167c.106-2.187.22-8.28-1.907-9.273-1.933-.9-4.587.666-4.587.666s-2.74 1.48-2.96 3.6z"/> + <path fill="#42d4c6" d="M160.666 11.274a3.212 3.212 0 0 0-1.406.38 2.713 2.713 0 0 0-.074.4c-.26 2.413 5.42 5.593 7.154 6.513-.907-2.167-3.38-7.333-5.674-7.293z"/> + <path fill="#7de2d1" d="M188.206 27.78c-.8-2.2-6.88-2.667-9.066-2.713a1.4 1.4 0 0 0-1.213.606l-.261.387a1.393 1.393 0 0 0-.14 1.333c.86 2 3.427 7.547 5.774 7.507 2.133-.04 3.826-2.62 3.826-2.62s1.807-2.493 1.08-4.5z"/> + <path fill="#7de2d1" d="M184.78 34.133c.247-2.333-5.04-5.38-6.967-6.413a1.406 1.406 0 0 0-1.333 0l-.407.226a1.394 1.394 0 0 0-.74 1.174c-.113 2.186-.22 8.286 1.907 9.273 1.933.9 4.587-.667 4.587-.667s2.726-1.473 2.953-3.593z"/> + <path fill="#42d4c6" d="M177.619 27.62c.933 2.16 3.413 7.333 5.68 7.293.492-.02.974-.151 1.407-.386.031-.13.056-.261.073-.394.253-2.413-5.447-5.6-7.16-6.513z"/> + <path fill="#00bfb3" d="M57.106 44.174H.333v7.433h56.773z"/> + <path fill="#ff957d" d="M85.16 61.326H25.147v7.433H85.16z"/> + <path fill="#fa744e" d="M63.333 68.76h21.833v-7.427h-19.96a42.007 42.007 0 0 0-1.873 7.427z"/> + <path fill="#294492" d="M153.753 43.7a1.624 1.624 0 0 1-1.59-2.016 1.62 1.62 0 0 1 .503-.831c.9-.787 1.833-1.627 2.767-2.487a1.632 1.632 0 0 1 2.206 2.4c-.96.88-1.906 1.734-2.826 2.534-.293.257-.67.4-1.06.4zm8.353-7.82a1.624 1.624 0 0 1-1.503-1.007 1.62 1.62 0 0 1 .356-1.773c1.6-1.594 2.594-2.667 2.6-2.667a1.628 1.628 0 0 1 2.36 2.233c-.04.04-1.033 1.087-2.666 2.714a1.64 1.64 0 0 1-1.147.5z"/> + <path fill="#f04e98" d="M54.22 21.241 41.506 33.955a3.807 3.807 0 0 0 0 5.383L54.22 52.052a3.807 3.807 0 0 0 5.383 0l12.714-12.714a3.807 3.807 0 0 0 0-5.383L59.603 21.24a3.807 3.807 0 0 0-5.383 0z"/> + <path fill="#01ada1" d="M31.247 47.473a.621.621 0 0 0-.367-.807c-.26-.126-.573.054-.74.42a.455.455 0 0 0 .193.667.974.974 0 0 0 .914-.28zm-10.027 3.76c-.053.107.12.254.2.307a.18.18 0 0 0 .227-.08.293.293 0 0 0-.074-.287c-.053-.04-.3-.046-.353.06zm2.36-4.853c.12.053.247-.133.293-.213a.234.234 0 0 0-.093-.24.446.446 0 0 0-.273.146c-.06.074-.04.254.073.307zm2.453 3.88c-.26-.04-.52-.12-.773-.18a.38.38 0 0 0-.607.113.5.5 0 0 0 .194.667c.143.089.266.207.36.347a.732.732 0 0 0 .446.393h.287a.968.968 0 0 0 .473-.767c-.02-.246-.026-.52-.38-.573zm1.18-2.694a.301.301 0 0 0-.453.214c.006.144.035.285.086.42a.46.46 0 0 0 .594-.28c.06-.207-.074-.294-.227-.354zm5.713-.366a.46.46 0 0 0 .62-.193.775.775 0 0 0 0-.12.42.42 0 0 0 .267-.273.407.407 0 0 0-.14-.34.506.506 0 0 0-.414 0 .366.366 0 0 0-.093.113.52.52 0 0 0-.527.26.46.46 0 0 0 .287.553zm-3.54 3.734a.426.426 0 0 0-.553.2.414.414 0 0 0 .04.473h.746a.377.377 0 0 0 .074-.107.452.452 0 0 0-.307-.566zM29.2 49.02c.06-.207-.074-.287-.227-.354-.153-.066-.433 0-.453.214.004.144.034.286.086.42a.447.447 0 0 0 .594-.28zM27.186 46a.26.26 0 0 0 .094.3c.1.046.24-.054.28-.12.04-.067.046-.327-.054-.367s-.293.093-.32.187zm-3.939 1.56c-.374-.04-.48.447-.354.667s.054.173-.093.413-.22.667.26.773a.934.934 0 0 0 1.033-.666 1.086 1.086 0 0 0-.846-1.187zm20.326-.86c-.287-.667-.827-.273-.973-.447-.147-.173-.18-.633-.374-.913a.733.733 0 0 0-1.106-.253.807.807 0 0 0-.134 1.18.398.398 0 0 0-.04.073.88.88 0 0 0 .567.82.786.786 0 0 0 .4 0 .52.52 0 0 0 0 .507c.227.573.86.726 1.113.366.254-.36.074-.666.154-.753.08-.087.52-.26.393-.58zm1.527.78a.555.555 0 0 0-.18 0 .48.48 0 0 0-.313-.374.72.72 0 0 0-.747.487.394.394 0 0 0 .32.493c.1.03.207.03.307 0a.533.533 0 0 0 .2.507c.426.233.786-.127.966-.32s-.146-.707-.553-.793zm.607-2.86a.667.667 0 0 0-.754-.353c-.12 0-.233.04-.36.06l-.12-.153h-1.7c0 .04.06.08.094.113a.84.84 0 0 0 .14.247c-.134.46.133.707.5.907-.094.24-.32.466 0 .706a.42.42 0 0 0 .553 0c.3-.226.127-.486 0-.74l.353-.326.447.44c.165-.03.325-.077.48-.14a.628.628 0 0 0 .22-.234.667.667 0 0 0 .207-.233.301.301 0 0 0-.06-.294zm-10.994 2.353a.46.46 0 0 0 0-.773.627.627 0 0 0-.546 0 .52.52 0 0 0-.194.446.534.534 0 0 0 .74.327zm6.267-2.206c.087-.14-.053-.487-.24-.593h-.22a.42.42 0 0 0-.253.393c.04.247.606.367.713.2zm-2.76 2.813c-.127.36 0 .666.246.766a.913.913 0 0 0 1.014-.453.667.667 0 0 0-.454-.667.574.574 0 0 0-.806.354zm-2.82-3.406h-.553c.046.133.1.24.16.267s.326-.067.393-.267zm3.12 2.833c.44-.567.707-.133 1.153-.433a.852.852 0 0 0 .16-.187.58.58 0 0 0-.533-.907c-.323.008-.644.06-.953.154a.9.9 0 0 0-.494.94c.034.206.274.606.667.433zm.98-1.674a.32.32 0 0 0 .46-.233.6.6 0 0 0-.107-.447c-.093-.087-.533-.047-.607.14a.426.426 0 0 0 .254.54zm-7.927.507c.26-.08.293-.294.18-.787-.253-.047-.54-.193-.727.14a.433.433 0 0 0 .12.587.506.506 0 0 0 .427.06zm16.58 4.413a.447.447 0 0 0-.6.247.4.4 0 0 0 .287.513.46.46 0 0 0 .58-.293.362.362 0 0 0-.13-.405.36.36 0 0 0-.137-.062zm-3.1-.573a.248.248 0 0 0-.1 0 .867.867 0 0 0-.467-.453.707.707 0 0 0-.773.533.43.43 0 0 0 0 .16 1.44 1.44 0 0 0-.094.56c.047.127.287.4.067.554-.22.153-.56.14-.667.406l-.04.094a.38.38 0 0 0-.173-.1.667.667 0 0 0-.487.146h1.914a.606.606 0 0 1 0-.326c.073-.154.213-.3.433-.167.22.133.62.053.867-.42.246-.473-.094-1.04-.48-.987zm2.1 1.047c-.227-.06-.58.233-.627.52a.333.333 0 0 0 .16.36h.873a.266.266 0 0 0 .074-.127.74.74 0 0 0-.48-.753zm-1.693.879h1.133a.947.947 0 0 0-.326-.2.666.666 0 0 0-.807.2zm2.253-2.213c.12-.453-.114-.787-.667-.913a1.168 1.168 0 0 0-.36 0 .48.48 0 0 0-.447.227.96.96 0 0 0-.173.34.818.818 0 0 0 .74.953.834.834 0 0 0 .907-.607zm3.9.321a.267.267 0 0 0 .227-.054l-1.073-1.073a1.08 1.08 0 0 0-.274.373.6.6 0 0 0 .354.834.714.714 0 0 0 .766-.08zm-2.533 1.48a.474.474 0 0 0-.454.413h.893a.367.367 0 0 0-.44-.413zm3.493-.794c-.4-.053-.893.033-1.04.267a.194.194 0 0 0-.04.113c.113.38-.187.52-.393.727a.48.48 0 0 0-.074.1h1.247a.43.43 0 0 1 .28-.2c.067.06.133.146.2.2h.58a.76.76 0 0 0 .113-.04.254.254 0 0 0 .1-.22z"/> + <path fill="#01ada1" d="M49.18 47.413a.83.83 0 0 0-.247-.08.666.666 0 0 0 .22 0 .56.56 0 0 0 .226-.113l-1.533-1.533c-.087.186-.16.346-.233.36-.074.013-.667-.487-1.187-.28a.94.94 0 0 0-.507 1.193 1.58 1.58 0 0 0 1.48.826c.427-.113.44-.666.56-.713s.474.253.887.287a.746.746 0 0 0-.667.427.907.907 0 0 0-.073.546.726.726 0 0 0-.113.24.975.975 0 0 0 .813 1.154.94.94 0 0 0 1.147-.714.748.748 0 0 0-.107-.666.986.986 0 0 0-.667-.934zM42.14 50a.087.087 0 0 1 0-.04.5.5 0 0 0 .38-.38.46.46 0 0 0-.42-.493.433.433 0 0 0-.247 0 .833.833 0 0 0-.927.34.827.827 0 0 0-.32-.134.494.494 0 0 0-.667.294.954.954 0 0 0 .467.893.566.566 0 0 0 .667-.453c.066.058.14.108.22.146a.591.591 0 0 0 .846-.173zm-6.06 1.606h.7a.808.808 0 0 0-.2-.147.374.374 0 0 0-.5.147zm.36-2.379c.113-.293 0-.707-.213-.793a.86.86 0 0 0-.6.04.433.433 0 0 0-.26-.167.473.473 0 0 0-.514.26 1.566 1.566 0 0 0-.853-.12.668.668 0 0 1-.367-.067.586.586 0 0 0-.966.287.339.339 0 0 0-.14-.1.526.526 0 0 0-.554.38.54.54 0 0 0 .247.72.454.454 0 0 0 .573-.307c.12.096.262.16.414.187a.88.88 0 0 1 .58.26.934.934 0 0 0 .893.22.813.813 0 0 0 .62-.667v-.086a.76.76 0 0 0 .407.406c.293.114.573-.073.733-.453zm1.227-4.56a.58.58 0 0 0-1.054.053c-.117.24-.204.492-.26.753a.114.114 0 0 0-.093-.093.14.14 0 0 0-.147.113c0 .04.074.12.107.127a.14.14 0 0 0 .1-.033.92.92 0 0 0 .607.927.36.36 0 0 0 .473-.147c.27-.443.397-.957.367-1.474a1.003 1.003 0 0 0-.1-.226zm-2.054 6.539a.507.507 0 0 0-.666.287.313.313 0 0 0 0 .113h.94a.453.453 0 0 0-.274-.4zm3.433-1.346A.813.813 0 0 0 38 51.074a.42.42 0 0 0-.094.18.526.526 0 0 0 0 .353h.76v-.04a.473.473 0 0 0 0-.193.948.948 0 0 0 .667-.534.76.76 0 0 0-.287-.98zm3.407 1.513c.08-.187-.06-.4-.167-.533-.107-.134-.387 0-.473.16s0 .42.153.466c.153.047.407.087.487-.093zm-11.067-.566c-.353-.133-.746 0-.84.22a.787.787 0 0 0 .107.58h1.127a.574.574 0 0 0-.394-.8zm8.527.8h.533a.554.554 0 0 0-.2-.094.406.406 0 0 0-.333.094zm.087-2.78a.454.454 0 0 0-.267-.313c-.113 0-.433 0-.487.146-.053.147.2.367.314.434.113.066.44-.08.44-.267z"/> + <path fill="#fff" d="M58.54 39.7h-3.66l-1.22-11.393h6.1zm.406 5.083a2.237 2.237 0 1 0-4.473 0 2.237 2.237 0 0 0 4.473 0z"/> +</svg> diff --git a/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx b/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx index 7554716c5949..208b1e576c0d 100644 --- a/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx +++ b/x-pack/plugins/data_usage/public/app/components/chart_panel.tsx @@ -16,10 +16,11 @@ import { niceTimeFormatter, DARK_THEME, LIGHT_THEME, + LineSeries, } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import { LegendAction } from './legend_action'; -import { MetricTypes, MetricSeries } from '../../../common/rest_types'; +import { type MetricTypes, type MetricSeries } from '../../../common/rest_types'; import { formatBytes } from '../../utils/format_bytes'; // TODO: Remove this when we have a title for each metric type @@ -59,8 +60,23 @@ export const ChartPanel: React.FC<ChartPanelProps> = ({ [minTimestamp, maxTimestamp] ); + // Calculate the total for each time bucket + const totalSeries = useMemo(() => { + const totalsMap = new Map<number, number>(); + + series.forEach((stream) => { + stream.data.forEach((point) => { + totalsMap.set(point.x, (totalsMap.get(point.x) || 0) + point.y); + }); + }); + + return Array.from(totalsMap.entries()).map(([x, y]) => ({ x, y })); + }, [series]); const renderLegendAction = useCallback( ({ label }: { label: string }) => { + if (label === 'Total') { + return null; + } return ( <LegendAction label={label} @@ -87,6 +103,19 @@ export const ChartPanel: React.FC<ChartPanelProps> = ({ xDomain={{ min: minTimestamp, max: maxTimestamp }} legendAction={renderLegendAction} /> + <LineSeries + id="Total" + name="Total" + data={totalSeries} + xScaleType={ScaleType.Time} + yScaleType={ScaleType.Linear} + xAccessor="x" + yAccessors={['y']} + lineSeriesStyle={{ + line: { strokeWidth: 2 }, + point: { visible: 'always', radius: 5 }, + }} + /> {series.map((stream, streamIdx) => ( <BarSeries key={streamIdx} diff --git a/x-pack/plugins/data_usage/public/app/components/charts.tsx b/x-pack/plugins/data_usage/public/app/components/charts.tsx index 56857e7a63ff..271cfe432402 100644 --- a/x-pack/plugins/data_usage/public/app/components/charts.tsx +++ b/x-pack/plugins/data_usage/public/app/components/charts.tsx @@ -6,9 +6,8 @@ */ import React, { useCallback, useState } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; -import { MetricTypes } from '../../../common/rest_types'; import { ChartPanel } from './chart_panel'; -import { UsageMetricsResponseSchemaBody } from '../../../common/rest_types'; +import type { UsageMetricsResponseSchemaBody, MetricTypes } from '../../../common/rest_types'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; interface ChartsProps { data: UsageMetricsResponseSchemaBody; @@ -24,7 +23,7 @@ export const Charts: React.FC<ChartsProps> = ({ data, 'data-test-subj': dataTest return ( <EuiFlexGroup direction="column" data-test-subj={getTestId('charts')}> - {Object.entries(data.metrics).map(([metricType, series], idx) => ( + {Object.entries(data).map(([metricType, series], idx) => ( <ChartPanel key={metricType} metricType={metricType as MetricTypes} diff --git a/x-pack/plugins/data_usage/public/app/components/charts_loading.tsx b/x-pack/plugins/data_usage/public/app/components/charts_loading.tsx new file mode 100644 index 000000000000..08c37934c451 --- /dev/null +++ b/x-pack/plugins/data_usage/public/app/components/charts_loading.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiLoadingChart } from '@elastic/eui'; +import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; + +export const ChartsLoading = ({ + 'data-test-subj': dataTestSubj, +}: { + 'data-test-subj'?: string; +}) => { + const getTestId = useTestIdGenerator(dataTestSubj); + // returns 2 loading icons for the two charts + return ( + <EuiFlexGroup + direction="column" + alignItems="center" + data-test-subj={getTestId('charts-loading')} + > + {[...Array(2)].map((i) => ( + <EuiFlexItem key={i}> + <EuiPanel paddingSize="xl" hasShadow={false} hasBorder={false}> + <EuiLoadingChart size="l" /> + </EuiPanel> + </EuiFlexItem> + ))} + </EuiFlexGroup> + ); +}; + +ChartsLoading.displayName = 'ChartsLoading'; diff --git a/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.test.tsx b/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.test.tsx index 72814a0f09c5..91e2fd5ddafa 100644 --- a/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.test.tsx +++ b/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.test.tsx @@ -5,7 +5,8 @@ * 2.0. */ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { TestProvider } from '../../../common/test_utils'; +import { render, waitFor, within, type RenderResult } from '@testing-library/react'; import userEvent, { type UserEvent } from '@testing-library/user-event'; import { DataUsageMetrics } from './data_usage_metrics'; import { useGetDataUsageMetrics } from '../../hooks/use_get_usage_metrics'; @@ -102,21 +103,6 @@ jest.mock('@kbn/kibana-react-plugin/public', () => { to: 'now', display: 'Last 7 days', }, - { - from: 'now-30d', - to: 'now', - display: 'Last 30 days', - }, - { - from: 'now-90d', - to: 'now', - display: 'Last 90 days', - }, - { - from: 'now-1y', - to: 'now', - display: 'Last 1 year', - }, ], }; return x[k]; @@ -156,6 +142,7 @@ describe('DataUsageMetrics', () => { let user: UserEvent; const testId = 'test'; const testIdFilter = `${testId}-filter`; + let renderComponent: () => RenderResult; beforeAll(() => { jest.useFakeTimers(); @@ -167,18 +154,24 @@ describe('DataUsageMetrics', () => { beforeEach(() => { jest.clearAllMocks(); + renderComponent = () => + render( + <TestProvider> + <DataUsageMetrics data-test-subj={testId} /> + </TestProvider> + ); user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime, pointerEventsCheck: 0 }); mockUseGetDataUsageMetrics.mockReturnValue(getBaseMockedDataUsageMetrics); mockUseGetDataUsageDataStreams.mockReturnValue(getBaseMockedDataStreams); }); it('renders', () => { - const { getByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId } = renderComponent(); expect(getByTestId(`${testId}`)).toBeTruthy(); }); it('should show date filter', () => { - const { getByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId } = renderComponent(); const dateFilter = getByTestId(`${testIdFilter}-date-range`); expect(dateFilter).toBeTruthy(); expect(dateFilter.textContent).toContain('to'); @@ -190,12 +183,12 @@ describe('DataUsageMetrics', () => { ...getBaseMockedDataStreams, isFetching: true, }); - const { queryByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { queryByTestId } = renderComponent(); expect(queryByTestId(`${testIdFilter}-dataStreams-popoverButton`)).not.toBeTruthy(); }); it('should show data streams filter', () => { - const { getByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId } = renderComponent(); expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toBeTruthy(); }); @@ -205,7 +198,7 @@ describe('DataUsageMetrics', () => { data: generateDataStreams(5), isFetching: false, }); - const { getByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId } = renderComponent(); expect(getByTestId(`${testIdFilter}-dataStreams-popoverButton`)).toHaveTextContent( 'Data streams5' ); @@ -217,29 +210,76 @@ describe('DataUsageMetrics', () => { data: generateDataStreams(100), isFetching: false, }); - const { getByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId } = renderComponent(); const toggleFilterButton = getByTestId(`${testIdFilter}-dataStreams-popoverButton`); expect(toggleFilterButton).toHaveTextContent('Data streams50'); }); - it('should allow de-selecting all but one data stream option', async () => { + it('should allow de-selecting data stream options', async () => { mockUseGetDataUsageDataStreams.mockReturnValue({ error: undefined, - data: generateDataStreams(5), + data: generateDataStreams(10), isFetching: false, }); - const { getByTestId, getAllByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId, getAllByTestId } = renderComponent(); const toggleFilterButton = getByTestId(`${testIdFilter}-dataStreams-popoverButton`); - expect(toggleFilterButton).toHaveTextContent('Data streams5'); + expect(toggleFilterButton).toHaveTextContent('Data streams10'); await user.click(toggleFilterButton); const allFilterOptions = getAllByTestId('dataStreams-filter-option'); - for (let i = 0; i < allFilterOptions.length - 1; i++) { + // deselect 9 options + for (let i = 0; i < allFilterOptions.length; i++) { await user.click(allFilterOptions[i]); } expect(toggleFilterButton).toHaveTextContent('Data streams1'); + expect(within(toggleFilterButton).getByRole('marquee').getAttribute('aria-label')).toEqual( + '1 active filters' + ); + }); + + it('should allow selecting/deselecting all data stream options using `select all` and `clear all`', async () => { + mockUseGetDataUsageDataStreams.mockReturnValue({ + error: undefined, + data: generateDataStreams(10), + isFetching: false, + }); + const { getByTestId } = renderComponent(); + const toggleFilterButton = getByTestId(`${testIdFilter}-dataStreams-popoverButton`); + + expect(toggleFilterButton).toHaveTextContent('Data streams10'); + await user.click(toggleFilterButton); + + // all options are selected on load + expect(within(toggleFilterButton).getByRole('marquee').getAttribute('aria-label')).toEqual( + '10 active filters' + ); + + const selectAllButton = getByTestId(`${testIdFilter}-dataStreams-selectAllButton`); + const clearAllButton = getByTestId(`${testIdFilter}-dataStreams-clearAllButton`); + + // select all is disabled + expect(selectAllButton).toBeTruthy(); + expect(selectAllButton.getAttribute('disabled')).not.toBeNull(); + + // clear all is enabled + expect(clearAllButton).toBeTruthy(); + expect(clearAllButton.getAttribute('disabled')).toBeNull(); + // click clear all and expect all options to be deselected + await user.click(clearAllButton); + expect(within(toggleFilterButton).getByRole('marquee').getAttribute('aria-label')).toEqual( + '10 available filters' + ); + // select all is enabled again + expect(await selectAllButton.getAttribute('disabled')).toBeNull(); + // click select all + await user.click(selectAllButton); + + // all options are selected and clear all is disabled + expect(within(toggleFilterButton).getByRole('marquee').getAttribute('aria-label')).toEqual( + '10 active filters' + ); }); it('should not call usage metrics API if no data streams', async () => { @@ -247,7 +287,7 @@ describe('DataUsageMetrics', () => { ...getBaseMockedDataStreams, data: [], }); - render(<DataUsageMetrics data-test-subj={testId} />); + renderComponent(); expect(mockUseGetDataUsageMetrics).toHaveBeenCalledWith( expect.any(Object), expect.objectContaining({ enabled: false }) @@ -259,7 +299,7 @@ describe('DataUsageMetrics', () => { ...getBaseMockedDataUsageMetrics, isFetching: true, }); - const { getByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId } = renderComponent(); expect(getByTestId(`${testId}-charts-loading`)).toBeTruthy(); }); @@ -268,34 +308,41 @@ describe('DataUsageMetrics', () => { ...getBaseMockedDataUsageMetrics, isFetched: true, data: { - metrics: { - ingest_rate: [ - { - name: '.ds-1', - data: [{ x: new Date(), y: 1000 }], - }, - { - name: '.ds-10', - data: [{ x: new Date(), y: 1100 }], - }, - ], - storage_retained: [ - { - name: '.ds-2', - data: [{ x: new Date(), y: 2000 }], - }, - { - name: '.ds-20', - data: [{ x: new Date(), y: 2100 }], - }, - ], - }, + ingest_rate: [ + { + name: '.ds-1', + data: [{ x: new Date(), y: 1000 }], + }, + { + name: '.ds-10', + data: [{ x: new Date(), y: 1100 }], + }, + ], + storage_retained: [ + { + name: '.ds-2', + data: [{ x: new Date(), y: 2000 }], + }, + { + name: '.ds-20', + data: [{ x: new Date(), y: 2100 }], + }, + ], }, }); - const { getByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId } = renderComponent(); expect(getByTestId(`${testId}-charts`)).toBeTruthy(); }); + it('should show no charts callout', () => { + mockUseGetDataUsageMetrics.mockReturnValue({ + ...getBaseMockedDataUsageMetrics, + isFetched: false, + }); + const { getByTestId } = renderComponent(); + expect(getByTestId(`${testId}-no-charts-callout`)).toBeTruthy(); + }); + it('should refetch usage metrics with `Refresh` button click', async () => { const refetch = jest.fn(); mockUseGetDataUsageMetrics.mockReturnValue({ @@ -308,7 +355,7 @@ describe('DataUsageMetrics', () => { isFetched: true, refetch, }); - const { getByTestId } = render(<DataUsageMetrics data-test-subj={testId} />); + const { getByTestId } = renderComponent(); const refreshButton = getByTestId(`${testIdFilter}-super-refresh-button`); // click refresh 5 times for (let i = 0; i < 5; i++) { @@ -328,7 +375,7 @@ describe('DataUsageMetrics', () => { isFetched: true, error: new Error('Uh oh!'), }); - render(<DataUsageMetrics data-test-subj={testId} />); + renderComponent(); await waitFor(() => { expect(mockServices.notifications.toasts.addDanger).toHaveBeenCalledWith({ title: 'Error getting usage metrics', @@ -343,7 +390,7 @@ describe('DataUsageMetrics', () => { isFetched: true, error: new Error('Uh oh!'), }); - render(<DataUsageMetrics data-test-subj={testId} />); + renderComponent(); await waitFor(() => { expect(mockServices.notifications.toasts.addDanger).toHaveBeenCalledWith({ title: 'Error getting data streams', diff --git a/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx b/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx index 8bedde117785..d7d6417cf144 100644 --- a/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx +++ b/x-pack/plugins/data_usage/public/app/components/data_usage_metrics.tsx @@ -7,18 +7,20 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { css } from '@emotion/react'; -import { EuiFlexGroup, EuiFlexItem, EuiLoadingElastic } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Charts } from './charts'; import { useBreadcrumbs } from '../../utils/use_breadcrumbs'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; -import { PLUGIN_NAME } from '../../../common'; +import { DEFAULT_METRIC_TYPES, type UsageMetricsRequestBody } from '../../../common/rest_types'; +import { PLUGIN_NAME } from '../../translations'; import { useGetDataUsageMetrics } from '../../hooks/use_get_usage_metrics'; import { useGetDataUsageDataStreams } from '../../hooks/use_get_data_streams'; import { useDataUsageMetricsUrlParams } from '../hooks/use_charts_url_params'; import { DEFAULT_DATE_RANGE_OPTIONS, useDateRangePicker } from '../hooks/use_date_picker'; -import { DEFAULT_METRIC_TYPES, UsageMetricsRequestBody } from '../../../common/rest_types'; import { ChartFilters, ChartFiltersProps } from './filters/charts_filters'; +import { ChartsLoading } from './charts_loading'; +import { NoDataCallout } from './no_data_callout'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; const EuiItemCss = css` @@ -33,6 +35,8 @@ export const DataUsageMetrics = memo( ({ 'data-test-subj': dataTestSubj = 'data-usage-metrics' }: { 'data-test-subj'?: string }) => { const getTestId = useTestIdGenerator(dataTestSubj); + const [isFirstPageLoad, setIsFirstPageLoad] = useState(true); + const { services: { chrome, appParams, notifications }, } = useKibanaContextForPlugin(); @@ -68,10 +72,10 @@ export const DataUsageMetrics = memo( }); useEffect(() => { - if (!metricTypesFromUrl) { + if (!metricTypesFromUrl && isFirstPageLoad) { setUrlMetricTypesFilter(metricsFilters.metricTypes.join(',')); } - if (!dataStreamsFromUrl && dataStreams) { + if (!dataStreamsFromUrl && dataStreams && isFirstPageLoad) { const hasMoreThan50 = dataStreams.length > 50; const _dataStreams = hasMoreThan50 ? dataStreams.slice(0, 50) : dataStreams; setUrlDataStreamsFilter(_dataStreams.map((ds) => ds.name).join(',')); @@ -83,6 +87,7 @@ export const DataUsageMetrics = memo( dataStreams, dataStreamsFromUrl, endDateFromUrl, + isFirstPageLoad, metricTypesFromUrl, metricsFilters.dataStreams, metricsFilters.from, @@ -106,9 +111,9 @@ export const DataUsageMetrics = memo( const { error: errorFetchingDataUsageMetrics, - data, + data: usageMetricsData, isFetching, - isFetched, + isFetched: hasFetchedDataUsageMetricsData, refetch: refetchDataUsageMetrics, } = useGetDataUsageMetrics( { @@ -118,10 +123,16 @@ export const DataUsageMetrics = memo( }, { retry: false, - enabled: !!metricsFilters.dataStreams.length, + enabled: !!(metricsFilters.dataStreams.length && metricsFilters.metricTypes.length), } ); + useEffect(() => { + if (!isFetching && hasFetchedDataUsageMetricsData) { + setIsFirstPageLoad(false); + } + }, [isFetching, hasFetchedDataUsageMetricsData]); + const onRefresh = useCallback(() => { refetchDataUsageMetrics(); }, [refetchDataUsageMetrics]); @@ -204,13 +215,14 @@ export const DataUsageMetrics = memo( data-test-subj={getTestId('filter')} /> </FlexItemWithCss> - <FlexItemWithCss> - {isFetched && data?.metrics ? ( - <Charts data={data} data-test-subj={dataTestSubj} /> + {hasFetchedDataUsageMetricsData && usageMetricsData ? ( + <Charts data={usageMetricsData} data-test-subj={dataTestSubj} /> ) : isFetching ? ( - <EuiLoadingElastic data-test-subj={getTestId('charts-loading')} /> - ) : null} + <ChartsLoading data-test-subj={dataTestSubj} /> + ) : ( + <NoDataCallout data-test-subj={dataTestSubj} /> + )} </FlexItemWithCss> </EuiFlexGroup> ); diff --git a/x-pack/plugins/data_usage/public/app/components/dataset_quality_link.tsx b/x-pack/plugins/data_usage/public/app/components/dataset_quality_link.tsx index d6627f3d8dca..8e81e6091156 100644 --- a/x-pack/plugins/data_usage/public/app/components/dataset_quality_link.tsx +++ b/x-pack/plugins/data_usage/public/app/components/dataset_quality_link.tsx @@ -6,13 +6,14 @@ */ import React from 'react'; -import { EuiListGroupItem } from '@elastic/eui'; import { DataQualityDetailsLocatorParams, DATA_QUALITY_DETAILS_LOCATOR_ID, } from '@kbn/deeplinks-observability'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; import { useDateRangePicker } from '../hooks/use_date_picker'; +import { LegendActionItem } from './legend_action_item'; +import { UX_LABELS } from '../../translations'; interface DatasetQualityLinkProps { dataStreamName: string; @@ -39,6 +40,8 @@ export const DatasetQualityLink: React.FC<DatasetQualityLinkProps> = React.memo( await locator.navigate(locatorParams); } }; - return <EuiListGroupItem label="View data quality" onClick={onClickDataQuality} />; + return ( + <LegendActionItem label={UX_LABELS.dataQualityPopup.view} onClick={onClickDataQuality} /> + ); } ); diff --git a/x-pack/plugins/data_usage/public/app/components/filters/charts_filter.tsx b/x-pack/plugins/data_usage/public/app/components/filters/charts_filter.tsx index 6b4806537e74..fcff6fc13f26 100644 --- a/x-pack/plugins/data_usage/public/app/components/filters/charts_filter.tsx +++ b/x-pack/plugins/data_usage/public/app/components/filters/charts_filter.tsx @@ -5,18 +5,15 @@ * 2.0. */ -import { orderBy } from 'lodash/fp'; +import { orderBy, findKey } from 'lodash/fp'; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { EuiPopoverTitle, EuiSelectable } from '@elastic/eui'; +import { EuiPopoverTitle, EuiSelectable, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; -import { - METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP, - type MetricTypes, -} from '../../../../common/rest_types'; - -import { UX_LABELS } from '../../translations'; +import { METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP } from '../../../../common/rest_types'; +import { UX_LABELS } from '../../../translations'; import { ChartsFilterPopover } from './charts_filter_popover'; +import { ToggleAllButton } from './toggle_all_button'; import { FilterItems, FilterName, useChartsFilter } from '../../hooks'; const getSearchPlaceholder = (filterName: FilterName) => { @@ -84,6 +81,11 @@ export const ChartsFilter = memo<ChartsFilterProps>( }, }); + const addHeightToPopover = useMemo( + () => isDataStreamsFilter && numFilters + numActiveFilters > 15, + [isDataStreamsFilter, numFilters, numActiveFilters] + ); + // track popover state to pin selected options const wasPopoverOpen = useRef(isPopoverOpen); @@ -102,7 +104,7 @@ export const ChartsFilter = memo<ChartsFilterProps>( ); // augmented options based on the dataStreams filter - const sortedHostsFilterOptions = useMemo(() => { + const sortedDataStreamsFilterOptions = useMemo(() => { if (shouldPinSelectedDataStreams() || areDataStreamsSelectedOnMount) { // pin checked items to the top return orderBy('checked', 'asc', items); @@ -116,12 +118,6 @@ export const ChartsFilter = memo<ChartsFilterProps>( const onOptionsChange = useCallback( (newOptions: FilterItems) => { const optionItemsToSet = newOptions.map((option) => option); - const currChecks = optionItemsToSet.filter((option) => option.checked === 'on'); - - // don't update filter state if trying to uncheck all options - if (currChecks.length < 1) { - return; - } // update filter UI options state setItems(optionItemsToSet); @@ -136,13 +132,9 @@ export const ChartsFilter = memo<ChartsFilterProps>( // update URL params if (isMetricsFilter) { - setUrlMetricTypesFilter( - selectedItems - .map((item) => METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP[item as MetricTypes]) - .join() - ); + setUrlMetricTypesFilter(selectedItems.join(',')); } else if (isDataStreamsFilter) { - setUrlDataStreamsFilter(selectedItems.join()); + setUrlDataStreamsFilter(selectedItems.join(',')); } // reset shouldPinSelectedDataStreams, setAreDataStreamsSelectedOnMount shouldPinSelectedDataStreams(false); @@ -162,6 +154,63 @@ export const ChartsFilter = memo<ChartsFilterProps>( ] ); + const onSelectAll = useCallback(() => { + const allItems: FilterItems = items.map((item) => { + return { + ...item, + checked: 'on', + }; + }); + setItems(allItems); + const optionsToSelect = allItems.map((i) => i.label); + onChangeFilterOptions(optionsToSelect); + + if (isDataStreamsFilter) { + setUrlDataStreamsFilter(optionsToSelect.join(',')); + } + if (isMetricsFilter) { + setUrlMetricTypesFilter( + optionsToSelect + .map((option) => findKey(METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP, option)) + .join(',') + ); + } + }, [ + items, + isDataStreamsFilter, + isMetricsFilter, + setItems, + onChangeFilterOptions, + setUrlDataStreamsFilter, + setUrlMetricTypesFilter, + ]); + + const onClearAll = useCallback(() => { + setItems( + items.map((item) => { + return { + ...item, + checked: undefined, + }; + }) + ); + onChangeFilterOptions([]); + if (isDataStreamsFilter) { + setUrlDataStreamsFilter(''); + } + if (isMetricsFilter) { + setUrlMetricTypesFilter(''); + } + }, [ + items, + isDataStreamsFilter, + isMetricsFilter, + setItems, + onChangeFilterOptions, + setUrlDataStreamsFilter, + setUrlMetricTypesFilter, + ]); + useEffect(() => { return () => { wasPopoverOpen.current = isPopoverOpen; @@ -182,9 +231,10 @@ export const ChartsFilter = memo<ChartsFilterProps>( <EuiSelectable aria-label={`${filterName}`} emptyMessage={UX_LABELS.filterEmptyMessage(filterName)} + height={addHeightToPopover ? 380 : undefined} isLoading={isFilterLoading} onChange={onOptionsChange} - options={sortedHostsFilterOptions} + options={sortedDataStreamsFilterOptions} searchable={isSearchable ? true : undefined} searchProps={{ placeholder: getSearchPlaceholder(filterName), @@ -203,6 +253,28 @@ export const ChartsFilter = memo<ChartsFilterProps>( </EuiPopoverTitle> )} {list} + <EuiFlexGroup gutterSize="none"> + <EuiFlexItem grow={1}> + <ToggleAllButton + color="primary" + data-test-subj={getTestId(`${filterName}-selectAllButton`)} + icon="check" + label={UX_LABELS.filterSelectAll} + isDisabled={hasActiveFilters && numFilters === 0} + onClick={onSelectAll} + /> + </EuiFlexItem> + <EuiFlexItem grow={1}> + <ToggleAllButton + color="danger" + data-test-subj={getTestId(`${filterName}-clearAllButton`)} + icon="cross" + label={UX_LABELS.filterClearAll} + isDisabled={!hasActiveFilters} + onClick={onClearAll} + /> + </EuiFlexItem> + </EuiFlexGroup> </div> ); }} diff --git a/x-pack/plugins/data_usage/public/app/components/filters/charts_filter_popover.tsx b/x-pack/plugins/data_usage/public/app/components/filters/charts_filter_popover.tsx index 3c0237c84a0c..a2f4585e592c 100644 --- a/x-pack/plugins/data_usage/public/app/components/filters/charts_filter_popover.tsx +++ b/x-pack/plugins/data_usage/public/app/components/filters/charts_filter_popover.tsx @@ -9,7 +9,7 @@ import React, { memo, useMemo } from 'react'; import { EuiFilterButton, EuiPopover, useGeneratedHtmlId } from '@elastic/eui'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; import { type FilterName } from '../../hooks/use_charts_filter'; -import { FILTER_NAMES } from '../../translations'; +import { FILTER_NAMES } from '../../../translations'; export const ChartsFilterPopover = memo( ({ diff --git a/x-pack/plugins/data_usage/public/app/components/filters/date_picker.tsx b/x-pack/plugins/data_usage/public/app/components/filters/date_picker.tsx index 62c6cc542a52..81ab435670f8 100644 --- a/x-pack/plugins/data_usage/public/app/components/filters/date_picker.tsx +++ b/x-pack/plugins/data_usage/public/app/components/filters/date_picker.tsx @@ -15,8 +15,9 @@ import type { OnRefreshChangeProps, } from '@elastic/eui/src/components/date_picker/types'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import moment from 'moment'; +import { momentDateParser } from '../../../../common/utils'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { DEFAULT_DATE_RANGE_OPTIONS } from '../../hooks/use_date_picker'; export interface DateRangePickerValues { autoRefreshOptions: { @@ -50,16 +51,23 @@ export const UsageMetricsDateRangePicker = memo<UsageMetricsDateRangePickerProps const kibana = useKibana<IUnifiedSearchPluginServices>(); const { uiSettings } = kibana.services; const [commonlyUsedRanges] = useState(() => { - return ( - uiSettings - ?.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES) - ?.map(({ from, to, display }: { from: string; to: string; display: string }) => { - return { + const _commonlyUsedRanges: Array<{ from: string; to: string; display: string }> = + uiSettings.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES); + if (!_commonlyUsedRanges) { + return []; + } + return _commonlyUsedRanges.reduce<DurationRange[]>( + (acc, { from, to, display }: { from: string; to: string; display: string }) => { + if (!['now-30d/d', 'now-90d/d', 'now-1y/d'].includes(from)) { + acc.push({ start: from, end: to, label: display, - }; - }) ?? [] + }); + } + return acc; + }, + [] ); }); @@ -80,9 +88,9 @@ export const UsageMetricsDateRangePicker = memo<UsageMetricsDateRangePickerProps showUpdateButton={false} timeFormat={'HH:mm'} updateButtonProps={{ iconOnly: false, fill: false }} - utcOffset={moment().utcOffset() / 60} - maxDate={moment()} - minDate={moment().subtract(9, 'days').startOf('day')} + utcOffset={0} + maxDate={momentDateParser(DEFAULT_DATE_RANGE_OPTIONS.maxDate)} + minDate={momentDateParser(DEFAULT_DATE_RANGE_OPTIONS.minDate)} width="auto" /> ); diff --git a/x-pack/plugins/data_usage/public/app/components/filters/toggle_all_button.tsx b/x-pack/plugins/data_usage/public/app/components/filters/toggle_all_button.tsx new file mode 100644 index 000000000000..3d1c4080fcc9 --- /dev/null +++ b/x-pack/plugins/data_usage/public/app/components/filters/toggle_all_button.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import React, { memo } from 'react'; +import { EuiButtonEmpty, EuiButtonEmptyProps } from '@elastic/eui'; + +const EuiButtonEmptyCss = css` + border-top: ${euiThemeVars.euiBorderThin}; + border-radius: 0; +`; + +interface ToggleAllButtonProps { + 'data-test-subj'?: string; + color: EuiButtonEmptyProps['color']; + icon: EuiButtonEmptyProps['iconType']; + isDisabled: boolean; + onClick: () => void; + label: string; +} + +export const ToggleAllButton = memo<ToggleAllButtonProps>( + ({ color, 'data-test-subj': dataTestSubj, icon, isDisabled, label, onClick }) => { + // const getTestId = useTestIdGenerator(dataTestSubj); + return ( + <EuiButtonEmpty + color={color} + css={EuiButtonEmptyCss} + data-test-subj={dataTestSubj} + iconType={icon} + isDisabled={isDisabled} + onClick={onClick} + > + {label} + </EuiButtonEmpty> + ); + } +); + +ToggleAllButton.displayName = 'ToggleAllButton'; diff --git a/x-pack/plugins/data_usage/public/app/components/legend_action.tsx b/x-pack/plugins/data_usage/public/app/components/legend_action.tsx index c9059037c444..b748b7716324 100644 --- a/x-pack/plugins/data_usage/public/app/components/legend_action.tsx +++ b/x-pack/plugins/data_usage/public/app/components/legend_action.tsx @@ -5,18 +5,12 @@ * 2.0. */ import React, { useCallback } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiButtonIcon, - EuiPopover, - EuiListGroup, - EuiListGroupItem, - EuiSpacer, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiPopover, EuiListGroup } from '@elastic/eui'; import { IndexManagementLocatorParams } from '@kbn/index-management-shared-types'; import { DatasetQualityLink } from './dataset_quality_link'; import { useKibanaContextForPlugin } from '../../utils/use_kibana'; +import { LegendActionItem } from './legend_action_item'; +import { UX_LABELS } from '../../translations'; interface LegendActionProps { idx: number; @@ -63,7 +57,7 @@ export const LegendAction: React.FC<LegendActionProps> = React.memo( <EuiFlexItem grow={false}> <EuiButtonIcon iconType="boxesHorizontal" - aria-label="Open data stream actions" + aria-label={UX_LABELS.dataQualityPopup.open} onClick={() => togglePopover(uniqueStreamName)} /> </EuiFlexItem> @@ -74,11 +68,15 @@ export const LegendAction: React.FC<LegendActionProps> = React.memo( anchorPosition="downRight" > <EuiListGroup gutterSize="none"> - <EuiListGroupItem label="Copy data stream name" onClick={onCopyDataStreamName} /> - <EuiSpacer size="s" /> - + <LegendActionItem + label={UX_LABELS.dataQualityPopup.copy} + onClick={onCopyDataStreamName} + /> {hasIndexManagementFeature && ( - <EuiListGroupItem label="Manage data stream" onClick={onClickIndexManagement} /> + <LegendActionItem + label={UX_LABELS.dataQualityPopup.manage} + onClick={onClickIndexManagement} + /> )} {hasDataSetQualityFeature && <DatasetQualityLink dataStreamName={label} />} </EuiListGroup> diff --git a/x-pack/plugins/data_usage/public/app/components/legend_action_item.tsx b/x-pack/plugins/data_usage/public/app/components/legend_action_item.tsx new file mode 100644 index 000000000000..3b4f0d9f698f --- /dev/null +++ b/x-pack/plugins/data_usage/public/app/components/legend_action_item.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { EuiListGroupItem } from '@elastic/eui'; + +export const LegendActionItem = memo( + ({ label, onClick }: { label: string; onClick: () => Promise<void> | void }) => ( + <EuiListGroupItem label={label} onClick={onClick} size="s" /> + ) +); + +LegendActionItem.displayName = 'LegendActionItem'; diff --git a/x-pack/plugins/data_usage/public/app/components/no_data_callout.tsx b/x-pack/plugins/data_usage/public/app/components/no_data_callout.tsx new file mode 100644 index 000000000000..c8c06db35106 --- /dev/null +++ b/x-pack/plugins/data_usage/public/app/components/no_data_callout.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiImage, EuiText, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import icon from './assets/illustration_product_no_results_magnifying_glass.svg'; +import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; + +export const NoDataCallout = ({ + 'data-test-subj': dateTestSubj, +}: { + 'data-test-subj'?: string; +}) => { + const getTestId = useTestIdGenerator(dateTestSubj); + + return ( + <EuiFlexGroup + style={{ height: 490 }} + alignItems="center" + justifyContent="center" + data-test-subj={getTestId('no-charts-callout')} + > + <EuiFlexItem grow={false}> + <EuiPanel hasBorder={true} style={{ maxWidth: 500 }}> + <EuiFlexGroup> + <EuiFlexItem> + <EuiText size="s"> + <EuiTitle> + <h3> + <FormattedMessage + id="xpack.dataUsage.noCharts.title" + defaultMessage="No chart data without data streams" + /> + </h3> + </EuiTitle> + <p> + <FormattedMessage + id="xpack.dataUsage.noCharts.description" + defaultMessage="Try searching with at least one data stream." + /> + </p> + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiImage style={{ width: 200, height: 148 }} size="200" alt="" url={icon} /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPanel> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +NoDataCallout.displayName = 'NoDataCallout'; diff --git a/x-pack/plugins/data_usage/public/app/data_usage_metrics_page.tsx b/x-pack/plugins/data_usage/public/app/data_usage_metrics_page.tsx index 69edb7a7f01c..adc53e12b574 100644 --- a/x-pack/plugins/data_usage/public/app/data_usage_metrics_page.tsx +++ b/x-pack/plugins/data_usage/public/app/data_usage_metrics_page.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { DataUsagePage } from './components/page'; -import { DATA_USAGE_PAGE } from './translations'; +import { DATA_USAGE_PAGE } from '../translations'; import { DataUsageMetrics } from './components/data_usage_metrics'; export const DataUsageMetricsPage = () => { diff --git a/x-pack/plugins/data_usage/public/app/hooks/use_charts_filter.tsx b/x-pack/plugins/data_usage/public/app/hooks/use_charts_filter.tsx index d2c5dc554ff2..012a6027aadb 100644 --- a/x-pack/plugins/data_usage/public/app/hooks/use_charts_filter.tsx +++ b/x-pack/plugins/data_usage/public/app/hooks/use_charts_filter.tsx @@ -6,13 +6,13 @@ */ import { useState, useEffect, useMemo } from 'react'; +import { DEFAULT_SELECTED_OPTIONS } from '../../../common'; import { - isDefaultMetricType, - METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP, METRIC_TYPE_VALUES, + METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP, + isDefaultMetricType, } from '../../../common/rest_types'; -import { DEFAULT_SELECTED_OPTIONS } from '../../../common'; -import { FILTER_NAMES } from '../translations'; +import { FILTER_NAMES } from '../../translations'; import { useDataUsageMetricsUrlParams } from './use_charts_url_params'; import { formatBytes } from '../../utils/format_bytes'; import { ChartsFilterProps } from '../components/filters/charts_filter'; @@ -48,6 +48,7 @@ export const useChartsFilter = ({ } => { const { dataStreams: selectedDataStreamsFromUrl, + metricTypes: selectedMetricTypesFromUrl, setUrlMetricTypesFilter, setUrlDataStreamsFilter, } = useDataUsageMetricsUrlParams(); @@ -73,8 +74,13 @@ export const useChartsFilter = ({ ? METRIC_TYPE_VALUES.map((metricType) => ({ key: metricType, label: METRIC_TYPE_API_VALUES_TO_UI_OPTIONS_MAP[metricType], - checked: isDefaultMetricType(metricType) ? 'on' : undefined, // default metrics are selected by default - disabled: isDefaultMetricType(metricType), + checked: selectedMetricTypesFromUrl + ? selectedMetricTypesFromUrl.includes(metricType) + ? 'on' + : undefined + : isDefaultMetricType(metricType) // default metrics are selected by default + ? 'on' + : undefined, 'data-test-subj': `${filterOptions.filterName}-filter-option`, })) : isDataStreamsFilter && !!filterOptions.options.length diff --git a/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.test.tsx b/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.test.tsx index 2b009f05f3bb..20f091029f5b 100644 --- a/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.test.tsx +++ b/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import moment from 'moment'; -import { METRIC_TYPE_VALUES, MetricTypes } from '../../../common/rest_types'; +import { METRIC_TYPE_VALUES, type MetricTypes } from '../../../common/rest_types'; import { getDataUsageMetricsFiltersFromUrlParams } from './use_charts_url_params'; describe('#getDataUsageMetricsFiltersFromUrlParams', () => { @@ -57,12 +56,12 @@ describe('#getDataUsageMetricsFiltersFromUrlParams', () => { it('should use given relative startDate and endDate values URL params', () => { expect( getDataUsageMetricsFiltersFromUrlParams({ - startDate: moment().subtract(24, 'hours').toISOString(), - endDate: moment().toISOString(), + startDate: 'now-9d', + endDate: 'now-24h/h', }) ).toEqual({ - endDate: moment().toISOString(), - startDate: moment().subtract(24, 'hours').toISOString(), + endDate: 'now-24h/h', + startDate: 'now-9d', }); }); diff --git a/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.tsx b/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.tsx index ed833393ad7e..3a1ba7dc1de6 100644 --- a/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.tsx +++ b/x-pack/plugins/data_usage/public/app/hooks/use_charts_url_params.tsx @@ -6,7 +6,7 @@ */ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; -import { MetricTypes, isMetricType } from '../../../common/rest_types'; +import { type MetricTypes, isMetricType } from '../../../common/rest_types'; import { useUrlParams } from '../../hooks/use_url_params'; import { DEFAULT_DATE_RANGE_OPTIONS } from './use_date_picker'; diff --git a/x-pack/plugins/data_usage/public/app/hooks/use_date_picker.tsx b/x-pack/plugins/data_usage/public/app/hooks/use_date_picker.tsx index 1b4b7e38e355..f4d198461f73 100644 --- a/x-pack/plugins/data_usage/public/app/hooks/use_date_picker.tsx +++ b/x-pack/plugins/data_usage/public/app/hooks/use_date_picker.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import moment from 'moment'; import { useCallback, useState } from 'react'; import type { DurationRange, @@ -19,8 +18,10 @@ export const DEFAULT_DATE_RANGE_OPTIONS = Object.freeze({ enabled: false, duration: 10000, }, - startDate: moment().subtract(24, 'hours').startOf('day').toISOString(), - endDate: moment().toISOString(), + startDate: 'now-24h/h', + endDate: 'now', + maxDate: 'now+1s', + minDate: 'now-9d', recentlyUsedDateRanges: [], }); diff --git a/x-pack/plugins/data_usage/public/application.tsx b/x-pack/plugins/data_usage/public/application.tsx index 0e6cdc6192c7..7bd2c794d5b3 100644 --- a/x-pack/plugins/data_usage/public/application.tsx +++ b/x-pack/plugins/data_usage/public/application.tsx @@ -16,8 +16,8 @@ import { PerformanceContextProvider } from '@kbn/ebt-tools'; import { useKibanaContextForPluginProvider } from './utils/use_kibana'; import { DataUsageStartDependencies, DataUsagePublicStart } from './types'; import { PLUGIN_ID } from '../common'; -import { DataUsageMetricsPage } from './app/data_usage_metrics_page'; import { DataUsageReactQueryClientProvider } from '../common/query_client'; +import { DataUsageMetricsPage } from './app/data_usage_metrics_page'; export const renderApp = ( core: CoreStart, diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.test.tsx b/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.test.tsx index 04cee589a523..5e224e635dca 100644 --- a/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.test.tsx +++ b/x-pack/plugins/data_usage/public/hooks/use_get_data_streams.test.tsx @@ -11,7 +11,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { useGetDataUsageDataStreams } from './use_get_data_streams'; import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '../../common'; import { coreMock as mockCore } from '@kbn/core/public/mocks'; -import { dataUsageTestQueryClientOptions } from '../../common/test_utils/test_query_client_options'; +import { dataUsageTestQueryClientOptions } from '../../common/test_utils'; const useQueryMock = _useQuery as jest.Mock; diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.test.tsx b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.test.tsx index 677bd4bdfcef..1ddb84d89ffc 100644 --- a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.test.tsx +++ b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.test.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import moment from 'moment'; import React, { ReactNode } from 'react'; import { QueryClient, QueryClientProvider, useQuery as _useQuery } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react-hooks'; import { useGetDataUsageMetrics } from './use_get_usage_metrics'; import { DATA_USAGE_METRICS_API_ROUTE } from '../../common'; import { coreMock as mockCore } from '@kbn/core/public/mocks'; -import { dataUsageTestQueryClientOptions } from '../../common/test_utils/test_query_client_options'; +import { dataUsageTestQueryClientOptions, timeXMinutesAgo } from '../../common/test_utils'; const useQueryMock = _useQuery as jest.Mock; @@ -42,8 +41,8 @@ jest.mock('../utils/use_kibana', () => { }); const defaultUsageMetricsRequestBody = { - from: moment().subtract(15, 'minutes').toISOString(), - to: moment().toISOString(), + from: timeXMinutesAgo(15), + to: timeXMinutesAgo(0), metricTypes: ['ingest_rate'], dataStreams: ['ds-1'], }; diff --git a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts index 6b2ef5316b0f..da5f3004d002 100644 --- a/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts +++ b/x-pack/plugins/data_usage/public/hooks/use_get_usage_metrics.ts @@ -8,8 +8,12 @@ import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { UsageMetricsRequestBody, UsageMetricsResponseSchemaBody } from '../../common/rest_types'; +import { dateParser } from '../../common/utils'; import { DATA_USAGE_METRICS_API_ROUTE } from '../../common'; +import type { + UsageMetricsRequestBody, + UsageMetricsResponseSchemaBody, +} from '../../common/rest_types'; import { useKibanaContextForPlugin } from '../utils/use_kibana'; interface ErrorType { @@ -33,8 +37,8 @@ export const useGetDataUsageMetrics = ( signal, version: '1', body: JSON.stringify({ - from: body.from, - to: body.to, + from: dateParser(body.from), + to: dateParser(body.to), metricTypes: body.metricTypes, dataStreams: body.dataStreams, }), diff --git a/x-pack/plugins/data_usage/public/index.ts b/x-pack/plugins/data_usage/public/index.ts index e18b801a6a38..3ac8c6950a04 100644 --- a/x-pack/plugins/data_usage/public/index.ts +++ b/x-pack/plugins/data_usage/public/index.ts @@ -11,7 +11,6 @@ import type { DataUsagePublicStart, DataUsageSetupDependencies, DataUsageStartDependencies, - ConfigSchema, } from './types'; import { DataUsagePlugin } from './plugin'; @@ -22,4 +21,5 @@ export const plugin: PluginInitializer< DataUsagePublicStart, DataUsageSetupDependencies, DataUsageStartDependencies -> = (pluginInitializerContext: PluginInitializerContext<ConfigSchema>) => new DataUsagePlugin(); +> = (pluginInitializerContext: PluginInitializerContext) => + new DataUsagePlugin(pluginInitializerContext); diff --git a/x-pack/plugins/data_usage/public/plugin.ts b/x-pack/plugins/data_usage/public/plugin.ts index aa3b02c2b671..5878f8503882 100644 --- a/x-pack/plugins/data_usage/public/plugin.ts +++ b/x-pack/plugins/data_usage/public/plugin.ts @@ -5,16 +5,21 @@ * 2.0. */ -import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import { ManagementAppMountParams } from '@kbn/management-plugin/public'; import { DataUsagePublicSetup, DataUsagePublicStart, DataUsageStartDependencies, DataUsageSetupDependencies, + DataUsagePublicConfigType, } from './types'; -import { PLUGIN_ID, PLUGIN_NAME } from '../common'; - +import { PLUGIN_ID } from '../common'; +import { PLUGIN_NAME } from './translations'; +import { + ExperimentalFeatures, + parseExperimentalConfigValue, +} from '../common/experimental_features'; export class DataUsagePlugin implements Plugin< @@ -24,30 +29,46 @@ export class DataUsagePlugin DataUsageStartDependencies > { + private config: DataUsagePublicConfigType; + private experimentalFeatures: ExperimentalFeatures; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.config = this.initializerContext.config.get<DataUsagePublicConfigType>(); + this.experimentalFeatures = {} as ExperimentalFeatures; + } + public setup( core: CoreSetup<DataUsageStartDependencies, DataUsagePublicStart>, plugins: DataUsageSetupDependencies ): DataUsagePublicSetup { const { management } = plugins; - management.sections.section.data.registerApp({ - id: PLUGIN_ID, - title: PLUGIN_NAME, - order: 6, - keywords: ['data usage', 'usage'], - async mount(params: ManagementAppMountParams) { - const [{ renderApp }, [coreStart, pluginsStartDeps, pluginStart]] = await Promise.all([ - import('./application'), - core.getStartServices(), - ]); - - return renderApp(coreStart, pluginsStartDeps, pluginStart, params); - }, - }); + this.experimentalFeatures = parseExperimentalConfigValue( + this.config.enableExperimental + ).features; + + const experimentalFeatures = this.experimentalFeatures; + + if (!experimentalFeatures.dataUsageDisabled) { + management.sections.section.data.registerApp({ + id: PLUGIN_ID, + title: PLUGIN_NAME, + order: 6, + keywords: ['data usage', 'usage'], + async mount(params: ManagementAppMountParams) { + const [{ renderApp }, [coreStart, pluginsStartDeps, pluginStart]] = await Promise.all([ + import('./application'), + core.getStartServices(), + ]); + + return renderApp(coreStart, pluginsStartDeps, pluginStart, params); + }, + }); + } return {}; } - public start(_core: CoreStart): DataUsagePublicStart { + public start(_core: CoreStart, plugins: DataUsageStartDependencies): DataUsagePublicStart { return {}; } diff --git a/x-pack/plugins/data_usage/public/app/translations.tsx b/x-pack/plugins/data_usage/public/translations.tsx similarity index 68% rename from x-pack/plugins/data_usage/public/app/translations.tsx rename to x-pack/plugins/data_usage/public/translations.tsx index ee42d3b58906..0996ec2bb6d5 100644 --- a/x-pack/plugins/data_usage/public/app/translations.tsx +++ b/x-pack/plugins/data_usage/public/translations.tsx @@ -7,6 +7,10 @@ import { i18n } from '@kbn/i18n'; +export const PLUGIN_NAME = i18n.translate('xpack.dataUsage.name', { + defaultMessage: 'Data Usage', +}); + export const FILTER_NAMES = Object.freeze({ metricTypes: i18n.translate('xpack.dataUsage.metrics.filter.metricTypes', { defaultMessage: 'Metric types', @@ -35,6 +39,9 @@ export const DATA_USAGE_PAGE = Object.freeze({ }); export const UX_LABELS = Object.freeze({ + filterSelectAll: i18n.translate('xpack.dataUsage.metrics.filter.selectAll', { + defaultMessage: 'Select all', + }), filterClearAll: i18n.translate('xpack.dataUsage.metrics.filter.clearAll', { defaultMessage: 'Clear all', }), @@ -48,4 +55,18 @@ export const UX_LABELS = Object.freeze({ defaultMessage: 'No {filterName} available', values: { filterName }, }), + dataQualityPopup: { + open: i18n.translate('xpack.dataUsage.metrics.dataQuality.open.actions', { + defaultMessage: 'Open data stream actions', + }), + copy: i18n.translate('xpack.dataUsage.metrics.dataQuality.copy.dataStream', { + defaultMessage: 'Copy data stream name', + }), + manage: i18n.translate('xpack.dataUsage.metrics.dataQuality.manage.dataStream', { + defaultMessage: 'Manage data stream', + }), + view: i18n.translate('xpack.dataUsage.metrics.dataQuality.view', { + defaultMessage: 'View data quality', + }), + }, }); diff --git a/x-pack/plugins/data_usage/public/types.ts b/x-pack/plugins/data_usage/public/types.ts index e65865dc3182..8c92d27c3d9b 100644 --- a/x-pack/plugins/data_usage/public/types.ts +++ b/x-pack/plugins/data_usage/public/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { schema, TypeOf } from '@kbn/config-schema'; import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; @@ -23,5 +24,21 @@ export interface DataUsageStartDependencies { management: ManagementStart; share: SharePluginStart; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ConfigSchema {} + +const schemaObject = schema.object({ + /** + * For internal use. A list of string values (comma delimited) that will enable experimental + * type of functionality that is not yet released. Valid values for this settings need to + * be defined in: + * `x-pack/plugins/dataUsage/common/experimental_features.ts` + * under the `allowedExperimentalValues` object + * + * @example + * xpack.dataUsage.enableExperimental: ['someFeature'] + */ + enableExperimental: schema.arrayOf(schema.string(), { + defaultValue: () => [], + }), +}); + +export type DataUsagePublicConfigType = TypeOf<typeof schemaObject>; diff --git a/x-pack/plugins/data_usage/server/config.ts b/x-pack/plugins/data_usage/server/config.ts index 7dd664f35288..c00c08bb1b05 100644 --- a/x-pack/plugins/data_usage/server/config.ts +++ b/x-pack/plugins/data_usage/server/config.ts @@ -20,13 +20,25 @@ export const configSchema = schema.object({ schema.object({ certificate: schema.maybe(schema.string()), key: schema.maybe(schema.string()), - ca: schema.maybe(schema.string()), }) ), }) ), }) ), + /** + * For internal use. A list of string values (comma delimited) that will enable experimental + * type of functionality that is not yet released. Valid values for this settings need to + * be defined in: + * `x-pack/plugins/dataUsage/common/experimental_features.ts` + * under the `allowedExperimentalValues` object + * + * @example + * xpack.dataUsage.enableExperimental: ['someFeature'] + */ + enableExperimental: schema.arrayOf(schema.string(), { + defaultValue: () => [], + }), }); export type DataUsageConfigType = TypeOf<typeof configSchema>; diff --git a/x-pack/plugins/data_usage/server/index.ts b/x-pack/plugins/data_usage/server/index.ts index 66d839303d71..826486dc717f 100644 --- a/x-pack/plugins/data_usage/server/index.ts +++ b/x-pack/plugins/data_usage/server/index.ts @@ -25,6 +25,9 @@ export type { DataUsageServerSetup, DataUsageServerStart }; export const config: PluginConfigDescriptor<DataUsageConfigType> = { schema: configSchema, + exposeToBrowser: { + enableExperimental: true, + }, }; export const plugin: PluginInitializer< diff --git a/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts b/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts index 2330e465d9b1..374c4b9c82e7 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts @@ -84,6 +84,48 @@ describe('registerDataStreamsRoute', () => { }); }); + it('should not include data streams with 0 size', async () => { + mockGetMeteringStats.mockResolvedValue({ + datastreams: [ + { + name: 'datastream1', + size_in_bytes: 100, + }, + { + name: 'datastream2', + size_in_bytes: 200, + }, + { + name: 'datastream3', + size_in_bytes: 0, + }, + { + name: 'datastream4', + size_in_bytes: 0, + }, + ], + }); + const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); + const mockResponse = httpServerMock.createResponseFactory(); + const mockRouter = mockCore.http.createRouter.mock.results[0].value; + const [[, handler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls; + await handler(context, mockRequest, mockResponse); + + expect(mockResponse.ok).toHaveBeenCalledTimes(1); + expect(mockResponse.ok.mock.calls[0][0]).toEqual({ + body: [ + { + name: 'datastream2', + storageSizeBytes: 200, + }, + { + name: 'datastream1', + storageSizeBytes: 100, + }, + ], + }); + }); + it('should return correct error if metering stats request fails', async () => { // using custom error for test here to avoid having to import the actual error class mockGetMeteringStats.mockRejectedValue( @@ -105,7 +147,7 @@ describe('registerDataStreamsRoute', () => { it.each([ ['no datastreams', {}, []], ['empty array', { datastreams: [] }, []], - ['an empty element', { datastreams: [{}] }, [{ name: undefined, storageSizeBytes: 0 }]], + ['an empty element', { datastreams: [{}] }, []], ])('should return empty array when no stats data with %s', async (_, stats, res) => { mockGetMeteringStats.mockResolvedValue(stats); const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); diff --git a/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts index 9abd898358e9..99b4e982c5a4 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts @@ -27,10 +27,15 @@ export const getDataStreamsHandler = ( meteringStats && !!meteringStats.length ? meteringStats .sort((a, b) => b.size_in_bytes - a.size_in_bytes) - .map((stat) => ({ - name: stat.name, - storageSizeBytes: stat.size_in_bytes ?? 0, - })) + .reduce<Array<{ name: string; storageSizeBytes: number }>>((acc, stat) => { + if (stat.size_in_bytes > 0) { + acc.push({ + name: stat.name, + storageSizeBytes: stat.size_in_bytes ?? 0, + }); + } + return acc; + }, []) : []; return response.ok({ diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts index d6337bbcc8dc..c0eb0e5e8ef2 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import moment from 'moment'; import type { MockedKeys } from '@kbn/utility-types-jest'; import type { CoreSetup } from '@kbn/core/server'; import { registerUsageMetricsRoute } from './usage_metrics'; @@ -20,6 +19,7 @@ import { DATA_USAGE_METRICS_API_ROUTE } from '../../../common'; import { createMockedDataUsageContext } from '../../mocks'; import { CustomHttpRequestError } from '../../utils'; import { AutoOpsError } from '../../services/errors'; +import { timeXMinutesAgo } from '../../../common/test_utils'; describe('registerUsageMetricsRoute', () => { let mockCore: MockedKeys<CoreSetup<{}, DataUsageServerStart>>; @@ -56,8 +56,8 @@ describe('registerUsageMetricsRoute', () => { const mockRequest = httpServerMock.createKibanaRequest({ body: { - from: moment().subtract(15, 'minutes').toISOString(), - to: moment().toISOString(), + from: timeXMinutesAgo(15), + to: timeXMinutesAgo(0), metricTypes: ['ingest_rate'], dataStreams: [], }, @@ -123,8 +123,8 @@ describe('registerUsageMetricsRoute', () => { const mockRequest = httpServerMock.createKibanaRequest({ body: { - from: moment().subtract(15, 'minutes').toISOString(), - to: moment().toISOString(), + from: timeXMinutesAgo(15), + to: timeXMinutesAgo(0), metricTypes: ['ingest_rate', 'storage_retained'], dataStreams: ['.ds-1', '.ds-2'], }, @@ -191,8 +191,8 @@ describe('registerUsageMetricsRoute', () => { const mockRequest = httpServerMock.createKibanaRequest({ body: { - from: moment().subtract(15, 'minutes').toISOString(), - to: moment().toISOString(), + from: timeXMinutesAgo(15), + to: timeXMinutesAgo(0), metricTypes: ['ingest_rate'], dataStreams: ['.ds-1', '.ds-2'], }, diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts index 07625ad4c089..c2dee4ca2ce5 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts @@ -5,8 +5,9 @@ * 2.0. */ +import { chunk } from 'lodash/fp'; import { RequestHandler } from '@kbn/core/server'; -import { +import type { MetricTypes, UsageMetricsAutoOpsResponseSchemaBody, UsageMetricsRequestBody, @@ -30,6 +31,8 @@ export const getUsageMetricsHandler = ( const core = await context.core; const esClient = core.elasticsearch.client.asCurrentUser; + const getDataStreams = (name: string[]) => + esClient.indices.getDataStream({ name, expand_wildcards: 'all' }); logger.debug(`Retrieving usage metrics`); const { from, to, metricTypes, dataStreams: requestDsNames } = request.body; @@ -43,15 +46,24 @@ export const getUsageMetricsHandler = ( new CustomHttpRequestError('[request body.dataStreams]: no data streams selected', 400) ); } - let dataStreamsResponse; + + let dataStreamsResponse: Array<{ name: string }>; try { - // Attempt to fetch data streams - const { data_streams: dataStreams } = await esClient.indices.getDataStream({ - name: requestDsNames, - expand_wildcards: 'all', - }); - dataStreamsResponse = dataStreams; + if (requestDsNames.length <= 50) { + logger.debug(`Retrieving usage metrics`); + const { data_streams: dataStreams } = await getDataStreams(requestDsNames); + dataStreamsResponse = dataStreams; + } else { + logger.debug(`Retrieving usage metrics in chunks of 50`); + // Attempt to fetch data streams in chunks of 50 + const dataStreamsChunks = Math.ceil(requestDsNames.length / 50); + const chunkedDsLists = chunk(dataStreamsChunks, requestDsNames); + const chunkedDataStreams = await Promise.all( + chunkedDsLists.map((dsList) => getDataStreams(dsList)) + ); + dataStreamsResponse = chunkedDataStreams.flatMap((ds) => ds.data_streams); + } } catch (error) { return errorHandler( logger, @@ -83,18 +95,16 @@ export const getUsageMetricsHandler = ( export function transformMetricsData( data: UsageMetricsAutoOpsResponseSchemaBody ): UsageMetricsResponseSchemaBody { - return { - metrics: Object.fromEntries( - Object.entries(data.metrics).map(([metricType, series]) => [ - metricType, - series.map((metricSeries) => ({ - name: metricSeries.name, - data: (metricSeries.data as Array<[number, number]>).map(([timestamp, value]) => ({ - x: timestamp, - y: value, - })), + return Object.fromEntries( + Object.entries(data).map(([metricType, series]) => [ + metricType, + series.map((metricSeries) => ({ + name: metricSeries.name, + data: (metricSeries.data as Array<[number, number]>).map(([timestamp, value]) => ({ + x: timestamp, + y: value, })), - ]) - ), - }; + })), + ]) + ) as UsageMetricsResponseSchemaBody; } diff --git a/x-pack/plugins/data_usage/server/services/autoops_api.ts b/x-pack/plugins/data_usage/server/services/autoops_api.ts index c1b96a973d9d..2ff824e04f6d 100644 --- a/x-pack/plugins/data_usage/server/services/autoops_api.ts +++ b/x-pack/plugins/data_usage/server/services/autoops_api.ts @@ -6,7 +6,7 @@ */ import https from 'https'; -import dateMath from '@kbn/datemath'; + import { SslConfig, sslSchema } from '@kbn/server-http-tools'; import apm from 'elastic-apm-node'; @@ -16,9 +16,10 @@ import axios from 'axios'; import { LogMeta } from '@kbn/core/server'; import { UsageMetricsAutoOpsResponseSchema, - UsageMetricsAutoOpsResponseSchemaBody, - UsageMetricsRequestBody, + type UsageMetricsAutoOpsResponseSchemaBody, + type UsageMetricsRequestBody, } from '../../common/rest_types'; +import { dateParser } from '../../common/utils'; import { AutoOpsConfig } from '../types'; import { AutoOpsError } from './errors'; import { appContextService } from './app_context'; @@ -30,7 +31,6 @@ const AUTO_OPS_MISSING_CONFIG_ERROR = 'Missing autoops configuration'; const getAutoOpsAPIRequestUrl = (url?: string, projectId?: string): string => `${url}/monitoring/serverless/v1/projects/${projectId}/metrics`; -const dateParser = (date: string) => dateMath.parse(date)?.toISOString(); export class AutoOpsAPIService { private logger: Logger; constructor(logger: Logger) { @@ -52,12 +52,23 @@ export class AutoOpsAPIService { throw new AutoOpsError(AUTO_OPS_MISSING_CONFIG_ERROR); } + if (!autoopsConfig.api?.url) { + this.logger.error(`[AutoOps API] Missing API URL in the configuration.`, errorMetadata); + throw new AutoOpsError('Missing API URL in AutoOps configuration.'); + } + + if (!autoopsConfig.api?.tls?.certificate || !autoopsConfig.api?.tls?.key) { + this.logger.error( + `[AutoOps API] Missing required TLS certificate or key in the configuration.`, + errorMetadata + ); + throw new AutoOpsError('Missing required TLS certificate or key in AutoOps configuration.'); + } + this.logger.debug( - `[AutoOps API] Creating autoops agent with TLS cert: ${ - autoopsConfig?.api?.tls?.certificate ? '[REDACTED]' : 'undefined' - } and TLS key: ${autoopsConfig?.api?.tls?.key ? '[REDACTED]' : 'undefined'} - and TLS ca: ${autoopsConfig?.api?.tls?.ca ? '[REDACTED]' : 'undefined'}` + `[AutoOps API] Creating autoops agent with request URL: ${autoopsConfig.api.url} and TLS cert: [REDACTED] and TLS key: [REDACTED]` ); + const controller = new AbortController(); const tlsConfig = this.createTlsConfig(autoopsConfig); const cloudSetup = appContextService.getCloud(); @@ -153,11 +164,7 @@ export class AutoOpsAPIService { } ); - const validatedResponse = response.data.metrics - ? UsageMetricsAutoOpsResponseSchema.body().validate(response.data) - : UsageMetricsAutoOpsResponseSchema.body().validate({ - metrics: response.data, - }); + const validatedResponse = UsageMetricsAutoOpsResponseSchema.body().validate(response.data); this.logger.debug(`[AutoOps API] Successfully created an autoops agent ${response}`); return validatedResponse; @@ -169,7 +176,6 @@ export class AutoOpsAPIService { enabled: true, certificate: autoopsConfig?.api?.tls?.certificate, key: autoopsConfig?.api?.tls?.key, - certificateAuthorities: autoopsConfig?.api?.tls?.ca, }) ); } @@ -187,7 +193,6 @@ export class AutoOpsAPIService { ...requestConfig.httpsAgent.options, cert: requestConfig.httpsAgent.options.cert ? 'REDACTED' : undefined, key: requestConfig.httpsAgent.options.key ? 'REDACTED' : undefined, - ca: requestConfig.httpsAgent.options.ca ? 'REDACTED' : undefined, }, }, }); diff --git a/x-pack/plugins/data_usage/server/services/index.ts b/x-pack/plugins/data_usage/server/services/index.ts index 69db6b590c6f..56e449c8a567 100644 --- a/x-pack/plugins/data_usage/server/services/index.ts +++ b/x-pack/plugins/data_usage/server/services/index.ts @@ -6,7 +6,7 @@ */ import { ValidationError } from '@kbn/config-schema'; import { Logger } from '@kbn/logging'; -import { MetricTypes } from '../../common/rest_types'; +import type { MetricTypes } from '../../common/rest_types'; import { AutoOpsError } from './errors'; import { AutoOpsAPIService } from './autoops_api'; diff --git a/x-pack/plugins/data_usage/tsconfig.json b/x-pack/plugins/data_usage/tsconfig.json index 309bad3e1b63..8647f7957451 100644 --- a/x-pack/plugins/data_usage/tsconfig.json +++ b/x-pack/plugins/data_usage/tsconfig.json @@ -33,6 +33,8 @@ "@kbn/server-http-tools", "@kbn/utility-types-jest", "@kbn/datemath", + "@kbn/ui-theme", + "@kbn/i18n-react", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/use_column_chart.test.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/use_column_chart.test.tsx index 544ebe261f2e..55346af2b49c 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/use_column_chart.test.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/use_column_chart.test.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, renderHook } from '@testing-library/react'; import { KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts index 231aa1c319da..c3ce7fb1a43a 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts @@ -178,9 +178,22 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient { public createInferenceEndpoint = async () => { const elserId = await this.options.getElserId(); this.options.logger.debug(`Deploying ELSER model '${elserId}'...`); + const esClient = await this.options.elasticsearchClientPromise; + try { - const esClient = await this.options.elasticsearchClientPromise; + await esClient.inference.delete({ + inference_id: ASSISTANT_ELSER_INFERENCE_ID, + // it's being used in the mapping so we need to force delete + force: true, + }); + this.options.logger.debug(`Deleted existing inference endpoint for ELSER model '${elserId}'`); + } catch (error) { + this.options.logger.error( + `Error deleting inference endpoint for ELSER model '${elserId}':\n${error}` + ); + } + try { await esClient.inference.put({ task_type: 'sparse_embedding', inference_id: ASSISTANT_ELSER_INFERENCE_ID, @@ -198,6 +211,9 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient { task_settings: {}, }, }); + + // await for the model to be deployed + await this.isInferenceEndpointExists(); } catch (error) { this.options.logger.error( `Error creating inference endpoint for ELSER model '${elserId}':\n${error}` diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 797f94fa29e5..2603ea3d8901 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -27,7 +27,7 @@ import { IngestPipelineParams } from '@kbn/search-connectors'; import { ProductFeatures } from './types'; export const SEARCH_PRODUCT_NAME = i18n.translate('xpack.enterpriseSearch.search.productName', { - defaultMessage: 'Search', + defaultMessage: 'Elasticsearch', }); export const ENTERPRISE_SEARCH_PRODUCT_NAME = i18n.translate('xpack.enterpriseSearch.productName', { defaultMessage: 'Enterprise Search', @@ -211,7 +211,7 @@ export const SEARCH_RELEVANCE_PLUGIN = { DESCRIPTION: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.description', { defaultMessage: 'Manage your inference endpoints for semantic search and AI use cases.', }), - URL: '/app/enterprise_search/relevance', + URL: '/app/elasticsearch/relevance', LOGO: 'logoEnterpriseSearch', SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/', }; diff --git a/x-pack/plugins/enterprise_search/common/locators/index.ts b/x-pack/plugins/enterprise_search/common/locators/index.ts index 0f9d2060cd82..35c1d43b3b30 100644 --- a/x-pack/plugins/enterprise_search/common/locators/index.ts +++ b/x-pack/plugins/enterprise_search/common/locators/index.ts @@ -6,14 +6,17 @@ */ import type { SharePluginSetup } from '@kbn/share-plugin/public'; +import { SerializableRecord } from '@kbn/utility-types'; import { CreateIndexLocatorDefinition, type CreateIndexLocatorParams, } from './create_index_locator'; +import { SearchInferenceEndpointLocatorDefinition } from './inference_locator'; import { PlaygroundLocatorDefinition, type PlaygroundLocatorParams } from './playground_locator'; export function registerLocators(share: SharePluginSetup) { share.url.locators.create<CreateIndexLocatorParams>(new CreateIndexLocatorDefinition()); share.url.locators.create<PlaygroundLocatorParams>(new PlaygroundLocatorDefinition()); + share.url.locators.create<SerializableRecord>(new SearchInferenceEndpointLocatorDefinition()); } diff --git a/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx b/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx new file mode 100644 index 000000000000..f20d628bf189 --- /dev/null +++ b/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LocatorDefinition } from '@kbn/share-plugin/common'; +import type { SharePluginSetup } from '@kbn/share-plugin/public'; +import type { SerializableRecord } from '@kbn/utility-types'; + +import { SEARCH_RELEVANCE_PLUGIN } from '../constants'; + +export function registerLocators(share: SharePluginSetup) { + share.url.locators.create<SerializableRecord>(new SearchInferenceEndpointLocatorDefinition()); +} + +export class SearchInferenceEndpointLocatorDefinition + implements LocatorDefinition<SerializableRecord> +{ + public readonly getLocation = async () => { + return { + app: SEARCH_RELEVANCE_PLUGIN.ID, + path: '/inference_endpoints', + state: {}, + }; + }; + + public readonly id = 'SEARCH_INFERENCE_ENDPOINTS'; +} diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index e284ae186214..65343904ba7f 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -20,7 +20,7 @@ "logsShared", "logsDataAccess", "esUiShared", - "navigation" + "navigation", ], "optionalPlugins": [ "customIntegrations", @@ -34,8 +34,9 @@ "guidedOnboarding", "console", "searchConnectors", - "searchPlayground", "searchInferenceEndpoints", + "searchNavigation", + "searchPlayground", "embeddable", "discover", "charts", diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/ai_search_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/ai_search_guide.tsx index 7374ecd0ac35..4ce68d1bbd6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/ai_search_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/ai_search_guide/ai_search_guide.tsx @@ -40,7 +40,7 @@ export const AISearchGuide: React.FC = () => { bottomBorder={false} pageHeader={{ pageTitle: i18n.translate('xpack.enterpriseSearch.aiSearch.guide.pageTitle', { - defaultMessage: 'Improve search revelance with AI', + defaultMessage: 'Improve search relevance with AI', }), }} > diff --git a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/layout/page_template.test.tsx index 42a84efd0ccc..dfcc1695e4ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/ai_search/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/ai_search/components/layout/page_template.test.tsx @@ -13,6 +13,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { i18n } from '@kbn/i18n'; + import { SetAiSearchChrome } from '../../../shared/kibana_chrome'; import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; @@ -23,12 +25,14 @@ describe('EnterpriseSearchAISearchPageTemplate', () => { it('renders', () => { const wrapper = shallow( <EnterpriseSearchAISearchPageTemplate> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchAISearchPageTemplate> ); expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ name: 'Search', items: [] }); + expect(wrapper.prop('solutionNav')).toEqual({ name: 'Elasticsearch', items: [] }); expect(wrapper.find('.hello').text()).toEqual('world'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/api_key_modal/generate_analytics_api_key_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/api_key_modal/generate_analytics_api_key_modal.test.tsx index 2dc27c7f7bfa..fa390d39e99d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/api_key_modal/generate_analytics_api_key_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/api_key_modal/generate_analytics_api_key_modal.test.tsx @@ -9,9 +9,10 @@ import { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logi import React from 'react'; -import { shallow, mount } from 'enzyme'; +import { shallow } from 'enzyme'; import { EuiModal, EuiFieldText, EuiCodeBlock } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; const mockActions = { makeRequest: jest.fn(), setKeyName: jest.fn() }; @@ -47,7 +48,9 @@ describe('GenerateAnalyticsApiKeyModal', () => { }); it('pre-set the key name with collection name', () => { - mount(<GenerateAnalyticsApiKeyModal collectionName="puggles" onClose={onCloseMock} />); + mountWithIntl( + <GenerateAnalyticsApiKeyModal collectionName="puggles" onClose={onCloseMock} /> + ); expect(mockActions.setKeyName).toHaveBeenCalledWith('puggles API key'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/api_key_modal/generate_analytics_api_key_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/api_key_modal/generate_analytics_api_key_modal.tsx index 7cf5b76490c4..bd5b7eed2a89 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/api_key_modal/generate_analytics_api_key_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_integrate/api_key_modal/generate_analytics_api_key_modal.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React, { useRef, useEffect } from 'react'; import { useValues, useActions } from 'kea'; @@ -24,12 +24,13 @@ import { EuiFieldText, EuiFormRow, EuiText, - EuiSpacer, - EuiFormLabel, EuiCodeBlock, + EuiCallOut, + useGeneratedHtmlId, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { generateAnalyticsApiKeyLogic } from '../../../../api/generate_analytics_api_key/generate_analytics_api_key_logic'; @@ -47,15 +48,23 @@ export const GenerateAnalyticsApiKeyModal: React.FC<GenerateAnalyticsApiKeyModal const { keyName, apiKey, isLoading, isSuccess } = useValues(GenerateApiKeyModalLogic); const { setKeyName } = useActions(GenerateApiKeyModalLogic); const { makeRequest } = useActions(generateAnalyticsApiKeyLogic); + const copyApiKeyRef = useRef<HTMLAnchorElement>(null); + const modalTitleId = useGeneratedHtmlId(); + + useEffect(() => { + if (isSuccess) { + copyApiKeyRef.current?.focus(); + } + }, [isSuccess]); useEffect(() => { setKeyName(`${collectionName} API key`); }, [collectionName]); return ( - <EuiModal onClose={onClose}> + <EuiModal onClose={onClose} aria-labelledby={modalTitleId}> <EuiModalHeader> - <EuiModalHeaderTitle> + <EuiModalHeaderTitle id={modalTitleId}> {i18n.translate( 'xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.title', { @@ -66,15 +75,24 @@ export const GenerateAnalyticsApiKeyModal: React.FC<GenerateAnalyticsApiKeyModal </EuiModalHeader> <EuiModalBody> <> - <EuiPanel hasShadow={false} color="primary"> + <EuiPanel hasShadow={false} color={!isSuccess ? 'primary' : 'success'}> <EuiFlexGroup direction="column"> <EuiFlexItem> <EuiFlexGroup direction="row" alignItems="flexEnd"> {!isSuccess ? ( <> <EuiFlexItem> - <EuiFormRow label="Name your API key" fullWidth> + <EuiFormRow + label={ + <FormattedMessage + id="xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.nameYourAPIKeyLabel" + defaultMessage="Name your API key" + /> + } + fullWidth + > <EuiFieldText + data-test-subj="enterpriseSearchGenerateAnalyticsApiKeyModalFieldText" data-telemetry-id="entSearchContent-analyticss-api-generateAnalyticsApiKeyModal-editName" fullWidth placeholder="Type a name for your API key" @@ -111,8 +129,20 @@ export const GenerateAnalyticsApiKeyModal: React.FC<GenerateAnalyticsApiKeyModal </> ) : ( <EuiFlexItem> - <EuiFormLabel>{keyName}</EuiFormLabel> - <EuiSpacer size="xs" /> + <EuiCallOut + title={ + <FormattedMessage + id="xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.callOutMessage" + defaultMessage="Done! The {name} API key was generated." + values={{ + name: <strong>{keyName}</strong>, + }} + /> + } + color="success" + iconType="check" + role="alert" + /> <EuiFlexGroup alignItems="center"> <EuiFlexItem> <EuiCodeBlock @@ -127,6 +157,8 @@ export const GenerateAnalyticsApiKeyModal: React.FC<GenerateAnalyticsApiKeyModal </EuiFlexItem> <EuiFlexItem grow={false}> <EuiButtonIcon + buttonRef={copyApiKeyRef} + data-test-subj="enterpriseSearchGenerateAnalyticsApiKeyModalButton" data-telemetry-id="entSearchContent-analyticss-api-generateAnalyticsApiKeyModal-csvDownloadButton" aria-label={i18n.translate( 'xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.csvDownloadButton', @@ -166,6 +198,7 @@ export const GenerateAnalyticsApiKeyModal: React.FC<GenerateAnalyticsApiKeyModal <EuiModalFooter> {apiKey ? ( <EuiButton + data-test-subj="enterpriseSearchGenerateAnalyticsApiKeyModalDoneButton" data-telemetry-id="entSearchContent-analyticss-api-generateAnalyticsApiKeyModal-done" fill onClick={onClose} @@ -179,6 +212,7 @@ export const GenerateAnalyticsApiKeyModal: React.FC<GenerateAnalyticsApiKeyModal </EuiButton> ) : ( <EuiButtonEmpty + data-test-subj="enterpriseSearchGenerateAnalyticsApiKeyModalCancelButton" data-telemetry-id="entSearchContent-analyticss-api-generateAnalyticsApiKeyModal-cancel" onClick={onClose} > diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/analytics/components/layout/page_template.test.tsx index b43f1fbcee7d..f59a750e1a06 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/layout/page_template.test.tsx @@ -43,7 +43,7 @@ describe('EnterpriseSearchAnalyticsPageTemplate', () => { ); expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ name: 'Search', items: [] }); + expect(wrapper.prop('solutionNav')).toEqual({ name: 'Elasticsearch', items: [] }); expect(wrapper.find('.hello').text()).toEqual('world'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate.tsx index 85cea5e8bf3c..c2385efe4706 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/app_search_gate/app_search_gate.tsx @@ -554,7 +554,7 @@ export const AppSearchGate: React.FC = () => { )} > <EuiSelect - hasNoInitialSelection + hasNoInitialSelection={participateInUXLabs === null} options={[ { text: i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_application/add_indices_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_application/add_indices_flyout.tsx index 0a0c7af7ed02..2f483d757505 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_application/add_indices_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_application/add_indices_flyout.tsx @@ -19,7 +19,6 @@ import { EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, - EuiFormRow, EuiSpacer, EuiTitle, } from '@elastic/eui'; @@ -90,25 +89,22 @@ export const AddIndicesFlyout: React.FC<AddIndicesFlyoutProps> = ({ onClose }) = )} </EuiFlyoutHeader> <EuiFlyoutBody> - <EuiFormRow + <IndicesSelectComboBox fullWidth + onChange={onIndicesChange} + selectedOptions={selectedOptions} + ignoredOptions={existingIndices} label={i18n.translate( 'xpack.enterpriseSearch.searchApplications.searchApplication.indices.addIndicesFlyout.selectableLabel', { defaultMessage: 'Select searchable indices' } )} - > - <IndicesSelectComboBox - fullWidth - onChange={onIndicesChange} - selectedOptions={selectedOptions} - ignoredOptions={existingIndices} - /> - </EuiFormRow> + /> </EuiFlyoutBody> <EuiFlyoutFooter> <EuiFlexGroup justifyContent="spaceBetween" direction="rowReverse"> <EuiFlexItem grow={false}> <EuiButton + data-test-subj="enterpriseSearchAddIndicesFlyoutAddSelectedButton" fill data-telemetry-id="entSearchApplications-indices-addNewIndices-submit" iconType="plusInCircle" @@ -122,6 +118,7 @@ export const AddIndicesFlyout: React.FC<AddIndicesFlyoutProps> = ({ onClose }) = </EuiFlexItem> <EuiFlexItem grow={false}> <EuiButtonEmpty + data-test-subj="enterpriseSearchAddIndicesFlyoutCancelButton" data-telemetry-id="entSearchApplications-indices-addNewIndices-cancel" flush="left" onClick={onClose} diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/components/indices_select_combobox.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/components/indices_select_combobox.tsx index 76880766438a..b35b5ac45c01 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/components/indices_select_combobox.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/components/indices_select_combobox.tsx @@ -17,6 +17,7 @@ import { EuiFlexItem, EuiHealth, EuiHighlight, + EuiFormRow, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -36,12 +37,14 @@ export type IndicesSelectComboBoxProps = Omit< > & { 'data-telemetry-id'?: string; ignoredOptions?: string[]; + label?: string; }; export const IndicesSelectComboBox = ({ ignoredOptions, ...props }: IndicesSelectComboBoxProps) => { const [searchQuery, setSearchQuery] = useState<string | undefined>(undefined); const { makeRequest } = useActions(FetchIndicesForSearchApplicationsAPILogic); const { status, data } = useValues(FetchIndicesForSearchApplicationsAPILogic); + const isInvalid = Boolean(searchQuery && !props.selectedOptions?.length); useEffect(() => { makeRequest({ searchQuery }); @@ -85,7 +88,20 @@ export const IndicesSelectComboBox = ({ ignoredOptions, ...props }: IndicesSelec renderOption, ...props, }; - return <EuiComboBox async {...defaultedProps} />; + + return ( + <EuiFormRow + label={props.label || null} + fullWidth={props.fullWidth} + isInvalid={isInvalid} + error={i18n.translate( + 'xpack.enterpriseSearch.searchApplications.indicesSelectComboBox.error', + { defaultMessage: 'No indices match the entered value' } + )} + > + <EuiComboBox async isInvalid={isInvalid} {...defaultedProps} /> + </EuiFormRow> + ); }; export const indexToOption = ( diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_create_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_create_button.test.tsx new file mode 100644 index 000000000000..548314dcda43 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_create_button.test.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 React, { type ReactNode } from 'react'; + +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { waitForEuiToolTipVisible } from '@elastic/eui/lib/test/rtl'; + +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; + +import { CreateSearchApplicationButton } from './search_applications_list'; + +function Container({ children }: { children?: ReactNode }) { + return <IntlProvider locale="en">{children}</IntlProvider>; +} + +describe('CreateSearchApplicationButton', () => { + test('disabled={false}', async () => { + render( + <Container> + <CreateSearchApplicationButton disabled={false} /> + </Container> + ); + + await userEvent.hover( + await screen.findByTestId('enterprise-search-search-applications-creation-button') + ); + + await waitForEuiToolTipVisible(); + + expect( + await screen.findByTestId('create-search-application-button-popover-content') + ).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_list.test.tsx index f34aac9e4359..2683a0afc0ca 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_list.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_list.test.tsx @@ -9,7 +9,7 @@ import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic'; import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { Status } from '../../../../../common/types/api'; @@ -116,80 +116,3 @@ describe('SearchApplicationsList', () => { expect(wrapper.find(LicensingCallout)).toHaveLength(0); }); }); - -describe('CreateSearchApplicationButton', () => { - describe('disabled={true}', () => { - it('renders a disabled button that shows a popover when hovered', () => { - const wrapper = mountWithIntl(<CreateSearchApplicationButton disabled />); - - const button = wrapper.find( - 'button[data-test-subj="enterprise-search-search-applications-creation-button"]' - ); - - expect(button).toHaveLength(1); - expect(button.prop('disabled')).toBeTruthy(); - - let popover = wrapper.find( - 'div[data-test-subj="create-search-application-button-popover-content"]' - ); - - expect(popover).toHaveLength(0); - - const hoverTarget = wrapper.find( - 'div[data-test-subj="create-search-application-button-hover-target"]' - ); - - expect(hoverTarget).toHaveLength(1); - - hoverTarget.simulate('mouseEnter'); - - wrapper.update(); - - popover = wrapper.find( - 'div[data-test-subj="create-search-application-button-popover-content"]' - ); - - expect(popover).toHaveLength(1); - expect(popover.text()).toMatch( - 'This functionality may be changed or removed completely in a future release.' - ); - }); - }); - describe('disabled={false}', () => { - it('renders a button and shows a popover when hovered', () => { - const wrapper = mountWithIntl(<CreateSearchApplicationButton disabled={false} />); - - const button = wrapper.find( - 'button[data-test-subj="enterprise-search-search-applications-creation-button"]' - ); - - expect(button).toHaveLength(1); - expect(button.prop('disabled')).toBeFalsy(); - - let popover = wrapper.find( - 'div[data-test-subj="create-search-application-button-popover-content"]' - ); - - expect(popover).toHaveLength(0); - - const hoverTarget = wrapper.find( - 'div[data-test-subj="create-search-application-button-hover-target"]' - ); - - expect(hoverTarget).toHaveLength(1); - - hoverTarget.simulate('mouseEnter'); - - wrapper.update(); - - popover = wrapper.find( - 'div[data-test-subj="create-search-application-button-popover-content"]' - ); - - expect(popover).toHaveLength(1); - expect(popover.text()).toMatch( - 'This functionality may be changed or removed completely in a future release.' - ); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_list.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_list.tsx index 126c44b6b5dc..fbc07eef5c5e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/search_applications/search_applications_list.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { useActions, useValues } from 'kea'; import useThrottle from 'react-use/lib/useThrottle'; @@ -17,10 +17,9 @@ import { EuiFlexItem, EuiIcon, EuiLink, - EuiPopover, - EuiPopoverTitle, EuiSpacer, EuiText, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -53,38 +52,10 @@ interface CreateSearchApplicationButtonProps { export const CreateSearchApplicationButton: React.FC<CreateSearchApplicationButtonProps> = ({ disabled, }) => { - const [showPopover, setShowPopover] = useState<boolean>(false); - return ( - <EuiPopover - isOpen={showPopover} - closePopover={() => setShowPopover(false)} - button={ - <div - data-test-subj="create-search-application-button-hover-target" - onMouseEnter={() => setShowPopover(true)} - onMouseLeave={() => setShowPopover(false)} - tabIndex={0} - > - <EuiButton - fill - iconType="plusInCircle" - data-test-subj="enterprise-search-search-applications-creation-button" - data-telemetry-id="entSearchApplications-list-createSearchApplication" - isDisabled={disabled} - onClick={() => KibanaLogic.values.navigateToUrl(SEARCH_APPLICATION_CREATION_PATH)} - > - {i18n.translate( - 'xpack.enterpriseSearch.searchApplications.list.createSearchApplicationButton.label', - { - defaultMessage: 'Create', - } - )} - </EuiButton> - </div> - } - > - <EuiPopoverTitle> + <EuiToolTip + position="top" + title={ <EuiFlexGroup justifyContent="center" gutterSize="s"> <EuiFlexItem grow={false}> <EuiIcon type="beaker" /> @@ -96,23 +67,35 @@ export const CreateSearchApplicationButton: React.FC<CreateSearchApplicationButt /> </EuiFlexItem> </EuiFlexGroup> - </EuiPopoverTitle> - <div - style={{ width: '300px' }} - data-test-subj="create-search-application-button-popover-content" + } + content={ + <EuiText size="s" data-test-subj="create-search-application-button-popover-content"> + <FormattedMessage + id="xpack.enterpriseSearch.searchApplications.list.createSearchApplicationTechnicalPreviewPopover.body" + defaultMessage="This functionality may be changed or removed completely in a future release." + /> + </EuiText> + } + > + <EuiButton + fill + iconType="plusInCircle" + data-test-subj="enterprise-search-search-applications-creation-button" + data-telemetry-id="entSearchApplications-list-createSearchApplication" + isDisabled={disabled} + onClick={() => KibanaLogic.values.navigateToUrl(SEARCH_APPLICATION_CREATION_PATH)} > - <EuiFlexGroup direction="column" gutterSize="m"> - <EuiText size="s"> - <FormattedMessage - id="xpack.enterpriseSearch.searchApplications.list.createSearchApplicationTechnicalPreviewPopover.body" - defaultMessage="This functionality may be changed or removed completely in a future release." - /> - </EuiText> - </EuiFlexGroup> - </div> - </EuiPopover> + {i18n.translate( + 'xpack.enterpriseSearch.searchApplications.list.createSearchApplicationButton.label', + { + defaultMessage: 'Create', + } + )} + </EuiButton> + </EuiToolTip> ); }; + interface ListProps { createSearchApplicationFlyoutOpen?: boolean; } @@ -223,6 +206,7 @@ export const SearchApplicationsList: React.FC<ListProps> = ({ <> <div> <EuiFieldSearch + data-test-subj="enterpriseSearchSearchApplicationsListFieldSearch" value={searchQuery} placeholder={i18n.translate( 'xpack.enterpriseSearch.searchApplications.list.searchBar.placeholder', diff --git a/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/layout/page_template.test.tsx index 99eba7d57b10..53fdc507ccfb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/layout/page_template.test.tsx @@ -13,6 +13,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { i18n } from '@kbn/i18n'; + import { SetElasticsearchChrome } from '../../../shared/kibana_chrome'; import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; @@ -23,12 +25,14 @@ describe('EnterpriseSearchElasticsearchPageTemplate', () => { it('renders', () => { const wrapper = shallow( <EnterpriseSearchElasticsearchPageTemplate> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchElasticsearchPageTemplate> ); expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ name: 'Search', items: [] }); + expect(wrapper.prop('solutionNav')).toEqual({ name: 'Elasticsearch', items: [] }); expect(wrapper.find('.hello').text()).toEqual('world'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx index 8b5bb17020ac..5a2e279026bd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx @@ -222,6 +222,12 @@ export const AttachIndexBox: React.FC<AttachIndexBoxProps> = ({ connector }) => )} isLoading={isLoading} options={groupedOptions} + onKeyDown={(event) => { + // Index name should not contain spaces + if (event.key === ' ') { + event.preventDefault(); + } + }} onSearchChange={(searchValue) => { setQuery({ isFullMatch: options.some((option) => option.label === searchValue), diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_options_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_options_buttons.tsx index c0dd0ff23622..233b6ddb6737 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_options_buttons.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/components/run_options_buttons.tsx @@ -42,6 +42,11 @@ export const RunOptionsButtons: React.FC<RunOptionsButtonsProps> = ({ onChange={() => selectDeploymentMethod('docker')} id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.runConnectorService.docker" checked={selectedDeploymentMethod === 'docker'} + aria-label={i18n.translate( + 'xpack.enterpriseSearch.connectorConfiguration.dockerTextLabel.ariaLabel', + { defaultMessage: 'Run with Docker' } + )} + name="deployment-method-run-connector" label={ <EuiFlexGroup responsive={false} gutterSize="s" alignItems="center"> <EuiFlexItem grow={false}> @@ -64,6 +69,11 @@ export const RunOptionsButtons: React.FC<RunOptionsButtonsProps> = ({ onChange={() => selectDeploymentMethod('source')} id="xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.runConnectorService.source" checked={selectedDeploymentMethod === 'source'} + aria-label={i18n.translate( + 'xpack.enterpriseSearch.connectorConfiguration.sourceTextLabel.ariaLabel', + { defaultMessage: 'Run from source' } + )} + name="deployment-method-run-connector" label={ <EuiFlexGroup responsive={false} gutterSize="s" alignItems="center"> <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/choose_connector_selectable.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/choose_connector.tsx similarity index 54% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/choose_connector_selectable.tsx rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/choose_connector.tsx index 7019fcbb71e3..21fa8e3f89f6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/choose_connector_selectable.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/components/choose_connector.tsx @@ -7,6 +7,7 @@ import React, { useEffect, useMemo, useState } from 'react'; +import { css } from '@emotion/react'; import { useActions, useValues } from 'kea'; import { @@ -18,11 +19,18 @@ import { EuiFlexGroup, EuiText, useEuiTheme, + EuiTextTruncate, + EuiBadgeGroup, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import connectorLogo from '../../../../../../assets/images/connector.svg'; +import { + BETA_LABEL, + TECH_PREVIEW_LABEL, + CONNECTOR_CLIENT_LABEL, +} from '../../../../../shared/constants'; import { KibanaLogic } from '../../../../../shared/kibana'; import { NewConnectorLogic } from '../../../new_index/method_connector/new_connector_logic'; import { SelfManagePreference } from '../create_connector'; @@ -34,9 +42,7 @@ interface OptionData { secondaryContent?: string; } -export const ChooseConnectorSelectable: React.FC<ChooseConnectorSelectableProps> = ({ - selfManaged, -}) => { +export const ChooseConnector: React.FC<ChooseConnectorSelectableProps> = ({ selfManaged }) => { const { euiTheme } = useEuiTheme(); const [selectedOption, setSelectedOption] = useState<Array<EuiComboBoxOptionOption<OptionData>>>( [] @@ -52,20 +58,26 @@ export const ChooseConnectorSelectable: React.FC<ChooseConnectorSelectableProps> }; return ( <EuiFlexGroup - gutterSize="m" - key={key + '-span'} - justifyContent="spaceBetween" className={contentClassName} + key={key + '-span'} + gutterSize="m" + responsive={false} + direction="row" > - <EuiFlexGroup gutterSize="m"> - <EuiFlexItem grow={false}>{_prepend}</EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiText size="s" textAlign="left"> - {label} - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexItem grow={false}>{_append}</EuiFlexItem> + <EuiFlexItem grow={false}>{_prepend}</EuiFlexItem> + <EuiFlexItem + css={css` + overflow: auto; + `} + grow + > + <EuiText textAlign="left" size="s"> + <EuiTextTruncate text={label} truncation="end" /> + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiBadgeGroup gutterSize="xs">{_append}</EuiBadgeGroup> + </EuiFlexItem> </EuiFlexGroup> ); }; @@ -83,43 +95,39 @@ export const ChooseConnectorSelectable: React.FC<ChooseConnectorSelectableProps> const getInitialOptions = () => { return allConnectors.map((connector, key) => { const _append: JSX.Element[] = []; + let _ariaLabelAppend = ''; if (connector.isTechPreview) { _append.push( - <EuiBadge key={key + '-preview'} iconType="beaker" color="hollow"> - {i18n.translate( - 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.thechPreviewBadgeLabel', - { defaultMessage: 'Tech preview' } - )} + <EuiBadge + aria-label={TECH_PREVIEW_LABEL} + key={key + '-preview'} + iconType="beaker" + color="hollow" + > + {TECH_PREVIEW_LABEL} </EuiBadge> ); + _ariaLabelAppend += `, ${TECH_PREVIEW_LABEL}`; } if (connector.isBeta) { _append.push( - <EuiBadge key={key + '-beta'} iconType={'beta'} color="hollow"> - {i18n.translate( - 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.BetaBadgeLabel', - { - defaultMessage: 'Beta', - } - )} + <EuiBadge aria-label={BETA_LABEL} key={key + '-beta'} iconType={'beta'} color="hollow"> + {BETA_LABEL} </EuiBadge> ); + _ariaLabelAppend += `, ${BETA_LABEL}`; } if (selfManaged === 'native' && !connector.isNative) { _append.push( <EuiBadge key={key + '-self'} iconType={'warning'} color="warning"> - {i18n.translate( - 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.OnlySelfManagedBadgeLabel', - { - defaultMessage: 'Self managed', - } - )} + {CONNECTOR_CLIENT_LABEL} </EuiBadge> ); } return { _append, _prepend: <EuiIcon size="l" type={connector.iconPath} />, + 'aria-label': connector.name + _ariaLabelAppend, key: key.toString(), label: connector.name, }; @@ -133,33 +141,31 @@ export const ChooseConnectorSelectable: React.FC<ChooseConnectorSelectableProps> }, [selfManaged]); return ( - <EuiFlexItem> - <EuiComboBox - aria-label={i18n.translate( - 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.euiComboBox.accessibleScreenReaderLabelLabel', - { defaultMessage: 'Select a data source for your connector to use.' } - )} - prepend={<EuiIcon type={selectedConnector?.iconPath ?? connectorLogo} size="l" />} - singleSelection - fullWidth - placeholder={i18n.translate( - 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.placeholder.text', - { defaultMessage: 'Choose a data source' } - )} - options={selectableOptions} - selectedOptions={selectedOption} - onChange={(selectedItem) => { - setSelectedOption(selectedItem); - if (selectedItem.length === 0) { - setSelectedConnector(null); - return; - } - const keySelected = Number(selectedItem[0].key); - setSelectedConnector(allConnectors[keySelected]); - }} - renderOption={renderOption} - rowHeight={(euiTheme.base / 2) * 5} - /> - </EuiFlexItem> + <EuiComboBox + aria-label={i18n.translate( + 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.euiComboBox.accessibleScreenReaderLabelLabel', + { defaultMessage: 'Select a data source for your connector to use.' } + )} + prepend={<EuiIcon type={selectedConnector?.iconPath ?? connectorLogo} size="l" />} + singleSelection + fullWidth + placeholder={i18n.translate( + 'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.placeholder.text', + { defaultMessage: 'Choose a data source' } + )} + options={selectableOptions} + selectedOptions={selectedOption} + onChange={(selectedItem) => { + setSelectedOption(selectedItem); + if (selectedItem.length === 0) { + setSelectedConnector(null); + return; + } + const keySelected = Number(selectedItem[0].key); + setSelectedConnector(allConnectors[keySelected]); + }} + renderOption={renderOption} + rowHeight={(euiTheme.base / 2) * 5} + /> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx index 7e23474b207f..46840d577e4d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx @@ -20,6 +20,7 @@ import { EuiRadio, EuiSpacer, EuiText, + useIsWithinBreakpoints, EuiTitle, useGeneratedHtmlId, } from '@elastic/eui'; @@ -33,7 +34,7 @@ import { GeneratedConfigFields } from '../../connector_detail/components/generat import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic'; import { NewConnectorLogic } from '../../new_index/method_connector/new_connector_logic'; -import { ChooseConnectorSelectable } from './components/choose_connector_selectable'; +import { ChooseConnector } from './components/choose_connector'; import { ConnectorDescriptionPopover } from './components/connector_description_popover'; import { ManualConfiguration } from './components/manual_configuration'; import { SelfManagePreference } from './create_connector'; @@ -53,6 +54,7 @@ export const StartStep: React.FC<StartStepProps> = ({ onSelfManagePreferenceChange, error, }) => { + const isMediumDevice = useIsWithinBreakpoints(['xs', 's', 'm', 'l']); const elasticManagedRadioButtonId = useGeneratedHtmlId({ prefix: 'elasticManagedRadioButton' }); const selfManagedRadioButtonId = useGeneratedHtmlId({ prefix: 'selfManagedRadioButton' }); @@ -93,8 +95,8 @@ export const StartStep: React.FC<StartStepProps> = ({ <h3>{title}</h3> </EuiTitle> <EuiSpacer size="m" /> - <EuiFlexGroup> - <EuiFlexItem> + <EuiFlexGroup direction={isMediumDevice ? 'column' : 'row'}> + <EuiFlexItem grow={7}> <EuiFormRow fullWidth label={i18n.translate( @@ -102,10 +104,10 @@ export const StartStep: React.FC<StartStepProps> = ({ { defaultMessage: 'Connector' } )} > - <ChooseConnectorSelectable selfManaged={selfManagePreference} /> + <ChooseConnector selfManaged={selfManagePreference} /> </EuiFormRow> </EuiFlexItem> - <EuiFlexItem> + <EuiFlexItem grow={5}> <EuiFormRow fullWidth isInvalid={!!error} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/page_template.test.tsx index 3f072abb3c0b..f6f7355b04e2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/page_template.test.tsx @@ -13,6 +13,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { i18n } from '@kbn/i18n'; + import { SetEnterpriseSearchContentChrome } from '../../../shared/kibana_chrome'; import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; @@ -23,12 +25,14 @@ describe('EnterpriseSearchContentPageTemplate', () => { it('renders', () => { const wrapper = shallow( <EnterpriseSearchContentPageTemplate> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchContentPageTemplate> ); expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ name: 'Search', items: [] }); + expect(wrapper.prop('solutionNav')).toEqual({ name: 'Elasticsearch', items: [] }); expect(wrapper.find('.hello').text()).toEqual('world'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.tsx index d19568bea9e3..5c0ba545bb56 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/generate_api_key_modal/modal.tsx @@ -28,6 +28,7 @@ import { EuiLink, EuiCodeBlock, EuiCallOut, + useGeneratedHtmlId, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -51,6 +52,7 @@ export const GenerateApiKeyModal: React.FC<GenerateApiKeyModalProps> = ({ indexN const { setKeyName } = useActions(GenerateApiKeyModalLogic); const { makeRequest } = useActions(GenerateApiKeyLogic); const copyApiKeyRef = useRef<HTMLAnchorElement>(null); + const modalTitleId = useGeneratedHtmlId(); useEffect(() => { if (isSuccess) { @@ -59,9 +61,9 @@ export const GenerateApiKeyModal: React.FC<GenerateApiKeyModalProps> = ({ indexN }, [isSuccess]); return ( - <EuiModal onClose={onClose}> + <EuiModal onClose={onClose} aria-labelledby={modalTitleId}> <EuiModalHeader> - <EuiModalHeaderTitle> + <EuiModalHeaderTitle id={modalTitleId}> {i18n.translate('xpack.enterpriseSearch.content.overview.generateApiKeyModal.title', { defaultMessage: 'Generate API Key', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx index 107be669d885..ddc98157f80c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx @@ -93,6 +93,7 @@ export const AutomaticCrawlScheduler: React.FC = () => { <EuiFlexItem> <EuiCheckableCard id="specificTimeSchedulingCard" + name="scheduling-card" label={ <> <EuiTitle size="xxs"> @@ -137,6 +138,7 @@ export const AutomaticCrawlScheduler: React.FC = () => { <EuiFlexItem> <EuiCheckableCard id="intervalSchedulingCard" + name="scheduling-card" label={ <> <EuiTitle size="xxs"> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx index c8d24525747e..bbc9c6877f0c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx @@ -42,6 +42,7 @@ export const AuthenticationPanelEditContent: React.FC = () => { <EuiCheckableCard data-telemetry-id="entSearchContent-crawler-domainDetail-authentication-basicAuthentication" id="basicAuthenticationCheckableCard" + name="authenticationCard" className="authenticationCheckable" label={ <EuiTitle size="xxs"> @@ -75,6 +76,7 @@ export const AuthenticationPanelEditContent: React.FC = () => { <EuiCheckableCard data-telemetry-id="entSearchContent-crawler-domainDetail-authentication-authenticationHeader" id="authenticationHeaderCheckableCard" + name="authenticationCard" className="authenticationCheckable" label={ <EuiTitle size="xxs"> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_field_rule_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_field_rule_flyout.tsx index 253d66d63820..6dbda38ab113 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_field_rule_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_field_rule_flyout.tsx @@ -199,6 +199,7 @@ export const EditFieldRuleFlyout: React.FC<EditFieldRuleFlyoutProps> = ({ > <EuiRadioGroup data-telemetry-id="entSearchContent-crawler-domainDetail-extractionRules-editContentRuleSource" + name="source_type_radiogroup" options={[ { id: FieldType.HTML, @@ -361,6 +362,7 @@ export const EditFieldRuleFlyout: React.FC<EditFieldRuleFlyoutProps> = ({ > <EuiRadioGroup data-telemetry-id="entSearchContent-crawler-domainDetail-extractionRules-editContentRuleExtraction" + name="content_from.value_type_radiogroup" options={[ { id: ContentFrom.EXTRACTED, @@ -408,6 +410,7 @@ export const EditFieldRuleFlyout: React.FC<EditFieldRuleFlyoutProps> = ({ > <EuiRadioGroup data-telemetry-id="entSearchContent-crawler-domainDetail-extractionRules-editContentRuleMultipleObjects" + name="multiple_objects_handling_radiogroup" options={[ { id: MultipleObjectsHandling.STRING, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_error.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_error.tsx index 951fb942a7fa..1ed3857b2c7c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_error.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_error.tsx @@ -31,10 +31,17 @@ export interface IndexErrorProps { } interface SemanticTextProperty extends MappingPropertyBase { - inference_id: string; + inference_id?: string; type: 'semantic_text'; } +/* + This will be repalce once we add default elser inference_id + with the index mapping response. +*/ +const ELSER_PRECONFIGURED_ENDPOINTS = '.elser-2-elasticsearch'; +const isInferencePreconfigured = (inferenceId: string) => inferenceId.startsWith('.'); + const parseMapping = (mappings: MappingTypeMapping) => { const fields = mappings.properties; if (!fields) { @@ -49,6 +56,11 @@ const getSemanticTextFields = ( ): Array<{ path: string; source: SemanticTextProperty }> => { return Object.entries(fields).flatMap(([key, value]) => { const currentPath: string = path ? `${path}.${key}` : key; + if (value.type === 'semantic_text') { + value = value.inference_id + ? value + : { ...value, inference_id: ELSER_PRECONFIGURED_ENDPOINTS }; + } const currentField: Array<{ path: string; source: SemanticTextProperty }> = value.type === 'semantic_text' ? [{ path: currentPath, source: value }] : []; if (hasProperties(value)) { @@ -115,7 +127,7 @@ export const IndexError: React.FC<IndexErrorProps> = ({ indexName }) => { field, }; } - if (isLocalModel(model)) { + if (isLocalModel(model) && !isInferencePreconfigured(model.inference_id)) { const modelId = model.service_settings.model_id; const modelStats = trainedModelStats?.trained_model_stats.find( (value) => diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/default_settings_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/default_settings_flyout.tsx index 3c109e213572..e62c08f3cdaa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/default_settings_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/default_settings_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useRef } from 'react'; import { useValues, useActions } from 'kea'; @@ -56,6 +56,8 @@ export const DefaultSettingsFlyout: React.FC<DefaultSettingsFlyoutProps> = ({ cl reduce_whitespace: reduceWhitespace, run_ml_inference: runMLInference, } = pipelineState; + // Reference the first focusable element in the flyout for accessibility on click or Enter key action either Reset or Save button + const firstFocusInFlyoutRef = useRef<HTMLAnchorElement>(null); return ( <EuiFlyout onClose={closeFlyout} size="s" paddingSize="l"> <EuiFlyoutHeader hasBorder> @@ -81,6 +83,7 @@ export const DefaultSettingsFlyout: React.FC<DefaultSettingsFlyoutProps> = ({ cl data-telemetry-id="entSearchContent-defaultSettingsFlyout-ingestPipelinesLink" href={docLinks.ingestPipelines} target="_blank" + ref={firstFocusInFlyoutRef} > {i18n.translate( 'xpack.enterpriseSearch.defaultSettingsFlyout.body.description.ingestPipelinesLink.link', @@ -204,7 +207,10 @@ export const DefaultSettingsFlyout: React.FC<DefaultSettingsFlyoutProps> = ({ cl color="primary" disabled={hasNoChanges} isLoading={isLoading} - onClick={() => setPipeline(defaultPipeline)} + onClick={() => { + setPipeline(defaultPipeline); + firstFocusInFlyoutRef.current?.focus(); + }} data-test-subj={'entSearchContentSettingsResetButton'} > {i18n.translate('xpack.enterpriseSearch.content.settings.resetButtonLabel', { @@ -218,7 +224,10 @@ export const DefaultSettingsFlyout: React.FC<DefaultSettingsFlyoutProps> = ({ cl fill disabled={hasNoChanges} isLoading={isLoading} - onClick={() => makeRequest(pipelineState)} + onClick={() => { + makeRequest(pipelineState); + firstFocusInFlyoutRef.current?.focus(); + }} data-test-subj={'entSearchContentSettingsSaveButton'} > {i18n.translate('xpack.enterpriseSearch.content.settings.saveButtonLabel', { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/layout/page_template.test.tsx index ea0e3f4b7749..92b400fa9b19 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/layout/page_template.test.tsx @@ -13,6 +13,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { i18n } from '@kbn/i18n'; + import { SetSearchChrome } from '../../../shared/kibana_chrome'; import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; @@ -23,12 +25,14 @@ describe('EnterpriseSearchOverviewPageTemplate', () => { it('renders', () => { const wrapper = shallow( <EnterpriseSearchOverviewPageTemplate> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchOverviewPageTemplate> ); expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ name: 'Search', items: [] }); + expect(wrapper.prop('solutionNav')).toEqual({ name: 'Elasticsearch', items: [] }); expect(wrapper.find('.hello').text()).toEqual('world'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.test.tsx index 70f8412eeb5b..dd67bf33d987 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.test.tsx @@ -13,7 +13,6 @@ import { shallow } from 'enzyme'; import { ErrorStateCallout } from '../../../shared/error_state'; -import { SetupGuideCta } from '../setup_guide'; import { TrialCallout } from '../trial_callout'; import { ElasticsearchProductCard } from './elasticsearch_product_card'; @@ -26,7 +25,6 @@ describe('ProductSelector', () => { const wrapper = shallow(<ProductSelector />); expect(wrapper.find(ElasticsearchProductCard)).toHaveLength(1); - expect(wrapper.find(SetupGuideCta)).toHaveLength(1); }); it('renders the trial callout', () => { @@ -62,14 +60,12 @@ describe('ProductSelector', () => { const wrapper = shallow(<ProductSelector />); expect(wrapper.find(ElasticsearchProductCard)).toHaveLength(1); - expect(wrapper.find(SetupGuideCta)).toHaveLength(0); }); it('does not render EnterpriseSearch card without access', () => { const wrapper = shallow(<ProductSelector />); expect(wrapper.find(ElasticsearchProductCard)).toHaveLength(1); - expect(wrapper.find(SetupGuideCta)).toHaveLength(0); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx index 71139a8b3640..1f25f5f69c2e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx @@ -30,7 +30,6 @@ import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/ import headerImage from '../../assets/search_header.png'; import { EnterpriseSearchOverviewPageTemplate } from '../layout'; -import { SetupGuideCta } from '../setup_guide'; import { TrialCallout } from '../trial_callout'; import { ElasticsearchProductCard } from './elasticsearch_product_card'; @@ -121,11 +120,6 @@ export const ProductSelector: React.FC = () => { <EuiFlexItem> <SearchLabsBanner /> </EuiFlexItem> - {!config.host && config.canDeployEntSearch && ( - <EuiFlexItem> - <SetupGuideCta /> - </EuiFlexItem> - )} </EuiFlexGroup> </EuiPageTemplate.Section> </EnterpriseSearchOverviewPageTemplate> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/index.ts index ba2e47b20464..8f952f940610 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/index.ts @@ -6,4 +6,3 @@ */ export { SetupGuide } from './setup_guide'; -export { SetupGuideCta } from './setup_guide_cta'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.scss b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.scss deleted file mode 100644 index b3e2ffd8c11e..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.scss +++ /dev/null @@ -1,10 +0,0 @@ -.enterpriseSearchSetupCta { - &__image { - width: $euiSize * 10; - margin: 0 auto; - - @include euiBreakpoint('xs', 's') { - width: $euiSize * 15; - } - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.tsx deleted file mode 100644 index 346477b08dbb..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.tsx +++ /dev/null @@ -1,45 +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 React from 'react'; - -import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiText, EuiImage } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { EuiPanelTo } from '../../../shared/react_router_helpers'; -import { PRODUCT_SELECTOR_CALLOUT_HEADING } from '../../constants'; - -import CtaImage from './assets/getting_started.png'; -import './setup_guide_cta.scss'; - -export const SetupGuideCta: React.FC = () => ( - <EuiPanelTo - to="/setup_guide" - paddingSize="l" - className="enterpriseSearchSetupCta" - data-test-subj="setupGuideLink" - hasBorder - color="transparent" - > - <EuiFlexGroup alignItems="center" justifyContent="spaceBetween"> - <EuiFlexItem> - <EuiTitle size="s"> - <h2>{PRODUCT_SELECTOR_CALLOUT_HEADING}</h2> - </EuiTitle> - <EuiText size="s" color="subdued"> - {i18n.translate('xpack.enterpriseSearch.overview.setupCta.description', { - defaultMessage: - 'Add search to your app or internal organization with Elastic App Search and Workplace Search. Watch the video to see what you can do when search is made easy.', - })} - </EuiText> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiImage src={CtaImage} alt="" className="enterpriseSearchSetupCta__image" /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiPanelTo> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx index 6b9e6b891a59..246c9835a015 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx @@ -32,7 +32,7 @@ describe('EnterpriseSearchRelevancePageTemplate', () => { ); expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ items: [], name: 'Search' }); + expect(wrapper.prop('solutionNav')).toEqual({ items: [], name: 'Elasticsearch' }); expect(wrapper.find('.hello').text()).toEqual('world'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.test.tsx index ef1e5ffa7059..0fccdb62d4d2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/search_experiences/components/layout/page_template.test.tsx @@ -13,6 +13,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { i18n } from '@kbn/i18n'; + import { SetSearchExperiencesChrome } from '../../../shared/kibana_chrome'; import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; @@ -23,12 +25,14 @@ describe('EnterpriseSearchSearchExperiencesPageTemplate', () => { it('renders', () => { const wrapper = shallow( <EnterpriseSearchSearchExperiencesPageTemplate> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchSearchExperiencesPageTemplate> ); expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ name: 'Search', items: [] }); + expect(wrapper.prop('solutionNav')).toEqual({ name: 'Elasticsearch', items: [] }); expect(wrapper.find('.hello').text()).toEqual('world'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts index f1da7cefa5cc..dd4e13a6df27 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts @@ -27,6 +27,10 @@ export const BETA_LABEL = i18n.translate('xpack.enterpriseSearch.betaLabel', { defaultMessage: 'Beta', }); +export const TECH_PREVIEW_LABEL = i18n.translate('xpack.enterpriseSearch.techPreviewLabel', { + defaultMessage: 'Tech preview', +}); + export const NATIVE_LABEL = i18n.translate('xpack.enterpriseSearch.nativeLabel', { defaultMessage: 'Elastic managed', }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.test.ts index c767706fb0d6..47dcd899d82c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.test.ts @@ -162,7 +162,7 @@ describe('useSearchBreadcrumbs', () => { expect(useSearchBreadcrumbs(breadcrumbs)).toEqual([ { - text: 'Search', + text: 'Elasticsearch', href: '/app/enterprise_search/overview', onClick: expect.any(Function), }, @@ -180,7 +180,7 @@ describe('useSearchBreadcrumbs', () => { it('shows just the root if breadcrumbs is empty', () => { expect(useSearchBreadcrumbs()).toEqual([ { - text: 'Search', + text: 'Elasticsearch', }, ]); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.test.ts index 511445240095..5db04cc0a15c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.test.ts @@ -17,17 +17,17 @@ describe('generateTitle', () => { describe('searchTitle', () => { it('automatically appends the Enterprise Search product onto the pages array', () => { const title = searchTitle(['Setup Guide']); - expect(title).toEqual('Setup Guide - Search'); + expect(title).toEqual('Setup Guide - Elasticsearch'); }); it('can be mixed and matched', () => { const title = searchTitle([appSearchTitle(['Some Page'])]); - expect(title).toEqual('Some Page - App Search - Search'); + expect(title).toEqual('Some Page - App Search - Elasticsearch'); }); it('falls back to product name', () => { const title = searchTitle(); - expect(title).toEqual('Search'); + expect(title).toEqual('Elasticsearch'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx index b971ab6deff5..a8fff53d8a9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx @@ -17,10 +17,11 @@ import { SEARCH_AI_SEARCH, } from '@kbn/deeplinks-search'; import { i18n } from '@kbn/i18n'; +import type { ClassicNavItem } from '@kbn/search-navigation/public'; import { GETTING_STARTED_TITLE } from '../../../../common/constants'; -import { ClassicNavItem, BuildClassicNavParameters } from '../types'; +import { BuildClassicNavParameters } from '../types'; export const buildBaseClassicNavItems = ({ productAccess, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts index 514072ba297a..d43d14aba223 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts @@ -8,6 +8,7 @@ import { mockKibanaValues } from '../../__mocks__/kea_logic'; import type { ChromeNavLink } from '@kbn/core-chrome-browser'; +import type { ClassicNavItem } from '@kbn/search-navigation/public'; import '../../__mocks__/react_router'; @@ -15,8 +16,6 @@ jest.mock('../react_router_helpers/link_events', () => ({ letBrowserHandleEvent: jest.fn(), })); -import { ClassicNavItem } from '../types'; - import { generateSideNavItems } from './classic_nav_helpers'; describe('generateSideNavItems', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.ts index 89f3c2ab5b59..4609e01beb6f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.ts @@ -6,12 +6,9 @@ */ import { ChromeNavLink, EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; +import type { ClassicNavItem } from '@kbn/search-navigation/public'; -import { - ClassicNavItem, - GenerateNavLinkFromDeepLinkParameters, - GenerateNavLinkParameters, -} from '../types'; +import type { GenerateNavLinkFromDeepLinkParameters, GenerateNavLinkParameters } from '../types'; import { generateNavLink } from './nav_link_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 3305e92dd8d9..a6cbf5669173 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -101,7 +101,7 @@ const baseNavItems = [ items: [ { 'data-test-subj': 'searchSideNav-InferenceEndpoints', - href: '/app/enterprise_search/relevance/inference_endpoints', + href: '/app/elasticsearch/relevance/inference_endpoints', id: 'inference_endpoints', items: undefined, name: 'Inference Endpoints', @@ -205,7 +205,7 @@ const mockNavLinks = [ { id: 'searchInferenceEndpoints:inferenceEndpoints', title: 'Inference Endpoints', - url: '/app/enterprise_search/relevance/inference_endpoints', + url: '/app/elasticsearch/relevance/inference_endpoints', }, { id: 'appSearch:engines', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx index 4c2dc4030934..370972ebe53e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.test.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; @@ -35,7 +36,9 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { it('renders children', () => { const wrapper = shallow( <EnterpriseSearchPageTemplateWrapper> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchPageTemplateWrapper> ); @@ -71,7 +74,13 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { const wrapper = shallow( <EnterpriseSearchPageTemplateWrapper isEmptyState - emptyState={<div className="emptyState">Nothing here yet!</div>} + emptyState={ + <div className="emptyState"> + {i18n.translate('xpack.enterpriseSearch..div.nothingHereYetLabel', { + defaultMessage: 'Nothing here yet!', + })} + </div> + } > <div className="test" /> </EnterpriseSearchPageTemplateWrapper> @@ -88,7 +97,13 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { const wrapper = shallow( <EnterpriseSearchPageTemplateWrapper isEmptyState={false} - emptyState={<div className="emptyState">Nothing here yet!</div>} + emptyState={ + <div className="emptyState"> + {i18n.translate('xpack.enterpriseSearch..div.nothingHereYetLabel', { + defaultMessage: 'Nothing here yet!', + })} + </div> + } > <div className="test" /> </EnterpriseSearchPageTemplateWrapper> @@ -201,14 +216,14 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { ); }); - it('automatically sets the Search logo onto passed solution navs', () => { + it('automatically sets the Elasticsearch logo onto passed solution navs', () => { const wrapper = shallow( - <EnterpriseSearchPageTemplateWrapper solutionNav={{ name: 'Search', items: [] }} /> + <EnterpriseSearchPageTemplateWrapper solutionNav={{ name: 'Elasticsearch', items: [] }} /> ); expect(wrapper.find(KibanaPageTemplate).prop('solutionNav')).toEqual({ icon: 'logoEnterpriseSearch', - name: 'Search', + name: 'Elasticsearch', items: [], }); }); @@ -216,14 +231,14 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { it('sets the solutionNavIcon passed', () => { const wrapper = shallow( <EnterpriseSearchPageTemplateWrapper - solutionNav={{ name: 'Search', items: [] }} + solutionNav={{ name: 'Elasticsearch', items: [] }} solutionNavIcon="logoElasticsearch" /> ); expect(wrapper.find(KibanaPageTemplate).prop('solutionNav')).toEqual({ icon: 'logoElasticsearch', - name: 'Search', + name: 'Elasticsearch', items: [], }); }); @@ -231,7 +246,13 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { describe('Embedded Console', () => { it('renders embedded console if available', () => { - const FakeEmbeddedConsole: React.FC = () => <div className="embedded_console">foo</div>; + const FakeEmbeddedConsole: React.FC = () => ( + <div className="embedded_console"> + {i18n.translate('xpack.enterpriseSearch.fakeEmbeddedConsole.div.fooLabel', { + defaultMessage: 'foo', + })} + </div> + ); const consolePlugin = { EmbeddableConsole: FakeEmbeddedConsole }; setMockValues({ @@ -241,14 +262,22 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { const wrapper = shallow( <EnterpriseSearchPageTemplateWrapper> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchPageTemplateWrapper> ); expect(wrapper.find(consolePlugin.EmbeddableConsole).exists()).toBe(true); }); it('Hides embedded console if available but page template prop set to hide', () => { - const FakeEmbeddedConsole: React.FC = () => <div className="embedded_console">foo</div>; + const FakeEmbeddedConsole: React.FC = () => ( + <div className="embedded_console"> + {i18n.translate('xpack.enterpriseSearch.fakeEmbeddedConsole.div.fooLabel', { + defaultMessage: 'foo', + })} + </div> + ); const consolePlugin = { EmbeddableConsole: FakeEmbeddedConsole }; setMockValues({ @@ -258,7 +287,9 @@ describe('EnterpriseSearchPageTemplateWrapper', () => { const wrapper = shallow( <EnterpriseSearchPageTemplateWrapper hideEmbeddedConsole> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchPageTemplateWrapper> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index 095f1dddfcc4..25fce6c62d05 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -5,8 +5,6 @@ * 2.0. */ -import type { ReactNode } from 'react'; - import type { AppDeepLinkId, EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants'; @@ -87,12 +85,3 @@ export interface GenerateNavLinkFromDeepLinkParameters { export interface BuildClassicNavParameters { productAccess: ProductAccess; } - -export interface ClassicNavItem { - 'data-test-subj'?: string; - deepLink?: GenerateNavLinkFromDeepLinkParameters; - iconToString?: string; - id: string; - items?: ClassicNavItem[]; - name?: ReactNode; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/vector_search/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/vector_search/components/layout/page_template.test.tsx index 5f6fe605858b..561b6d69ed09 100644 --- a/x-pack/plugins/enterprise_search/public/applications/vector_search/components/layout/page_template.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/vector_search/components/layout/page_template.test.tsx @@ -13,6 +13,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { i18n } from '@kbn/i18n'; + import { SetVectorSearchChrome } from '../../../shared/kibana_chrome'; import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; @@ -23,12 +25,14 @@ describe('EnterpriseSearchVectorSearchPageTemplate', () => { it('renders', () => { const wrapper = shallow( <EnterpriseSearchVectorSearchPageTemplate> - <div className="hello">world</div> + <div className="hello"> + {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} + </div> </EnterpriseSearchVectorSearchPageTemplate> ); expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ items: [], name: 'Search' }); + expect(wrapper.prop('solutionNav')).toEqual({ items: [], name: 'Elasticsearch' }); expect(wrapper.find('.hello').text()).toEqual('world'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx index dedcc2fc53d0..714ee3ccdef6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx @@ -645,7 +645,8 @@ export const WorkplaceSearchGate: React.FC = () => { )} > <EuiSelect - hasNoInitialSelection + data-test-subj="enterpriseSearchWorkplaceSearchGateSelect" + hasNoInitialSelection={participateInUXLabs === null} options={[ { text: i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/navigation_tree.ts b/x-pack/plugins/enterprise_search/public/navigation_tree.ts index 2f41db6bef48..25ad8ec37743 100644 --- a/x-pack/plugins/enterprise_search/public/navigation_tree.ts +++ b/x-pack/plugins/enterprise_search/public/navigation_tree.ts @@ -31,7 +31,7 @@ export interface DynamicSideNavItems { const title = i18n.translate( 'xpack.enterpriseSearch.searchNav.headerSolutionSwitcher.searchSolutionTitle', { - defaultMessage: 'Search', + defaultMessage: 'Elasticsearch', } ); const icon = 'logoElasticsearch'; diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 4d2c66eee2e9..4d357956f6bb 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -35,6 +35,7 @@ import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public' import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants'; import { SearchConnectorsPluginStart } from '@kbn/search-connectors-plugin/public'; import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public'; +import type { SearchNavigationPluginStart } from '@kbn/search-navigation/public'; import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; @@ -55,7 +56,7 @@ import { SEARCH_RELEVANCE_PLUGIN, } from '../common/constants'; import { registerLocators } from '../common/locators'; -import { ClientConfigType, InitialAppData } from '../common/types'; +import { ClientConfigType, InitialAppData, ProductAccess } from '../common/types'; import { hasEnterpriseLicense } from '../common/utils/licensing'; import { ENGINES_PATH } from './applications/app_search/routes'; @@ -99,6 +100,7 @@ export interface PluginsStart { navigation: NavigationPublicPluginStart; searchConnectors?: SearchConnectorsPluginStart; searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart; + searchNavigation?: SearchNavigationPluginStart; searchPlayground?: SearchPlaygroundPluginStart; security?: SecurityPluginStart; share?: SharePluginStart; @@ -618,6 +620,27 @@ export class EnterpriseSearchPlugin implements Plugin { }) ); }); + if (plugins.searchNavigation !== undefined) { + // while we have ent-search apps in the side nav, we need to provide access + // to the base set of classic side nav items to the search-navigation plugin. + import('./applications/shared/layout/base_nav').then(({ buildBaseClassicNavItems }) => { + plugins.searchNavigation?.setGetBaseClassicNavItems(() => { + const productAccess: ProductAccess = this.data?.access ?? { + hasAppSearchAccess: false, + hasWorkplaceSearchAccess: false, + }; + + return buildBaseClassicNavItems({ productAccess }); + }); + }); + + // This is needed so that we can fetch product access for plugins + // that need to share the classic nav. This can be removed when we + // remove product access and ent-search apps. + plugins.searchNavigation.registerOnAppMountHandler(async () => { + return this.getInitialData(core.http); + }); + } plugins.licensing?.license$.subscribe((license) => { if (hasEnterpriseLicense(license)) { diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts index 744369ca1880..cff429f3934a 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts @@ -48,7 +48,7 @@ export const createIndexPipelineDefinitions = async ( const ingestPipeline = { _meta: { managed: true, - managed_by: 'Search', + managed_by: 'Elasticsearch', }, description: `Ingest pipeline for the '${indexName}' index`, id: `${indexName}`, diff --git a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts index 49e94e76cd7a..3e7a0777dad2 100644 --- a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts +++ b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts @@ -66,13 +66,13 @@ const connectors = [ }, ]; -describe('Enterprise Search search provider', () => { +describe('Search search provider', () => { const crawlerResult = { icon: 'crawlerIcon.svg', id: 'elastic-crawler', score: 75, title: 'Elastic Web Crawler', - type: 'Search', + type: 'Elasticsearch', url: { path: `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}/crawlers/new_crawler`, prependBasePath: true, @@ -84,7 +84,7 @@ describe('Enterprise Search search provider', () => { id: 'mongodb', score: 75, title: 'MongoDB', - type: 'Search', + type: 'Elasticsearch', url: { path: `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}/connectors/new_connector?connector_type=connector_client&service_type=mongodb`, prependBasePath: true, @@ -96,7 +96,7 @@ describe('Enterprise Search search provider', () => { id: 'mongodb', score: 75, title: 'MongoDB', - type: 'Search', + type: 'Elasticsearch', url: { path: `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}/connectors/new_connector?connector_type=native&service_type=mongodb`, prependBasePath: true, @@ -108,7 +108,7 @@ describe('Enterprise Search search provider', () => { id: '', score: 75, title: 'Customized connector', - type: 'Search', + type: 'Elasticsearch', url: { path: `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}/connectors/new_connector?connector_type=connector_client&service_type=`, prependBasePath: true, diff --git a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts index a5ce3aeb367d..6da354495ea0 100644 --- a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts +++ b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts @@ -62,9 +62,7 @@ export function toSearchResult({ id: serviceType, score, title: name, - type: i18n.translate('xpack.enterpriseSearch.searchProvider.type.name', { - defaultMessage: 'Search', - }), + type: 'Elasticsearch', url: { path: url ?? newUrl, prependBasePath: true, @@ -108,18 +106,15 @@ export function getSearchResultProvider( ] : []), ...(config.hasConnectors ? connectorTypes : []), - ...(config.canDeployEntSearch - ? [ - { - keywords: ['esre', 'search'], - name: i18n.translate('xpack.enterpriseSearch.searchProvider.aiSearch.name', { - defaultMessage: 'Search AI', - }), - serviceType: 'ai_search', - url: AI_SEARCH_PLUGIN.URL, - }, - ] - : []), + + { + keywords: ['esre', 'search'], + name: i18n.translate('xpack.enterpriseSearch.searchProvider.aiSearch.name', { + defaultMessage: 'Search AI', + }), + serviceType: 'ai_search', + url: AI_SEARCH_PLUGIN.URL, + }, ]; const result = services .map((service) => { diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json index 7b7556729a76..de98a647e0a9 100644 --- a/x-pack/plugins/enterprise_search/tsconfig.json +++ b/x-pack/plugins/enterprise_search/tsconfig.json @@ -83,6 +83,7 @@ "@kbn/security-plugin-types-common", "@kbn/core-security-server", "@kbn/core-security-server-mocks", - "@kbn/unsaved-changes-prompt" + "@kbn/unsaved-changes-prompt", + "@kbn/search-navigation", ] } diff --git a/x-pack/plugins/entity_manager/kibana.jsonc b/x-pack/plugins/entity_manager/kibana.jsonc index d5dadcf8fd2b..c18822d48ac0 100644 --- a/x-pack/plugins/entity_manager/kibana.jsonc +++ b/x-pack/plugins/entity_manager/kibana.jsonc @@ -8,9 +8,13 @@ "plugin": { "id": "entityManager", "configPath": ["xpack", "entityManager"], - "requiredPlugins": ["security", "encryptedSavedObjects", "licensing"], "browser": true, "server": true, + "requiredPlugins": [ + "security", + "encryptedSavedObjects", + "licensing" + ], "requiredBundles": [] } } diff --git a/x-pack/plugins/entity_manager/public/index.ts b/x-pack/plugins/entity_manager/public/index.ts index 85a9285b1692..73d23ad45e9c 100644 --- a/x-pack/plugins/entity_manager/public/index.ts +++ b/x-pack/plugins/entity_manager/public/index.ts @@ -16,6 +16,8 @@ export const plugin: PluginInitializer< return new Plugin(context); }; +export { EntityClient } from './lib/entity_client'; + export type { EntityManagerPublicPluginSetup, EntityManagerPublicPluginStart }; export type EntityManagerAppId = 'entityManager'; diff --git a/x-pack/plugins/entity_manager/public/lib/entity_client.test.ts b/x-pack/plugins/entity_manager/public/lib/entity_client.test.ts index 6679140314cb..33363dea05e7 100644 --- a/x-pack/plugins/entity_manager/public/lib/entity_client.test.ts +++ b/x-pack/plugins/entity_manager/public/lib/entity_client.test.ts @@ -39,7 +39,22 @@ describe('EntityClient', () => { }; const result = entityClient.asKqlFilter(entityLatest); - expect(result).toEqual('service.name: my-service'); + expect(result).toEqual('service.name: "my-service"'); + }); + + it('should return the kql filter when an indentity field value contain special characters', () => { + const entityLatest: EntityInstance = { + entity: { + ...commonEntityFields.entity, + identity_fields: ['host.name', 'foo.bar'], + }, + host: { + name: 'my-host:some-value:some-other-value', + }, + }; + + const result = entityClient.asKqlFilter(entityLatest); + expect(result).toEqual('host.name: "my-host:some-value:some-other-value"'); }); it('should return the kql filter when indentity_fields is composed by multiple fields', () => { @@ -56,7 +71,7 @@ describe('EntityClient', () => { }; const result = entityClient.asKqlFilter(entityLatest); - expect(result).toEqual('(service.name: my-service AND service.environment: staging)'); + expect(result).toEqual('(service.name: "my-service" AND service.environment: "staging")'); }); it('should ignore fields that are not present in the entity', () => { @@ -71,7 +86,7 @@ describe('EntityClient', () => { }; const result = entityClient.asKqlFilter(entityLatest); - expect(result).toEqual('host.name: my-host'); + expect(result).toEqual('host.name: "my-host"'); }); }); diff --git a/x-pack/plugins/entity_manager/public/lib/entity_client.ts b/x-pack/plugins/entity_manager/public/lib/entity_client.ts index 7132dc50330d..9db1c37888d4 100644 --- a/x-pack/plugins/entity_manager/public/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/public/lib/entity_client.ts @@ -95,7 +95,7 @@ export class EntityClient { const identityFieldsValue = this.getIdentityFieldsValue(entityInstance); const nodes: KueryNode[] = Object.entries(identityFieldsValue).map(([identityField, value]) => { - return nodeTypes.function.buildNode('is', identityField, value); + return nodeTypes.function.buildNode('is', identityField, `"${value}"`); }); if (nodes.length === 0) return ''; diff --git a/x-pack/plugins/entity_manager/public/plugin.ts b/x-pack/plugins/entity_manager/public/plugin.ts index 6d6d56a95b75..7ff6354c997e 100644 --- a/x-pack/plugins/entity_manager/public/plugin.ts +++ b/x-pack/plugins/entity_manager/public/plugin.ts @@ -22,16 +22,14 @@ export class Plugin implements EntityManagerPluginClass { } setup(core: CoreSetup) { - const entityClient = new EntityClient(core); return { - entityClient, + entityClient: new EntityClient(core), }; } start(core: CoreStart) { - const entityClient = new EntityClient(core); return { - entityClient, + entityClient: new EntityClient(core), }; } diff --git a/x-pack/plugins/entity_manager/public/types.ts b/x-pack/plugins/entity_manager/public/types.ts index 66499479299d..90d9026e8b9b 100644 --- a/x-pack/plugins/entity_manager/public/types.ts +++ b/x-pack/plugins/entity_manager/public/types.ts @@ -10,7 +10,6 @@ import type { EntityClient } from './lib/entity_client'; export interface EntityManagerPublicPluginSetup { entityClient: EntityClient; } - export interface EntityManagerPublicPluginStart { entityClient: EntityClient; } diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/cron_job.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/cron_job.ts index 7849dcdc73f5..06bc9dba9fce 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/cron_job.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/cron_job.ts @@ -13,7 +13,7 @@ import { commonEcsMetadata } from '../common/ecs_metadata'; export const builtInKubernetesCronJobEcsEntityDefinition: EntityDefinition = entityDefinitionSchema.parse({ id: `${BUILT_IN_ID_PREFIX}kubernetes_cron_job_ecs`, - filter: 'kubernetes.cronjob.uid : *', + filter: 'kubernetes.cronjob.name : *', managed: true, version: '0.1.0', name: 'Kubernetes CronJob from ECS data', @@ -21,7 +21,7 @@ export const builtInKubernetesCronJobEcsEntityDefinition: EntityDefinition = 'This definition extracts Kubernetes cron job entities from the Kubernetes integration data streams', type: 'k8s.cronjob.ecs', indexPatterns: commonEcsIndexPatterns, - identityFields: ['kubernetes.cronjob.uid'], + identityFields: ['kubernetes.cronjob.name'], displayNameTemplate: '{{kubernetes.cronjob.name}}', latest: { timestampField: '@timestamp', diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/daemon_set.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/daemon_set.ts index 5b57cdd6ae2f..c69a1c5c7e62 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/daemon_set.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/daemon_set.ts @@ -13,7 +13,7 @@ import { commonEcsMetadata } from '../common/ecs_metadata'; export const builtInKubernetesDaemonSetEcsEntityDefinition: EntityDefinition = entityDefinitionSchema.parse({ id: `${BUILT_IN_ID_PREFIX}kubernetes_daemon_set_ecs`, - filter: 'kubernetes.daemonset.uid : *', + filter: 'kubernetes.daemonset.name : *', managed: true, version: '0.1.0', name: 'Kubernetes DaemonSet from ECS data', diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/deployment.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/deployment.ts index d33c14db7e2c..f8e8f920e2f4 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/deployment.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/deployment.ts @@ -13,7 +13,7 @@ import { commonEcsIndexPatterns } from '../common/ecs_index_patterns'; export const builtInKubernetesDeploymentEcsEntityDefinition: EntityDefinition = entityDefinitionSchema.parse({ id: `${BUILT_IN_ID_PREFIX}kubernetes_deployment_ecs`, - filter: 'kubernetes.deployment.uid : *', + filter: 'kubernetes.deployment.name : *', managed: true, version: '0.1.0', name: 'Kubernetes Deployment from ECS data', @@ -21,7 +21,7 @@ export const builtInKubernetesDeploymentEcsEntityDefinition: EntityDefinition = 'This definition extracts Kubernetes deployment entities from the Kubernetes integration data streams', type: 'k8s.deployment.ecs', indexPatterns: commonEcsIndexPatterns, - identityFields: ['kubernetes.deployment.uid'], + identityFields: ['kubernetes.deployment.name'], displayNameTemplate: '{{kubernetes.deployment.name}}', latest: { timestampField: '@timestamp', diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/job.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/job.ts index 92c6d1325155..4efc41dc9ea8 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/job.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/job.ts @@ -13,7 +13,7 @@ import { commonEcsMetadata } from '../common/ecs_metadata'; export const builtInKubernetesJobEcsEntityDefinition: EntityDefinition = entityDefinitionSchema.parse({ id: `${BUILT_IN_ID_PREFIX}kubernetes_job_ecs`, - filter: 'kubernetes.job.uid : *', + filter: 'kubernetes.job.name : *', managed: true, version: '0.1.0', name: 'Kubernetes Job from ECS data', @@ -21,7 +21,7 @@ export const builtInKubernetesJobEcsEntityDefinition: EntityDefinition = 'This definition extracts Kubernetes job entities from the Kubernetes integration data streams', type: 'k8s.job.ecs', indexPatterns: commonEcsIndexPatterns, - identityFields: ['kubernetes.job.uid'], + identityFields: ['kubernetes.job.name'], displayNameTemplate: '{{kubernetes.job.name}}', latest: { timestampField: '@timestamp', diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/node.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/node.ts index f3fdcdfaf04b..033bd8313928 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/node.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/node.ts @@ -13,7 +13,7 @@ import { commonEcsMetadata } from '../common/ecs_metadata'; export const builtInKubernetesNodeEcsEntityDefinition: EntityDefinition = entityDefinitionSchema.parse({ id: `${BUILT_IN_ID_PREFIX}kubernetes_node_ecs`, - filer: 'kubernetes.node.uid : *', + filer: 'kubernetes.node.name : *', managed: true, version: '0.1.0', name: 'Kubernetes Node from ECS data', @@ -21,7 +21,7 @@ export const builtInKubernetesNodeEcsEntityDefinition: EntityDefinition = 'This definition extracts Kubernetes node entities from the Kubernetes integration data streams', type: 'k8s.node.ecs', indexPatterns: commonEcsIndexPatterns, - identityFields: ['kubernetes.node.uid'], + identityFields: ['kubernetes.node.name'], displayNameTemplate: '{{kubernetes.node.name}}', latest: { timestampField: '@timestamp', diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/pod.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/pod.ts index 7aa53da6e5a7..32029617d992 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/pod.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/pod.ts @@ -21,7 +21,7 @@ export const builtInKubernetesPodEcsEntityDefinition: EntityDefinition = 'This definition extracts Kubernetes pod entities from the Kubernetes integration data streams', type: 'k8s.pod.ecs', indexPatterns: commonEcsIndexPatterns, - identityFields: ['kubernetes.pod.name'], + identityFields: ['kubernetes.pod.uid'], displayNameTemplate: '{{kubernetes.pod.name}}', latest: { timestampField: '@timestamp', @@ -30,5 +30,12 @@ export const builtInKubernetesPodEcsEntityDefinition: EntityDefinition = frequency: '5m', }, }, - metadata: commonEcsMetadata, + metadata: [ + ...commonEcsMetadata, + { + source: 'kubernetes.pod.name', + destination: 'kubernetes.pod.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/replica_set.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/replica_set.ts index cc059c14979d..e9f534be8f1d 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/replica_set.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/replica_set.ts @@ -13,6 +13,7 @@ import { commonEcsIndexPatterns } from '../common/ecs_index_patterns'; export const builtInKubernetesReplicaSetEcsEntityDefinition: EntityDefinition = entityDefinitionSchema.parse({ id: `${BUILT_IN_ID_PREFIX}kubernetes_replica_set_ecs`, + filer: 'kubernetes.replicaset.name : *', managed: true, version: '0.1.0', name: 'Kubernetes ReplicaSet from ECS data', @@ -20,7 +21,7 @@ export const builtInKubernetesReplicaSetEcsEntityDefinition: EntityDefinition = 'This definition extracts Kubernetes replica set entities from the Kubernetes integration data streams', type: 'k8s.replicaset.ecs', indexPatterns: commonEcsIndexPatterns, - identityFields: ['kubernetes.replicaset.uid'], + identityFields: ['kubernetes.replicaset.name'], displayNameTemplate: '{{kubernetes.replicaset.name}}', latest: { timestampField: '@timestamp', diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/stateful_set.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/stateful_set.ts index 79f9d4489216..927c8a259276 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/stateful_set.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/ecs/stateful_set.ts @@ -13,7 +13,7 @@ import { commonEcsIndexPatterns } from '../common/ecs_index_patterns'; export const builtInKubernetesStatefulSetEcsEntityDefinition: EntityDefinition = entityDefinitionSchema.parse({ id: `${BUILT_IN_ID_PREFIX}kubernetes_stateful_set_ecs`, - filter: 'kubernetes.statefulset.uid : *', + filter: 'kubernetes.statefulset.name : *', managed: true, version: '0.1.0', name: 'Kubernetes StatefulSet from ECS data', @@ -21,7 +21,7 @@ export const builtInKubernetesStatefulSetEcsEntityDefinition: EntityDefinition = 'This definition extracts Kubernetes stateful set entities from the Kubernetes integration data streams', type: 'k8s.statefulset.ecs', indexPatterns: commonEcsIndexPatterns, - identityFields: ['kubernetes.statefulset.uid'], + identityFields: ['kubernetes.statefulset.name'], displayNameTemplate: '{{kubernetes.statefulset.name}}', latest: { timestampField: '@timestamp', diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/cluster.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/cluster.ts index 0ec244ec617f..71024cfb166f 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/cluster.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/cluster.ts @@ -30,5 +30,12 @@ export const builtInKubernetesClusterSemConvEntityDefinition: EntityDefinition = frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.cluster.name', + destination: 'k8s.cluster.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/cron_job.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/cron_job.ts index 6d677943976d..fff257bcf8e5 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/cron_job.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/cron_job.ts @@ -30,5 +30,12 @@ export const builtInKubernetesCronJobSemConvEntityDefinition: EntityDefinition = frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.cronjob.name', + destination: 'k8s.cronjob.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/daemon_set.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/daemon_set.ts index a4b61933ad31..cf89dcc30e67 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/daemon_set.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/daemon_set.ts @@ -30,5 +30,12 @@ export const builtInKubernetesDaemonSetSemConvEntityDefinition: EntityDefinition frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.daemonset.name', + destination: 'k8s.daemonset.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/deployment.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/deployment.ts index bdb3cb1cef59..05a89d67ead3 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/deployment.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/deployment.ts @@ -30,5 +30,12 @@ export const builtInKubernetesDeploymentSemConvEntityDefinition: EntityDefinitio frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.deployment.name', + destination: 'k8s.deployment.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/job.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/job.ts index b2e48cf7494f..557afa54ca55 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/job.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/job.ts @@ -30,5 +30,12 @@ export const builtInKubernetesJobSemConvEntityDefinition: EntityDefinition = frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.job.name', + destination: 'k8s.job.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/node.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/node.ts index 456f03042107..35bbed42e6a4 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/node.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/node.ts @@ -22,7 +22,7 @@ export const builtInKubernetesNodeSemConvEntityDefinition: EntityDefinition = type: 'k8s.node.otel', indexPatterns: commonOtelIndexPatterns, identityFields: ['k8s.node.uid'], - displayNameTemplate: '{{k8s.node.uid}}', + displayNameTemplate: '{{k8s.node.name}}', latest: { timestampField: '@timestamp', lookbackPeriod: '10m', @@ -30,5 +30,12 @@ export const builtInKubernetesNodeSemConvEntityDefinition: EntityDefinition = frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.node.name', + destination: 'k8s.node.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/pod.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/pod.ts index 6dc879d761dd..05d22163fbac 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/pod.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/pod.ts @@ -30,5 +30,12 @@ export const builtInKubernetesPodSemConvEntityDefinition: EntityDefinition = frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.pod.name', + destination: 'k8s.pod.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/replica_set.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/replica_set.ts index 47bad6bf8a64..ff4e33d789da 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/replica_set.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/replica_set.ts @@ -19,9 +19,9 @@ export const builtInKubernetesReplicaSetSemConvEntityDefinition: EntityDefinitio name: 'Kubernetes ReplicaSet from SemConv data', description: 'This definition extracts Kubernetes replica set entities using data collected with OpenTelemetry', - type: 'kubernetes_replica_set_semconv', + type: 'k8s.replicaset.otel', indexPatterns: commonOtelIndexPatterns, - identityFields: ['k8s.replicaset.name'], + identityFields: ['k8s.replicaset.uid'], displayNameTemplate: '{{k8s.replicaset.name}}', latest: { timestampField: '@timestamp', @@ -30,5 +30,12 @@ export const builtInKubernetesReplicaSetSemConvEntityDefinition: EntityDefinitio frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.replicaset.name', + destination: 'k8s.replicaset.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/stateful_set.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/stateful_set.ts index c61d7e5d965c..9c8b385f05c7 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/stateful_set.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/kubernetes/semconv/stateful_set.ts @@ -30,5 +30,12 @@ export const builtInKubernetesStatefulSetSemConvEntityDefinition: EntityDefiniti frequency: '5m', }, }, - metadata: commonOtelMetadata, + metadata: [ + ...commonOtelMetadata, + { + source: 'k8s.statefulset.name', + destination: 'k8s.statefulset.name', + aggregation: { type: 'top_value', sort: { '@timestamp': 'desc' } }, + }, + ], }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/errors/unknown_entity_type.ts b/x-pack/plugins/entity_manager/server/lib/entities/errors/unknown_entity_type.ts new file mode 100644 index 000000000000..5d29e24a1cca --- /dev/null +++ b/x-pack/plugins/entity_manager/server/lib/entities/errors/unknown_entity_type.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export class UnknownEntityType extends Error { + constructor(message: string) { + super(message); + this.name = 'UnknownEntityType'; + } +} diff --git a/x-pack/plugins/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/entity_manager/server/lib/entity_client.ts index 8bb51941092f..7045bee1fc53 100644 --- a/x-pack/plugins/entity_manager/server/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/server/lib/entity_client.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EntityDefinition, EntityDefinitionUpdate } from '@kbn/entities-schema'; +import { EntityV2, EntityDefinition, EntityDefinitionUpdate } from '@kbn/entities-schema'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; @@ -23,6 +23,9 @@ import { stopTransforms } from './entities/stop_transforms'; import { deleteIndices } from './entities/delete_index'; import { EntityDefinitionWithState } from './entities/types'; import { EntityDefinitionUpdateConflict } from './entities/errors/entity_definition_update_conflict'; +import { EntitySource, getEntityInstancesQuery } from './queries'; +import { mergeEntitiesList, runESQLQuery } from './queries/utils'; +import { UnknownEntityType } from './entities/errors/unknown_entity_type'; export class EntityClient { constructor( @@ -126,8 +129,6 @@ export class EntityClient { }); if (deleteData) { - // delete data with current user as system user does not have - // .entities privileges await deleteIndices(this.options.esClient, definition, this.options.logger); } } @@ -170,4 +171,114 @@ export class EntityClient { this.options.logger.info(`Stopping transforms for definition [${definition.id}]`); return stopTransforms(this.options.esClient, definition, this.options.logger); } + + async getEntitySources({ type }: { type: string }) { + const result = await this.options.esClient.search<EntitySource>({ + index: 'kibana_entity_definitions', + query: { + bool: { + must: { + term: { entity_type: type }, + }, + }, + }, + }); + + return result.hits.hits.map((hit) => hit._source) as EntitySource[]; + } + + async searchEntities({ + type, + start, + end, + metadataFields = [], + filters = [], + limit = 10, + }: { + type: string; + start: string; + end: string; + metadataFields?: string[]; + filters?: string[]; + limit?: number; + }) { + const sources = await this.getEntitySources({ type }); + if (sources.length === 0) { + throw new UnknownEntityType(`No sources found for entity type [${type}]`); + } + + return this.searchEntitiesBySources({ + sources, + start, + end, + metadataFields, + filters, + limit, + }); + } + + async searchEntitiesBySources({ + sources, + start, + end, + metadataFields = [], + filters = [], + limit = 10, + }: { + sources: EntitySource[]; + start: string; + end: string; + metadataFields?: string[]; + filters?: string[]; + limit?: number; + }) { + const entities = await Promise.all( + sources.map(async (source) => { + const mandatoryFields = [source.timestamp_field, ...source.identity_fields]; + const metaFields = [...metadataFields, ...source.metadata_fields]; + const { fields } = await this.options.esClient.fieldCaps({ + index: source.index_patterns, + fields: [...mandatoryFields, ...metaFields], + }); + + const sourceHasMandatoryFields = mandatoryFields.every((field) => !!fields[field]); + if (!sourceHasMandatoryFields) { + // we can't build entities without id fields so we ignore the source. + // filters should likely behave similarly. + this.options.logger.info( + `Ignoring source for type [${source.type}] with index_patterns [${source.index_patterns}] because some mandatory fields [${mandatoryFields}] are not mapped` + ); + return []; + } + + // but metadata field not being available is fine + const availableMetadataFields = metaFields.filter((field) => fields[field]); + + const query = getEntityInstancesQuery({ + source: { + ...source, + metadata_fields: availableMetadataFields, + filters: [...source.filters, ...filters], + }, + start, + end, + limit, + }); + this.options.logger.debug(`Entity query: ${query}`); + + const rawEntities = await runESQLQuery<EntityV2>({ + query, + esClient: this.options.esClient, + }); + + return rawEntities.map((entity) => { + entity['entity.id'] = source.identity_fields.map((field) => entity[field]).join(':'); + entity['entity.type'] = source.type; + return entity; + }); + }) + ).then((results) => results.flat()); + + return mergeEntitiesList(entities).slice(0, limit); + } } diff --git a/x-pack/plugins/entity_manager/server/lib/queries/index.test.ts b/x-pack/plugins/entity_manager/server/lib/queries/index.test.ts new file mode 100644 index 000000000000..539d20c46479 --- /dev/null +++ b/x-pack/plugins/entity_manager/server/lib/queries/index.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getEntityInstancesQuery } from '.'; + +describe('getEntityInstancesQuery', () => { + describe('getEntityInstancesQuery', () => { + it('generates a valid esql query', () => { + const query = getEntityInstancesQuery({ + source: { + type: 'service', + index_patterns: ['logs-*', 'metrics-*'], + identity_fields: ['service.name'], + metadata_fields: ['host.name'], + filters: [], + timestamp_field: 'custom_timestamp_field', + }, + limit: 5, + start: '2024-11-20T19:00:00.000Z', + end: '2024-11-20T20:00:00.000Z', + }); + + expect(query).toEqual( + 'FROM logs-*,metrics-*|' + + 'WHERE custom_timestamp_field >= "2024-11-20T19:00:00.000Z"|' + + 'WHERE custom_timestamp_field <= "2024-11-20T20:00:00.000Z"|' + + 'WHERE service.name IS NOT NULL|' + + 'STATS entity.last_seen_timestamp=MAX(custom_timestamp_field),metadata.host.name=VALUES(host.name) BY service.name|' + + 'SORT entity.last_seen_timestamp DESC|' + + 'LIMIT 5' + ); + }); + }); +}); diff --git a/x-pack/plugins/entity_manager/server/lib/queries/index.ts b/x-pack/plugins/entity_manager/server/lib/queries/index.ts new file mode 100644 index 000000000000..9fc7ae00c9aa --- /dev/null +++ b/x-pack/plugins/entity_manager/server/lib/queries/index.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from '@kbn/zod'; + +export const entitySourceSchema = z.object({ + type: z.string(), + timestamp_field: z.optional(z.string()).default('@timestamp'), + index_patterns: z.array(z.string()), + identity_fields: z.array(z.string()), + metadata_fields: z.array(z.string()), + filters: z.array(z.string()), +}); + +export type EntitySource = z.infer<typeof entitySourceSchema>; + +const sourceCommand = ({ source }: { source: EntitySource }) => { + let query = `FROM ${source.index_patterns}`; + + const esMetadataFields = source.metadata_fields.filter((field) => + ['_index', '_id'].includes(field) + ); + if (esMetadataFields.length) { + query += ` METADATA ${esMetadataFields.join(',')}`; + } + + return query; +}; + +const filterCommands = ({ + source, + start, + end, +}: { + source: EntitySource; + start: string; + end: string; +}) => { + const commands = [ + `WHERE ${source.timestamp_field} >= "${start}"`, + `WHERE ${source.timestamp_field} <= "${end}"`, + ]; + + source.identity_fields.forEach((field) => { + commands.push(`WHERE ${field} IS NOT NULL`); + }); + + source.filters.forEach((filter) => { + commands.push(`WHERE ${filter}`); + }); + + return commands; +}; + +const statsCommand = ({ source }: { source: EntitySource }) => { + const aggs = [ + // default 'last_seen' attribute + `entity.last_seen_timestamp=MAX(${source.timestamp_field})`, + ...source.metadata_fields + .filter((field) => !source.identity_fields.some((idField) => idField === field)) + .map((field) => `metadata.${field}=VALUES(${field})`), + ]; + + return `STATS ${aggs.join(',')} BY ${source.identity_fields.join(',')}`; +}; + +export function getEntityInstancesQuery({ + source, + limit, + start, + end, +}: { + source: EntitySource; + limit: number; + start: string; + end: string; +}): string { + const commands = [ + sourceCommand({ source }), + ...filterCommands({ source, start, end }), + statsCommand({ source }), + `SORT entity.last_seen_timestamp DESC`, + `LIMIT ${limit}`, + ]; + + return commands.join('|'); +} diff --git a/x-pack/plugins/entity_manager/server/lib/queries/utils.test.ts b/x-pack/plugins/entity_manager/server/lib/queries/utils.test.ts new file mode 100644 index 000000000000..5d5702567172 --- /dev/null +++ b/x-pack/plugins/entity_manager/server/lib/queries/utils.test.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mergeEntitiesList } from './utils'; + +describe('mergeEntitiesList', () => { + describe('mergeEntitiesList', () => { + it('merges entities on entity.id', () => { + const entities = [ + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T18:00:00.000Z', + 'entity.type': 'service', + }, + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T18:00:00.000Z', + 'entity.type': 'service', + }, + ]; + + const mergedEntities = mergeEntitiesList(entities); + expect(mergedEntities.length).toEqual(1); + expect(mergedEntities[0]).toEqual({ + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T18:00:00.000Z', + 'entity.type': 'service', + }); + }); + + it('merges metadata fields', () => { + const entities = [ + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T18:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': 'host-1', + 'metadata.agent.name': 'agent-1', + 'metadata.service.environment': ['dev', 'staging'], + 'metadata.only_in_record_1': 'foo', + }, + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T18:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': ['host-2', 'host-3'], + 'metadata.agent.name': 'agent-2', + 'metadata.service.environment': 'prod', + 'metadata.only_in_record_2': 'bar', + }, + ]; + + const mergedEntities = mergeEntitiesList(entities); + expect(mergedEntities.length).toEqual(1); + expect(mergedEntities[0]).toEqual({ + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T18:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': ['host-1', 'host-2', 'host-3'], + 'metadata.agent.name': ['agent-1', 'agent-2'], + 'metadata.service.environment': ['dev', 'staging', 'prod'], + 'metadata.only_in_record_1': 'foo', + 'metadata.only_in_record_2': 'bar', + }); + }); + + it('picks most recent timestamp when merging', () => { + const entities = [ + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T18:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': 'host-1', + }, + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T20:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': 'host-2', + }, + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T16:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': 'host-3', + }, + ]; + + const mergedEntities = mergeEntitiesList(entities); + expect(mergedEntities.length).toEqual(1); + expect(mergedEntities[0]).toEqual({ + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T20:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': ['host-1', 'host-2', 'host-3'], + }); + }); + + it('deduplicates metadata values', () => { + const entities = [ + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T18:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': 'host-1', + }, + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T20:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': 'host-2', + }, + { + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T16:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': ['host-1', 'host-2'], + }, + ]; + + const mergedEntities = mergeEntitiesList(entities); + expect(mergedEntities.length).toEqual(1); + expect(mergedEntities[0]).toEqual({ + 'entity.id': 'foo', + 'entity.last_seen_timestamp': '2024-11-20T20:00:00.000Z', + 'entity.type': 'service', + 'metadata.host.name': ['host-1', 'host-2'], + }); + }); + }); +}); diff --git a/x-pack/plugins/entity_manager/server/lib/queries/utils.ts b/x-pack/plugins/entity_manager/server/lib/queries/utils.ts new file mode 100644 index 000000000000..68f5b0f11aff --- /dev/null +++ b/x-pack/plugins/entity_manager/server/lib/queries/utils.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { EntityV2 } from '@kbn/entities-schema'; +import { ESQLSearchResponse } from '@kbn/es-types'; +import { uniq } from 'lodash'; + +function mergeEntities(entity1: EntityV2, entity2: EntityV2): EntityV2 { + const merged: EntityV2 = { + ...entity1, + 'entity.last_seen_timestamp': new Date( + Math.max( + Date.parse(entity1['entity.last_seen_timestamp']), + Date.parse(entity2['entity.last_seen_timestamp']) + ) + ).toISOString(), + }; + + for (const [key, value] of Object.entries(entity2).filter(([_key]) => + _key.startsWith('metadata.') + )) { + if (merged[key]) { + merged[key] = uniq([ + ...(Array.isArray(merged[key]) ? merged[key] : [merged[key]]), + ...(Array.isArray(value) ? value : [value]), + ]); + } else { + merged[key] = value; + } + } + return merged; +} + +export function mergeEntitiesList(entities: EntityV2[]): EntityV2[] { + const instances: { [key: string]: EntityV2 } = {}; + + for (let i = 0; i < entities.length; i++) { + const entity = entities[i]; + const id = entity['entity.id']; + + if (instances[id]) { + instances[id] = mergeEntities(instances[id], entity); + } else { + instances[id] = entity; + } + } + + return Object.values(instances); +} + +export async function runESQLQuery<T>({ + esClient, + query, +}: { + esClient: ElasticsearchClient; + query: string; +}): Promise<T[]> { + const esqlResponse = (await esClient.esql.query( + { + query, + format: 'json', + }, + { querystring: { drop_null_columns: true } } + )) as unknown as ESQLSearchResponse; + + const documents = esqlResponse.values.map((row) => + row.reduce<Record<string, any>>((acc, value, index) => { + const column = esqlResponse.columns[index]; + + if (!column) { + return acc; + } + + // Removes the type suffix from the column name + const name = column.name.replace(/\.(text|keyword)$/, ''); + if (!acc[name]) { + acc[name] = value; + } + + return acc; + }, {}) + ) as T[]; + + return documents; +} diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/check.ts b/x-pack/plugins/entity_manager/server/routes/enablement/check.ts index 5373ac9df50f..100b0ac382dc 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/check.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/check.ts @@ -45,13 +45,11 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ */ export const checkEntityDiscoveryEnabledRoute = createEntityManagerServerRoute({ endpoint: 'GET /internal/entities/managed/enablement', - options: { - security: { - authz: { - enabled: false, - reason: - 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', - }, + security: { + authz: { + enabled: false, + reason: + 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', }, }, handler: async ({ response, logger, server }) => { diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts index a71a317045c4..1c8755c682f7 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts @@ -44,13 +44,11 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ */ export const disableEntityDiscoveryRoute = createEntityManagerServerRoute({ endpoint: 'DELETE /internal/entities/managed/enablement', - options: { - security: { - authz: { - enabled: false, - reason: - 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', - }, + security: { + authz: { + enabled: false, + reason: + 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', }, }, params: z.object({ diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts index 562b798a598a..6ddb65804b90 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts @@ -63,13 +63,11 @@ import { startTransforms } from '../../lib/entities/start_transforms'; */ export const enableEntityDiscoveryRoute = createEntityManagerServerRoute({ endpoint: 'PUT /internal/entities/managed/enablement', - options: { - security: { - authz: { - enabled: false, - reason: - 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', - }, + security: { + authz: { + enabled: false, + reason: + 'This endpoint leverages the security plugin to evaluate the privileges needed as part of its core flow', }, }, params: z.object({ diff --git a/x-pack/plugins/entity_manager/server/routes/entities/create.ts b/x-pack/plugins/entity_manager/server/routes/entities/create.ts index a22916f3e69f..fc0f4c6e9a1e 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/create.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/create.ts @@ -50,13 +50,11 @@ import { canManageEntityDefinition } from '../../lib/auth'; */ export const createEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'POST /internal/entities/definition', - options: { - security: { - authz: { - enabled: false, - reason: - 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', - }, + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', }, }, params: z.object({ diff --git a/x-pack/plugins/entity_manager/server/routes/entities/delete.ts b/x-pack/plugins/entity_manager/server/routes/entities/delete.ts index ff5b9624dbb3..ec5b4ada3039 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/delete.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/delete.ts @@ -52,13 +52,11 @@ import { canDeleteEntityDefinition } from '../../lib/auth/privileges'; */ export const deleteEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'DELETE /internal/entities/definition/{id}', - options: { - security: { - authz: { - enabled: false, - reason: - 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', - }, + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', }, }, params: z.object({ diff --git a/x-pack/plugins/entity_manager/server/routes/entities/get.ts b/x-pack/plugins/entity_manager/server/routes/entities/get.ts index f22e0890e60a..738ed0f44064 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/get.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/get.ts @@ -50,13 +50,11 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ */ export const getEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'GET /internal/entities/definition/{id?}', - options: { - security: { - authz: { - enabled: false, - reason: - 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', - }, + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', }, }, params: z.object({ diff --git a/x-pack/plugins/entity_manager/server/routes/entities/index.ts b/x-pack/plugins/entity_manager/server/routes/entities/index.ts index 539423c6a5e1..52300ab2601b 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/index.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/index.ts @@ -10,6 +10,7 @@ import { deleteEntityDefinitionRoute } from './delete'; import { getEntityDefinitionRoute } from './get'; import { resetEntityDefinitionRoute } from './reset'; import { updateEntityDefinitionRoute } from './update'; +import { searchEntitiesRoute, searchEntitiesPreviewRoute } from '../v2/search'; export const entitiesRoutes = { ...createEntityDefinitionRoute, @@ -17,4 +18,6 @@ export const entitiesRoutes = { ...getEntityDefinitionRoute, ...resetEntityDefinitionRoute, ...updateEntityDefinitionRoute, + ...searchEntitiesRoute, + ...searchEntitiesPreviewRoute, }; diff --git a/x-pack/plugins/entity_manager/server/routes/entities/reset.ts b/x-pack/plugins/entity_manager/server/routes/entities/reset.ts index ab4ba29fa148..5da7a608aed8 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/reset.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/reset.ts @@ -25,13 +25,11 @@ import { stopTransforms } from '../../lib/entities/stop_transforms'; export const resetEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'POST /internal/entities/definition/{id}/_reset', - options: { - security: { - authz: { - enabled: false, - reason: - 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', - }, + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', }, }, params: z.object({ diff --git a/x-pack/plugins/entity_manager/server/routes/entities/update.ts b/x-pack/plugins/entity_manager/server/routes/entities/update.ts index f1118028cda9..4f23486375f9 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/update.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/update.ts @@ -54,13 +54,11 @@ import { canManageEntityDefinition } from '../../lib/auth'; */ export const updateEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'PATCH /internal/entities/definition/{id}', - options: { - security: { - authz: { - enabled: false, - reason: - 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', - }, + security: { + authz: { + enabled: false, + reason: + 'This endpoint mainly manages Elasticsearch resources using the requesting users credentials', }, }, params: z.object({ diff --git a/x-pack/plugins/entity_manager/server/routes/v2/search.ts b/x-pack/plugins/entity_manager/server/routes/v2/search.ts new file mode 100644 index 000000000000..0b975da748a8 --- /dev/null +++ b/x-pack/plugins/entity_manager/server/routes/v2/search.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { z } from '@kbn/zod'; +import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { entitySourceSchema } from '../../lib/queries'; +import { UnknownEntityType } from '../../lib/entities/errors/unknown_entity_type'; + +export const searchEntitiesRoute = createEntityManagerServerRoute({ + endpoint: 'POST /internal/entities/v2/_search', + params: z.object({ + body: z.object({ + type: z.string(), + metadata_fields: z.optional(z.array(z.string())).default([]), + filters: z.optional(z.array(z.string())).default([]), + start: z + .optional(z.string()) + .default(() => moment().subtract(5, 'minutes').toISOString()) + .refine((val) => moment(val).isValid(), { + message: 'start should be a date in ISO format', + }), + end: z + .optional(z.string()) + .default(() => moment().toISOString()) + .refine((val) => moment(val).isValid(), { + message: 'start should be a date in ISO format', + }), + limit: z.optional(z.number()).default(10), + }), + }), + handler: async ({ request, response, params, logger, getScopedClient }) => { + try { + const { type, start, end, limit, filters, metadata_fields: metadataFields } = params.body; + + const client = await getScopedClient({ request }); + const entities = await client.searchEntities({ + type, + filters, + metadataFields, + start, + end, + limit, + }); + + return response.ok({ body: { entities } }); + } catch (e) { + logger.error(e); + + if (e instanceof UnknownEntityType) { + return response.notFound({ body: e }); + } + + return response.customError({ body: e, statusCode: 500 }); + } + }, +}); + +export const searchEntitiesPreviewRoute = createEntityManagerServerRoute({ + endpoint: 'POST /internal/entities/v2/_search/preview', + params: z.object({ + body: z.object({ + sources: z.array(entitySourceSchema), + start: z + .optional(z.string()) + .default(() => moment().subtract(5, 'minutes').toISOString()) + .refine((val) => moment(val).isValid(), { + message: 'start should be a date in ISO format', + }), + end: z + .optional(z.string()) + .default(() => moment().toISOString()) + .refine((val) => moment(val).isValid(), { + message: 'start should be a date in ISO format', + }), + limit: z.optional(z.number()).default(10), + }), + }), + handler: async ({ request, response, params, logger, getScopedClient }) => { + const { sources, start, end, limit } = params.body; + + const client = await getScopedClient({ request }); + const entities = await client.searchEntitiesBySources({ + sources, + start, + end, + limit, + }); + + return response.ok({ body: { entities } }); + }, +}); diff --git a/x-pack/plugins/entity_manager/tsconfig.json b/x-pack/plugins/entity_manager/tsconfig.json index 34c57a27dd82..2ef8551f373f 100644 --- a/x-pack/plugins/entity_manager/tsconfig.json +++ b/x-pack/plugins/entity_manager/tsconfig.json @@ -35,5 +35,6 @@ "@kbn/encrypted-saved-objects-plugin", "@kbn/licensing-plugin", "@kbn/core-saved-objects-server", + "@kbn/es-types", ] } diff --git a/x-pack/plugins/fields_metadata/public/hooks/use_fields_metadata/use_fields_metadata.test.ts b/x-pack/plugins/fields_metadata/public/hooks/use_fields_metadata/use_fields_metadata.test.ts index eaae7c5542c0..8f69c11a1043 100644 --- a/x-pack/plugins/fields_metadata/public/hooks/use_fields_metadata/use_fields_metadata.test.ts +++ b/x-pack/plugins/fields_metadata/public/hooks/use_fields_metadata/use_fields_metadata.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { createUseFieldsMetadataHook, UseFieldsMetadataParams } from './use_fields_metadata'; import { FindFieldsMetadataResponsePayload } from '../../../common/latest'; @@ -46,12 +46,12 @@ describe('useFieldsMetadata', () => { it('should return the fieldsMetadata value from the API', async () => { fieldsMetadataClient.find.mockResolvedValue(mockedFieldsMetadataResponse); - const { result, waitForNextUpdate } = renderHook(() => useFieldsMetadata()); + const { result } = renderHook(() => useFieldsMetadata()); expect(result.current.loading).toBe(true); expect(result.current.fieldsMetadata).toEqual(undefined); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { fieldsMetadata, loading, error } = result.current; expect(fieldsMetadata).toEqual(fields); @@ -68,21 +68,17 @@ describe('useFieldsMetadata', () => { dataset: 'dataset_name', }; - const { waitForNextUpdate } = renderHook(() => useFieldsMetadata(params)); + renderHook(() => useFieldsMetadata(params)); - await waitForNextUpdate(); - - expect(fieldsMetadataClient.find).toHaveBeenCalledWith(params); + await waitFor(() => expect(fieldsMetadataClient.find).toHaveBeenCalledWith(params)); }); it('should return an error if the API call fails', async () => { const error = new Error('Fetch fields metadata Failed'); fieldsMetadataClient.find.mockRejectedValueOnce(error); - const { result, waitForNextUpdate } = renderHook(() => useFieldsMetadata()); - - await waitForNextUpdate(); + const { result } = renderHook(() => useFieldsMetadata()); - expect(result.current.error?.message).toMatch(error.message); + await waitFor(() => expect(result.current.error?.message).toMatch(error.message)); }); }); diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index b89577ed7c36..c3baf3b6e175 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -38,7 +38,5 @@ export const LICENSE_FOR_SCHEDULE_UPGRADE = 'platinum'; export const DEFAULT_MAX_AGENT_POLICIES_WITH_INACTIVITY_TIMEOUT = 750; -export const AGENTLESS_POLICY_ID = 'agentless'; // the policy id defined here: https://github.com/elastic/project-controller/blob/main/internal/project/security/security_kibana_config.go#L86 - export const AGENT_LOG_LEVELS = ['error', 'warning', 'info', 'debug'] as const; export const DEFAULT_LOG_LEVEL = 'info' as const; diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts index 8ebfe005960c..f51e85f22c52 100644 --- a/x-pack/plugins/fleet/common/constants/index.ts +++ b/x-pack/plugins/fleet/common/constants/index.ts @@ -25,6 +25,7 @@ export * from './message_signing_keys'; export * from './locators'; export * from './secrets'; export * from './uninstall_token'; +export * from './space_awareness'; // TODO: This is the default `index.max_result_window` ES setting, which dictates // the maximum amount of results allowed to be returned from a search. It's possible diff --git a/x-pack/plugins/fleet/common/constants/mappings.ts b/x-pack/plugins/fleet/common/constants/mappings.ts index 6499da7f86cc..f3d2b200cac5 100644 --- a/x-pack/plugins/fleet/common/constants/mappings.ts +++ b/x-pack/plugins/fleet/common/constants/mappings.ts @@ -72,6 +72,7 @@ export const PACKAGE_POLICIES_MAPPINGS = { properties: {}, }, secret_references: { properties: { id: { type: 'keyword' } } }, + supports_agentless: { type: 'boolean' }, revision: { type: 'integer' }, updated_at: { type: 'date' }, updated_by: { type: 'keyword' }, diff --git a/x-pack/plugins/fleet/common/constants/space_awareness.ts b/x-pack/plugins/fleet/common/constants/space_awareness.ts new file mode 100644 index 000000000000..c89d0f4cddb0 --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/space_awareness.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * The identifier in a saved object's `namespaces` array when it is shared to an unknown space (e.g., one that the end user is not authorized to see). + */ +export const UNKNOWN_SPACE = '?'; diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts index 730bcb393e98..07fd2caf0f06 100644 --- a/x-pack/plugins/fleet/common/experimental_features.ts +++ b/x-pack/plugins/fleet/common/experimental_features.ts @@ -21,7 +21,6 @@ const _allowedExperimentalValues = { kafkaOutput: true, outputSecretsStorage: true, remoteESOutput: true, - agentless: false, enableStrictKQLValidation: true, subfeaturePrivileges: false, advancedPolicySettings: true, diff --git a/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap b/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap index 7c549b030a33..f2d4067be434 100644 --- a/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap +++ b/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap @@ -238,6 +238,7 @@ Object { "policy_ids": Array [ "policy123", ], + "supports_agentless": undefined, "vars": undefined, } `; diff --git a/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts index 14c7b3888f77..5e39f9495948 100644 --- a/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts +++ b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.ts @@ -49,6 +49,7 @@ export interface SimplifiedPackagePolicy { description?: string; vars?: SimplifiedVars; inputs?: SimplifiedInputs; + supports_agentless?: boolean | null; } export interface FormattedPackagePolicy extends Omit<PackagePolicy, 'inputs' | 'vars'> { @@ -154,18 +155,19 @@ export function simplifiedPackagePolicytoNewPackagePolicy( description, inputs = {}, vars: packageLevelVars, + supports_agentless: supportsAgentless, } = data; - const packagePolicy = packageToPackagePolicy( - packageInfo, - policyId && isEmpty(policyIds) ? policyId : policyIds, - namespace, - name, - description - ); - - if (outputId) { - packagePolicy.output_id = outputId; - } + const packagePolicy = { + ...packageToPackagePolicy( + packageInfo, + policyId && isEmpty(policyIds) ? policyId : policyIds, + namespace, + name, + description + ), + supports_agentless: supportsAgentless, + output_id: outputId, + }; if (packagePolicy.package && options?.experimental_data_stream_features) { packagePolicy.package.experimental_data_stream_features = diff --git a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx index a4b41979840b..55a5885fa4e6 100644 --- a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx +++ b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx @@ -40,7 +40,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ api_field: { name: 'agent_limits_go_max_procs', }, - schema: z.number().int().min(0).default(0), + schema: z.number().int().min(0), }, { name: 'agent.download.timeout', @@ -59,7 +59,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ api_field: { name: 'agent_download_timeout', }, - schema: zodStringWithDurationValidation.default('2h'), + schema: zodStringWithDurationValidation, }, { name: 'agent.download.target_directory', @@ -103,7 +103,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ ), learnMoreLink: 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', - schema: zodStringWithDurationValidation.default('30s'), + schema: zodStringWithDurationValidation, }, { name: 'agent.logging.level', @@ -124,4 +124,76 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ 'https://www.elastic.co/guide/en/fleet/current/agent-policy.html#agent-policy-log-level', schema: z.enum(AGENT_LOG_LEVELS).default(DEFAULT_LOG_LEVEL), }, + { + name: 'agent.logging.to_files', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingToFilesTitle', { + defaultMessage: 'Agent logging to files', + }), + description: ( + <FormattedMessage + id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingToFilesDescription" + defaultMessage="Enables logging to rotating files." + /> + ), + api_field: { + name: 'agent_logging_to_files', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.boolean().default(true), + }, + { + name: 'agent.logging.files.rotateeverybytes', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileSizeTitle', { + defaultMessage: 'Agent logging file size limit', + }), + description: ( + <FormattedMessage + id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileSizeDescription" + defaultMessage="Configure log file size limit in bytes. If limit is reached, log file will be automatically rotated." + /> + ), + api_field: { + name: 'agent_logging_files_rotateeverybytes', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.number().int().min(0), + }, + { + name: 'agent.logging.files.keepfiles', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileLimitTitle', { + defaultMessage: 'Agent logging number of files', + }), + description: ( + <FormattedMessage + id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileLimitDescription" + defaultMessage="Number of rotated log files to keep. Oldest files will be deleted first." + /> + ), + api_field: { + name: 'agent_logging_files_keepfiles', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.number().int().min(0), + }, + { + name: 'agent.logging.files.interval', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileIntervalitle', { + defaultMessage: 'Agent logging number of files', + }), + description: ( + <FormattedMessage + id="xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileIntervalescription" + defaultMessage="Enable log file rotation on time intervals in addition to size-based rotation, i.e. 24h, 7d." + /> + ), + api_field: { + name: 'agent_logging_files_interval', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: zodStringWithDurationValidation, + }, ]; diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index ba1a0b182af7..fb19953a1f73 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -185,6 +185,18 @@ export interface FullAgentPolicy { uninstall_token_hash: string; signing_key: string; }; + logging?: { + level?: string; + to_files?: boolean; + files?: { + rotateeverybytes?: number; + keepfiles?: number; + interval?: string; + }; + }; + limits?: { + go_max_procs?: number; + }; }; secret_references?: PolicySecretReference[]; signed?: { @@ -260,7 +272,6 @@ export interface FleetServerPolicy { export interface AgentlessApiResponse { id: string; - region_id: string; } // Definitions for agent policy outputs endpoints diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index 354834d2571d..5c98729ff6cd 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -93,6 +93,7 @@ export interface NewPackagePolicy { [key: string]: any; }; overrides?: { inputs?: { [key: string]: any } } | null; + supports_agentless?: boolean | null; } export interface UpdatePackagePolicy extends NewPackagePolicy { diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index b990e5367bb4..57e0cc4f735f 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -220,6 +220,8 @@ export interface GetAgentStatusResponse { export interface GetAgentIncomingDataRequest { query: { agentsIds: string[]; + pkgName?: string; + pkgVersion?: string; previewData?: boolean; }; } diff --git a/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts b/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts index 271d2ba7b871..1c1191e32ded 100644 --- a/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/package_policy.cy.ts @@ -4,36 +4,46 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { EXISTING_HOSTS_TAB } from '../screens/fleet'; +import { + ADD_INTEGRATION_POLICY_BTN, + CREATE_PACKAGE_POLICY_SAVE_BTN, + POLICY_EDITOR, +} from '../screens/integrations'; +import { CONFIRM_MODAL } from '../screens/navigation'; import { login } from '../tasks/login'; -describe('Edit package policy', () => { - const policyConfig = { - id: 'policy-1', - name: 'fleet_server-1', - namespace: 'default', - package: { name: 'fleet_server', title: 'Fleet Server', version: '1.1.0' }, - enabled: true, - policy_id: 'fleet-server-policy', - policy_ids: ['fleet-server-policy'], - output_id: 'fleet-default-output', - inputs: [ - { - type: 'fleet-server', - policy_template: 'fleet_server', - enabled: true, - streams: [], - vars: { - host: { value: ['0.0.0.0'], type: 'text' }, - port: { value: [8220], type: 'integer' }, - max_connections: { type: 'integer' }, - custom: { value: '', type: 'yaml' }, - }, - compiled_input: { server: { port: 8220, host: '0.0.0.0' } }, - }, - ], - }; +describe('Package policy', () => { beforeEach(() => { login(); + }); + + it('should edit package policy', () => { + const policyConfig = { + id: 'policy-1', + name: 'fleet_server-1', + namespace: 'default', + package: { name: 'fleet_server', title: 'Fleet Server', version: '1.1.0' }, + enabled: true, + policy_id: 'fleet-server-policy', + policy_ids: ['fleet-server-policy'], + output_id: 'fleet-default-output', + inputs: [ + { + type: 'fleet-server', + policy_template: 'fleet_server', + enabled: true, + streams: [], + vars: { + host: { value: ['0.0.0.0'], type: 'text' }, + port: { value: [8220], type: 'integer' }, + max_connections: { type: 'integer' }, + custom: { value: '', type: 'yaml' }, + }, + compiled_input: { server: { port: 8220, host: '0.0.0.0' } }, + }, + ], + }; cy.intercept('/api/fleet/package_policies/policy-1', { item: policyConfig, @@ -111,9 +121,7 @@ describe('Edit package policy', () => { status: 'not_installed', }, }); - }); - it('should edit package policy', () => { cy.visit('/app/fleet/policies/fleet-server-policy/edit-integration/policy-1'); cy.getBySel('packagePolicyDescriptionInput').clear().type('desc'); @@ -128,4 +136,27 @@ describe('Edit package policy', () => { expect(interception.request.body.description).to.equal('desc'); }); }); + + it('should create a new orphaned package policy', () => { + cy.visit('/app/integrations/detail/system'); + cy.getBySel(ADD_INTEGRATION_POLICY_BTN).click(); + cy.getBySel(EXISTING_HOSTS_TAB).click(); + cy.getBySel(POLICY_EDITOR.AGENT_POLICY_SELECT).should('exist'); + cy.getBySel(POLICY_EDITOR.AGENT_POLICY_CLEAR).should('not.exist'); + cy.getBySel(POLICY_EDITOR.POLICY_NAME_INPUT).clear().type('system-orphaned-test'); + + cy.intercept({ + method: 'POST', + url: '/api/fleet/package_policies', + }).as('createPackagePolicy'); + + cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).should('be.enabled').click(); + cy.getBySel(CONFIRM_MODAL.CONFIRM_BUTTON).click(); + + cy.wait('@createPackagePolicy').then((interception) => { + expect(interception.request.body.name).to.equal('system-orphaned-test'); + expect(interception.request.body.policy_id).to.equal(undefined); + expect(interception.request.body.policy_ids).to.deep.equal([]); + }); + }); }); diff --git a/x-pack/plugins/fleet/cypress/screens/integrations.ts b/x-pack/plugins/fleet/cypress/screens/integrations.ts index 1a31d2dc5de3..8b29a95265ff 100644 --- a/x-pack/plugins/fleet/cypress/screens/integrations.ts +++ b/x-pack/plugins/fleet/cypress/screens/integrations.ts @@ -39,6 +39,7 @@ export const POLICY_EDITOR = { POLICY_NAME_INPUT: 'packagePolicyNameInput', DATASET_SELECT: 'datasetComboBox', AGENT_POLICY_SELECT: 'agentPolicyMultiSelect', + AGENT_POLICY_CLEAR: 'comboBoxClearButton', INSPECT_PIPELINES_BTN: 'datastreamInspectPipelineBtn', EDIT_MAPPINGS_BTN: 'datastreamEditMappingsBtn', CREATE_MAPPINGS_BTN: 'datastreamAddCustomComponentTemplateBtn', diff --git a/x-pack/plugins/fleet/dev_docs/space_awareness.md b/x-pack/plugins/fleet/dev_docs/space_awareness.md index fc4ce3a0acd0..90371d1f8bb0 100644 --- a/x-pack/plugins/fleet/dev_docs/space_awareness.md +++ b/x-pack/plugins/fleet/dev_docs/space_awareness.md @@ -15,7 +15,7 @@ xpack.fleet.enableExperimental: ['useSpaceAwareness', 'subfeaturePrivileges'] After the feature flag is enabled you will have to do another step to opt-in for the feature, that call will migrate the current space agnostic saved objects to new space aware saved objects. ```shell -curl -u elastic:changeme -XPOST "http://localhost:5601/internal/fleet/enable_space_awareness" -H "kbn-xsrf: reporting" -H 'elastic-api-version: 1' +curl -u elastic:changeme -XPOST "http://localhost:5601/internal/fleet/enable_space_awareness" -H "kbn-xsrf: reporting" -H 'elastic-api-version: 1' -H 'x-elastic-internal-origin: 1' ``` ## Space aware entities in Fleet diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx index 8bafc124ec36..768f9f913607 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx @@ -102,6 +102,35 @@ describe('ConfiguredSettings', () => { expect(mockUpdateAdvancedSettingsHasErrors).toHaveBeenCalledWith(true); }); + it('should render boolean field using checkbox', () => { + const result = render([ + { + name: 'agent.logging.to_files', + title: 'Agent logging to files', + description: 'Description', + learnMoreLink: '', + api_field: { + name: 'agent_logging_to_files', + }, + schema: z.boolean().default(false), + }, + ]); + + expect(result.getByText('Agent logging to files')).not.toBeNull(); + const input = result.getByTestId('configuredSetting-agent.logging.to_files'); + expect(input).not.toBeChecked(); + + act(() => { + fireEvent.click(input); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith( + expect.objectContaining({ + advanced_settings: expect.objectContaining({ agent_logging_to_files: true }), + }) + ); + }); + it('should not render field if hidden', () => { const result = render([ { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx index 7ebf80141c55..e93dd0b6cdd8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx @@ -7,7 +7,9 @@ import { ZodFirstPartyTypeKind } from '@kbn/zod'; import React from 'react'; -import { EuiFieldNumber, EuiFieldText, EuiSelect } from '@elastic/eui'; +import { EuiCheckbox, EuiFieldNumber, EuiFieldText, EuiSelect } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; import type { SettingsConfig } from '../../../../../common/settings/types'; @@ -68,7 +70,7 @@ settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodEnum, ({ disabled, ...sett <SettingsFieldWrapper disabled={disabled} settingsConfig={settingsConfig} - typeName={ZodFirstPartyTypeKind.ZodString} + typeName={ZodFirstPartyTypeKind.ZodEnum} renderItem={({ fieldKey, fieldValue, handleChange }: any) => ( <EuiSelect data-test-subj={fieldKey} @@ -86,6 +88,30 @@ settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodEnum, ({ disabled, ...sett ); }); +settingComponentRegistry.set( + ZodFirstPartyTypeKind.ZodBoolean, + ({ disabled, ...settingsConfig }) => { + return ( + <SettingsFieldWrapper + disabled={disabled} + settingsConfig={settingsConfig} + typeName={ZodFirstPartyTypeKind.ZodBoolean} + renderItem={({ fieldKey, fieldValue, handleChange }: any) => ( + <EuiCheckbox + data-test-subj={fieldKey} + id={fieldKey} + label={i18n.translate('xpack.fleet.configuredSettings.genericCheckboxLabel', { + defaultMessage: 'Enable', + })} + checked={fieldValue} + onChange={handleChange} + /> + )} + /> + ); + } +); + export function ConfiguredSettings({ configuredSettings, disabled, @@ -101,7 +127,7 @@ export function ConfiguredSettings({ const Component = settingComponentRegistry.get(getInnerType(configuredSetting.schema)); if (!Component) { - throw new Error(`Unknown setting type: ${configuredSetting.schema._type}}`); + throw new Error(`Unknown setting type: ${configuredSetting.schema._type}`); } return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx index 61adef4729a2..1885d466711f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx @@ -13,12 +13,15 @@ import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; import type { SettingsConfig } from '../../../../../common/settings/types'; import { useAgentPolicyFormContext } from '../../sections/agent_policy/components/agent_policy_form'; -export const convertValue = (value: string, type: keyof typeof ZodFirstPartyTypeKind): any => { +export const convertValue = ( + value: string | boolean, + type: keyof typeof ZodFirstPartyTypeKind +): any => { if (type === ZodFirstPartyTypeKind.ZodNumber) { if (value === '') { return 0; } - return parseInt(value, 10); + return parseInt(value as string, 10); } return value; }; @@ -48,7 +51,8 @@ export const SettingsFieldWrapper: React.FC<{ const coercedSchema = settingsConfig.schema as z.ZodString; const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { - const newValue = convertValue(e.target.value, typeName); + const value = typeName === ZodFirstPartyTypeKind.ZodBoolean ? e.target.checked : e.target.value; + const newValue = convertValue(value, typeName); const validationError = validateSchema(coercedSchema, newValue); if (validationError) { @@ -97,9 +101,13 @@ export const SettingsFieldWrapper: React.FC<{ }; export const getInnerType = (schema: z.ZodType<any, any>) => { - return schema instanceof z.ZodDefault - ? schema._def.innerType._def.typeName === 'ZodEffects' + if (schema._def.innerType) { + return schema._def.innerType._def.typeName === 'ZodEffects' ? schema._def.innerType._def.schema._def.typeName - : schema._def.innerType._def.typeName - : schema._def.typeName; + : schema._def.innerType._def.typeName; + } + if (schema._def.typeName === 'ZodEffects') { + return schema._def.schema._def.typeName; + } + return schema._def.typeName; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_apm_service_href.test.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_apm_service_href.test.ts index 4a2eaddbc45d..da4d0e2c2594 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_apm_service_href.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_apm_service_href.test.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; + +import { waitFor, renderHook } from '@testing-library/react'; import type { DataStream } from '../types'; import * as useLocatorModule from '../../../hooks/use_locator'; @@ -29,12 +30,12 @@ describe('useApmServiceHref hook', () => { package: 'elastic_agent', } as DataStream; - const { result, waitForNextUpdate } = renderHook(() => useAPMServiceDetailHref(datastream)); - - await waitForNextUpdate(); + const { result } = renderHook(() => useAPMServiceDetailHref(datastream)); - expect(result.current).toMatchObject({ isSuccessful: true, href: undefined }); - expect(apmLocatorMock).not.toBeCalled(); + await waitFor(() => { + expect(result.current).toMatchObject({ isSuccessful: true, href: undefined }); + expect(apmLocatorMock).not.toBeCalled(); + }); }); const testCases = [ @@ -83,12 +84,12 @@ describe('useApmServiceHref hook', () => { it.each(testCases)( 'it passes the correct params to apm locator for %s', async (datastream, locatorParams) => { - const { result, waitForNextUpdate } = renderHook(() => useAPMServiceDetailHref(datastream)); - - await waitForNextUpdate(); + const { result } = renderHook(() => useAPMServiceDetailHref(datastream)); - expect(result.current).toMatchObject({ isSuccessful: true, href: '' }); - expect(apmLocatorMock).toBeCalledWith(expect.objectContaining(locatorParams)); + await waitFor(() => { + expect(result.current).toMatchObject({ isSuccessful: true, href: '' }); + expect(apmLocatorMock).toBeCalledWith(expect.objectContaining(locatorParams)); + }); } ); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx index 85a241fe828c..599603d9d5f6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/hooks.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import { waitFor } from '@testing-library/react'; + import { createFleetTestRendererMock } from '../../../../../../mock'; import type { MockedFleetStartServices } from '../../../../../../mock'; import { useLicense } from '../../../../../../hooks/use_license'; @@ -189,12 +191,10 @@ describe('useOutputOptions', () => { hasAtLeast: () => true, } as unknown as LicenseService); mockApiCallsWithOutputs(testRenderer.startServices.http); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => - useOutputOptions({} as AgentPolicy) - ); + const { result } = testRenderer.renderHook(() => useOutputOptions({} as AgentPolicy)); expect(result.current.isLoading).toBeTruthy(); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.dataOutputOptions).toMatchInlineSnapshot(` Array [ Object { @@ -317,12 +317,10 @@ describe('useOutputOptions', () => { hasAtLeast: () => false, } as unknown as LicenseService); mockApiCallsWithOutputs(testRenderer.startServices.http); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => - useOutputOptions({} as AgentPolicy) - ); + const { result } = testRenderer.renderHook(() => useOutputOptions({} as AgentPolicy)); expect(result.current.isLoading).toBeTruthy(); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.dataOutputOptions).toMatchInlineSnapshot(` Array [ Object { @@ -445,12 +443,10 @@ describe('useOutputOptions', () => { hasAtLeast: () => true, } as unknown as LicenseService); mockApiCallsWithLogstashOutputs(testRenderer.startServices.http); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => - useOutputOptions({} as AgentPolicy) - ); + const { result } = testRenderer.renderHook(() => useOutputOptions({} as AgentPolicy)); expect(result.current.isLoading).toBeTruthy(); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.dataOutputOptions).toMatchInlineSnapshot(` Array [ Object { @@ -497,7 +493,7 @@ describe('useOutputOptions', () => { hasAtLeast: () => true, } as unknown as LicenseService); mockApiCallsWithLogstashOutputs(testRenderer.startServices.http); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => + const { result } = testRenderer.renderHook(() => useOutputOptions({ package_policies: [ { @@ -510,7 +506,7 @@ describe('useOutputOptions', () => { ); expect(result.current.isLoading).toBeTruthy(); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.dataOutputOptions).toMatchInlineSnapshot(` Array [ Object { @@ -601,12 +597,10 @@ describe('useOutputOptions', () => { hasAtLeast: () => true, } as unknown as LicenseService); mockApiCallsWithRemoteESOutputs(testRenderer.startServices.http); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => - useOutputOptions({} as AgentPolicy) - ); + const { result } = testRenderer.renderHook(() => useOutputOptions({} as AgentPolicy)); expect(result.current.isLoading).toBeTruthy(); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.dataOutputOptions.length).toEqual(2); expect(result.current.dataOutputOptions[1].value).toEqual('remote1'); expect(result.current.monitoringOutputOptions.length).toEqual(2); @@ -619,12 +613,10 @@ describe('useOutputOptions', () => { hasAtLeast: () => true, } as unknown as LicenseService); mockApiCallsWithInternalOutputs(testRenderer.startServices.http); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => - useOutputOptions({} as AgentPolicy) - ); + const { result } = testRenderer.renderHook(() => useOutputOptions({} as AgentPolicy)); expect(result.current.isLoading).toBeTruthy(); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.dataOutputOptions).toMatchInlineSnapshot(` Array [ Object { @@ -670,12 +662,10 @@ describe('useFleetServerHostsOptions', () => { it('should not enable internal fleet server hosts', async () => { const testRenderer = createFleetTestRendererMock(); mockApiCallsWithInternalFleetServerHost(testRenderer.startServices.http); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => - useFleetServerHostsOptions({} as AgentPolicy) - ); + const { result } = testRenderer.renderHook(() => useFleetServerHostsOptions({} as AgentPolicy)); expect(result.current.isLoading).toBeTruthy(); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.fleetServerHostsOptions).toMatchInlineSnapshot(` Array [ Object { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx index e404b30a37ce..61846bdbd7e2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx @@ -18,6 +18,8 @@ import type { AgentPolicy, NewAgentPolicy } from '../../../../../../../common/ty import { useLicense } from '../../../../../../hooks/use_license'; +import { useFleetStatus } from '../../../../hooks'; + import type { LicenseService } from '../../../../../../../common/services'; import { generateNewAgentPolicyWithDefaults } from '../../../../../../../common/services'; @@ -26,8 +28,13 @@ import type { ValidationResults } from '../agent_policy_validation'; import { AgentPolicyAdvancedOptionsContent } from '.'; jest.mock('../../../../../../hooks/use_license'); +jest.mock('../../../../hooks', () => ({ + ...jest.requireActual('../../../../hooks'), + useFleetStatus: jest.fn(), +})); const mockedUseLicence = useLicense as jest.MockedFunction<typeof useLicense>; +const mockedUseFleetStatus = useFleetStatus as jest.MockedFunction<typeof useFleetStatus>; describe('Agent policy advanced options content', () => { let testRender: TestRenderer; @@ -40,6 +47,10 @@ describe('Agent policy advanced options content', () => { hasAtLeast: () => true, isPlatinum: () => true, } as unknown as LicenseService); + const useSpaceAwareness = () => + mockedUseFleetStatus.mockReturnValue({ + isSpaceAwarenessEnabled: true, + } as any); const render = ({ isProtected = false, @@ -47,6 +58,7 @@ describe('Agent policy advanced options content', () => { policyId = 'agent-policy-1', newAgentPolicy = false, packagePolicy = [createPackagePolicyMock()], + spaceIds = ['default'], } = {}) => { if (newAgentPolicy) { mockAgentPolicy = generateNewAgentPolicyWithDefaults(); @@ -56,6 +68,7 @@ describe('Agent policy advanced options content', () => { package_policies: packagePolicy, id: policyId, is_managed: isManaged, + space_ids: spaceIds, }; } @@ -72,6 +85,7 @@ describe('Agent policy advanced options content', () => { }; beforeEach(() => { + mockedUseFleetStatus.mockReturnValue({} as any); testRender = createFleetTestRendererMock(); }); afterEach(() => { @@ -173,4 +187,39 @@ describe('Agent policy advanced options content', () => { expect(renderResult.queryByText('This policy has no custom fields')).toBeInTheDocument(); }); }); + + describe('Space selector', () => { + beforeEach(() => { + usePlatinumLicense(); + }); + + describe('when space awareness is disabled', () => { + it('should not be rendered', () => { + render(); + expect(renderResult.queryByTestId('spaceSelectorInput')).not.toBeInTheDocument(); + }); + }); + + describe('when space awareness is enabled', () => { + beforeEach(() => { + useSpaceAwareness(); + }); + + describe('when the user has access to all policy spaces', () => { + it('should render the space selection input with the Create space link', () => { + render(); + expect(renderResult.queryByTestId('spaceSelectorInput')).toBeInTheDocument(); + expect(renderResult.queryByTestId('spaceSelectorInputLink')).toBeInTheDocument(); + }); + }); + + describe('when the user does not have access to all policy spaces', () => { + it('should render the space selection input without the Create space link', () => { + render({ spaceIds: ['default', '?'] }); + expect(renderResult.queryByTestId('spaceSelectorInput')).toBeInTheDocument(); + expect(renderResult.queryByTestId('spaceSelectorInputLink')).not.toBeInTheDocument(); + }); + }); + }); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index 305148584f54..b0889f825727 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -34,6 +34,7 @@ import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, dataTypes, DEFAULT_MAX_AGENT_POLICIES_WITH_INACTIVITY_TIMEOUT, + UNKNOWN_SPACE, } from '../../../../../../../common/constants'; import type { NewAgentPolicy, AgentPolicy } from '../../../../types'; import { @@ -127,7 +128,12 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> = const isManagedorAgentlessPolicy = agentPolicy.is_managed === true || agentPolicy?.supports_agentless === true; - const agentPolicyFormContect = useAgentPolicyFormContext(); + const userHasAccessToAllPolicySpaces = useMemo( + () => 'space_ids' in agentPolicy && !agentPolicy.space_ids?.includes(UNKNOWN_SPACE), + [agentPolicy] + ); + + const agentPolicyFormContext = useAgentPolicyFormContext(); const AgentTamperProtectionSectionContent = useMemo( () => ( @@ -309,13 +315,14 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> = description={ <FormattedMessage id="xpack.fleet.agentPolicyForm.spaceDescription" - defaultMessage="Select one or more spaces for this policy or create a new one. {link}" + defaultMessage="Select one or more spaces for this policy or create a new one. {link}{tooltip}" values={{ - link: ( + link: userHasAccessToAllPolicySpaces && ( <EuiLink target="_blank" href={getAbsolutePath('/app/management/kibana/spaces/create')} external + data-test-subj="spaceSelectorInputLink" > <FormattedMessage id="xpack.fleet.agentPolicyForm.createSpaceLink" @@ -323,18 +330,30 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> = /> </EuiLink> ), + tooltip: !userHasAccessToAllPolicySpaces && ( + <EuiIconTip + type="iInCircle" + color="subdued" + content={i18n.translate('xpack.fleet.agentPolicyForm.spaceTooltip', { + defaultMessage: 'Access to all policy spaces is required for edit.', + })} + /> + ), }} /> } + data-test-subj="spaceSelectorInput" > <SpaceSelector - isDisabled={disabled || agentPolicy.is_managed === true} + isDisabled={ + disabled || agentPolicy.is_managed === true || !userHasAccessToAllPolicySpaces + } value={ 'space_ids' in agentPolicy && agentPolicy.space_ids - ? agentPolicy.space_ids + ? agentPolicy.space_ids.filter((id) => id !== UNKNOWN_SPACE) : [spaceId || 'default'] } - setInvalidSpaceError={agentPolicyFormContect?.setInvalidSpaceError} + setInvalidSpaceError={agentPolicyFormContext?.setInvalidSpaceError} onChange={(newValue) => { if (newValue.length === 0) { return; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.test.tsx index 5accdf37e95e..0222e8a238b9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.test.tsx @@ -360,11 +360,8 @@ describe('PackagePolicyInputPanel', () => { beforeEach(() => { useAgentlessMock.mockReturnValue({ isAgentlessEnabled: true, - isAgentlessPackagePolicy: jest.fn(), isAgentlessAgentPolicy: jest.fn(), isAgentlessIntegration: jest.fn(), - isAgentlessApiEnabled: true, - isDefaultAgentlessPolicyEnabled: false, }); }); @@ -395,11 +392,8 @@ describe('PackagePolicyInputPanel', () => { beforeEach(() => { useAgentlessMock.mockReturnValue({ isAgentlessEnabled: false, - isAgentlessPackagePolicy: jest.fn(), isAgentlessAgentPolicy: jest.fn(), isAgentlessIntegration: jest.fn(), - isAgentlessApiEnabled: true, - isDefaultAgentlessPolicyEnabled: false, }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx index 109e9c73bd77..1d6d6750e06e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.test.tsx @@ -129,7 +129,8 @@ describe('stepStepSelectAgentPolicy', () => { }); }); - describe('with multiple agent policies', () => { + // FLAKY: https://github.com/elastic/kibana/issues/197985 + describe.skip('with multiple agent policies', () => { beforeEach(() => { testRenderer = createFleetTestRendererMock(); useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: true }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx index 583957861bd7..dd3945664bd6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx @@ -123,7 +123,8 @@ describe('StepSelectHosts', () => { await waitFor(() => { expect(renderResult.getByText('New agent policy name')).toBeInTheDocument(); }); - expect(renderResult.queryByRole('tablist')).not.toBeInTheDocument(); + expect(renderResult.queryByRole('tablist')).toBeInTheDocument(); + expect(renderResult.getByText('Create agent policy')).toBeInTheDocument(); }); it('should display tabs with New hosts selected when agent policies exist', async () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx index c4504e659635..5a9d6fd6e31f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx @@ -105,7 +105,7 @@ export const StepSelectHosts: React.FunctionComponent<Props> = ({ const handleOnTabClick = (tab: EuiTabbedContentTab) => updateSelectedTab(tab.id as SelectedPolicyTab); - return existingAgentPolicies.length > 0 ? ( + return ( <StyledEuiTabbedContent initialSelectedTab={ initialSelectedTabIndex @@ -117,13 +117,5 @@ export const StepSelectHosts: React.FunctionComponent<Props> = ({ tabs={tabs} onTabClick={handleOnTabClick} /> - ) : ( - <AgentPolicyIntegrationForm - agentPolicy={newAgentPolicy} - updateAgentPolicy={updateNewAgentPolicy} - withSysMonitoring={withSysMonitoring} - updateSysMonitoring={updateSysMonitoring} - validation={validation} - /> ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx index 1642abe05d72..d6d4c3abd63f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx @@ -131,11 +131,11 @@ export const ConfirmIncomingDataWithPreview: React.FunctionComponent<Props> = ({ setAgentDataConfirmed, troubleshootLink, }) => { - const { incomingData, dataPreview, isLoading, hasReachedTimeout } = usePollingIncomingData( + const { incomingData, dataPreview, isLoading, hasReachedTimeout } = usePollingIncomingData({ agentIds, - true, - MAX_AGENT_DATA_PREVIEW_COUNT - ); + previewData: true, + stopPollingAfterPreviewLength: MAX_AGENT_DATA_PREVIEW_COUNT, + }); const { enrolledAgents, numAgentsWithData } = useGetAgentIncomingData(incomingData, packageInfo); const isGuidedOnboardingActive = useIsGuidedOnboardingActive(packageInfo?.name); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.test.tsx index 9283a8fa42c3..4ed0fd697850 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { act } from '@testing-library/react-hooks'; -import type { RenderHookResult } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { act, waitFor } from '@testing-library/react'; import type { TestRenderer } from '../../../../../../../mock'; import { createFleetTestRendererMock } from '../../../../../../../mock'; @@ -71,11 +71,11 @@ describe('useOnSubmit', () => { let testRenderer: TestRenderer; let renderResult: RenderHookResult< - Parameters<typeof useOnSubmit>, - ReturnType<typeof useOnSubmit> + ReturnType<typeof useOnSubmit>, + Parameters<typeof useOnSubmit> >; - const render = ({ isUpdate } = { isUpdate: false }) => - (renderResult = testRenderer.renderHook(() => + const render = async ({ isUpdate } = { isUpdate: false }) => { + renderResult = testRenderer.renderHook(() => useOnSubmit({ agentCount: 0, packageInfo, @@ -85,7 +85,12 @@ describe('useOnSubmit', () => { queryParamsPolicyId: undefined, hasFleetAddAgentsPrivileges: true, }) - )); + ); + + await waitFor(() => new Promise((resolve) => resolve(null))); + + return renderResult; + }; beforeEach(() => { testRenderer = createFleetTestRendererMock(); @@ -95,10 +100,8 @@ describe('useOnSubmit', () => { }); describe('default API response', () => { - beforeEach(() => { - act(() => { - render(); - }); + beforeEach(async () => { + await render(); }); it('should set new values when package policy changes', () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index 0c3f54d9e5df..4c4c7b311cb0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -178,8 +178,7 @@ export function useOnSubmit({ const [hasAgentPolicyError, setHasAgentPolicyError] = useState<boolean>(false); const hasErrors = validationResults ? validationHasErrors(validationResults) : false; - const { isAgentlessIntegration, isAgentlessAgentPolicy, isAgentlessPackagePolicy } = - useAgentless(); + const { isAgentlessIntegration, isAgentlessAgentPolicy } = useAgentless(); // Update agent policy method const updateAgentPolicies = useCallback( @@ -280,7 +279,7 @@ export function useOnSubmit({ useEffect(() => { if ( - agentPolicies.length > 0 && + (canUseMultipleAgentPolicies || agentPolicies.length > 0) && !isEqual( agentPolicies.map((policy) => policy.id), packagePolicy.policy_ids @@ -290,7 +289,7 @@ export function useOnSubmit({ policy_ids: agentPolicies.map((policy) => policy.id), }); } - }, [packagePolicy, agentPolicies, updatePackagePolicy]); + }, [packagePolicy, agentPolicies, updatePackagePolicy, canUseMultipleAgentPolicies]); const onSaveNavigate = useOnSaveNavigate({ packagePolicy, @@ -316,9 +315,7 @@ export function useOnSubmit({ (agentCount !== 0 || (agentPolicies.length === 0 && selectedPolicyTab !== SelectedPolicyTab.NEW)) && !( - isAgentlessIntegration(packageInfo) || - isAgentlessPackagePolicy(packagePolicy) || - isAgentlessAgentPolicy(overrideCreatedAgentPolicy) + isAgentlessIntegration(packageInfo) || isAgentlessAgentPolicy(overrideCreatedAgentPolicy) ) && formState !== 'CONFIRM' ) { @@ -365,9 +362,7 @@ export function useOnSubmit({ : packagePolicy.policy_ids; const shouldForceInstallOnAgentless = - isAgentlessAgentPolicy(createdPolicy) || - isAgentlessIntegration(packageInfo) || - isAgentlessPackagePolicy(packagePolicy); + isAgentlessAgentPolicy(createdPolicy) || isAgentlessIntegration(packageInfo); const forceInstall = force || shouldForceInstallOnAgentless; @@ -390,8 +385,7 @@ export function useOnSubmit({ const hasGoogleCloudShell = data?.item ? getCloudShellUrlFromPackagePolicy(data.item) : false; // Check if agentless is configured in ESS and Serverless until Agentless API migrates to Serverless - const isAgentlessConfigured = - isAgentlessAgentPolicy(createdPolicy) || (data && isAgentlessPackagePolicy(data.item)); + const isAgentlessConfigured = isAgentlessAgentPolicy(createdPolicy); // Removing this code will disabled the Save and Continue button. We need code below update form state and trigger correct modal depending on agent count if (hasFleetAddAgentsPrivileges && !isAgentlessConfigured) { @@ -479,7 +473,6 @@ export function useOnSubmit({ selectedPolicyTab, packagePolicy, isAgentlessAgentPolicy, - isAgentlessPackagePolicy, hasFleetAddAgentsPrivileges, withSysMonitoring, newAgentPolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts index 82e4f80c9b27..1564d934b960 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts @@ -5,16 +5,13 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks/dom'; - -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook, act } from '@testing-library/react'; import { createPackagePolicyMock } from '../../../../../../../../common/mocks'; import type { RegistryPolicyTemplate, PackageInfo } from '../../../../../../../../common/types'; import { SetupTechnology } from '../../../../../../../../common/types'; -import { ExperimentalFeaturesService } from '../../../../../services'; -import { sendGetOneAgentPolicy, useStartServices, useConfig } from '../../../../../hooks'; +import { useStartServices, useConfig } from '../../../../../hooks'; import { SelectedPolicyTab } from '../../components'; import { generateNewAgentPolicyWithDefaults } from '../../../../../../../../common/services/generate_new_agent_policy'; @@ -32,12 +29,7 @@ jest.mock('../../../../../../../../common/services/generate_new_agent_policy'); type MockFn = jest.MockedFunction<any>; describe('useAgentless', () => { - const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService); - beforeEach(() => { - mockedExperimentalFeaturesService.get.mockReturnValue({ - agentless: false, - } as any); (useConfig as MockFn).mockReturnValue({ agentless: undefined, } as any); @@ -50,33 +42,25 @@ describe('useAgentless', () => { jest.clearAllMocks(); }); - it('should not return isAgentless when agentless is not enabled', () => { + it('should return isAgentlessEnabled as falsy when agentless is not enabled', () => { const { result } = renderHook(() => useAgentless()); expect(result.current.isAgentlessEnabled).toBeFalsy(); - expect(result.current.isAgentlessApiEnabled).toBeFalsy(); - expect(result.current.isDefaultAgentlessPolicyEnabled).toBeFalsy(); }); - it('should return isAgentlessEnabled as falsy if agentless.enabled is true and experimental feature agentless is truthy without cloud or serverless', () => { + it('should return isAgentlessEnabled as falsy if agentless.enabled true without cloud or serverless', () => { (useConfig as MockFn).mockReturnValue({ agentless: { enabled: true, }, } as any); - mockedExperimentalFeaturesService.get.mockReturnValue({ - agentless: false, - } as any); - const { result } = renderHook(() => useAgentless()); expect(result.current.isAgentlessEnabled).toBeFalsy(); - expect(result.current.isAgentlessApiEnabled).toBeFalsy(); - expect(result.current.isDefaultAgentlessPolicyEnabled).toBeFalsy(); }); - it('should return isAgentlessEnabled and isAgentlessApiEnabled as truthy with isCloudEnabled', () => { + it('should return isAgentlessEnabled as truthy with isCloudEnabled', () => { (useConfig as MockFn).mockReturnValue({ agentless: { enabled: true, @@ -93,14 +77,9 @@ describe('useAgentless', () => { const { result } = renderHook(() => useAgentless()); expect(result.current.isAgentlessEnabled).toBeTruthy(); - expect(result.current.isAgentlessApiEnabled).toBeTruthy(); - expect(result.current.isDefaultAgentlessPolicyEnabled).toBeFalsy(); }); - it('should return isAgentlessEnabled and isDefaultAgentlessPolicyEnabled as truthy with isServerlessEnabled and experimental feature agentless is truthy', () => { - mockedExperimentalFeaturesService.get.mockReturnValue({ - agentless: true, - } as any); + it('should return isAgentlessEnabled truthy with isServerlessEnabled', () => { (useStartServices as MockFn).mockReturnValue({ cloud: { isServerlessEnabled: true, @@ -108,18 +87,18 @@ describe('useAgentless', () => { }, }); + (useConfig as MockFn).mockReturnValue({ + agentless: { + enabled: true, + }, + } as any); + const { result } = renderHook(() => useAgentless()); expect(result.current.isAgentlessEnabled).toBeTruthy(); - expect(result.current.isAgentlessApiEnabled).toBeFalsy(); - expect(result.current.isDefaultAgentlessPolicyEnabled).toBeTruthy(); }); - it('should return isAgentlessEnabled as falsy and isDefaultAgentlessPolicyEnabled as falsy with isServerlessEnabled and experimental feature agentless is falsy', () => { - mockedExperimentalFeaturesService.get.mockReturnValue({ - agentless: false, - } as any); - + it('should return isAgentlessEnabled as falsy with isServerlessEnabled and without agentless config', () => { (useStartServices as MockFn).mockReturnValue({ cloud: { isServerlessEnabled: true, @@ -130,14 +109,13 @@ describe('useAgentless', () => { const { result } = renderHook(() => useAgentless()); expect(result.current.isAgentlessEnabled).toBeFalsy(); - expect(result.current.isAgentlessApiEnabled).toBeFalsy(); - expect(result.current.isDefaultAgentlessPolicyEnabled).toBeFalsy(); }); }); describe('useSetupTechnology', () => { const setNewAgentPolicy = jest.fn(); const updateAgentPoliciesMock = jest.fn(); + const updatePackagePolicyMock = jest.fn(); const setSelectedPolicyTabMock = jest.fn(); const newAgentPolicyMock = { name: 'mock_new_agent_policy', @@ -180,20 +158,10 @@ describe('useSetupTechnology', () => { const packagePolicyMock = createPackagePolicyMock(); - const mockedExperimentalFeaturesService = jest.mocked(ExperimentalFeaturesService); - beforeEach(() => { - mockedExperimentalFeaturesService.get.mockReturnValue({ - agentless: true, - } as any); (useConfig as MockFn).mockReturnValue({ agentless: undefined, } as any); - (sendGetOneAgentPolicy as MockFn).mockResolvedValue({ - data: { - item: { id: 'agentless-policy-id' }, - }, - }); (useStartServices as MockFn).mockReturnValue({ cloud: { isServerlessEnabled: true, @@ -209,108 +177,20 @@ describe('useSetupTechnology', () => { }); it('should initialize with default values when agentless is disabled', () => { - mockedExperimentalFeaturesService.get.mockReturnValue({ - agentless: false, - } as any); - - const { result } = renderHook(() => - useSetupTechnology({ - setNewAgentPolicy, - newAgentPolicy: newAgentPolicyMock, - updateAgentPolicies: updateAgentPoliciesMock, - setSelectedPolicyTab: setSelectedPolicyTabMock, - packagePolicy: packagePolicyMock, - }) - ); - - expect(sendGetOneAgentPolicy).not.toHaveBeenCalled(); - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); - }); - - it('should set the default selected setup technology to agent-based when creating a non agentless-only package policy', async () => { - (useConfig as MockFn).mockReturnValue({ - agentless: { - enabled: true, - api: { - url: 'https://agentless.api.url', - }, - }, - } as any); - (useStartServices as MockFn).mockReturnValue({ - cloud: { - isCloudEnabled: true, - }, - }); - const { result } = renderHook(() => useSetupTechnology({ setNewAgentPolicy, newAgentPolicy: newAgentPolicyMock, updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, - packageInfo: packageInfoMock, packagePolicy: packagePolicyMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); }); - it('should set the default selected setup technology to agentless when creating an agentless-only package policy', async () => { - (useConfig as MockFn).mockReturnValue({ - agentless: { - enabled: true, - api: { - url: 'https://agentless.api.url', - }, - }, - } as any); - (useStartServices as MockFn).mockReturnValue({ - cloud: { - isCloudEnabled: true, - }, - }); - const agentlessOnlyPackageInfoMock = { - policy_templates: [ - { - deployment_modes: { - default: { enabled: false }, - agentless: { enabled: true }, - }, - }, - ], - } as PackageInfo; - - const { result } = renderHook(() => - useSetupTechnology({ - setNewAgentPolicy, - newAgentPolicy: newAgentPolicyMock, - updateAgentPolicies: updateAgentPoliciesMock, - setSelectedPolicyTab: setSelectedPolicyTabMock, - packageInfo: agentlessOnlyPackageInfoMock, - packagePolicy: packagePolicyMock, - }) - ); - - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); - }); - - it('should fetch agentless policy if agentless feature is enabled and isServerless is true', async () => { - renderHook(() => - useSetupTechnology({ - setNewAgentPolicy, - newAgentPolicy: newAgentPolicyMock, - updateAgentPolicies: updateAgentPoliciesMock, - setSelectedPolicyTab: setSelectedPolicyTabMock, - packagePolicy: packagePolicyMock, - }) - ); - - await waitFor(() => { - expect(sendGetOneAgentPolicy).toHaveBeenCalled(); - }); - }); - it('should set agentless setup technology if agent policy supports agentless in edit page', async () => { (useConfig as MockFn).mockReturnValue({ agentless: { @@ -334,13 +214,14 @@ describe('useSetupTechnology', () => { packagePolicy: packagePolicyMock, isEditPage: true, agentPolicies: [{ id: 'agentless-policy-id', supports_agentless: true } as any], + updatePackagePolicy: updatePackagePolicyMock, }) ); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); }); - it('should create agentless policy if agentless feature is enabled and isCloud is true and agentless.api.url', async () => { + it('should create agentless policy if isCloud and agentless.enabled', async () => { (useConfig as MockFn).mockReturnValue({ agentless: { enabled: true, @@ -361,6 +242,7 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); @@ -370,6 +252,7 @@ describe('useSetupTechnology', () => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); }); await waitFor(() => { + expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: true }); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); expect(setNewAgentPolicy).toHaveBeenCalledWith({ name: 'Agentless policy for endpoint-1', @@ -400,6 +283,7 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, + updatePackagePolicy: updatePackagePolicyMock, }; const { result, rerender } = renderHook((props = initialProps) => useSetupTechnology(props), { @@ -413,6 +297,7 @@ describe('useSetupTechnology', () => { }); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); + expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: true }); expect(setNewAgentPolicy).toHaveBeenCalledWith({ inactivity_timeout: 3600, name: 'Agentless policy for endpoint-1', @@ -428,9 +313,11 @@ describe('useSetupTechnology', () => { ...packagePolicyMock, name: 'endpoint-2', }, + updatePackagePolicy: updatePackagePolicyMock, }); await waitFor(() => { + expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); expect(setNewAgentPolicy).toHaveBeenCalledWith({ name: 'Agentless policy for endpoint-2', inactivity_timeout: 3600, @@ -439,7 +326,7 @@ describe('useSetupTechnology', () => { }); }); - it('should not create agentless policy if agentless feature is enabled and isCloud is true and agentless.api.url is not defined', async () => { + it('should not create agentless policy isCloud is true and agentless.api.url is not defined', async () => { (useConfig as MockFn).mockReturnValue({} as any); (useStartServices as MockFn).mockReturnValue({ cloud: { @@ -454,6 +341,7 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); @@ -466,10 +354,18 @@ describe('useSetupTechnology', () => { await waitFor(() => expect(setNewAgentPolicy).toHaveBeenCalledTimes(0)); }); - it('should not fetch agentless policy if agentless is enabled but serverless is disabled', async () => { + it('should update new agent policy and selected policy tab when setup technology is agent-based', async () => { + (useConfig as MockFn).mockReturnValue({ + agentless: { + enabled: true, + api: { + url: 'https://agentless.api.url', + }, + }, + } as any); (useStartServices as MockFn).mockReturnValue({ cloud: { - isServerlessEnabled: false, + isCloudEnabled: true, }, }); @@ -480,48 +376,7 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, - }) - ); - - expect(sendGetOneAgentPolicy).not.toHaveBeenCalled(); - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); - }); - - it('should update agent policy and selected policy tab when setup technology is agentless', async () => { - const { result } = renderHook(() => - useSetupTechnology({ - setNewAgentPolicy, - newAgentPolicy: newAgentPolicyMock, - updateAgentPolicies: updateAgentPoliciesMock, - setSelectedPolicyTab: setSelectedPolicyTabMock, - packagePolicy: packagePolicyMock, - }) - ); - - act(() => { - result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); - }); - - await waitFor(() => { - expect(updateAgentPoliciesMock).toHaveBeenCalledWith([ - { - inactivity_timeout: 3600, - name: 'Agentless policy for endpoint-1', - supports_agentless: true, - }, - ]); - expect(setSelectedPolicyTabMock).toHaveBeenCalledWith(SelectedPolicyTab.EXISTING); - }); - }); - - it('should update new agent policy and selected policy tab when setup technology is agent-based', async () => { - const { result } = renderHook(() => - useSetupTechnology({ - setNewAgentPolicy, - newAgentPolicy: newAgentPolicyMock, - updateAgentPolicies: updateAgentPoliciesMock, - setSelectedPolicyTab: setSelectedPolicyTabMock, - packagePolicy: packagePolicyMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); @@ -532,12 +387,14 @@ describe('useSetupTechnology', () => { }); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); + expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: true }); act(() => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENT_BASED); }); expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); + expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: false }); await waitFor(() => { expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock); @@ -545,30 +402,6 @@ describe('useSetupTechnology', () => { }); }); - it('should not update agent policy and selected policy tab when agentless is disabled', async () => { - mockedExperimentalFeaturesService.get.mockReturnValue({ - agentless: false, - } as any); - - const { result } = renderHook(() => - useSetupTechnology({ - setNewAgentPolicy, - newAgentPolicy: newAgentPolicyMock, - updateAgentPolicies: updateAgentPoliciesMock, - setSelectedPolicyTab: setSelectedPolicyTabMock, - packagePolicy: packagePolicyMock, - }) - ); - - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); - - act(() => { - result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); - }); - - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); - }); - it('should not update agent policy and selected policy tab when setup technology matches the current one ', async () => { const { result } = renderHook(() => useSetupTechnology({ @@ -577,12 +410,13 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); - await waitFor(() => new Promise((resolve) => resolve(null))); - - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); + await waitFor(() => + expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED) + ); act(() => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENT_BASED); @@ -590,11 +424,25 @@ describe('useSetupTechnology', () => { expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); + expect(updatePackagePolicyMock).not.toHaveBeenCalled(); expect(setNewAgentPolicy).not.toHaveBeenCalled(); expect(setSelectedPolicyTabMock).not.toHaveBeenCalled(); }); it('should revert the agent policy name to the original value when switching from agentless back to agent-based', async () => { + (useConfig as MockFn).mockReturnValue({ + agentless: { + enabled: true, + api: { + url: 'https://agentless.api.url', + }, + }, + } as any); + (useStartServices as MockFn).mockReturnValue({ + cloud: { + isServerlessEnabled: true, + }, + }); const { result } = renderHook(() => useSetupTechnology({ setNewAgentPolicy, @@ -602,6 +450,7 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); @@ -611,7 +460,9 @@ describe('useSetupTechnology', () => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); }); - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS); + await waitFor(() => + expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENTLESS) + ); await waitFor(() => { expect(setNewAgentPolicy).toHaveBeenCalledWith({ @@ -619,14 +470,18 @@ describe('useSetupTechnology', () => { supports_agentless: true, inactivity_timeout: 3600, }); + expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: true }); }); act(() => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENT_BASED); }); - expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); - expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock); + await waitFor(() => { + expect(result.current.selectedSetupTechnology).toBe(SetupTechnology.AGENT_BASED); + expect(setNewAgentPolicy).toHaveBeenCalledWith(newAgentPolicyMock); + expect(updatePackagePolicyMock).toHaveBeenCalledWith({ supports_agentless: false }); + }); }); it('should have global_data_tags with the integration team when creating agentless policy with global_data_tags', async () => { @@ -652,6 +507,7 @@ describe('useSetupTechnology', () => { setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, packageInfo: packageInfoMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); @@ -696,6 +552,7 @@ describe('useSetupTechnology', () => { setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, packageInfo: packageInfoMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); @@ -745,6 +602,7 @@ describe('useSetupTechnology', () => { setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, packageInfo: packageInfoMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); @@ -790,6 +648,7 @@ describe('useSetupTechnology', () => { updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); @@ -836,6 +695,7 @@ describe('useSetupTechnology', () => { setSelectedPolicyTab: setSelectedPolicyTabMock, packagePolicy: packagePolicyMock, packageInfo: packageInfoMock, + updatePackagePolicy: updatePackagePolicyMock, }) ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts index 6bd3288af2e0..85f5cbdc5fae 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts @@ -8,7 +8,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useConfig } from '../../../../../hooks'; -import { ExperimentalFeaturesService } from '../../../../../services'; import { generateNewAgentPolicyWithDefaults } from '../../../../../../../../common/services/generate_new_agent_policy'; import type { AgentPolicy, @@ -17,10 +16,9 @@ import type { PackageInfo, } from '../../../../../types'; import { SetupTechnology } from '../../../../../types'; -import { sendGetOneAgentPolicy, useStartServices } from '../../../../../hooks'; +import { useStartServices } from '../../../../../hooks'; import { SelectedPolicyTab } from '../../components'; import { - AGENTLESS_POLICY_ID, AGENTLESS_GLOBAL_TAG_NAME_ORGANIZATION, AGENTLESS_GLOBAL_TAG_NAME_DIVISION, AGENTLESS_GLOBAL_TAG_NAME_TEAM, @@ -33,23 +31,15 @@ import { export const useAgentless = () => { const config = useConfig(); - const { agentless: agentlessExperimentalFeatureEnabled } = ExperimentalFeaturesService.get(); const { cloud } = useStartServices(); const isServerless = !!cloud?.isServerlessEnabled; const isCloud = !!cloud?.isCloudEnabled; - const isAgentlessApiEnabled = (isCloud || isServerless) && config.agentless?.enabled; - const isDefaultAgentlessPolicyEnabled = - !isAgentlessApiEnabled && isServerless && agentlessExperimentalFeatureEnabled; - - const isAgentlessEnabled = isAgentlessApiEnabled || isDefaultAgentlessPolicyEnabled; + const isAgentlessEnabled = (isCloud || isServerless) && config.agentless?.enabled === true; const isAgentlessAgentPolicy = (agentPolicy: AgentPolicy | undefined) => { if (!agentPolicy) return false; - return ( - isAgentlessEnabled && - (agentPolicy?.id === AGENTLESS_POLICY_ID || !!agentPolicy?.supports_agentless) - ); + return isAgentlessEnabled && !!agentPolicy?.supports_agentless; }; // When an integration has at least a policy template enabled for agentless @@ -60,17 +50,10 @@ export const useAgentless = () => { return false; }; - // TODO: remove this check when CSPM implements the above flag and rely only on `isAgentlessIntegration` - const isAgentlessPackagePolicy = (packagePolicy: NewPackagePolicy) => { - return isAgentlessEnabled && packagePolicy.policy_ids.includes(AGENTLESS_POLICY_ID); - }; return { - isAgentlessApiEnabled, - isDefaultAgentlessPolicyEnabled, isAgentlessEnabled, isAgentlessAgentPolicy, isAgentlessIntegration, - isAgentlessPackagePolicy, }; }; @@ -78,6 +61,7 @@ export function useSetupTechnology({ setNewAgentPolicy, newAgentPolicy, updateAgentPolicies, + updatePackagePolicy, setSelectedPolicyTab, packageInfo, packagePolicy, @@ -87,14 +71,14 @@ export function useSetupTechnology({ setNewAgentPolicy: (policy: NewAgentPolicy) => void; newAgentPolicy: NewAgentPolicy; updateAgentPolicies: (policies: AgentPolicy[]) => void; + updatePackagePolicy: (policy: Partial<NewPackagePolicy>) => void; setSelectedPolicyTab: (tab: SelectedPolicyTab) => void; packageInfo?: PackageInfo; packagePolicy: NewPackagePolicy; isEditPage?: boolean; agentPolicies?: AgentPolicy[]; }) { - const { isAgentlessEnabled, isAgentlessApiEnabled, isDefaultAgentlessPolicyEnabled } = - useAgentless(); + const { isAgentlessEnabled } = useAgentless(); // this is a placeholder for the new agent-BASED policy that will be used when the user switches from agentless to agent-based and back const newAgentBasedPolicy = useRef<NewAgentPolicy>(newAgentPolicy); @@ -119,7 +103,7 @@ export function useSetupTechnology({ setSelectedSetupTechnology(SetupTechnology.AGENTLESS); return; } - if (isAgentlessApiEnabled && selectedSetupTechnology === SetupTechnology.AGENTLESS) { + if (isAgentlessEnabled && selectedSetupTechnology === SetupTechnology.AGENTLESS) { const nextNewAgentlessPolicy = { ...newAgentlessPolicy, name: getAgentlessAgentPolicyNameFromPackagePolicyName(packagePolicy.name), @@ -130,42 +114,35 @@ export function useSetupTechnology({ updateAgentPolicies([nextNewAgentlessPolicy] as AgentPolicy[]); } } + if ( + selectedSetupTechnology === SetupTechnology.AGENTLESS && + !packagePolicy.supports_agentless + ) { + updatePackagePolicy({ + supports_agentless: true, + }); + } else if ( + selectedSetupTechnology !== SetupTechnology.AGENTLESS && + packagePolicy.supports_agentless + ) { + updatePackagePolicy({ + supports_agentless: false, + }); + } }, [ - isAgentlessApiEnabled, + isAgentlessEnabled, isEditPage, newAgentlessPolicy, packagePolicy.name, + packagePolicy.supports_agentless, selectedSetupTechnology, updateAgentPolicies, setNewAgentPolicy, agentPolicies, setSelectedSetupTechnology, + updatePackagePolicy, ]); - // tech debt: remove this useEffect when Serverless uses the Agentless API - // https://github.com/elastic/security-team/issues/9781 - useEffect(() => { - const fetchAgentlessPolicy = async () => { - const { data, error } = await sendGetOneAgentPolicy(AGENTLESS_POLICY_ID); - const isAgentlessAvailable = !error && data && data.item; - - if (isAgentlessAvailable) { - setNewAgentlessPolicy(data.item); - } - }; - - if (isDefaultAgentlessPolicyEnabled) { - fetchAgentlessPolicy(); - } - }, [isDefaultAgentlessPolicyEnabled]); - - useEffect(() => { - if (isEditPage) { - return; - } - setSelectedSetupTechnology(defaultSetupTechnology); - }, [packageInfo, defaultSetupTechnology, isEditPage]); - const handleSetupTechnologyChange = useCallback( (setupTechnology: SetupTechnology, policyTemplateName?: string) => { if (!isAgentlessEnabled || setupTechnology === selectedSetupTechnology) { @@ -173,7 +150,7 @@ export function useSetupTechnology({ } if (setupTechnology === SetupTechnology.AGENTLESS) { - if (isAgentlessApiEnabled) { + if (isAgentlessEnabled) { const agentlessPolicy = { ...newAgentlessPolicy, ...getAdditionalAgentlessPolicyInfo(policyTemplateName, packageInfo), @@ -184,18 +161,17 @@ export function useSetupTechnology({ setSelectedPolicyTab(SelectedPolicyTab.NEW); updateAgentPolicies([agentlessPolicy] as AgentPolicy[]); } - // tech debt: remove this when Serverless uses the Agentless API - // https://github.com/elastic/security-team/issues/9781 - if (isDefaultAgentlessPolicyEnabled) { - setNewAgentPolicy(newAgentlessPolicy as AgentPolicy); - updateAgentPolicies([newAgentlessPolicy] as AgentPolicy[]); - setSelectedPolicyTab(SelectedPolicyTab.EXISTING); - } + updatePackagePolicy({ + supports_agentless: true, + }); } else if (setupTechnology === SetupTechnology.AGENT_BASED) { setNewAgentPolicy({ ...newAgentBasedPolicy.current, supports_agentless: false, }); + updatePackagePolicy({ + supports_agentless: false, + }); setSelectedPolicyTab(SelectedPolicyTab.NEW); updateAgentPolicies([newAgentBasedPolicy.current] as AgentPolicy[]); } @@ -204,13 +180,12 @@ export function useSetupTechnology({ [ isAgentlessEnabled, selectedSetupTechnology, - isAgentlessApiEnabled, - isDefaultAgentlessPolicyEnabled, + updatePackagePolicy, setNewAgentPolicy, newAgentlessPolicy, + packageInfo, setSelectedPolicyTab, updateAgentPolicies, - packageInfo, ] ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx index db223fee26bf..a79db9ea1edd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx @@ -13,17 +13,12 @@ import type { MockedFleetStartServices, TestRenderer } from '../../../../../../m import { createFleetTestRendererMock } from '../../../../../../mock'; import { FLEET_ROUTING_PATHS, pagePathGetters, PLUGIN_ID } from '../../../../constants'; import type { CreatePackagePolicyRouteState } from '../../../../types'; - -import { ExperimentalFeaturesService } from '../../../../../../services'; - import { sendCreatePackagePolicy, sendCreateAgentPolicy, sendGetAgentStatus, - sendGetOneAgentPolicy, useIntraAppState, useStartServices, - useGetAgentPolicies, useGetPackageInfoByKeyQuery, useConfig, } from '../../../../hooks'; @@ -138,10 +133,6 @@ jest.mock('react-router-dom', () => ({ }), })); -import { AGENTLESS_POLICY_ID } from '../../../../../../../common/constants'; - -import { useAllNonManagedAgentPolicies } from '../components/steps/components/use_policies'; - import { CreatePackagePolicySinglePage } from '.'; import { SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ } from './components/setup_technology_selector'; @@ -284,17 +275,15 @@ describe('When on the package policy create page', () => { let cancelButton: HTMLAnchorElement; beforeEach(async () => { - await act(async () => { - render(); + render(); - cancelLink = renderResult.getByTestId( - 'createPackagePolicy_cancelBackLink' - ) as HTMLAnchorElement; + cancelLink = renderResult.getByTestId( + 'createPackagePolicy_cancelBackLink' + ) as HTMLAnchorElement; - cancelButton = (await renderResult.findByTestId( - 'createPackagePolicyCancelButton' - )) as HTMLAnchorElement; - }); + cancelButton = (await renderResult.findByTestId( + 'createPackagePolicyCancelButton' + )) as HTMLAnchorElement; }); test('should use custom "cancel" URL', () => { @@ -694,108 +683,6 @@ describe('When on the package policy create page', () => { }); }); - describe('With agentless policy and Serverless available', () => { - beforeEach(async () => { - (useStartServices as jest.MockedFunction<any>).mockReturnValue({ - ...useStartServices(), - cloud: { - ...useStartServices().cloud, - isServerlessEnabled: true, - }, - }); - jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({ agentless: true } as any); - (useGetPackageInfoByKeyQuery as jest.Mock).mockReturnValue( - getMockPackageInfo({ requiresRoot: false, dataStreamRequiresRoot: false }) - ); - - (sendGetOneAgentPolicy as jest.MockedFunction<any>).mockResolvedValue({ - data: { item: { id: AGENTLESS_POLICY_ID, name: 'Agentless CSPM', namespace: 'default' } }, - }); - (useGetAgentPolicies as jest.MockedFunction<any>).mockReturnValue({ - data: { - items: [{ id: AGENTLESS_POLICY_ID, name: 'Agentless CSPM', namespace: 'default' }], - }, - error: undefined, - isLoading: false, - resendRequest: jest.fn(), - }); - (useAllNonManagedAgentPolicies as jest.MockedFunction<any>).mockReturnValue([ - { id: AGENTLESS_POLICY_ID, name: 'Agentless CSPM', namespace: 'default' }, - ]); - - await act(async () => { - render(); - }); - }); - - test('should not force create package policy when not in serverless', async () => { - jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({ agentless: false } as any); - (useStartServices as jest.MockedFunction<any>).mockReturnValue({ - ...useStartServices(), - cloud: { - ...useStartServices().cloud, - isServerlessEnabled: false, - }, - }); - await act(async () => { - fireEvent.click(renderResult.getByText('Existing hosts')!); - }); - - await act(async () => { - fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!); - }); - - expect(sendCreateAgentPolicy as jest.MockedFunction<any>).not.toHaveBeenCalled(); - expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledWith({ - ...newPackagePolicy, - force: false, - policy_ids: [AGENTLESS_POLICY_ID], - }); - - await waitFor(() => { - expect(renderResult.getByText('Nginx integration added')).toBeInTheDocument(); - }); - }); - - test('should force create package policy', async () => { - await act(async () => { - fireEvent.click(renderResult.getByText('Existing hosts')!); - }); - - await act(async () => { - fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!); - }); - - expect(sendCreateAgentPolicy as jest.MockedFunction<any>).not.toHaveBeenCalled(); - expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalledWith({ - ...newPackagePolicy, - force: true, - policy_ids: [AGENTLESS_POLICY_ID], - }); - - await waitFor(() => { - expect(renderResult.getByText('Nginx integration added')).toBeInTheDocument(); - }); - }); - - test('should not show confirmation modal', async () => { - (sendGetAgentStatus as jest.MockedFunction<any>).mockResolvedValueOnce({ - data: { results: { active: 1 } }, - }); - - await act(async () => { - fireEvent.click(renderResult.getByText('Existing hosts')!); - }); - - await act(async () => { - fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!); - }); - - expect(sendCreateAgentPolicy as jest.MockedFunction<any>).not.toHaveBeenCalled(); - expect(sendCreatePackagePolicy as jest.MockedFunction<any>).toHaveBeenCalled(); - }); - }); - describe('With agentless Cloud available', () => { beforeEach(async () => { (useConfig as jest.MockedFunction<any>).mockReturnValue({ @@ -821,10 +708,6 @@ describe('When on the package policy create page', () => { }, }); - (sendCreatePackagePolicy as jest.MockedFunction<any>).mockResolvedValue({ - data: { item: { id: 'policy-1', inputs: [], policy_ids: ['agentless-policy-1'] } }, - }); - jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({ agentless: true } as any); (useGetPackageInfoByKeyQuery as jest.Mock).mockReturnValue( getMockPackageInfo({ requiresRoot: false, @@ -842,9 +725,6 @@ describe('When on the package policy create page', () => { fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!); }); - // tech debt: this should be converted to use MSW to mock the API calls - // https://github.com/elastic/security-team/issues/9816 - expect(sendGetOneAgentPolicy).not.toHaveBeenCalled(); expect(sendCreateAgentPolicy).toHaveBeenCalledWith( expect.objectContaining({ monitoring_enabled: ['logs', 'metrics'], diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index 93631f63d0e0..3941c92a76c3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -355,6 +355,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ newAgentPolicy, setNewAgentPolicy, updateAgentPolicies, + updatePackagePolicy, setSelectedPolicyTab, packageInfo, packagePolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx index 9de7a3f22adf..e0ea955cf5af 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx @@ -39,13 +39,13 @@ import { import { DevtoolsRequestFlyoutButton } from '../../../../../components'; import { ExperimentalFeaturesService } from '../../../../../services'; import { generateUpdateAgentPolicyDevToolsRequest } from '../../../services'; +import { UNKNOWN_SPACE } from '../../../../../../../../common/constants'; -const pickAgentPolicyKeysToSend = (agentPolicy: AgentPolicy) => - pick(agentPolicy, [ +const pickAgentPolicyKeysToSend = (agentPolicy: AgentPolicy) => { + const partialPolicy = pick(agentPolicy, [ 'name', 'description', 'namespace', - 'space_ids', 'monitoring_enabled', 'unenroll_timeout', 'inactivity_timeout', @@ -61,6 +61,13 @@ const pickAgentPolicyKeysToSend = (agentPolicy: AgentPolicy) => 'monitoring_http', 'monitoring_diagnostics', ]); + return { + ...partialPolicy, + ...(!agentPolicy.space_ids?.includes(UNKNOWN_SPACE) && { + space_ids: agentPolicy.space_ids, + }), + }; +}; const FormWrapper = styled.div` max-width: 1200px; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx index e30fa6c22c5c..249b037a6f6c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.test.tsx @@ -124,8 +124,9 @@ describe('StepEditHosts', () => { render(); - expect(renderResult.getByText('New agent policy name')).toBeInTheDocument(); expect(renderResult.queryByRole('tablist')).not.toBeInTheDocument(); + expect(renderResult.getByText('For existing hosts:')).toBeInTheDocument(); + expect(renderResult.getByText('For a new host:')).toBeInTheDocument(); }); it('should display new policy button and existing policies when agent policies exist', () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.tsx index 64d104172af5..54e210eddc3c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/step_edit_hosts.tsx @@ -60,7 +60,7 @@ export const StepEditHosts: React.FunctionComponent<Props> = ({ } }, [existingAgentPolicies.length]); // eslint-disable-line react-hooks/exhaustive-deps - return existingAgentPolicies.length > 0 ? ( + return ( <EuiFlexGroup direction="column" alignItems="flexStart"> <EuiFlexItem> <EuiTitle size="xs"> @@ -141,13 +141,5 @@ export const StepEditHosts: React.FunctionComponent<Props> = ({ </> )} </EuiFlexGroup> - ) : ( - <AgentPolicyIntegrationForm - agentPolicy={newAgentPolicy} - updateAgentPolicy={updateNewAgentPolicy} - withSysMonitoring={withSysMonitoring} - updateSysMonitoring={updateSysMonitoring} - validation={validation} - /> ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.test.tsx index 491c5f627694..cac13357ffa7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act } from '@testing-library/react-hooks'; +import { act, waitFor } from '@testing-library/react'; import { createFleetTestRendererMock } from '../../../../../../mock'; @@ -36,7 +36,7 @@ describe('useHistoryBlock', () => { act(() => renderer.mountHistory.push('/test')); // needed because we have an async useEffect - await act(() => new Promise((resolve) => resolve())); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(renderer.startServices.overlays.openConfirm).toBeCalled(); expect(renderer.startServices.application.navigateToUrl).toBeCalledWith( @@ -53,7 +53,7 @@ describe('useHistoryBlock', () => { act(() => renderer.mountHistory.push('/test')); // needed because we have an async useEffect - await act(() => new Promise((resolve) => resolve())); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(renderer.startServices.overlays.openConfirm).toBeCalled(); expect(renderer.startServices.application.navigateToUrl).not.toBeCalled(); @@ -81,7 +81,7 @@ describe('useHistoryBlock', () => { act(() => renderer.mountHistory.push('/test?param=test')); // needed because we have an async useEffect - await act(() => new Promise((resolve) => resolve())); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(renderer.startServices.overlays.openConfirm).toBeCalled(); expect(renderer.startServices.application.navigateToUrl).toBeCalledWith( @@ -98,7 +98,7 @@ describe('useHistoryBlock', () => { act(() => renderer.mountHistory.push('/test?param=test')); // needed because we have an async useEffect - await act(() => new Promise((resolve) => resolve())); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(renderer.startServices.overlays.openConfirm).toBeCalled(); expect(renderer.startServices.application.navigateToUrl).not.toBeCalled(); @@ -127,7 +127,7 @@ describe('useHistoryBlock', () => { act(() => renderer.mountHistory.push('/test#/hash')); // needed because we have an async useEffect - await act(() => new Promise((resolve) => resolve())); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(renderer.startServices.overlays.openConfirm).toBeCalled(); expect(renderer.startServices.application.navigateToUrl).toBeCalledWith( @@ -144,7 +144,7 @@ describe('useHistoryBlock', () => { act(() => renderer.mountHistory.push('/test#/hash')); // needed because we have an async useEffect - await act(() => new Promise((resolve) => resolve())); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(renderer.startServices.overlays.openConfirm).toBeCalled(); expect(renderer.startServices.application.navigateToUrl).not.toBeCalled(); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.test.tsx index 67b8f35a22eb..2df9363a3daa 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.test.tsx @@ -12,6 +12,7 @@ * 2.0. */ +import { waitFor } from '@testing-library/react'; import { omit } from 'lodash'; import { sendGetPackageInfoByKey, sendUpgradePackagePolicyDryRun } from '../../../../../../hooks'; @@ -274,22 +275,22 @@ jest.mock('../../../../../../hooks/use_request', () => ({ describe('usePackagePolicy', () => { it('should load the package policy if this is a not an upgrade', async () => { const renderer = createFleetTestRendererMock(); - const { result, waitForNextUpdate } = renderer.renderHook(() => + const { result } = renderer.renderHook(() => usePackagePolicyWithRelatedData('package-policy-1', {}) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.packagePolicy).toEqual(omit(mockPackagePolicy, 'id')); }); it('should load the package policy if this is an upgrade', async () => { const renderer = createFleetTestRendererMock(); - const { result, waitForNextUpdate } = renderer.renderHook(() => + const { result } = renderer.renderHook(() => usePackagePolicyWithRelatedData('package-policy-1', { forceUpgrade: true, }) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.packagePolicy).toMatchInlineSnapshot(` Object { "description": "Nginx description", @@ -514,12 +515,12 @@ describe('usePackagePolicy', () => { isLoading: false, } as any); const renderer = createFleetTestRendererMock(); - const { result, waitForNextUpdate } = renderer.renderHook(() => + const { result } = renderer.renderHook(() => usePackagePolicyWithRelatedData('package-policy-2', { forceUpgrade: true, }) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.packagePolicy).toMatchInlineSnapshot(` Object { "description": "Nginx description", diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx index 1f2bdecf9e5a..56f6a747dd57 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy_steps.tsx @@ -134,6 +134,7 @@ export function usePackagePolicySteps({ newAgentPolicy, setNewAgentPolicy, updateAgentPolicies, + updatePackagePolicy, setSelectedPolicyTab, packagePolicy, isEditPage: true, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx index 8badac52213e..aa866106e842 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/services/devtools_request.tsx @@ -18,6 +18,7 @@ import type { UpdatePackagePolicy, UpdateAgentPolicyRequest, } from '../../../types'; +import { canUseMultipleAgentPolicies } from '../../../hooks'; function generateKibanaDevToolsRequest(method: string, path: string, body: any) { return `${method} kbn:${path}\n${JSON.stringify(body, null, 2)}\n`; @@ -49,9 +50,13 @@ export function generateCreateAgentPolicyDevToolsRequest( export function generateCreatePackagePolicyDevToolsRequest( packagePolicy: NewPackagePolicy & { force?: boolean } ) { + const canHaveNoAgentPolicies = canUseMultipleAgentPolicies(); + return generateKibanaDevToolsRequest('POST', packagePolicyRouteService.getCreatePath(), { policy_ids: - packagePolicy.policy_ids.length > 0 ? packagePolicy.policy_ids : ['<agent_policy_id>'], + packagePolicy.policy_ids.length > 0 || canHaveNoAgentPolicies + ? packagePolicy.policy_ids + : ['<agent_policy_id>'], package: formatPackage(packagePolicy.package), ...omit(packagePolicy, 'policy_ids', 'package', 'enabled'), inputs: formatInputs(packagePolicy.inputs), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_dashboard_link.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_dashboard_link.test.tsx index 79908819ff86..03f7b2b33a83 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_dashboard_link.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_dashboard_link.test.tsx @@ -21,6 +21,7 @@ jest.mock('../../../../../../hooks/use_fleet_status', () => ({ FleetStatusProvider: (props: any) => { return props.children; }, + useFleetStatus: jest.fn().mockReturnValue({ spaceId: 'default' }), })); jest.mock('../../../../../../hooks/use_request/epm'); @@ -30,7 +31,7 @@ jest.mock('../../../../../../hooks/use_locator', () => { useDashboardLocator: jest.fn().mockImplementation(() => { return { id: 'DASHBOARD_APP_LOCATOR', - getRedirectUrl: jest.fn().mockResolvedValue('app/dashboards#/view/elastic_agent-a0001'), + getRedirectUrl: jest.fn().mockReturnValue('app/dashboards#/view/elastic_agent-a0001'), }; }), }; @@ -43,6 +44,10 @@ describe('AgentDashboardLink', () => { data: { item: { status: 'installed', + installationInfo: { + install_status: 'installed', + installed_kibana_space_id: 'default', + }, }, }, } as ReturnType<typeof useGetPackageInfoByKeyQuery>); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_dashboard_link.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_dashboard_link.tsx index 6832f81961dd..c6a7c6b1a7d4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_dashboard_link.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_dashboard_link.tsx @@ -10,21 +10,49 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; import styled from 'styled-components'; -import { useGetPackageInfoByKeyQuery, useLink, useDashboardLocator } from '../../../../hooks'; +import type { GetInfoResponse } from '../../../../../../../common/types'; +import { + useGetPackageInfoByKeyQuery, + useLink, + useDashboardLocator, + useFleetStatus, +} from '../../../../hooks'; import type { Agent, AgentPolicy } from '../../../../types'; import { FLEET_ELASTIC_AGENT_PACKAGE, DASHBOARD_LOCATORS_IDS, } from '../../../../../../../common/constants'; +import { getDashboardIdForSpace } from '../../services/dashboard_helpers'; + +function isKibanaAssetsInstalledInSpace(spaceId: string | undefined, res?: GetInfoResponse) { + if (res?.item?.status !== 'installed') { + return false; + } + + const installationInfo = res.item.installationInfo; + + if (!installationInfo || installationInfo.install_status !== 'installed') { + return false; + } + return ( + installationInfo.installed_kibana_space_id === spaceId || + (spaceId && installationInfo.additional_spaces_installed_kibana?.[spaceId]) + ); +} function useAgentDashboardLink(agent: Agent) { const { isLoading, data } = useGetPackageInfoByKeyQuery(FLEET_ELASTIC_AGENT_PACKAGE); + const { spaceId } = useFleetStatus(); - const isInstalled = data?.item.status === 'installed'; + const isInstalled = isKibanaAssetsInstalledInSpace(spaceId, data); const dashboardLocator = useDashboardLocator(); const link = dashboardLocator?.getRedirectUrl({ - dashboardId: DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_AGENT_METRICS, + dashboardId: getDashboardIdForSpace( + spaceId, + data, + DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_AGENT_METRICS + ), query: { language: 'kuery', query: `elastic_agent.id:${agent.id}`, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integration.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integration.tsx index db14401edfad..84895a021df3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integration.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integration.tsx @@ -97,114 +97,122 @@ export const AgentDetailsIntegration: React.FunctionComponent<{ agent: Agent; agentPolicy: AgentPolicy; packagePolicy: PackagePolicy; + linkToLogs: boolean; 'data-test-subj'?: string; -}> = memo(({ agent, agentPolicy, packagePolicy, 'data-test-subj': dataTestSubj }) => { - const { getHref } = useLink(); - const theme = useEuiTheme(); +}> = memo( + ({ agent, agentPolicy, packagePolicy, linkToLogs = true, 'data-test-subj': dataTestSubj }) => { + const { getHref } = useLink(); + const theme = useEuiTheme(); - const [isAttentionBadgeNeededForPolicyResponse, setIsAttentionBadgeNeededForPolicyResponse] = - useState(false); + const [isAttentionBadgeNeededForPolicyResponse, setIsAttentionBadgeNeededForPolicyResponse] = + useState(false); - const policyResponseExtensionView = useUIExtension( - packagePolicy.package?.name ?? '', - 'package-policy-response' - ); - - const policyResponseExtensionViewWrapper = useMemo(() => { - return ( - policyResponseExtensionView && ( - <ExtensionWrapper> - <policyResponseExtensionView.Component - agent={agent} - onShowNeedsAttentionBadge={setIsAttentionBadgeNeededForPolicyResponse} - /> - </ExtensionWrapper> - ) - ); - }, [agent, policyResponseExtensionView]); - - const packageErrors = useMemo(() => { - if (!agent.components) { - return []; - } - return getInputUnitsByPackage(agent.components, packagePolicy).filter( - (u) => u.status === 'DEGRADED' || u.status === 'FAILED' + const policyResponseExtensionView = useUIExtension( + packagePolicy.package?.name ?? '', + 'package-policy-response' ); - }, [agent.components, packagePolicy]); - const showNeedsAttentionBadge = isAttentionBadgeNeededForPolicyResponse || !!packageErrors.length; + const policyResponseExtensionViewWrapper = useMemo(() => { + return ( + policyResponseExtensionView && ( + <ExtensionWrapper> + <policyResponseExtensionView.Component + agent={agent} + onShowNeedsAttentionBadge={setIsAttentionBadgeNeededForPolicyResponse} + /> + </ExtensionWrapper> + ) + ); + }, [agent, policyResponseExtensionView]); + + const packageErrors = useMemo(() => { + if (!agent.components) { + return []; + } + return getInputUnitsByPackage(agent.components, packagePolicy).filter( + (u) => u.status === 'DEGRADED' || u.status === 'FAILED' + ); + }, [agent.components, packagePolicy]); - const genericErrorsListExtensionView = useUIExtension( - packagePolicy.package?.name ?? '', - 'package-generic-errors-list' - ); + const showNeedsAttentionBadge = + isAttentionBadgeNeededForPolicyResponse || !!packageErrors.length; - const genericErrorsListExtensionViewWrapper = useMemo(() => { - return ( - genericErrorsListExtensionView && ( - <ExtensionWrapper> - <genericErrorsListExtensionView.Component packageErrors={packageErrors} /> - </ExtensionWrapper> - ) + const genericErrorsListExtensionView = useUIExtension( + packagePolicy.package?.name ?? '', + 'package-generic-errors-list' ); - }, [packageErrors, genericErrorsListExtensionView]); - return ( - <CollapsablePanel - id={packagePolicy.id} - data-test-subj={dataTestSubj} - title={ - <EuiTitle size="xs"> - <h3> - <EuiFlexGroup gutterSize="s" alignItems="center"> - <EuiFlexItem grow={false}> - {packagePolicy.package ? ( - <PackageIcon - packageName={packagePolicy.package.name} - version={packagePolicy.package.version} - size="l" - tryApi={true} - /> - ) : ( - <PackageIcon size="l" packageName="default" version="0" /> - )} - </EuiFlexItem> - <EuiFlexItem className="eui-textTruncate"> - <EuiLink - className="eui-textTruncate" - data-test-subj="agentPolicyDetailsLink" - href={getHref('edit_integration', { - policyId: agentPolicy.id, - packagePolicyId: packagePolicy.id, - })} - > - {packagePolicy.name} - </EuiLink> - </EuiFlexItem> - {showNeedsAttentionBadge && ( + const genericErrorsListExtensionViewWrapper = useMemo(() => { + return ( + genericErrorsListExtensionView && ( + <ExtensionWrapper> + <genericErrorsListExtensionView.Component packageErrors={packageErrors} /> + </ExtensionWrapper> + ) + ); + }, [packageErrors, genericErrorsListExtensionView]); + + return ( + <CollapsablePanel + id={packagePolicy.id} + data-test-subj={dataTestSubj} + title={ + <EuiTitle size="xs"> + <h3> + <EuiFlexGroup gutterSize="s" alignItems="center"> <EuiFlexItem grow={false}> - <EuiBadge - color={theme.euiTheme.colors.danger} - iconType="warning" - iconSide="left" - data-test-subj={dataTestSubj ? `${dataTestSubj}-needsAttention` : undefined} - > - <FormattedMessage - id="xpack.fleet.agentDetailsIntegrations.needsAttention.label" - defaultMessage="Needs attention" + {packagePolicy.package ? ( + <PackageIcon + packageName={packagePolicy.package.name} + version={packagePolicy.package.version} + size="l" + tryApi={true} /> - </EuiBadge> + ) : ( + <PackageIcon size="l" packageName="default" version="0" /> + )} </EuiFlexItem> - )} - </EuiFlexGroup> - </h3> - </EuiTitle> - } - > - <AgentDetailsIntegrationInputs agent={agent} packagePolicy={packagePolicy} /> - {policyResponseExtensionViewWrapper} - {genericErrorsListExtensionViewWrapper} - <EuiSpacer /> - </CollapsablePanel> - ); -}); + <EuiFlexItem className="eui-textTruncate"> + <EuiLink + className="eui-textTruncate" + data-test-subj="agentPolicyDetailsLink" + href={getHref('edit_integration', { + policyId: agentPolicy.id, + packagePolicyId: packagePolicy.id, + })} + > + {packagePolicy.name} + </EuiLink> + </EuiFlexItem> + {showNeedsAttentionBadge && ( + <EuiFlexItem grow={false}> + <EuiBadge + color={theme.euiTheme.colors.danger} + iconType="warning" + iconSide="left" + data-test-subj={dataTestSubj ? `${dataTestSubj}-needsAttention` : undefined} + > + <FormattedMessage + id="xpack.fleet.agentDetailsIntegrations.needsAttention.label" + defaultMessage="Needs attention" + /> + </EuiBadge> + </EuiFlexItem> + )} + </EuiFlexGroup> + </h3> + </EuiTitle> + } + > + <AgentDetailsIntegrationInputs + agent={agent} + packagePolicy={packagePolicy} + linkToLogs={linkToLogs} + /> + {policyResponseExtensionViewWrapper} + {genericErrorsListExtensionViewWrapper} + <EuiSpacer size="s" /> + </CollapsablePanel> + ); + } +); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integration_inputs.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integration_inputs.tsx index 71b1a1ad6fe3..7630b61c08be 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integration_inputs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integration_inputs.tsx @@ -66,8 +66,9 @@ const StyledEuiTreeView = styled(EuiTreeView)` export const AgentDetailsIntegrationInputs: React.FunctionComponent<{ agent: Agent; packagePolicy: PackagePolicy; + linkToLogs?: boolean; 'data-test-subj'?: string; -}> = memo(({ agent, packagePolicy, 'data-test-subj': dataTestSubj }) => { +}> = memo(({ agent, packagePolicy, linkToLogs = true, 'data-test-subj': dataTestSubj }) => { const { getHref } = useLink(); const inputStatusMap = useMemo( @@ -138,21 +139,25 @@ export const AgentDetailsIntegrationInputs: React.FunctionComponent<{ defaultMessage: 'View logs', })} > - <StyledEuiLink - href={getHref('agent_details', { - agentId: agent.id, - tabId: 'logs', - logQuery: getLogsQueryByInputType(current.type), - })} - aria-label={i18n.translate( - 'xpack.fleet.agentDetailsIntegrations.viewLogsButton', - { - defaultMessage: 'View logs', - } - )} - > - {displayInputType(current.type)} - </StyledEuiLink> + {linkToLogs ? ( + <StyledEuiLink + href={getHref('agent_details', { + agentId: agent.id, + tabId: 'logs', + logQuery: getLogsQueryByInputType(current.type), + })} + aria-label={i18n.translate( + 'xpack.fleet.agentDetailsIntegrations.viewLogsButton', + { + defaultMessage: 'View logs', + } + )} + > + {displayInputType(current.type)} + </StyledEuiLink> + ) : ( + <>{displayInputType(current.type)}</> + )} </EuiToolTip> ), id: current.type, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx index 5a7b135b4644..0a1df537e7bc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx @@ -15,7 +15,8 @@ import { AgentDetailsIntegration } from './agent_details_integration'; export const AgentDetailsIntegrations: React.FunctionComponent<{ agent: Agent; agentPolicy?: AgentPolicy; -}> = memo(({ agent, agentPolicy }) => { + linkToLogs?: boolean; +}> = memo(({ agent, agentPolicy, linkToLogs = true }) => { if (!agentPolicy || !agentPolicy.package_policies) { return null; } @@ -31,6 +32,7 @@ export const AgentDetailsIntegrations: React.FunctionComponent<{ agent={agent} agentPolicy={agentPolicy} packagePolicy={packagePolicy} + linkToLogs={linkToLogs} data-test-subj={`${testSubj}-accordion`} /> </EuiFlexItem> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx index d70ed6724720..deb8402af5be 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx @@ -24,16 +24,14 @@ import { isAgentUpgradeable, ExperimentalFeaturesService } from '../../../../ser import { AgentHealth } from '../../components'; import type { Pagination } from '../../../../hooks'; -import { useAgentVersion, useGetListOutputsForPolicies } from '../../../../hooks'; +import { useAgentVersion } from '../../../../hooks'; import { useLink, useAuthz } from '../../../../hooks'; import { AgentPolicySummaryLine } from '../../../../components'; import { Tags } from '../../components/tags'; -import type { AgentMetrics, OutputsForAgentPolicy } from '../../../../../../../common/types'; +import type { AgentMetrics } from '../../../../../../../common/types'; import { formatAgentCPU, formatAgentMemory } from '../../services/agent_metrics'; -import { AgentPolicyOutputsSummary } from './agent_policy_outputs_summary'; - import { AgentUpgradeStatus } from './agent_upgrade_status'; import { EmptyPrompt } from './empty_prompt'; @@ -45,8 +43,6 @@ const AGENTS_TABLE_FIELDS = { METRICS: 'metrics', VERSION: 'local_metadata.elastic.agent.version', LAST_CHECKIN: 'last_checkin', - OUTPUT_INTEGRATION: 'output_integrations', - OUTPUT_MONITORING: 'output_monitoring', }; function safeMetadata(val: any) { @@ -128,14 +124,6 @@ export const AgentListTable: React.FC<Props> = (props: Props) => { : []; }, [agents, isAgentSelectable, showUpgradeable, totalAgents]); - // get the policyIds of the agents shown on the page - const policyIds = useMemo(() => { - return agentsShown.map((agent) => agent?.policy_id ?? ''); - }, [agentsShown]); - const allOutputs = useGetListOutputsForPolicies({ - ids: policyIds, - }); - const noItemsMessage = isLoading && isCurrentRequestIncremented ? ( <FormattedMessage @@ -305,40 +293,6 @@ export const AgentListTable: React.FC<Props> = (props: Props) => { render: (lastCheckin: string) => lastCheckin ? <FormattedRelative value={lastCheckin} /> : undefined, }, - { - field: AGENTS_TABLE_FIELDS.OUTPUT_INTEGRATION, - sortable: true, - truncateText: true, - name: i18n.translate('xpack.fleet.agentList.integrationsOutputTitle', { - defaultMessage: 'Output for integrations', - }), - width: '180px', - render: (outputs: OutputsForAgentPolicy[], agent: Agent) => { - if (!agent?.policy_id) return null; - - const outputsForPolicy = allOutputs?.data?.items.find( - (item) => item.agentPolicyId === agent?.policy_id - ); - return <AgentPolicyOutputsSummary outputs={outputsForPolicy} />; - }, - }, - { - field: AGENTS_TABLE_FIELDS.OUTPUT_MONITORING, - sortable: true, - truncateText: true, - name: i18n.translate('xpack.fleet.agentList.monitoringOutputTitle', { - defaultMessage: 'Output for monitoring', - }), - width: '180px', - render: (outputs: OutputsForAgentPolicy[], agent: Agent) => { - if (!agent?.policy_id) return null; - - const outputsForPolicy = allOutputs?.data?.items.find( - (item) => item.agentPolicyId === agent?.policy_id - ); - return <AgentPolicyOutputsSummary outputs={outputsForPolicy} isMonitoring={true} />; - }, - }, { field: AGENTS_TABLE_FIELDS.VERSION, sortable: true, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/dashboards_buttons.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/dashboards_buttons.tsx index 25c394e2606b..3e5025807157 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/dashboards_buttons.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/dashboards_buttons.tsx @@ -5,49 +5,69 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useQuery } from '@tanstack/react-query'; -import { DASHBOARD_LOCATORS_IDS } from '../../../../../../../common/constants'; +import { + DASHBOARD_LOCATORS_IDS, + FLEET_ELASTIC_AGENT_PACKAGE, +} from '../../../../../../../common/constants'; -import { useDashboardLocator, useStartServices } from '../../../../hooks'; +import { + useDashboardLocator, + useFleetStatus, + useGetPackageInfoByKeyQuery, + useStartServices, +} from '../../../../hooks'; + +import { getDashboardIdForSpace } from '../../services/dashboard_helpers'; const useDashboardExists = (dashboardId: string) => { - const [dashboardExists, setDashboardExists] = React.useState<boolean>(false); - const [loading, setLoading] = React.useState<boolean>(true); const { dashboard: dashboardPlugin } = useStartServices(); - useEffect(() => { - const fetchDashboard = async () => { + const { data, isLoading } = useQuery({ + queryKey: ['dashboard_exists', dashboardId], + queryFn: async () => { try { const findDashboardsService = await dashboardPlugin.findDashboardsService(); const [dashboard] = await findDashboardsService.findByIds([dashboardId]); - setLoading(false); - setDashboardExists(dashboard?.status === 'success'); + return dashboard?.status === 'success'; } catch (e) { - setLoading(false); - setDashboardExists(false); + return false; } - }; - - fetchDashboard(); - }, [dashboardId, dashboardPlugin]); - - return { dashboardExists, loading }; + }, + }); + return { dashboardExists: data ?? false, loading: isLoading }; }; export const DashboardsButtons: React.FunctionComponent = () => { + const { data } = useGetPackageInfoByKeyQuery(FLEET_ELASTIC_AGENT_PACKAGE); + const { spaceId } = useFleetStatus(); + const dashboardLocator = useDashboardLocator(); const getDashboardHref = (dashboardId: string) => { return dashboardLocator?.getRedirectUrl({ dashboardId }) || ''; }; - const { dashboardExists, loading: dashboardLoading } = useDashboardExists( + const elasticAgentOverviewDashboardId = getDashboardIdForSpace( + spaceId, + data, DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_OVERVIEW ); + const elasticAgentInfoDashboardId = getDashboardIdForSpace( + spaceId, + data, + DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_AGENT_INFO + ); + + const { dashboardExists, loading: dashboardLoading } = useDashboardExists( + elasticAgentOverviewDashboardId + ); + if (dashboardLoading || !dashboardExists) { return null; } @@ -58,7 +78,7 @@ export const DashboardsButtons: React.FunctionComponent = () => { <EuiFlexItem grow={false}> <EuiButtonEmpty iconType="dashboardApp" - href={getDashboardHref(DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_OVERVIEW)} + href={getDashboardHref(elasticAgentOverviewDashboardId)} data-test-subj="ingestOverviewLinkButton" > <FormattedMessage @@ -70,7 +90,7 @@ export const DashboardsButtons: React.FunctionComponent = () => { <EuiFlexItem grow={false}> <EuiButtonEmpty iconType="dashboardApp" - href={getDashboardHref(DASHBOARD_LOCATORS_IDS.ELASTIC_AGENT_AGENT_INFO)} + href={getDashboardHref(elasticAgentInfoDashboardId)} data-test-subj="agentInfoLinkButton" > <FormattedMessage diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/export_csv.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/export_csv.test.tsx index b2fdf7a1023f..48bed25131b8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/export_csv.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/export_csv.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { act } from '@testing-library/react-hooks'; +import { act, type RenderHookResult } from '@testing-library/react'; import { createFleetTestRendererMock } from '../../../../../../mock'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.test.tsx index 1b4f3e6faff7..76e8aeb78c3d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_action_status.test.tsx @@ -5,7 +5,9 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; + +import type { ActionStatus } from '../../../../../../../common/types'; import { sendGetActionStatus, sendPostCancelAction, useStartServices } from '../../../../hooks'; @@ -41,7 +43,7 @@ describe('useActionStatus', () => { nbAgentsFailed: 0, nbAgentsActioned: 2, creationTime: '2022-09-19T12:07:27.102Z', - }, + } as ActionStatus, ]; beforeEach(() => { mockSendGetActionStatus.mockReset(); @@ -63,20 +65,15 @@ describe('useActionStatus', () => { it('should refresh statuses on refresh flag', async () => { let refresh = false; - await act(async () => { - const result = renderHook(() => useActionStatus(mockOnAbortSuccess, refresh, 20, null)); - refresh = true; - result.rerender(); - }); - expect(mockSendGetActionStatus).toHaveBeenCalledTimes(5); + const result = renderHook(() => useActionStatus(mockOnAbortSuccess, refresh, 20, null)); + refresh = true; + result.rerender(); + await waitFor(() => expect(mockSendGetActionStatus).toHaveBeenCalled()); }); it('should post cancel and invoke callback on cancel upgrade', async () => { mockSendPostCancelAction.mockResolvedValue({}); - let result: any | undefined; - await act(async () => { - ({ result } = renderHook(() => useActionStatus(mockOnAbortSuccess, false, 20, null))); - }); + const { result } = renderHook(() => useActionStatus(mockOnAbortSuccess, false, 20, null)); await act(async () => { await result.current.abortUpgrade(mockActionStatuses[0]); }); @@ -89,10 +86,7 @@ describe('useActionStatus', () => { it('should post cancel and invoke callback on cancel upgrade - plural', async () => { mockSendPostCancelAction.mockResolvedValue({}); - let result: any | undefined; - await act(async () => { - ({ result } = renderHook(() => useActionStatus(mockOnAbortSuccess, false, 20, null))); - }); + const { result } = renderHook(() => useActionStatus(mockOnAbortSuccess, false, 20, null)); await act(async () => { await result.current.abortUpgrade({ ...mockActionStatuses[0], nbAgentsAck: 0 }); }); @@ -106,10 +100,7 @@ describe('useActionStatus', () => { it('should report error on cancel upgrade failure', async () => { const error = new Error('error'); mockSendPostCancelAction.mockRejectedValue(error); - let result: any | undefined; - await act(async () => { - ({ result } = renderHook(() => useActionStatus(mockOnAbortSuccess, false, 20, null))); - }); + const { result } = renderHook(() => useActionStatus(mockOnAbortSuccess, false, 20, null)); await act(async () => { await result.current.abortUpgrade(mockActionStatuses[0]); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_agent_soft_limit.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_agent_soft_limit.test.tsx index 4b9d908baa84..46c72c9c5213 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_agent_soft_limit.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_agent_soft_limit.test.tsx @@ -38,10 +38,12 @@ describe('useAgentSoftLimit', () => { total: 5, }, } as any); - const { result, waitForNextUpdate } = renderer.renderHook(() => useAgentSoftLimit()); - await waitForNextUpdate(); + const { result, rerender } = renderer.renderHook(() => useAgentSoftLimit()); + await renderer.waitFor(() => expect(mockedSendGetAgents).toBeCalled()); + + // re-render so cache is updated to value from most recent call + rerender(); - expect(mockedSendGetAgents).toBeCalled(); expect(result.current.shouldDisplayAgentSoftLimit).toEqual(false); }); @@ -53,10 +55,12 @@ describe('useAgentSoftLimit', () => { total: 15, }, } as any); - const { result, waitForNextUpdate } = renderer.renderHook(() => useAgentSoftLimit()); - await waitForNextUpdate(); + const { result, rerender } = renderer.renderHook(() => useAgentSoftLimit()); + await renderer.waitFor(() => expect(mockedSendGetAgents).toBeCalled()); + + // re-render so cache is updated to value from most recent call + rerender(); - expect(mockedSendGetAgents).toBeCalled(); expect(result.current.shouldDisplayAgentSoftLimit).toEqual(true); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.test.tsx index db4cff14e480..27cb000bdb15 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_fetch_agents_data.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act } from '@testing-library/react-hooks'; +import { act, waitFor } from '@testing-library/react'; import { useStartServices } from '../../../../hooks'; @@ -118,10 +118,8 @@ describe('useFetchAgentsData', () => { it('should fetch agents and agent policies data', async () => { const renderer = createFleetTestRendererMock(); - const { result, waitForNextUpdate } = renderer.renderHook(() => useFetchAgentsData()); - await act(async () => { - await waitForNextUpdate(); - }); + const { result } = renderer.renderHook(() => useFetchAgentsData()); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result?.current.selectedStatus).toEqual(['healthy', 'unhealthy', 'updating', 'offline']); expect(result?.current.allAgentPolicies).toEqual([ @@ -155,27 +153,22 @@ describe('useFetchAgentsData', () => { it('sync querystring kuery with current search', async () => { const renderer = createFleetTestRendererMock(); - const { result, waitForNextUpdate } = renderer.renderHook(() => useFetchAgentsData()); - await act(async () => { - await waitForNextUpdate(); - }); + const { result } = renderer.renderHook(() => useFetchAgentsData()); - expect(renderer.history.location.search).toEqual(''); + await waitFor(() => expect(renderer.history.location.search).toEqual('')); // Set search await act(async () => { result.current.setSearch('active:true'); - await waitForNextUpdate(); }); - expect(renderer.history.location.search).toEqual('?kuery=active%3Atrue'); + await waitFor(() => expect(renderer.history.location.search).toEqual('?kuery=active%3Atrue')); // Clear search await act(async () => { result.current.setSearch(''); - await waitForNextUpdate(); }); - expect(renderer.history.location.search).toEqual(''); + await waitFor(() => expect(renderer.history.location.search).toEqual('')); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.test.tsx index 89059d5a1797..0545f1b412f0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_update_tags.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, renderHook } from '@testing-library/react'; import { sendPostBulkAgentTagsUpdate, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx index fa8e263c1617..c7b71af5f97b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx @@ -8,6 +8,7 @@ import React, { useMemo, useState } from 'react'; import styled from 'styled-components'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n-react'; +import type { EuiBadgeProps } from '@elastic/eui'; import { EuiBadge, EuiButton, @@ -34,67 +35,75 @@ import { useAgentRefresh } from '../agent_details_page/hooks'; import { AgentUpgradeAgentModal } from './agent_upgrade_modal'; -interface Props { +type Props = EuiBadgeProps & { agent: Agent; fromDetails?: boolean; -} - -const Status = { - Healthy: ( - <EuiBadge color="success"> - <FormattedMessage id="xpack.fleet.agentHealth.healthyStatusText" defaultMessage="Healthy" /> - </EuiBadge> - ), - Offline: ( - <EuiBadge color="default"> - <FormattedMessage id="xpack.fleet.agentHealth.offlineStatusText" defaultMessage="Offline" /> - </EuiBadge> - ), - Inactive: ( - <EuiBadge color={euiVars.euiColorDarkShade}> - <FormattedMessage id="xpack.fleet.agentHealth.inactiveStatusText" defaultMessage="Inactive" /> - </EuiBadge> - ), - Unenrolled: ( - <EuiBadge color={euiVars.euiColorDisabled}> - <FormattedMessage - id="xpack.fleet.agentHealth.unenrolledStatusText" - defaultMessage="Unenrolled" - /> - </EuiBadge> - ), - Unhealthy: ( - <EuiBadge color="warning"> - <FormattedMessage - id="xpack.fleet.agentHealth.unhealthyStatusText" - defaultMessage="Unhealthy" - /> - </EuiBadge> - ), - Updating: ( - <EuiBadge color="primary"> - <FormattedMessage id="xpack.fleet.agentHealth.updatingStatusText" defaultMessage="Updating" /> - </EuiBadge> - ), }; -function getStatusComponent(status: Agent['status']): React.ReactElement { +function getStatusComponent({ + status, + ...restOfProps +}: { + status: Agent['status']; +} & EuiBadgeProps): React.ReactElement { switch (status) { case 'error': case 'degraded': - return Status.Unhealthy; + return ( + <EuiBadge color="warning" {...restOfProps}> + <FormattedMessage + id="xpack.fleet.agentHealth.unhealthyStatusText" + defaultMessage="Unhealthy" + /> + </EuiBadge> + ); case 'inactive': - return Status.Inactive; + return ( + <EuiBadge color={euiVars.euiColorDarkShade} {...restOfProps}> + <FormattedMessage + id="xpack.fleet.agentHealth.inactiveStatusText" + defaultMessage="Inactive" + /> + </EuiBadge> + ); case 'offline': - return Status.Offline; + return ( + <EuiBadge color="default" {...restOfProps}> + <FormattedMessage + id="xpack.fleet.agentHealth.offlineStatusText" + defaultMessage="Offline" + /> + </EuiBadge> + ); case 'unenrolling': case 'enrolling': case 'updating': - return Status.Updating; + return ( + <EuiBadge color="primary" {...restOfProps}> + <FormattedMessage + id="xpack.fleet.agentHealth.updatingStatusText" + defaultMessage="Updating" + /> + </EuiBadge> + ); case 'unenrolled': - return Status.Unenrolled; + return ( + <EuiBadge color={euiVars.euiColorDisabled} {...restOfProps}> + <FormattedMessage + id="xpack.fleet.agentHealth.unenrolledStatusText" + defaultMessage="Unenrolled" + /> + </EuiBadge> + ); default: - return Status.Healthy; + return ( + <EuiBadge color="success" {...restOfProps}> + <FormattedMessage + id="xpack.fleet.agentHealth.healthyStatusText" + defaultMessage="Healthy" + /> + </EuiBadge> + ); } } @@ -102,7 +111,11 @@ const WrappedEuiCallOut = styled(EuiCallOut)` white-space: wrap !important; `; -export const AgentHealth: React.FunctionComponent<Props> = ({ agent, fromDetails }) => { +export const AgentHealth: React.FunctionComponent<Props> = ({ + agent, + fromDetails, + ...restOfProps +}) => { const { last_checkin: lastCheckIn, last_checkin_message: lastCheckInMessage } = agent; const msLastCheckIn = new Date(lastCheckIn || 0).getTime(); const lastCheckInMessageText = lastCheckInMessage ? ( @@ -172,14 +185,16 @@ export const AgentHealth: React.FunctionComponent<Props> = ({ agent, fromDetails > {isStuckInUpdating(agent) && !fromDetails ? ( <div className="eui-textNoWrap"> - {getStatusComponent(agent.status)} + {getStatusComponent({ status: agent.status, ...restOfProps })}   <EuiIcon type="warning" color="warning" /> </div> ) : ( <> - {getStatusComponent(agent.status)} - {previousToOfflineStatus ? getStatusComponent(previousToOfflineStatus) : null} + {getStatusComponent({ status: agent.status, ...restOfProps })} + {previousToOfflineStatus + ? getStatusComponent({ status: previousToOfflineStatus, ...restOfProps }) + : null} </> )} </EuiToolTip> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/hooks.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/hooks.test.tsx index 8aea16559058..be5de3cd0434 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/hooks.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/hooks.test.tsx @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { act } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; import { createFleetTestRendererMock } from '../../../../../../mock'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy.test.tsx index 7372edf0ada2..c4caa7e47c07 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy.test.tsx @@ -22,7 +22,7 @@ jest.mock('../../../../../hooks/use_authz', () => ({ describe('useFleetServerUnhealthy', () => { const testRenderer = createFleetTestRendererMock(); - it('should return isUnHealthy:false with an online fleet slerver', async () => { + it('should return isUnHealthy:false with an online fleet server', async () => { jest.mocked(sendGetEnrollmentSettings).mockResolvedValueOnce({ error: null, data: { @@ -46,13 +46,12 @@ describe('useFleetServerUnhealthy', () => { }, }); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => useFleetServerUnhealthy()); - await waitForNextUpdate(); - expect(result.current.isLoading).toBeFalsy(); + const { result } = testRenderer.renderHook(() => useFleetServerUnhealthy()); + await testRenderer.waitFor(() => expect(result.current.isLoading).toBeFalsy()); expect(result.current.isUnhealthy).toBeFalsy(); }); - it('should return isUnHealthy:true with only one offline fleet slerver', async () => { + it('should return isUnHealthy:true with only one offline fleet server', async () => { jest.mocked(sendGetEnrollmentSettings).mockResolvedValue({ error: null, data: { @@ -62,9 +61,8 @@ describe('useFleetServerUnhealthy', () => { }, }, }); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => useFleetServerUnhealthy()); - await waitForNextUpdate(); - expect(result.current.isLoading).toBeFalsy(); + const { result } = testRenderer.renderHook(() => useFleetServerUnhealthy()); + await testRenderer.waitFor(() => expect(result.current.isLoading).toBeFalsy()); expect(result.current.isUnhealthy).toBeTruthy(); }); @@ -73,9 +71,8 @@ describe('useFleetServerUnhealthy', () => { error: new Error('Invalid request'), data: null, }); - const { result, waitForNextUpdate } = testRenderer.renderHook(() => useFleetServerUnhealthy()); - await waitForNextUpdate(); - expect(result.current.isLoading).toBeFalsy(); + const { result } = testRenderer.renderHook(() => useFleetServerUnhealthy()); + await testRenderer.waitFor(() => expect(result.current.isLoading).toBeFalsy()); expect(result.current.isUnhealthy).toBeFalsy(); expect(testRenderer.startServices.notifications.toasts.addError).toBeCalled(); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/dashboard_helper.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/dashboard_helper.test.ts new file mode 100644 index 000000000000..cdf654cbeb70 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/dashboard_helper.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { GetInfoResponse } from '../../../../../../common'; + +import { getDashboardIdForSpace } from './dashboard_helpers'; + +const PKG_INFO = { + item: { + status: 'installed', + installationInfo: { + install_status: 'installed', + installed_kibana_space_id: 'default', + additional_spaces_installed_kibana: { + test: [ + { + id: 'test-destination-1', + originId: 'test-id-1', + }, + ], + }, + }, + }, +} as unknown as GetInfoResponse; + +describe('getDashboardIdForSpace', () => { + it('return the same id if package is installed in the same space', () => { + expect(() => getDashboardIdForSpace('default', PKG_INFO, 'test-id-1')); + }); + + it('return the destination ID if package is installed in an additionnal space', () => { + expect(() => getDashboardIdForSpace('test', PKG_INFO, 'test-id-1')); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/dashboard_helpers.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/dashboard_helpers.ts new file mode 100644 index 000000000000..bc46118b93fe --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/dashboard_helpers.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; + +import type { GetInfoResponse } from '../../../../../../common'; + +export function getDashboardIdForSpace( + spaceId: string = DEFAULT_SPACE_ID, + res: GetInfoResponse | undefined, + dashboardId: string +) { + if (res?.item?.status !== 'installed') { + return dashboardId; + } + + const installationInfo = res.item.installationInfo; + + if (!installationInfo || installationInfo?.installed_kibana_space_id === spaceId) { + return dashboardId; + } + + return ( + installationInfo.additional_spaces_installed_kibana?.[spaceId]?.find( + ({ originId }) => originId === dashboardId + )?.id ?? dashboardId + ); +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_fleet_proxy_flyout/use_fleet_proxy_form.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_fleet_proxy_flyout/use_fleet_proxy_form.test.tsx index 4650b97bfd65..7e3d3f87137d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_fleet_proxy_flyout/use_fleet_proxy_form.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_fleet_proxy_flyout/use_fleet_proxy_form.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act } from 'react-test-renderer'; +import { act } from '@testing-library/react'; import { createFleetTestRendererMock } from '../../../../../../mock'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx index 95a94848f479..87555b034680 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act } from 'react-test-renderer'; +import { act } from '@testing-library/react'; import { createFleetTestRendererMock } from '../../../../../../mock'; @@ -28,20 +28,22 @@ describe('useFleetServerHostsForm', () => { await act(() => result.current.submit()); - expect(result.current.inputs.hostUrlsInput.props.errors).toMatchInlineSnapshot(` - Array [ - Object { - "index": 0, - "message": "Duplicate URL", - }, - Object { - "index": 1, - "message": "Duplicate URL", - }, - ] - `); - expect(onSuccess).not.toBeCalled(); - expect(result.current.isDisabled).toBeTruthy(); + await testRenderer.waitFor(() => { + expect(result.current.inputs.hostUrlsInput.props.errors).toMatchInlineSnapshot(` + Array [ + Object { + "index": 0, + "message": "Duplicate URL", + }, + Object { + "index": 1, + "message": "Duplicate URL", + }, + ] + `); + expect(onSuccess).not.toBeCalled(); + expect(result.current.isDisabled).toBeTruthy(); + }); }); it('should submit a valid form', async () => { @@ -64,7 +66,8 @@ describe('useFleetServerHostsForm', () => { act(() => result.current.inputs.hostUrlsInput.props.onChange(['https://test.fr'])); await act(() => result.current.submit()); - expect(onSuccess).toBeCalled(); + + await testRenderer.waitFor(() => expect(onSuccess).toBeCalled()); }); it('should allow the user to correct and submit a invalid form', async () => { @@ -89,13 +92,16 @@ describe('useFleetServerHostsForm', () => { ); await act(() => result.current.submit()); - expect(onSuccess).not.toBeCalled(); - expect(result.current.isDisabled).toBeTruthy(); + + await testRenderer.waitFor(() => { + expect(onSuccess).not.toBeCalled(); + expect(result.current.isDisabled).toBeTruthy(); + }); act(() => result.current.inputs.hostUrlsInput.props.onChange(['https://test.fr'])); expect(result.current.isDisabled).toBeFalsy(); await act(() => result.current.submit()); - expect(onSuccess).toBeCalled(); + await testRenderer.waitFor(() => expect(onSuccess).toBeCalled()); }); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts index d137d908ca3b..f5aed7de52ab 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_merge_epr_with_replacements.test.ts @@ -8,7 +8,7 @@ import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common'; import type { IntegrationCategory } from '@kbn/custom-integrations-plugin/common'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { PackageListItem } from '../../../../common/types/models'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.test.tsx index 6040c3bacd70..d73d233ac0b0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { act, type WrapperComponent } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; import { coreMock } from '@kbn/core/public/mocks'; import { createIntegrationsTestRendererMock } from '../../../mock'; @@ -48,9 +48,10 @@ describe('usePackageInstall', () => { throw error; }) as any); - const wrapper: WrapperComponent<any> = ({ children }) => ( + const wrapper = ({ children }: React.PropsWithChildren<unknown>) => ( <PackageInstallProvider startServices={coreStart}>{children}</PackageInstallProvider> ); + const { result } = renderer.renderHook(() => useInstallPackage(), wrapper); const installPackage = result.current; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.test.tsx index 716f59231a4c..f3c7665f3b0e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.test.tsx @@ -68,7 +68,8 @@ function renderPackageCard(props: PackageCardProps) { return { utils }; } -describe('package card', () => { +// FLAKY: https://github.com/elastic/kibana/issues/200848 +describe.skip('package card', () => { let mockNavigateToApp: jest.Mock; let mockNavigateToUrl: jest.Mock; const mockGetLineClamp = getLineClampStyles as jest.Mock; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 9a707500bb03..0e7e6117d2cf 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -846,7 +846,7 @@ export function Detail() { </Route> <Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_policies}> {canReadIntegrationPolicies ? ( - <PackagePoliciesPage name={packageInfo.name} version={packageInfo.version} /> + <PackagePoliciesPage packageInfo={packageInfo} /> ) : ( <PermissionsError error="MISSING_PRIVILEGES" diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agent_based_table.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agent_based_table.test.tsx new file mode 100644 index 000000000000..77ce5720b294 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agent_based_table.test.tsx @@ -0,0 +1,126 @@ +/* + * 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 { screen, fireEvent, act } from '@testing-library/react'; + +import { createIntegrationsTestRendererMock } from '../../../../../../../../mock'; + +import { AgentBasedPackagePoliciesTable } from './agent_based_table'; + +const mockPackagePolicies = [ + { + agentPolicies: [ + { + id: '1', + name: 'Agent Policy 1', + status: 'active' as const, + is_managed: false, + is_protected: false, + updated_at: '2023-01-01T00:00:00Z', + updated_by: 'user', + created_at: '2023-01-01T00:00:00Z', + created_by: 'user', + namespace: 'default', + revision: 1, + monitoring_enabled: [], + }, + ], + packagePolicy: { + id: 'pkg1', + name: 'Package Policy 1', + package: { name: 'package-name', title: 'Package Title', version: '1.0.0' }, + hasUpgrade: true, + inputs: [], + revision: 1, + updated_at: '2023-01-01T00:00:00Z', + updated_by: 'user', + created_at: '2023-01-01T00:00:00Z', + created_by: 'user', + namespace: 'default', + policy_id: 'policy1', + policy_ids: ['policy1'], + enabled: true, + }, + rowIndex: 0, + }, +]; + +const mockPagination = { + pagination: { currentPage: 1, pageSize: 10, totalItemCount: 1, pageSizeOptions: [10, 20, 50] }, + setPagination: jest.fn(), + pageSizeOptions: [10, 20, 50], +}; + +describe('AgentBasedPackagePoliciesTable', () => { + it('renders the table with package policies', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render( + <AgentBasedPackagePoliciesTable + isLoading={false} + packagePolicies={mockPackagePolicies} + packagePoliciesTotal={1} + refreshPackagePolicies={jest.fn()} + pagination={mockPagination} + /> + ); + await act(async () => { + expect(result.getByText('Integration policy')).toBeInTheDocument(); + expect(result.getByText('Package Policy 1')).toBeInTheDocument(); + expect(result.getByText('v1.0.0')).toBeInTheDocument(); + }); + }); + + it('shows loading message when isLoading is true', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render( + <AgentBasedPackagePoliciesTable + isLoading={true} + packagePolicies={[]} + packagePoliciesTotal={0} + refreshPackagePolicies={jest.fn()} + pagination={mockPagination} + /> + ); + await act(async () => { + expect(result.getByText('Loading integration policies…')).toBeInTheDocument(); + }); + }); + + it('shows no policies message when there are no package policies', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render( + <AgentBasedPackagePoliciesTable + isLoading={false} + packagePolicies={[]} + packagePoliciesTotal={0} + refreshPackagePolicies={jest.fn()} + pagination={mockPagination} + /> + ); + await act(async () => { + expect(result.getByText('No integration policies')).toBeInTheDocument(); + }); + }); + + it('opens the agent enrollment flyout when add agent button is clicked', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render( + <AgentBasedPackagePoliciesTable + isLoading={false} + packagePolicies={mockPackagePolicies} + packagePoliciesTotal={1} + refreshPackagePolicies={jest.fn()} + pagination={mockPagination} + /> + ); + + await act(async () => { + fireEvent.click(screen.getByText('Add agent')); + }); + expect(result.getByTestId('agentEnrollmentFlyout')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agent_based_table.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agent_based_table.tsx new file mode 100644 index 000000000000..197fa84bf4dd --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agent_based_table.tsx @@ -0,0 +1,346 @@ +/* + * 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 { stringify, parse } from 'query-string'; +import React, { useEffect, useState } from 'react'; +import { useLocation, useHistory } from 'react-router-dom'; +import { + EuiBasicTable, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiButton, + EuiIcon, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react'; + +import type { AgentPolicy, InMemoryPackagePolicy, PackagePolicy } from '../../../../../../types'; +import type { usePagination } from '../../../../../../hooks'; +import { useLink, useAuthz, useMultipleAgentPolicies } from '../../../../../../hooks'; +import { + AgentEnrollmentFlyout, + MultipleAgentPoliciesSummaryLine, + AgentPolicySummaryLine, + PackagePolicyActionsMenu, +} from '../../../../../../components'; + +import { Persona } from '../persona'; + +import { PackagePolicyAgentsCell } from './package_policy_agents_cell'; + +export const AgentBasedPackagePoliciesTable = ({ + isLoading, + packagePolicies, + packagePoliciesTotal, + refreshPackagePolicies, + pagination, + addAgentToPolicyIdFromParams, + showAddAgentHelpForPolicyId, +}: { + isLoading: boolean; + packagePolicies: Array<{ + agentPolicies: AgentPolicy[]; + packagePolicy: InMemoryPackagePolicy; + rowIndex: number; + }>; + packagePoliciesTotal: number; + refreshPackagePolicies: () => void; + pagination: ReturnType<typeof usePagination>; + addAgentToPolicyIdFromParams?: string | null; + showAddAgentHelpForPolicyId?: string | null; +}) => { + const { getHref } = useLink(); + const { search } = useLocation(); + const history = useHistory(); + + const [selectedTableIndex, setSelectedTableIndex] = useState<number | undefined>(); + const { canUseMultipleAgentPolicies } = useMultipleAgentPolicies(); + const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies; + const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies; + const canReadAgentPolicies = useAuthz().fleet.readAgentPolicies; + const canShowMultiplePoliciesCell = + canUseMultipleAgentPolicies && canReadIntegrationPolicies && canReadAgentPolicies; + + // Show tour help for adding agents to a policy + const addAgentHelpForPolicyId = packagePolicies.find(({ agentPolicies }) => + agentPolicies.find((agentPolicy) => agentPolicy.id === showAddAgentHelpForPolicyId) + )?.packagePolicy?.id; + + // Handle the "add agent" link displayed in post-installation toast notifications in the case + // where a user is clicking the link while on the package policies listing page + const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState<string | null>( + addAgentToPolicyIdFromParams || null + ); + useEffect(() => { + const unlisten = history.listen((location) => { + const params = new URLSearchParams(location.search); + const addAgentToPolicyId = params.get('addAgentToPolicyId'); + + if (addAgentToPolicyId) { + setFlyoutOpenForPolicyId(addAgentToPolicyId); + } + }); + + return () => unlisten(); + }, [history]); + + const selectedPolicies = + selectedTableIndex !== undefined ? packagePolicies[selectedTableIndex] : undefined; + const selectedAgentPolicies = selectedPolicies?.agentPolicies; + const selectedPackagePolicy = selectedPolicies?.packagePolicy; + const flyoutPolicy = selectedAgentPolicies?.length === 1 ? selectedAgentPolicies[0] : undefined; + + return ( + <> + <EuiBasicTable<{ + agentPolicies: AgentPolicy[]; + packagePolicy: InMemoryPackagePolicy; + rowIndex: number; + }> + items={packagePolicies || []} + columns={[ + { + field: 'packagePolicy.name', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', { + defaultMessage: 'Integration policy', + }), + render(_, { agentPolicies, packagePolicy }) { + return ( + <EuiLink + className="eui-textTruncate" + data-test-subj="integrationNameLink" + href={getHref('integration_policy_edit', { + packagePolicyId: packagePolicy.id, + })} + > + {packagePolicy.name} + </EuiLink> + ); + }, + }, + { + field: 'packagePolicy.package.version', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.version', { + defaultMessage: 'Version', + }), + render(_version, { agentPolicies, packagePolicy }) { + return ( + <EuiFlexGroup gutterSize="s" alignItems="center" wrap={true}> + <EuiFlexItem grow={false}> + <EuiText + size="s" + className="eui-textNoWrap" + data-test-subj="packageVersionText" + > + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.packageVersion" + defaultMessage="v{version}" + values={{ version: _version }} + /> + </EuiText> + </EuiFlexItem> + + {agentPolicies.length > 0 && packagePolicy.hasUpgrade && ( + <EuiFlexItem grow={false}> + <EuiButton + size="s" + minWidth="0" + href={`${getHref('upgrade_package_policy', { + policyId: agentPolicies[0].id, + packagePolicyId: packagePolicy.id, + })}?from=integrations-policy-list`} + data-test-subj="integrationPolicyUpgradeBtn" + isDisabled={!canWriteIntegrationPolicies} + > + <FormattedMessage + id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeButton" + defaultMessage="Upgrade" + /> + </EuiButton> + </EuiFlexItem> + )} + </EuiFlexGroup> + ); + }, + }, + { + field: 'packagePolicy.policy_ids', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentPolicy', { + defaultMessage: 'Agent policies', + }), + truncateText: true, + render(ids, { agentPolicies, packagePolicy }) { + return agentPolicies.length > 0 ? ( + canShowMultiplePoliciesCell ? ( + <MultipleAgentPoliciesSummaryLine + policies={agentPolicies} + packagePolicyId={packagePolicy.id} + onAgentPoliciesChange={refreshPackagePolicies} + /> + ) : ( + <AgentPolicySummaryLine policy={agentPolicies[0]} /> + ) + ) : ids.length === 0 ? ( + <EuiText color="subdued" size="xs"> + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.noAgentPolicies" + defaultMessage="No agent policies" + /> + </EuiText> + ) : ( + <EuiText color="subdued" size="xs"> + <EuiIcon size="m" type="warning" color="warning" /> +   + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.agentPolicyDeletedWarning" + defaultMessage="Policy not found" + /> + </EuiText> + ); + }, + }, + { + field: 'packagePolicy.updated_by', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedBy', { + defaultMessage: 'Last updated by', + }), + truncateText: true, + render(updatedBy: PackagePolicy['updated_by']) { + return <Persona size="s" name={updatedBy} title={updatedBy} />; + }, + }, + { + field: 'packagePolicy.updated_at', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedAt', { + defaultMessage: 'Last updated', + }), + truncateText: true, + render(updatedAt: PackagePolicy['updated_at']) { + return ( + <span className="eui-textTruncate" title={updatedAt}> + <FormattedRelative value={updatedAt} /> + </span> + ); + }, + }, + { + field: '', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentCount', { + defaultMessage: 'Agents', + }), + render({ + agentPolicies, + packagePolicy, + rowIndex, + }: { + agentPolicies: AgentPolicy[]; + packagePolicy: InMemoryPackagePolicy; + rowIndex: number; + }) { + if (agentPolicies.length === 0) { + return ( + <EuiText color="subdued" size="xs"> + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.noAgents" + defaultMessage="No agents" + /> + </EuiText> + ); + } + return ( + <PackagePolicyAgentsCell + agentPolicies={agentPolicies} + onAddAgent={() => { + setSelectedTableIndex(rowIndex); + setFlyoutOpenForPolicyId(agentPolicies[0].id); + }} + hasHelpPopover={addAgentHelpForPolicyId === packagePolicy.id} + /> + ); + }, + }, + { + field: '', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.actions', { + defaultMessage: 'Actions', + }), + width: '8ch', + align: 'right', + render({ + agentPolicies, + packagePolicy, + }: { + agentPolicies: AgentPolicy[]; + packagePolicy: InMemoryPackagePolicy; + }) { + const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies + return ( + <PackagePolicyActionsMenu + agentPolicies={agentPolicies} + packagePolicy={packagePolicy} + showAddAgent={true} + upgradePackagePolicyHref={ + agentPolicy + ? `${getHref('upgrade_package_policy', { + policyId: agentPolicy.id, + packagePolicyId: packagePolicy.id, + })}?from=integrations-policy-list` + : undefined + } + /> + ); + }, + }, + ]} + loading={isLoading} + data-test-subj="integrationPolicyTable" + pagination={{ + pageIndex: pagination.pagination.currentPage - 1, + pageSize: pagination.pagination.pageSize, + totalItemCount: packagePoliciesTotal, + pageSizeOptions: pagination.pageSizeOptions, + }} + onChange={({ page }: { page: { index: number; size: number } }) => { + pagination.setPagination({ + currentPage: page.index + 1, + pageSize: page.size, + }); + }} + noItemsMessage={ + isLoading ? ( + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.loadingPoliciesMessage" + defaultMessage="Loading integration policies…" + /> + ) : ( + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.noPoliciesMessage" + defaultMessage="No integration policies" + /> + ) + } + /> + {flyoutOpenForPolicyId && selectedAgentPolicies && !isLoading && ( + <AgentEnrollmentFlyout + onClose={() => { + setFlyoutOpenForPolicyId(null); + const { addAgentToPolicyId, ...rest } = parse(search); + history.replace({ search: stringify(rest) }); + }} + agentPolicy={flyoutPolicy} + selectedAgentPolicies={selectedAgentPolicies} + isIntegrationFlow={true} + installedPackagePolicy={{ + name: selectedPackagePolicy?.package?.name || '', + version: selectedPackagePolicy?.package?.version || '', + }} + /> + )} + </> + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agentless_table.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agentless_table.test.tsx new file mode 100644 index 000000000000..25a9933fd719 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agentless_table.test.tsx @@ -0,0 +1,158 @@ +/* + * 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 { fireEvent, act, waitFor } from '@testing-library/react'; + +import { AGENTS_PREFIX } from '../../../../../../../../../common/constants'; +import { sendGetAgents } from '../../../../../../hooks'; +import { createIntegrationsTestRendererMock } from '../../../../../../../../mock'; + +import { AgentlessPackagePoliciesTable } from './agentless_table'; + +jest.mock('../../../../../../hooks', () => ({ + ...jest.requireActual('../../../../../../hooks'), + useConfirmForceInstall: jest.fn(), + sendGetAgents: jest.fn(), +})); + +describe('AgentlessPackagePoliciesTable', () => { + const mockSendGetAgents = sendGetAgents as jest.MockedFunction<typeof sendGetAgents>; + + beforeEach(() => { + mockSendGetAgents.mockResolvedValue({ + data: { + items: [ + { + policy_id: 'policy1', + id: 'agent1', + packages: ['package'], + type: 'PERMANENT', + active: true, + enrolled_at: '2023-01-01T00:00:00Z', + local_metadata: {}, + status: 'online', + }, + ], + total: 1, + page: 1, + perPage: 10000, + }, + error: null, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + const defaultProps = { + isLoading: false, + packagePolicies: [ + { + agentPolicies: [ + { + id: 'policy1', + name: 'Policy 1', + status: 'active' as const, + is_managed: false, + updated_at: '2023-01-01T00:00:00Z', + updated_by: 'user1', + namespace: 'default', + monitoring_enabled: [], + revision: 1, + is_protected: false, + }, + ], + packagePolicy: { + id: 'packagePolicy1', + name: 'Package Policy 1', + updated_by: 'user1', + updated_at: '2023-01-01T00:00:00Z', + inputs: [], + policy_id: 'policy1', + namespace: 'default', + enabled: true, + package: { + name: 'package', + title: 'Package', + version: '1.0.0', + }, + hasUpgrade: false, + revision: 1, + created_at: '2023-01-01T00:00:00Z', + created_by: 'user1', + policy_ids: ['policy1'], + }, + rowIndex: 0, + }, + ], + packagePoliciesTotal: 1, + refreshPackagePolicies: jest.fn(), + pagination: { + pagination: { currentPage: 1, pageSize: 10 }, + setPagination: jest.fn(), + pageSizeOptions: [10, 20, 50], + }, + }; + + it('shows loading message when isLoading is true', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render( + <AgentlessPackagePoliciesTable {...defaultProps} packagePolicies={[]} isLoading={true} /> + ); + await act(async () => { + expect(result.getByText('Loading integration policies…')).toBeInTheDocument(); + }); + }); + + it('shows no items message when there are no package policies', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render( + <AgentlessPackagePoliciesTable {...defaultProps} packagePolicies={[]} /> + ); + await act(async () => { + expect(result.getByText('No agentless integration policies')).toBeInTheDocument(); + }); + }); + + it('renders the table with package policies', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render(<AgentlessPackagePoliciesTable {...defaultProps} />); + + await act(async () => { + expect(result.getByText('Package Policy 1')).toBeInTheDocument(); + expect(result.getByText('user1')).toBeInTheDocument(); + }); + }); + + it('displays agent health status when agents are loaded', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render(<AgentlessPackagePoliciesTable {...defaultProps} />); + await waitFor(() => { + expect(mockSendGetAgents).toHaveBeenCalledWith({ + perPage: 10000, + kuery: `${AGENTS_PREFIX}.policy_id: "policy1"`, + }); + }); + expect(await result.findByText('Healthy')).toBeInTheDocument(); + }); + + it('opens flyout when status badge is clicked', async () => { + const renderer = createIntegrationsTestRendererMock(); + const result = renderer.render(<AgentlessPackagePoliciesTable {...defaultProps} />); + await waitFor(() => { + expect(mockSendGetAgents).toHaveBeenCalledWith({ + perPage: 10000, + kuery: `${AGENTS_PREFIX}.policy_id: "policy1"`, + }); + }); + await act(async () => { + fireEvent.click(await result.findByText('Healthy')); + }); + expect(result.getByText('Confirm agentless enrollment')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agentless_table.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agentless_table.tsx new file mode 100644 index 000000000000..22348f137c51 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/components/agentless_table.tsx @@ -0,0 +1,300 @@ +/* + * 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, { useEffect, useMemo, useState } from 'react'; +import type { HorizontalAlignment } from '@elastic/eui'; +import { EuiBadge, EuiBasicTable, EuiLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react'; + +import type { + Agent, + AgentPolicy, + InMemoryPackagePolicy, + PackagePolicy, +} from '../../../../../../types'; +import { AGENTS_PREFIX, SO_SEARCH_LIMIT } from '../../../../../../../../../common/constants'; +import type { usePagination } from '../../../../../../hooks'; +import { useLink, sendGetAgents, useAuthz, useStartServices } from '../../../../../../hooks'; +import { + Loading, + PackagePolicyActionsMenu, + AgentlessEnrollmentFlyout, +} from '../../../../../../components'; + +import { Persona } from '../persona'; +import { AgentHealth } from '../../../../../../../fleet/sections/agents/components'; + +const REFRESH_INTERVAL_MS = 30000; + +export const AgentlessPackagePoliciesTable = ({ + isLoading, + packagePolicies, + packagePoliciesTotal, + refreshPackagePolicies, + pagination, +}: { + isLoading: boolean; + packagePolicies: Array<{ + agentPolicies: AgentPolicy[]; + packagePolicy: InMemoryPackagePolicy; + rowIndex: number; + }>; + packagePoliciesTotal: number; + refreshPackagePolicies: () => void; + pagination: ReturnType<typeof usePagination>; +}) => { + const core = useStartServices(); + const { notifications } = core; + const authz = useAuthz(); + const { getHref } = useLink(); + const [isAgentsLoading, setIsAgentsLoading] = useState<boolean>(false); + const [agentsByPolicyId, setAgentsByPolicyId] = useState<Record<string, Agent>>({}); + const canReadAgents = authz.fleet.readAgents; + + // Kuery for all agents enrolled into the agent policies associated with the package policies + // We use the first agent policy as agentless package policies have a 1:1 relationship with agent policies + // Maximum # of agent policies is 50, based on the max page size in UI + const agentsKuery = useMemo(() => { + return packagePolicies + .reduce((policyIds, { agentPolicies }) => { + return [...policyIds, ...(agentPolicies[0] ? [agentPolicies[0]?.id] : [])]; + }, [] as string[]) + .map((policyId) => `${AGENTS_PREFIX}.policy_id: "${policyId}"`) + .join(' or '); + }, [packagePolicies]); + + // Fetch agents using above kuery, if the user has access to read agents + // Polls every 30 seconds + useEffect(() => { + const fetchAgents = async () => { + const { data: agentsData, error } = await sendGetAgents({ + perPage: SO_SEARCH_LIMIT, + kuery: agentsKuery, + }); + + setAgentsByPolicyId( + (agentsData?.items || []).reduce((acc, agent) => { + if (agent.policy_id) { + acc[agent.policy_id] = agent; + } + return acc; + }, {} as Record<string, Agent>) + ); + + if (error) { + notifications.toasts.addError(error, { + title: i18n.translate( + 'xpack.fleet.epm.packageDetails.integrationList.agentlessStatusError', + { + defaultMessage: 'Error fetching agentless status information', + } + ), + }); + } + setIsAgentsLoading(false); + }; + + if (canReadAgents) { + setIsAgentsLoading(true); + fetchAgents(); + const interval = setInterval(() => { + fetchAgents(); + }, REFRESH_INTERVAL_MS); + return () => clearInterval(interval); + } + }, [agentsKuery, canReadAgents, notifications.toasts]); + + // Flyout state + const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState<string>(); + const [flyoutPackagePolicy, setFlyoutPackagePolicy] = useState<PackagePolicy>(); + const [flyoutAgentPolicy, setFlyoutAgentPolicy] = useState<AgentPolicy>(); + + return ( + <> + <EuiBasicTable<{ + agentPolicies: AgentPolicy[]; + packagePolicy: InMemoryPackagePolicy; + rowIndex: number; + }> + items={packagePolicies || []} + columns={[ + { + field: 'packagePolicy.name', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', { + defaultMessage: 'Integration policy', + }), + render(_, { agentPolicies, packagePolicy }) { + return ( + <EuiLink + className="eui-textTruncate" + data-test-subj="agentlessIntegrationNameLink" + href={getHref('integration_policy_edit', { + packagePolicyId: packagePolicy.id, + })} + > + {packagePolicy.name} + </EuiLink> + ); + }, + }, + { + field: 'packagePolicy.updated_by', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedBy', { + defaultMessage: 'Last updated by', + }), + truncateText: true, + render(updatedBy: PackagePolicy['updated_by']) { + return <Persona size="s" name={updatedBy} title={updatedBy} />; + }, + }, + { + field: 'packagePolicy.updated_at', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedAt', { + defaultMessage: 'Last updated', + }), + truncateText: true, + render(updatedAt: PackagePolicy['updated_at']) { + return ( + <span className="eui-textTruncate" title={updatedAt}> + <FormattedRelative value={updatedAt} /> + </span> + ); + }, + }, + ...(canReadAgents + ? [ + { + field: '', + name: i18n.translate( + 'xpack.fleet.epm.packageDetails.integrationList.agentlessStatus', + { + defaultMessage: 'Status', + } + ), + align: 'left' as HorizontalAlignment, + render({ + agentPolicies, + packagePolicy, + }: { + agentPolicies: AgentPolicy[]; + packagePolicy: InMemoryPackagePolicy; + rowIndex: number; + }) { + if (isAgentsLoading) { + return <Loading size="s" />; + } + // Use the first agent policy ID associated with the package policy + // because agentless package policies are only associated with one agent policy + const agentPolicy = agentPolicies[0]; + const agent = + (agentPolicy?.id && agentsByPolicyId[agentPolicy.id]) || undefined; + + // Status badge click handler + const statusBadgeProps = { + onClick: () => { + setFlyoutOpenForPolicyId(packagePolicy.id); + setFlyoutPackagePolicy(packagePolicy); + setFlyoutAgentPolicy(agentPolicy); + }, + 'data-test-subj': 'agentlessStatusBadge', + onClickAriaLabel: i18n.translate( + 'xpack.fleet.epm.packageDetails.integrationList.agentlessStatusAriaLabel', + { + defaultMessage: 'Open status details', + } + ), + }; + + return agent ? ( + <AgentHealth agent={agent} {...statusBadgeProps} /> + ) : ( + <EuiBadge color="default" {...statusBadgeProps}> + <FormattedMessage + id="xpack.fleet.packageDetails.integrationList.pendingAgentlessStatus" + defaultMessage="Pending" + /> + </EuiBadge> + ); + }, + }, + ] + : []), + { + field: '', + name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.actions', { + defaultMessage: 'Actions', + }), + width: '8ch', + align: 'right' as HorizontalAlignment, + render({ + agentPolicies, + packagePolicy, + }: { + agentPolicies: AgentPolicy[]; + packagePolicy: InMemoryPackagePolicy; + }) { + const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies + return ( + <PackagePolicyActionsMenu + agentPolicies={agentPolicies} + packagePolicy={packagePolicy} + showAddAgent={true} + upgradePackagePolicyHref={ + agentPolicy + ? `${getHref('upgrade_package_policy', { + policyId: agentPolicy.id, + packagePolicyId: packagePolicy.id, + })}?from=integrations-policy-list` + : undefined + } + /> + ); + }, + }, + ]} + loading={isLoading} + data-test-subj="integrationPolicyTable" + pagination={{ + pageIndex: pagination.pagination.currentPage - 1, + pageSize: pagination.pagination.pageSize, + totalItemCount: packagePoliciesTotal, + pageSizeOptions: pagination.pageSizeOptions, + }} + onChange={({ page }: { page: { index: number; size: number } }) => { + pagination.setPagination({ + currentPage: page.index + 1, + pageSize: page.size, + }); + }} + noItemsMessage={ + isLoading ? ( + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.loadingPoliciesMessage" + defaultMessage="Loading integration policies…" + /> + ) : ( + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.noAgentlessPoliciesMessage" + defaultMessage="No agentless integration policies" + /> + ) + } + /> + {flyoutOpenForPolicyId && flyoutPackagePolicy && ( + <AgentlessEnrollmentFlyout + onClose={() => { + setFlyoutOpenForPolicyId(undefined); + setFlyoutPackagePolicy(undefined); + setFlyoutAgentPolicy(undefined); + }} + packagePolicy={flyoutPackagePolicy} + agentPolicy={flyoutAgentPolicy} + /> + )} + </> + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index 300de597f690..06096b1c3de5 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -4,80 +4,49 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { stringify, parse } from 'query-string'; -import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { Redirect, useLocation, useHistory } from 'react-router-dom'; -import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Redirect, useLocation } from 'react-router-dom'; import { - EuiBasicTable, - EuiLink, + EuiAccordion, EuiFlexGroup, EuiFlexItem, + EuiNotificationBadge, + EuiPanel, + EuiSpacer, EuiText, - EuiButton, - EuiIcon, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react'; + +import { FormattedMessage } from '@kbn/i18n-react'; import { InstallStatus } from '../../../../../types'; -import type { GetAgentPoliciesResponseItem, InMemoryPackagePolicy } from '../../../../../types'; +import type { + AgentPolicy, + GetAgentPoliciesResponseItem, + InMemoryPackagePolicy, + PackageInfo, + PackagePolicy, +} from '../../../../../types'; import { useLink, - useUrlPagination, useGetPackageInstallStatus, AgentPolicyRefreshContext, useIsPackagePolicyUpgradable, - useAuthz, - useMultipleAgentPolicies, + usePagination, } from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; -import { - AgentEnrollmentFlyout, - MultipleAgentPoliciesSummaryLine, - AgentPolicySummaryLine, - PackagePolicyActionsMenu, -} from '../../../../../components'; import { SideBarColumn } from '../../../components/side_bar_column'; -import { PackagePolicyAgentsCell } from './components/package_policy_agents_cell'; -import { usePackagePoliciesWithAgentPolicy } from './use_package_policies_with_agent_policy'; -import { Persona } from './persona'; - -interface PackagePoliciesPanelProps { - name: string; - version: string; -} - -interface InMemoryPackagePolicyAndAgentPolicy { - packagePolicy: InMemoryPackagePolicy; - agentPolicies: GetAgentPoliciesResponseItem[]; - rowIndex: number; -} +import { useAgentless } from '../../../../../../fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology'; -const IntegrationDetailsLink = memo<{ - packagePolicy: InMemoryPackagePolicyAndAgentPolicy['packagePolicy']; - agentPolicies: InMemoryPackagePolicyAndAgentPolicy['agentPolicies']; -}>(({ packagePolicy }) => { - const { getHref } = useLink(); - return ( - <EuiLink - className="eui-textTruncate" - data-test-subj="integrationNameLink" - href={getHref('integration_policy_edit', { - packagePolicyId: packagePolicy.id, - })} - > - {packagePolicy.name} - </EuiLink> - ); -}); +import { usePackagePoliciesWithAgentPolicy } from './use_package_policies_with_agent_policy'; +import { AgentBasedPackagePoliciesTable } from './components/agent_based_table'; +import { AgentlessPackagePoliciesTable } from './components/agentless_table'; -export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps) => { +export const PackagePoliciesPage = ({ packageInfo }: { packageInfo: PackageInfo }) => { + const { name, version } = packageInfo; const { search } = useLocation(); - const history = useHistory(); const queryParams = useMemo(() => new URLSearchParams(search), [search]); - const agentPolicyIdFromParams = useMemo( + const addAgentToPolicyIdFromParams = useMemo( () => queryParams.get('addAgentToPolicyId'), [queryParams] ); @@ -85,44 +54,27 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps () => queryParams.get('showAddAgentHelpForPolicyId'), [queryParams] ); - const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState<string | null>( - agentPolicyIdFromParams - ); - const [selectedTableIndex, setSelectedTableIndex] = useState<number | undefined>(); - - const { getPath, getHref } = useLink(); + const { getPath } = useLink(); const getPackageInstallStatus = useGetPackageInstallStatus(); const packageInstallStatus = getPackageInstallStatus(name); - const { pagination, pageSizeOptions, setPagination } = useUrlPagination(); - const { canUseMultipleAgentPolicies } = useMultipleAgentPolicies(); - const { - data, - isLoading, - resendRequest: refreshPolicies, - } = usePackagePoliciesWithAgentPolicy({ - page: pagination.currentPage, - perPage: pagination.pageSize, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`, - }); const { isPackagePolicyUpgradable } = useIsPackagePolicyUpgradable(); + const { isAgentlessIntegration } = useAgentless(); + const canHaveAgentlessPolicies = useMemo( + () => isAgentlessIntegration(packageInfo), + [isAgentlessIntegration, packageInfo] + ); - const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies; - const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies; - const canReadAgentPolicies = useAuthz().fleet.readAgentPolicies; - - const packageAndAgentPolicies = useMemo((): Array<{ - agentPolicies: GetAgentPoliciesResponseItem[]; - packagePolicy: InMemoryPackagePolicy; - rowIndex: number; - }> => { - if (!data?.items) { - return []; - } - - const newPolicies = data.items.map(({ agentPolicies, packagePolicy }, index) => { + // Helper function to map raw policies data for consumption by the table + const mapPoliciesData = useCallback( + ( + { + agentPolicies, + packagePolicy, + }: { agentPolicies: AgentPolicy[]; packagePolicy: PackagePolicy }, + index: number + ) => { const hasUpgrade = isPackagePolicyUpgradable(packagePolicy); - return { agentPolicies, packagePolicy: { @@ -131,284 +83,204 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps }, rowIndex: index, }; - }); - - return newPolicies; - }, [data?.items, isPackagePolicyUpgradable]); - - const showAddAgentHelpForPackagePolicyId = packageAndAgentPolicies.find(({ agentPolicies }) => - agentPolicies.find((agentPolicy) => agentPolicy.id === showAddAgentHelpForPolicyId) - )?.packagePolicy?.id; - // Handle the "add agent" link displayed in post-installation toast notifications in the case - // where a user is clicking the link while on the package policies listing page - useEffect(() => { - const unlisten = history.listen((location) => { - const params = new URLSearchParams(location.search); - const addAgentToPolicyId = params.get('addAgentToPolicyId'); - - if (addAgentToPolicyId) { - setFlyoutOpenForPolicyId(addAgentToPolicyId); - } - }); - - return () => unlisten(); - }, [history]); - - const handleTableOnChange = useCallback( - ({ page }: CriteriaWithPagination<InMemoryPackagePolicyAndAgentPolicy>) => { - setPagination({ - currentPage: page.index + 1, - pageSize: page.size, - }); }, - [setPagination] + [isPackagePolicyUpgradable] ); - const canShowMultiplePoliciesCell = - canUseMultipleAgentPolicies && canReadIntegrationPolicies && canReadAgentPolicies; - const columns: Array<EuiTableFieldDataColumnType<InMemoryPackagePolicyAndAgentPolicy>> = useMemo( - () => [ - { - field: 'packagePolicy.name', - name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', { - defaultMessage: 'Integration policy', - }), - render(_, { agentPolicies, packagePolicy }) { - return ( - <IntegrationDetailsLink packagePolicy={packagePolicy} agentPolicies={agentPolicies} /> - ); - }, - }, - { - field: 'packagePolicy.package.version', - name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.version', { - defaultMessage: 'Version', - }), - render(_version, { agentPolicies, packagePolicy }) { - return ( - <EuiFlexGroup gutterSize="s" alignItems="center" wrap={true}> - <EuiFlexItem grow={false}> - <EuiText size="s" className="eui-textNoWrap" data-test-subj="packageVersionText"> - <FormattedMessage - id="xpack.fleet.epm.packageDetails.integrationList.packageVersion" - defaultMessage="v{version}" - values={{ version: _version }} - /> - </EuiText> - </EuiFlexItem> - {agentPolicies.length > 0 && packagePolicy.hasUpgrade && ( - <EuiFlexItem grow={false}> - <EuiButton - size="s" - minWidth="0" - href={`${getHref('upgrade_package_policy', { - policyId: agentPolicies[0].id, - packagePolicyId: packagePolicy.id, - })}?from=integrations-policy-list`} - data-test-subj="integrationPolicyUpgradeBtn" - isDisabled={!canWriteIntegrationPolicies} - > - <FormattedMessage - id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeButton" - defaultMessage="Upgrade" - /> - </EuiButton> - </EuiFlexItem> - )} - </EuiFlexGroup> - ); - }, - }, - { - field: 'packagePolicy.policy_ids', - name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentPolicy', { - defaultMessage: 'Agent policies', - }), - truncateText: true, - render(ids, { agentPolicies, packagePolicy }) { - return agentPolicies.length > 0 ? ( - canShowMultiplePoliciesCell ? ( - <MultipleAgentPoliciesSummaryLine - policies={agentPolicies} - packagePolicyId={packagePolicy.id} - onAgentPoliciesChange={refreshPolicies} - /> - ) : ( - <AgentPolicySummaryLine policy={agentPolicies[0]} /> - ) - ) : ids.length === 0 ? ( - <EuiText color="subdued" size="xs"> - <FormattedMessage - id="xpack.fleet.epm.packageDetails.integrationList.noAgentPolicies" - defaultMessage="No agent policies" - /> - </EuiText> - ) : ( - <EuiText color="subdued" size="xs"> - <EuiIcon size="m" type="warning" color="warning" /> -   - <FormattedMessage - id="xpack.fleet.epm.packageDetails.integrationList.agentPolicyDeletedWarning" - defaultMessage="Policy not found" - /> - </EuiText> - ); - }, - }, - { - field: 'packagePolicy.updated_by', - name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedBy', { - defaultMessage: 'Last updated by', - }), - truncateText: true, - render(updatedBy) { - return <Persona size="s" name={updatedBy} title={updatedBy} />; - }, - }, - { - field: 'packagePolicy.updated_at', - name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.updatedAt', { - defaultMessage: 'Last updated', - }), - truncateText: true, - render(updatedAt: InMemoryPackagePolicyAndAgentPolicy['packagePolicy']['updated_at']) { - return ( - <span className="eui-textTruncate" title={updatedAt}> - <FormattedRelative value={updatedAt} /> - </span> - ); - }, - }, - { - field: '', - name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentCount', { - defaultMessage: 'Agents', - }), - render({ agentPolicies, packagePolicy, rowIndex }: InMemoryPackagePolicyAndAgentPolicy) { - if (agentPolicies.length === 0) { - return ( - <EuiText color="subdued" size="xs"> - <FormattedMessage - id="xpack.fleet.epm.packageDetails.integrationList.noAgents" - defaultMessage="No agents" - /> - </EuiText> - ); - } - return ( - <PackagePolicyAgentsCell - agentPolicies={agentPolicies} - onAddAgent={() => { - setSelectedTableIndex(rowIndex); - setFlyoutOpenForPolicyId(agentPolicies[0].id); - }} - hasHelpPopover={showAddAgentHelpForPackagePolicyId === packagePolicy.id} - /> - ); - }, - }, - { - field: '', - name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.actions', { - defaultMessage: 'Actions', - }), - width: '8ch', - align: 'right', - render({ agentPolicies, packagePolicy }) { - const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies - return ( - <PackagePolicyActionsMenu - agentPolicies={agentPolicies} - packagePolicy={packagePolicy} - showAddAgent={true} - upgradePackagePolicyHref={ - agentPolicy - ? `${getHref('upgrade_package_policy', { - policyId: agentPolicy.id, - packagePolicyId: packagePolicy.id, - })}?from=integrations-policy-list` - : undefined - } - /> - ); - }, - }, - ], - [ - getHref, - canWriteIntegrationPolicies, - canShowMultiplePoliciesCell, - showAddAgentHelpForPackagePolicyId, - refreshPolicies, - ] - ); - - const noItemsMessage = useMemo(() => { - return isLoading ? ( - <FormattedMessage - id="xpack.fleet.epm.packageDetails.integrationList.loadingPoliciesMessage" - defaultMessage="Loading integration policies…" - /> - ) : undefined; - }, [isLoading]); + // States and data for agent-based policies table + // If agentless is not supported or not an agentless integration, skip the + // conditional in the kuery + const { + pagination: agentBasedPagination, + pageSizeOptions: agentBasedPageSizeOptions, + setPagination: agentBasedSetPagination, + } = usePagination(); + const [agentBasedPackageAndAgentPolicies, setAgentBasedPackageAndAgentPolicies] = useState< + Array<{ + agentPolicies: GetAgentPoliciesResponseItem[]; + packagePolicy: InMemoryPackagePolicy; + rowIndex: number; + }> + >([]); + const { + data: agentBasedData, + isLoading: agentBasedIsLoading, + resendRequest: refreshAgentBasedPolicies, + } = usePackagePoliciesWithAgentPolicy({ + page: agentBasedPagination.currentPage, + perPage: agentBasedPagination.pageSize, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "${name}" ${ + canHaveAgentlessPolicies + ? `AND NOT ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.supports_agentless: true` + : `` + }`, + }); + useEffect(() => { + setAgentBasedPackageAndAgentPolicies( + !agentBasedData?.items ? [] : agentBasedData.items.map(mapPoliciesData) + ); + }, [agentBasedData, mapPoliciesData]); - const tablePagination = useMemo(() => { - return { - pageIndex: pagination.currentPage - 1, - pageSize: pagination.pageSize, - totalItemCount: data?.total ?? 0, - pageSizeOptions, - }; - }, [data?.total, pageSizeOptions, pagination.currentPage, pagination.pageSize]); + // States and data for agentless policies table + // If agentless is not supported or not an agentless integration, this block and + // initial request is unnessary but reduces code complexity + const { + pagination: agentlessPagination, + pageSizeOptions: agentlessPageSizeOptions, + setPagination: agentlessSetPagination, + } = usePagination(); + const [agentlessPackageAndAgentPolicies, setAgentlessPackageAndAgentPolicies] = useState< + Array<{ + agentPolicies: GetAgentPoliciesResponseItem[]; + packagePolicy: InMemoryPackagePolicy; + rowIndex: number; + }> + >([]); + const { + data: agentlessData, + isLoading: agentlessIsLoading, + resendRequest: refreshAgentlessPolicies, + } = usePackagePoliciesWithAgentPolicy({ + page: agentlessPagination.currentPage, + perPage: agentlessPagination.pageSize, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "${name}" AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.supports_agentless: true`, + }); + useEffect(() => { + setAgentlessPackageAndAgentPolicies( + !agentlessData?.items ? [] : agentlessData.items.map(mapPoliciesData) + ); + }, [agentlessData, mapPoliciesData]); // if they arrive at this page and the package is not installed, send them to overview // this happens if they arrive with a direct url or they uninstall while on this tab - // Check flyoutOpenForPolicyId otherwise right after installing a new integration the flyout won't open - if (packageInstallStatus.status !== InstallStatus.installed && !flyoutOpenForPolicyId) { + // Check `addAgentToPolicyIdFromParams` otherwise right after installing a new integration the flyout won't open + if (packageInstallStatus.status !== InstallStatus.installed && !addAgentToPolicyIdFromParams) { return ( <Redirect to={getPath('integration_details_overview', { pkgkey: `${name}-${version}` })} /> ); } - const selectedPolicies = - selectedTableIndex !== undefined ? packageAndAgentPolicies[selectedTableIndex] : undefined; - - const agentPolicies = selectedPolicies?.agentPolicies; - const packagePolicy = selectedPolicies?.packagePolicy; - const flyoutPolicy = agentPolicies?.length === 1 ? agentPolicies[0] : undefined; - return ( - <AgentPolicyRefreshContext.Provider value={{ refresh: refreshPolicies }}> + <AgentPolicyRefreshContext.Provider + value={{ + refresh: () => { + refreshAgentBasedPolicies(); + refreshAgentlessPolicies(); + }, + }} + > <EuiFlexGroup alignItems="flexStart"> <SideBarColumn grow={1} /> <EuiFlexItem grow={7}> - <EuiBasicTable - items={packageAndAgentPolicies || []} - columns={columns} - loading={isLoading} - data-test-subj="integrationPolicyTable" - pagination={tablePagination} - onChange={handleTableOnChange} - noItemsMessage={noItemsMessage} - /> + {!canHaveAgentlessPolicies ? ( + <AgentBasedPackagePoliciesTable + isLoading={agentBasedIsLoading} + packagePolicies={agentBasedPackageAndAgentPolicies} + packagePoliciesTotal={agentBasedData?.total ?? 0} + refreshPackagePolicies={refreshAgentBasedPolicies} + pagination={{ + pagination: agentBasedPagination, + pageSizeOptions: agentBasedPageSizeOptions, + setPagination: agentBasedSetPagination, + }} + addAgentToPolicyIdFromParams={addAgentToPolicyIdFromParams} + showAddAgentHelpForPolicyId={showAddAgentHelpForPolicyId} + /> + ) : ( + <> + <EuiAccordion + id="agentBasedAccordion" + initialIsOpen={true} + buttonContent={ + <EuiFlexGroup + justifyContent="center" + alignItems="center" + gutterSize="s" + responsive={false} + > + <EuiFlexItem grow={false}> + <EuiText size="m"> + <h3> + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.agentlessHeader" + defaultMessage="Agentless" + /> + </h3> + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiNotificationBadge color="subdued" size="m"> + <h3>{agentlessData?.total ?? 0}</h3> + </EuiNotificationBadge> + </EuiFlexItem> + </EuiFlexGroup> + } + > + <EuiSpacer size="m" /> + <EuiPanel hasBorder={true} hasShadow={false}> + <AgentlessPackagePoliciesTable + isLoading={agentlessIsLoading} + packagePolicies={agentlessPackageAndAgentPolicies} + packagePoliciesTotal={agentlessData?.total ?? 0} + refreshPackagePolicies={refreshAgentlessPolicies} + pagination={{ + pagination: agentlessPagination, + pageSizeOptions: agentlessPageSizeOptions, + setPagination: agentlessSetPagination, + }} + /> + </EuiPanel> + </EuiAccordion> + <EuiSpacer size="l" /> + <EuiAccordion + id="agentBasedAccordion" + initialIsOpen={true} + buttonContent={ + <EuiFlexGroup + justifyContent="center" + alignItems="center" + gutterSize="s" + responsive={false} + > + <EuiFlexItem grow={false}> + <EuiText size="m"> + <h3> + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.agentBasedHeader" + defaultMessage="Agent-based" + /> + </h3> + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiNotificationBadge color="subdued" size="m"> + <h3>{agentBasedData?.total ?? 0}</h3> + </EuiNotificationBadge> + </EuiFlexItem> + </EuiFlexGroup> + } + > + <EuiSpacer size="m" /> + <EuiPanel hasBorder={true} hasShadow={false}> + <AgentBasedPackagePoliciesTable + isLoading={agentBasedIsLoading} + packagePolicies={agentBasedPackageAndAgentPolicies} + packagePoliciesTotal={agentBasedData?.total ?? 0} + refreshPackagePolicies={refreshAgentBasedPolicies} + pagination={{ + pagination: agentBasedPagination, + pageSizeOptions: agentBasedPageSizeOptions, + setPagination: agentBasedSetPagination, + }} + addAgentToPolicyIdFromParams={addAgentToPolicyIdFromParams} + showAddAgentHelpForPolicyId={showAddAgentHelpForPolicyId} + /> + </EuiPanel> + </EuiAccordion> + </> + )} </EuiFlexItem> </EuiFlexGroup> - {flyoutOpenForPolicyId && agentPolicies && !isLoading && ( - <AgentEnrollmentFlyout - onClose={() => { - setFlyoutOpenForPolicyId(null); - const { addAgentToPolicyId, ...rest } = parse(search); - history.replace({ search: stringify(rest) }); - }} - agentPolicy={flyoutPolicy} - selectedAgentPolicies={agentPolicies} - isIntegrationFlow={true} - installedPackagePolicy={{ - name: packagePolicy?.package?.name || '', - version: packagePolicy?.package?.version || '', - }} - /> - )} </AgentPolicyRefreshContext.Provider> ); }; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.test.tsx index 4c02ddeeaaf2..8d9dd6c8b604 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_selection.test.tsx @@ -15,7 +15,8 @@ import type { AgentPolicy } from '../../types'; import { AgentPolicySelection } from '.'; -describe('step select agent policy', () => { +// FLAKY: https://github.com/elastic/kibana/issues/200777 +describe.skip('step select agent policy', () => { let testRenderer: TestRenderer; let renderResult: ReturnType<typeof testRenderer.render>; let agentPolicies: AgentPolicy[] = []; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx index 2a111e5d638a..412b221e88d4 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/confirm_incoming_data.tsx @@ -30,7 +30,7 @@ export const ConfirmIncomingData: React.FunctionComponent<Props> = ({ setAgentDataConfirmed, troubleshootLink, }) => { - const { incomingData, isLoading } = usePollingIncomingData(agentIds); + const { incomingData, isLoading } = usePollingIncomingData({ agentIds }); const isGuidedOnboardingActive = useIsGuidedOnboardingActive(installedPolicy?.name); const { guidedOnboarding } = useStartServices(); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/use_get_agent_incoming_data.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/use_get_agent_incoming_data.tsx index 19034151d80a..be08dbd186e5 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/use_get_agent_incoming_data.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/use_get_agent_incoming_data.tsx @@ -75,18 +75,26 @@ export const useGetAgentIncomingData = ( }; const POLLING_INTERVAL_MS = 5 * 1000; // 5 sec -const POLLING_TIMEOUT_MS = 5 * 60 * 1000; // 5 min +export const POLLING_TIMEOUT_MS = 5 * 60 * 1000; // 5 min /** - * Hook for polling incoming data for the selected agent policy. + * Hook for polling incoming data for the selected agent(s). * @param agentIds * @returns incomingData, isLoading */ -export const usePollingIncomingData = ( - agentIds: string[], - previewData?: boolean, - stopPollingAfterPreviewLength: number = 0 -) => { +export const usePollingIncomingData = ({ + agentIds, + pkgName, + pkgVersion, + previewData, + stopPollingAfterPreviewLength = 0, +}: { + agentIds: string[]; + pkgName?: string; + pkgVersion?: string; + previewData?: boolean; + stopPollingAfterPreviewLength?: number; +}) => { const timeout = useRef<number | undefined>(undefined); const [result, setResult] = useState<{ incomingData: IncomingDataList[]; @@ -117,7 +125,12 @@ export const usePollingIncomingData = ( setHasReachedTimeout(true); } - const { data } = await sendGetAgentIncomingData({ agentsIds: agentIds, previewData }); + const { data } = await sendGetAgentIncomingData({ + agentsIds: agentIds, + previewData, + pkgName, + pkgVersion, + }); if (data?.items) { // filter out agents that have `data = false` and keep polling const filtered = data?.items.filter((item) => { @@ -153,7 +166,15 @@ export const usePollingIncomingData = ( return () => { isAborted = true; }; - }, [agentIds, result, previewData, stopPollingAfterPreviewLength, startedPollingAt]); + }, [ + agentIds, + result, + previewData, + stopPollingAfterPreviewLength, + startedPollingAt, + pkgName, + pkgVersion, + ]); return { ...result, diff --git a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.test.tsx b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.test.tsx new file mode 100644 index 000000000000..ef3eb2b0da3c --- /dev/null +++ b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.test.tsx @@ -0,0 +1,151 @@ +/* + * 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 { act, waitFor } from '@testing-library/react'; + +import { sendGetAgents, useGetPackageInfoByKeyQuery } from '../../hooks'; +import { usePollingIncomingData } from '../agent_enrollment_flyout/use_get_agent_incoming_data'; +import { createIntegrationsTestRendererMock } from '../../mock'; + +import { AGENTS_PREFIX } from '../../constants'; + +import type { PackagePolicy } from '../../types'; + +import { AgentlessEnrollmentFlyout } from '.'; + +jest.mock('../../hooks', () => ({ + ...jest.requireActual('../../hooks'), + useGetPackageInfoByKeyQuery: jest.fn(), + sendGetAgents: jest.fn(), +})); + +jest.mock('../agent_enrollment_flyout/use_get_agent_incoming_data', () => ({ + usePollingIncomingData: jest.fn(), +})); + +const mockSendGetAgents = sendGetAgents as jest.Mock; +const mockUseGetPackageInfoByKeyQuery = useGetPackageInfoByKeyQuery as jest.Mock; +const mockUsePollingIncomingData = usePollingIncomingData as jest.Mock; + +describe('AgentlessEnrollmentFlyout', () => { + const onClose = jest.fn(); + const packagePolicy: PackagePolicy = { + id: 'test-package-policy-id', + name: 'test-package-policy', + namespace: 'default', + policy_ids: ['test-policy-id'], + policy_id: 'test-policy-id', + enabled: true, + output_id: '', + package: { name: 'test-package', title: 'Test Package', version: '1.0.0' }, + inputs: [{ enabled: true, policy_template: 'test-template', type: 'test-type', streams: [] }], + revision: 1, + created_at: '', + created_by: '', + updated_at: '', + updated_by: '', + }; + + beforeEach(() => { + mockSendGetAgents.mockResolvedValue({ data: { items: [] } }); + mockUseGetPackageInfoByKeyQuery.mockReturnValue({ data: { item: { title: 'Test Package' } } }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders the flyout with initial loading state', async () => { + const renderer = createIntegrationsTestRendererMock(); + const { getByText } = renderer.render( + <AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} /> + ); + await act(async () => { + expect(getByText('Confirm agentless enrollment')).toBeInTheDocument(); + expect(getByText('Step 1 is loading')).toBeInTheDocument(); + expect( + getByText('Listening for agentless connection... this could take several minutes') + ).toBeInTheDocument(); + expect(getByText('Confirm incoming data')).toBeInTheDocument(); + expect(getByText('Step 2 is disabled')).toBeInTheDocument(); + }); + }); + + it('updates step statuses when agent deployment fails', async () => { + const renderer = createIntegrationsTestRendererMock(); + const agentData = { status: 'error' }; + mockSendGetAgents.mockResolvedValueOnce({ data: { items: [agentData] } }); + + const { getByText } = renderer.render( + <AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} /> + ); + + await waitFor(() => { + expect(getByText('Confirm agentless enrollment')).toBeInTheDocument(); + expect(getByText('Step 1 has errors')).toBeInTheDocument(); + expect(getByText('Agentless deployment failed')).toBeInTheDocument(); + expect(getByText('Confirm incoming data')).toBeInTheDocument(); + expect(getByText('Step 2 is disabled')).toBeInTheDocument(); + }); + }); + + it('fetches agents data on mount and sets step statuses when agent deployment succeeds', async () => { + const renderer = createIntegrationsTestRendererMock(); + const agentData = { status: 'online' }; + mockSendGetAgents.mockResolvedValueOnce({ data: { items: [agentData] } }); + mockUsePollingIncomingData.mockReturnValue({ incomingData: [], hasReachedTimeout: false }); + + const { getByText } = renderer.render( + <AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} /> + ); + + await waitFor(() => { + expect(mockSendGetAgents).toHaveBeenCalledWith({ + kuery: `${AGENTS_PREFIX}.policy_id: "test-policy-id"`, + }); + expect(getByText('Confirm agentless enrollment')).toBeInTheDocument(); + expect(getByText('Step 1 is complete')).toBeInTheDocument(); + expect(getByText('Agentless deployment was successful')).toBeInTheDocument(); + expect(getByText('Confirm incoming data')).toBeInTheDocument(); + expect(getByText('Step 2 is loading')).toBeInTheDocument(); + }); + }); + + it('shows confirm data step as failed when timeout has been reached', async () => { + const renderer = createIntegrationsTestRendererMock(); + mockSendGetAgents.mockResolvedValueOnce({ data: { items: [{ status: 'online' }] } }); + mockUsePollingIncomingData.mockReturnValue({ incomingData: [], hasReachedTimeout: true }); + + const { getByText } = renderer.render( + <AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} /> + ); + + await waitFor(() => { + expect(getByText('Step 1 is complete')).toBeInTheDocument(); + expect(getByText('Confirm incoming data')).toBeInTheDocument(); + expect(getByText('Step 2 has errors')).toBeInTheDocument(); + expect(getByText('No incoming data received from agentless integration')).toBeInTheDocument(); + }); + }); + + it('shows confirm data step as successful when incoming data is received', async () => { + const renderer = createIntegrationsTestRendererMock(); + mockSendGetAgents.mockResolvedValueOnce({ data: { items: [{ status: 'online' }] } }); + mockUsePollingIncomingData.mockReturnValue({ incomingData: [{ data: 'test-data' }] }); + + const { getByText } = renderer.render( + <AgentlessEnrollmentFlyout onClose={onClose} packagePolicy={packagePolicy} /> + ); + + await waitFor(() => { + expect(getByText('Step 1 is complete')).toBeInTheDocument(); + expect(getByText('Confirm incoming data')).toBeInTheDocument(); + expect(getByText('Step 2 is complete')).toBeInTheDocument(); + expect(getByText('Incoming data received from agentless integration')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.tsx new file mode 100644 index 000000000000..0fbf8d278fab --- /dev/null +++ b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/index.tsx @@ -0,0 +1,212 @@ +/* + * 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, { useEffect, useMemo, useRef, useState } from 'react'; +import type { EuiStepStatus } from '@elastic/eui'; +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiFlyoutFooter, + EuiSteps, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { AGENTS_PREFIX, MAX_FLYOUT_WIDTH } from '../../constants'; +import type { Agent, AgentPolicy, PackagePolicy } from '../../types'; +import { sendGetAgents, useStartServices, useGetPackageInfoByKeyQuery } from '../../hooks'; + +import { AgentlessStepConfirmEnrollment } from './step_confirm_enrollment'; +import { AgentlessStepConfirmData } from './step_confirm_data'; + +const REFRESH_INTERVAL_MS = 30000; + +/** + * This component displays additional status details of an agentless agent enrolled + * the chosen package policy (and its agent policy). + * It also displays confirmation that the agentless agent is ingesting data from + * the chosen package policy. + */ +export const AgentlessEnrollmentFlyout = ({ + onClose, + packagePolicy, + agentPolicy, +}: { + onClose: () => void; + packagePolicy: PackagePolicy; + agentPolicy?: AgentPolicy; +}) => { + const core = useStartServices(); + const { notifications } = core; + const [confirmEnrollmentStatus, setConfirmEnrollmentStatus] = useState<EuiStepStatus>('loading'); + const [confirmDataStatus, setConfirmDataStatus] = useState<EuiStepStatus>('disabled'); + const [agentData, setAgentData] = useState<Agent>(); + + // Clear agent data polling + // Called when component is unmounted or when agent is healthy + const agentDataInterval = useRef<NodeJS.Timeout>(); + const clearAgentDataPolling = useMemo(() => { + return () => { + if (agentDataInterval.current) { + clearInterval(agentDataInterval.current); + } + }; + }, [agentDataInterval]); + + // Fetch agent(s) data for the first associated agent policy + // Polls every 30 seconds until agent is found and healthy + useEffect(() => { + const fetchAgents = async () => { + const { data: agentsData, error } = await sendGetAgents({ + kuery: `${AGENTS_PREFIX}.policy_id: "${packagePolicy.policy_ids[0]}"`, + }); + + if (error) { + notifications.toasts.addError(error, { + title: i18n.translate( + 'xpack.fleet.epm.packageDetails.integrationList.agentlessStatusError', + { + defaultMessage: 'Error fetching agentless status information', + } + ), + }); + } + + if (agentsData?.items?.[0]) { + setAgentData(agentsData.items?.[0]); + } + }; + + fetchAgents(); + agentDataInterval.current = setInterval(() => { + fetchAgents(); + }, REFRESH_INTERVAL_MS); + + return () => clearAgentDataPolling(); + }, [clearAgentDataPolling, notifications.toasts, packagePolicy.policy_ids]); + + // Watches agent data and updates step statuses and clears polling when agent is healthy + useEffect(() => { + if (agentData) { + if (agentData.status === 'online') { + setConfirmEnrollmentStatus('complete'); + setConfirmDataStatus('loading'); + clearAgentDataPolling(); + } else if (agentData.status === 'error' || agentData.status === 'degraded') { + setConfirmEnrollmentStatus('danger'); + setConfirmDataStatus('disabled'); + } else { + setConfirmEnrollmentStatus('loading'); + setConfirmDataStatus('disabled'); + } + } else { + setConfirmEnrollmentStatus('loading'); + setConfirmDataStatus('disabled'); + } + }, [agentData, clearAgentDataPolling]); + + // Calculate integration title from the base package info and what + // is configured on the package policy. + const { data: packageInfoData } = useGetPackageInfoByKeyQuery( + packagePolicy.package!.name, + packagePolicy.package!.version, + { + prerelease: true, + } + ); + + const integrationTitle = useMemo(() => { + if (packageInfoData?.item) { + const enabledInputs = packagePolicy.inputs?.filter((input) => input.enabled); + + // If only one input is enabled, find the input name from the package info and + // and use that for integration title. Otherwise, use the package name. + if (enabledInputs.length === 1 && enabledInputs[0].policy_template) { + const policyTemplate = packageInfoData.item.policy_templates?.find( + (template) => template.name === enabledInputs[0].policy_template + ); + const input = + policyTemplate && 'inputs' in policyTemplate + ? policyTemplate.inputs?.find((i) => i.type === enabledInputs[0].type) + : null; + return input?.title || packageInfoData.item.title; + } else { + return packageInfoData.item.title; + } + } + return packagePolicy.name; + }, [packageInfoData, packagePolicy]); + + return ( + <EuiFlyout + data-test-subj="agentlessEnrollmentFlyout" + onClose={onClose} + maxWidth={MAX_FLYOUT_WIDTH} + > + <EuiFlyoutHeader hasBorder aria-labelledby="FleetAgentlessEnrollmentFlyoutTitle"> + <EuiTitle size="m"> + <h2 id="FleetAgentlessEnrollmentFlyoutTitle">{packagePolicy.name}</h2> + </EuiTitle> + </EuiFlyoutHeader> + <EuiFlyoutBody> + <EuiSteps + steps={[ + { + title: i18n.translate( + 'xpack.fleet.agentlessEnrollmentFlyout.stepConfirmEnrollmentTitle', + { + defaultMessage: 'Confirm agentless enrollment', + } + ), + children: ( + <AgentlessStepConfirmEnrollment + agent={agentData} + agentPolicy={agentPolicy} + integrationTitle={integrationTitle} + /> + ), + status: confirmEnrollmentStatus, + }, + { + title: i18n.translate('xpack.fleet.agentlessEnrollmentFlyout.stepConfirmDataTitle', { + defaultMessage: 'Confirm incoming data', + }), + children: + agentData && confirmEnrollmentStatus === 'complete' ? ( + <AgentlessStepConfirmData + agent={agentData} + packagePolicy={packagePolicy} + setConfirmDataStatus={setConfirmDataStatus} + /> + ) : ( + <></> // Avoids React error about null children prop + ), + status: confirmDataStatus, + }, + ]} + /> + </EuiFlyoutBody> + <EuiFlyoutFooter> + <EuiFlexGroup justifyContent="flexStart"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty onClick={onClose}> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.closeFlyoutButtonLabel" + defaultMessage="Close" + /> + </EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlyoutFooter> + </EuiFlyout> + ); +}; diff --git a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_data.tsx b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_data.tsx new file mode 100644 index 000000000000..34e69d8ef839 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_data.tsx @@ -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 React, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { EuiStepStatus } from '@elastic/eui'; +import { EuiText, EuiLink, EuiSpacer, EuiCallOut } from '@elastic/eui'; + +import { useStartServices } from '../../hooks'; +import type { Agent, PackagePolicy } from '../../types'; +import { + usePollingIncomingData, + POLLING_TIMEOUT_MS, +} from '../agent_enrollment_flyout/use_get_agent_incoming_data'; + +export const AgentlessStepConfirmData = ({ + agent, + packagePolicy, + setConfirmDataStatus, +}: { + agent: Agent; + packagePolicy: PackagePolicy; + setConfirmDataStatus: (status: EuiStepStatus) => void; +}) => { + const { docLinks } = useStartServices(); + const [overallState, setOverallState] = useState<'pending' | 'success' | 'failure'>('pending'); + + // Fetch integration data for the given agent and package policy + const { incomingData, hasReachedTimeout } = usePollingIncomingData({ + agentIds: [agent.id], + pkgName: packagePolicy.package!.name, + pkgVersion: packagePolicy.package!.version, + }); + + // Calculate overall UI state from polling data + useEffect(() => { + if (incomingData.length > 0) { + setConfirmDataStatus('complete'); + setOverallState('success'); + } else if (hasReachedTimeout) { + setConfirmDataStatus('danger'); + setOverallState('failure'); + } else { + setConfirmDataStatus('loading'); + setOverallState('pending'); + } + }, [incomingData, hasReachedTimeout, setConfirmDataStatus]); + + if (overallState === 'success') { + return ( + <EuiCallOut + color="success" + title={i18n.translate('xpack.fleet.agentlessEnrollmentFlyout.confirmData.successText', { + defaultMessage: 'Incoming data received from agentless integration', + })} + iconType="check" + /> + ); + } else if (overallState === 'failure') { + return ( + <> + <EuiCallOut + color="danger" + title={i18n.translate('xpack.fleet.agentlessEnrollmentFlyout.confirmData.failureText', { + defaultMessage: 'No incoming data received from agentless integration', + })} + iconType="warning" + /> + <EuiSpacer size="m" /> + <EuiText> + <p> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmData.failureHelperText" + defaultMessage="No integration data receieved in the past {num} minutes. Check out the {troubleshootingGuideLink} for help." + values={{ + num: POLLING_TIMEOUT_MS / 1000 / 60, + troubleshootingGuideLink: ( + <EuiLink href={docLinks.links.fleet.troubleshooting} target="_blank"> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmData.pendingHelperText.troubleshootingLinkLabel" + defaultMessage="troubleshooting guide" + /> + </EuiLink> + ), + }} + /> + </p> + </EuiText> + </> + ); + } + + return null; +}; diff --git a/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_enrollment.tsx b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_enrollment.tsx new file mode 100644 index 000000000000..f8a5832e0487 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/agentless_enrollment_flyout/step_confirm_enrollment.tsx @@ -0,0 +1,151 @@ +/* + * 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, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButton, EuiPanel, EuiText, EuiLink, EuiSpacer, EuiCallOut } from '@elastic/eui'; + +import type { Agent, AgentPolicy } from '../../types'; +import { useStartServices } from '../../hooks'; +import { AgentDetailsIntegrations } from '../../applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations'; + +export const AgentlessStepConfirmEnrollment = ({ + agent, + agentPolicy, + integrationTitle, +}: { + agent?: Agent; + agentPolicy?: AgentPolicy; + integrationTitle: string; +}) => { + const { docLinks } = useStartServices(); + const [overallState, setOverallState] = useState<'pending' | 'success' | 'failure'>('pending'); + + // Calculate overall UI state from agent status + useEffect(() => { + if (agent && agent.status === 'online') { + setOverallState('success'); + } else if (agent && (agent.status === 'error' || agent.status === 'degraded')) { + setOverallState('failure'); + } else { + setOverallState('pending'); + } + }, [agent]); + + if (overallState === 'success') { + return ( + <> + <EuiCallOut + color="success" + title={i18n.translate( + 'xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.successText', + { + defaultMessage: 'Agentless deployment was successful', + } + )} + iconType="check" + /> + <EuiSpacer size="m" /> + <EuiText> + <p> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.successHelperText" + defaultMessage="{integrationTitle} agentless integration has been successfully established. You can now seamlessly monitor and manage your {integrationTitle} resources without the need for any additional agents." + values={{ + integrationTitle, + }} + /> + </p> + </EuiText> + </> + ); + } else if (overallState === 'failure') { + return ( + <> + <EuiCallOut + color="danger" + title={i18n.translate( + 'xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.failureText', + { + defaultMessage: 'Agentless deployment failed', + } + )} + iconType="warning" + > + {agent?.last_checkin_message && <p>{agent.last_checkin_message}</p>} + </EuiCallOut> + <EuiSpacer size="m" /> + <EuiText> + <p> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.failureHelperText" + defaultMessage="{integrationTitle} agentless integration failed to establish. Check out the {troubleshootingGuideLink} for help." + values={{ + integrationTitle, + troubleshootingGuideLink: ( + <EuiLink href={docLinks.links.fleet.troubleshooting} target="_blank"> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingHelperText.troubleshootingLinkLabel" + defaultMessage="troubleshooting guide" + /> + </EuiLink> + ), + }} + /> + </p> + </EuiText> + {agent && agentPolicy && ( + <> + <EuiSpacer size="m" /> + <AgentDetailsIntegrations agent={agent} agentPolicy={agentPolicy} linkToLogs={false} /> + </> + )} + </> + ); + } + + return ( + <> + <EuiPanel color="subdued" paddingSize="xl" className="eui-textCenter"> + <EuiButton disabled={true} size="s" isLoading={true}> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingText" + defaultMessage="Listening for agentless connection... this could take several minutes" + /> + </EuiButton> + </EuiPanel> + <EuiSpacer size="m" /> + <EuiText> + <p> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingHelperText" + defaultMessage="Getting ready to connect with your cloud account and confirm incoming data. If you're having trouble connecting, check out the {troubleshootingGuideLink}. You can track the latest status from {policyPagePath} Status column." + values={{ + troubleshootingGuideLink: ( + <EuiLink href={docLinks.links.fleet.troubleshooting} target="_blank"> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingHelperText.troubleshootingLinkLabel" + defaultMessage="troubleshooting guide" + /> + </EuiLink> + ), + policyPagePath: ( + <strong> + <FormattedMessage + id="xpack.fleet.agentlessEnrollmentFlyout.confirmEnrollment.pendingHelperText.policyPagePath" + defaultMessage="Integration policies → Agentless Integrations" + /> + </strong> + ), + }} + /> + </p> + </EuiText> + </> + ); +}; diff --git a/x-pack/plugins/fleet/public/components/index.ts b/x-pack/plugins/fleet/public/components/index.ts index 5a995ab1ecbc..39dcfa3e3cd6 100644 --- a/x-pack/plugins/fleet/public/components/index.ts +++ b/x-pack/plugins/fleet/public/components/index.ts @@ -29,3 +29,4 @@ export { HeaderReleaseBadge, InlineReleaseBadge } from './release_badge'; export { WithGuidedOnboardingTour } from './with_guided_onboarding_tour'; export { UninstallCommandFlyout } from './uninstall_command_flyout'; export { MultipleAgentPoliciesSummaryLine } from './multiple_agent_policy_summary_line'; +export { AgentlessEnrollmentFlyout } from './agentless_enrollment_flyout'; diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx index 7195eb73890f..b7759438986d 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx @@ -109,18 +109,18 @@ function createMockPackagePolicy( ...props, }; } -describe('PackagePolicyActionsMenu', () => { +// FLAKY: https://github.com/elastic/kibana/issues/191804 +describe.skip('PackagePolicyActionsMenu', () => { beforeAll(() => { useMultipleAgentPoliciesMock.mockReturnValue({ canUseMultipleAgentPolicies: false }); }); - it('Should disable upgrade button if package does not have upgrade', async () => { + it('Should not have upgrade button if package does not have upgrade', async () => { const agentPolicies = createMockAgentPolicies(); const packagePolicy = createMockPackagePolicy({ hasUpgrade: false }); const { utils } = renderMenu({ agentPolicies, packagePolicy }); await act(async () => { - const upgradeButton = utils.getByTestId('PackagePolicyActionsUpgradeItem'); - expect(upgradeButton).toBeDisabled(); + expect(utils.queryByTestId('PackagePolicyActionsUpgradeItem')).toBeNull(); }); }); diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx index e51d61ea8ab2..25ed040f05e7 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx @@ -108,24 +108,27 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ defaultMessage="Edit integration" /> </EuiContextMenuItem>, - <EuiContextMenuItem - data-test-subj="PackagePolicyActionsUpgradeItem" - disabled={ - !packagePolicy.hasUpgrade || - !canWriteIntegrationPolicies || - !upgradePackagePolicyHref || - agentPolicy?.supports_agentless === true - } - icon="refresh" - href={upgradePackagePolicyHref} - key="packagePolicyUpgrade" - > - <FormattedMessage - id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeActionTitle" - data-test-subj="UpgradeIntegrationPolicy" - defaultMessage="Upgrade integration policy" - /> - </EuiContextMenuItem>, + ...(packagePolicy.hasUpgrade + ? [ + <EuiContextMenuItem + data-test-subj="PackagePolicyActionsUpgradeItem" + disabled={ + !canWriteIntegrationPolicies || + !upgradePackagePolicyHref || + agentPolicy?.supports_agentless === true + } + icon="refresh" + href={upgradePackagePolicyHref} + key="packagePolicyUpgrade" + > + <FormattedMessage + id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeActionTitle" + data-test-subj="UpgradeIntegrationPolicy" + defaultMessage="Upgrade integration policy" + /> + </EuiContextMenuItem>, + ] + : []), // FIXME: implement Copy package policy action // <EuiContextMenuItem disabled icon="copy" onClick={() => {}} key="packagePolicyCopy"> // <FormattedMessage diff --git a/x-pack/plugins/fleet/public/hooks/use_agent_version.test.ts b/x-pack/plugins/fleet/public/hooks/use_agent_version.test.ts index 34a7afe33baa..36024a38fb51 100644 --- a/x-pack/plugins/fleet/public/hooks/use_agent_version.test.ts +++ b/x-pack/plugins/fleet/public/hooks/use_agent_version.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useAgentVersion } from './use_agent_version'; import { useKibanaVersion } from './use_kibana_version'; @@ -28,13 +28,11 @@ describe('useAgentVersion', () => { data: { items: mockAvailableVersions }, }); - const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + const { result } = renderHook(() => useAgentVersion()); expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); - await waitForNextUpdate(); - - expect(result.current).toEqual(mockKibanaVersion); + await waitFor(() => expect(result.current).toEqual(mockKibanaVersion)); }); it('should return agent version with newer patch than kibana', async () => { @@ -46,13 +44,11 @@ describe('useAgentVersion', () => { data: { items: mockAvailableVersions }, }); - const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + const { result } = renderHook(() => useAgentVersion()); expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); - await waitForNextUpdate(); - - expect(result.current).toEqual('8.8.2'); + await waitFor(() => expect(result.current).toEqual('8.8.2')); }); it('should return the latest availeble agent version if a version that matches Kibana version is not released', async () => { @@ -64,13 +60,11 @@ describe('useAgentVersion', () => { data: { items: mockAvailableVersions }, }); - const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + const { result } = renderHook(() => useAgentVersion()); expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); - await waitForNextUpdate(); - - expect(result.current).toEqual('8.9.2'); + await waitFor(() => expect(result.current).toEqual('8.9.2')); }); it('should return the agent version that is <= Kibana version if an agent version that matches Kibana version is not released', async () => { @@ -82,13 +76,11 @@ describe('useAgentVersion', () => { data: { items: mockAvailableVersions }, }); - const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + const { result } = renderHook(() => useAgentVersion()); expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); - await waitForNextUpdate(); - - expect(result.current).toEqual('8.8.2'); + await waitFor(() => expect(result.current).toEqual('8.8.2')); }); it('should return the latest availeble agent version if a snapshot version', async () => { @@ -100,13 +92,11 @@ describe('useAgentVersion', () => { data: { items: mockAvailableVersions }, }); - const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + const { result } = renderHook(() => useAgentVersion()); expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); - await waitForNextUpdate(); - - expect(result.current).toEqual('8.9.2'); + await waitFor(() => expect(result.current).toEqual('8.9.2')); }); it('should return kibana version if no agent versions available', async () => { @@ -118,13 +108,11 @@ describe('useAgentVersion', () => { data: { items: mockAvailableVersions }, }); - const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + const { result } = renderHook(() => useAgentVersion()); expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); - await waitForNextUpdate(); - - expect(result.current).toEqual('8.11.0'); + await waitFor(() => expect(result.current).toEqual('8.11.0')); }); it('should return kibana version if the list of available agent versions is not available', async () => { @@ -133,12 +121,10 @@ describe('useAgentVersion', () => { (useKibanaVersion as jest.Mock).mockReturnValue(mockKibanaVersion); (sendGetAgentsAvailableVersions as jest.Mock).mockRejectedValue(new Error('Fetching error')); - const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + const { result } = renderHook(() => useAgentVersion()); expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); - await waitForNextUpdate(); - - expect(result.current).toEqual(mockKibanaVersion); + await waitFor(() => expect(result.current).toEqual(mockKibanaVersion)); }); it('should return the latest availeble agent version if has build suffix', async () => { @@ -157,12 +143,10 @@ describe('useAgentVersion', () => { data: { items: mockAvailableVersions }, }); - const { result, waitForNextUpdate } = renderHook(() => useAgentVersion()); + const { result } = renderHook(() => useAgentVersion()); expect(sendGetAgentsAvailableVersions).toHaveBeenCalled(); - await waitForNextUpdate(); - - expect(result.current).toEqual('8.11.1+build123456789'); + await waitFor(() => expect(result.current).toEqual('8.11.1+build123456789')); }); }); diff --git a/x-pack/plugins/fleet/public/hooks/use_dismissable_tour.test.ts b/x-pack/plugins/fleet/public/hooks/use_dismissable_tour.test.ts index 394699fc3f34..7dbdcea26b25 100644 --- a/x-pack/plugins/fleet/public/hooks/use_dismissable_tour.test.ts +++ b/x-pack/plugins/fleet/public/hooks/use_dismissable_tour.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, renderHook } from '@testing-library/react'; import { TOUR_STORAGE_KEYS } from '../constants'; import { createStartServices } from '../mock'; diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.test.tsx b/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.test.tsx index 6f9c96d130eb..1676f07d2e11 100644 --- a/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.test.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_server_hosts_for_policy.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useFleetServerHostsForPolicy } from './use_fleet_server_hosts_for_policy'; import { useGetEnrollmentSettings } from './use_request/settings'; diff --git a/x-pack/plugins/fleet/public/hooks/use_is_package_policy_upgradable.test.tsx b/x-pack/plugins/fleet/public/hooks/use_is_package_policy_upgradable.test.tsx index b928a74c306e..80e0360f739a 100644 --- a/x-pack/plugins/fleet/public/hooks/use_is_package_policy_upgradable.test.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_is_package_policy_upgradable.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useIsPackagePolicyUpgradable } from './use_is_package_policy_upgradable'; import { useGetPackages } from './use_request/epm'; diff --git a/x-pack/plugins/fleet/public/hooks/use_multiple_agent_policies.ts b/x-pack/plugins/fleet/public/hooks/use_multiple_agent_policies.ts index b85b944c6ee0..b2648b1d55da 100644 --- a/x-pack/plugins/fleet/public/hooks/use_multiple_agent_policies.ts +++ b/x-pack/plugins/fleet/public/hooks/use_multiple_agent_policies.ts @@ -8,15 +8,16 @@ import { LICENCE_FOR_MULTIPLE_AGENT_POLICIES } from '../../common/constants'; import { ExperimentalFeaturesService } from '../services'; -import { useLicense } from './use_license'; +import { licenseService } from './use_license'; export function useMultipleAgentPolicies() { - const licenseService = useLicense(); + return { canUseMultipleAgentPolicies: canUseMultipleAgentPolicies() }; +} + +export function canUseMultipleAgentPolicies() { const { enableReusableIntegrationPolicies } = ExperimentalFeaturesService.get(); const hasEnterpriseLicence = licenseService.hasAtLeast(LICENCE_FOR_MULTIPLE_AGENT_POLICIES); - const canUseMultipleAgentPolicies = enableReusableIntegrationPolicies && hasEnterpriseLicence; - - return { canUseMultipleAgentPolicies }; + return Boolean(enableReusableIntegrationPolicies && hasEnterpriseLicence); } diff --git a/x-pack/plugins/fleet/public/hooks/use_space_settings_context.test.tsx b/x-pack/plugins/fleet/public/hooks/use_space_settings_context.test.tsx index 6f3d286db370..9f7bfc8ceaf8 100644 --- a/x-pack/plugins/fleet/public/hooks/use_space_settings_context.test.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_space_settings_context.test.tsx @@ -23,7 +23,7 @@ describe('useSpaceSettingsContext', () => { function renderHook() { return createFleetTestRendererMock().renderHook( () => useSpaceSettingsContext(), - ({ children }: { children: any }) => ( + ({ children }: React.PropsWithChildren) => ( <SpaceSettingsContextProvider>{children}</SpaceSettingsContextProvider> ) ); diff --git a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx index 66155cee7bb3..3eb6ade241d3 100644 --- a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx +++ b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx @@ -8,10 +8,8 @@ import type { History } from 'history'; import { createMemoryHistory } from 'history'; import React, { memo } from 'react'; -import type { RenderOptions, RenderResult } from '@testing-library/react'; -import { render as reactRender, act } from '@testing-library/react'; -import { renderHook, type WrapperComponent } from '@testing-library/react-hooks'; -import type { RenderHookResult } from '@testing-library/react-hooks'; +import type { RenderOptions, RenderResult, RenderHookResult } from '@testing-library/react'; +import { render as reactRender, act, waitFor, renderHook } from '@testing-library/react'; import { Router } from '@kbn/shared-ux-router'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -52,11 +50,12 @@ export interface TestRenderer { AppWrapper: React.FC<any>; HookWrapper: React.FC<any>; render: UiRender; - renderHook: <TProps, TResult>( + renderHook: <TResult, TProps>( callback: (props: TProps) => TResult, - wrapper?: WrapperComponent<any> - ) => RenderHookResult<TProps, TResult>; + wrapper?: React.FC<React.PropsWithChildren> + ) => RenderHookResult<TResult, TProps>; setHeaderActionMenu: Function; + waitFor: typeof waitFor; } // disable retries to avoid test flakiness @@ -124,17 +123,21 @@ export const createFleetTestRendererMock = (): TestRenderer => { HookWrapper, renderHook: ( callback, - ExtraWrapper: WrapperComponent<any> = memo(({ children }) => <>{children}</>) + ExtraWrapper: React.FC<React.PropsWithChildren<unknown>> = memo(({ children }) => ( + <>{children}</> + )) ) => { - const wrapper: WrapperComponent<any> = ({ children }) => ( + const wrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => ( <testRendererMocks.HookWrapper> <ExtraWrapper>{children}</ExtraWrapper> </testRendererMocks.HookWrapper> ); + return renderHook(callback, { wrapper, }); }, + waitFor, render: (ui, options) => { let renderResponse: RenderResult; act(() => { @@ -207,9 +210,11 @@ export const createIntegrationsTestRendererMock = (): TestRenderer => { }, renderHook: ( callback, - ExtraWrapper: WrapperComponent<any> = memo(({ children }) => <>{children}</>) + ExtraWrapper: React.FC<React.PropsWithChildren<unknown>> = memo(({ children }) => ( + <>{children}</> + )) ) => { - const wrapper: WrapperComponent<any> = ({ children }) => ( + const wrapper: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => ( <testRendererMocks.HookWrapper> <ExtraWrapper>{children}</ExtraWrapper> </testRendererMocks.HookWrapper> @@ -218,6 +223,7 @@ export const createIntegrationsTestRendererMock = (): TestRenderer => { wrapper, }); }, + waitFor, }; return testRendererMocks; diff --git a/x-pack/plugins/fleet/scripts/create_agent_policies/create_agent_policies.ts b/x-pack/plugins/fleet/scripts/create_agent_policies/create_agent_policies.ts index db47c056ce12..e000640a477e 100644 --- a/x-pack/plugins/fleet/scripts/create_agent_policies/create_agent_policies.ts +++ b/x-pack/plugins/fleet/scripts/create_agent_policies/create_agent_policies.ts @@ -9,8 +9,7 @@ import { ToolingLog } from '@kbn/tooling-log'; import yargs from 'yargs'; import { chunk } from 'lodash'; -import { LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../common/constants'; -import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../common'; +import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../common/constants'; import { packagePolicyFixture } from './fixtures'; @@ -30,20 +29,18 @@ const printUsage = () => const INDEX_BULK_OP = '{ "index":{ "_id": "{{id}}" } }\n'; +const space = 'default'; function getPolicyId(idx: number | string) { - return `test-policy-${idx}`; + return `test-policy-${space}-${idx}`; } async function createAgentPoliciesDocsBulk(range: number[]) { const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64'); const body = range .flatMap((idx) => [ - INDEX_BULK_OP.replace( - /{{id}}/, - `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}:${getPolicyId(idx)}` - ), + INDEX_BULK_OP.replace(/{{id}}/, `${AGENT_POLICY_SAVED_OBJECT_TYPE}:${getPolicyId(idx)}`), JSON.stringify({ - [LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]: { + [AGENT_POLICY_SAVED_OBJECT_TYPE]: { namespace: 'default', monitoring_enabled: ['logs', 'metrics', 'traces'], name: `Test Policy ${idx}`, @@ -60,11 +57,11 @@ async function createAgentPoliciesDocsBulk(range: number[]) { schema_version: '1.1.1', is_protected: false, }, - type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE, + namespaces: [space], + type: AGENT_POLICY_SAVED_OBJECT_TYPE, references: [], managed: false, coreMigrationVersion: '8.8.0', - typeMigrationVersion: '10.3.0', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }) + '\n', @@ -81,7 +78,7 @@ async function createAgentPoliciesDocsBulk(range: number[]) { const data = await res.json(); if (!data.items) { - logger.error('Error creating agent policies docs: ' + JSON.stringify(data)); + logger.error('Error creating agent policy docs: ' + JSON.stringify(data)); process.exit(1); } return data; @@ -91,14 +88,14 @@ async function createEnrollmentToken(range: number[]) { const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64'); const body = range .flatMap((idx) => [ - INDEX_BULK_OP.replace(/{{id}}/, `test-enrollment-token-${idx}`), + INDEX_BULK_OP.replace(/{{id}}/, `test-enrollment-token-${space}-${idx}`), JSON.stringify({ active: true, api_key_id: 'faketest123', api_key: 'test==', name: `Test Policy ${idx}`, policy_id: `${getPolicyId(idx)}`, - namespaces: [], + namespaces: [space], created_at: new Date().toISOString(), }) + '\n', ]) @@ -115,7 +112,7 @@ async function createEnrollmentToken(range: number[]) { const data = await res.json(); if (!data.items) { - logger.error('Error creating agent policies docs: ' + JSON.stringify(data)); + logger.error('Error creating enrollment key docs: ' + JSON.stringify(data)); process.exit(1); } return data; @@ -125,14 +122,12 @@ async function createPackagePolicies(range: number[]) { const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64'); const body = range .flatMap((idx) => [ - INDEX_BULK_OP.replace( - /{{id}}/, - `${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}:test-policy-${idx}` - ), + INDEX_BULK_OP.replace(/{{id}}/, `fleet-package-policies:test-policy-${space}-${idx}`), JSON.stringify( packagePolicyFixture({ idx, agentPolicyId: getPolicyId(idx), + space, }) ) + '\n', ]) @@ -150,7 +145,7 @@ async function createPackagePolicies(range: number[]) { const data = await res.json(); if (!data.items) { - logger.error('Error creating agent policies docs: ' + JSON.stringify(data)); + logger.error('Error creating package policy docs: ' + JSON.stringify(data)); process.exit(1); } return data; diff --git a/x-pack/plugins/fleet/scripts/create_agent_policies/fixtures.ts b/x-pack/plugins/fleet/scripts/create_agent_policies/fixtures.ts index b10f412ac43f..ddaa226c0729 100644 --- a/x-pack/plugins/fleet/scripts/create_agent_policies/fixtures.ts +++ b/x-pack/plugins/fleet/scripts/create_agent_policies/fixtures.ts @@ -8,11 +8,13 @@ export const packagePolicyFixture = ({ agentPolicyId, idx, + space, }: { idx: number; agentPolicyId: string; + space: string; }) => ({ - 'ingest-package-policies': { + 'fleet-package-policies': { name: `system-test-${idx}`, namespace: '', description: '', @@ -790,11 +792,12 @@ export const packagePolicyFixture = ({ updated_at: '2024-08-30T13:45:51.197Z', updated_by: 'system', }, - type: 'ingest-package-policies', + namespaces: [space], + type: 'fleet-package-policies', references: [], managed: false, coreMigrationVersion: '8.8.0', - typeMigrationVersion: '10.14.0', + typeMigrationVersion: '10.1.0', updated_at: '2024-08-30T13:45:51.197Z', created_at: '2024-08-30T13:45:51.197Z', }); diff --git a/x-pack/plugins/fleet/scripts/create_agent_policies/index.js b/x-pack/plugins/fleet/scripts/create_agent_policies/index.js index a51859ee684c..a61ed0e7e54d 100644 --- a/x-pack/plugins/fleet/scripts/create_agent_policies/index.js +++ b/x-pack/plugins/fleet/scripts/create_agent_policies/index.js @@ -12,6 +12,6 @@ require('./create_agent_policies').run(); Usage: cd x-pack/plugins/fleet -node scripts/create_agents/index.js +node scripts/create_agent_policies/index.js */ diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 8d452b394dd1..db100c5fcf7e 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -29,6 +29,7 @@ import { createFleetActionsClientMock } from '../services/actions/mocks'; import { createFleetFilesClientFactoryMock } from '../services/files/mocks'; import { createArtifactsClientMock } from '../services/artifacts/mocks'; +import { createOutputClientMock } from '../services/output_client.mock'; import type { PackagePolicyClient } from '../services/package_policy_service'; import type { AgentPolicyServiceInterface } from '../services'; @@ -303,6 +304,7 @@ export const createFleetStartContractMock = (): DeeplyMockedKeys<FleetStartContr uninstallTokenService: createUninstallTokenServiceMock(), createFleetActionsClient: jest.fn((_) => fleetActionsClient), getPackageSpecTagId: jest.fn(getPackageSpecTagId), + createOutputClient: jest.fn(async (_) => createOutputClientMock()), }; return startContract; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 4c5cdf707053..1620df27b82c 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -84,6 +84,8 @@ import { MessageSigningService, } from './services/security'; +import { OutputClient, type OutputClientInterface } from './services/output_client'; + import { ASSETS_SAVED_OBJECT_TYPE, DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, @@ -143,6 +145,7 @@ import { registerFieldsMetadataExtractors } from './services/register_fields_met import { registerUpgradeManagedPackagePoliciesTask } from './services/setup/managed_package_policies'; import { registerDeployAgentPoliciesTask } from './services/agent_policies/deploy_agent_policies_task'; import { DeleteUnenrolledAgentsTask } from './tasks/delete_unenrolled_agents_task'; +import { registerBumpAgentPoliciesTask } from './services/agent_policies/bump_agent_policies_task'; export interface FleetSetupDeps { security: SecurityPluginSetup; @@ -261,6 +264,12 @@ export interface FleetStartContract { Function exported to allow creating unique ids for saved object tags */ getPackageSpecTagId: (spaceId: string, pkgName: string, tagName: string) => string; + + /** + * Create a Fleet Output Client instance + * @param packageName + */ + createOutputClient: (request: KibanaRequest) => Promise<OutputClientInterface>; } export class FleetPlugin @@ -619,6 +628,7 @@ export class FleetPlugin // Register task registerUpgradeManagedPackagePoliciesTask(deps.taskManager); registerDeployAgentPoliciesTask(deps.taskManager); + registerBumpAgentPoliciesTask(deps.taskManager); this.bulkActionsResolver = new BulkActionsResolver(deps.taskManager, core); this.checkDeletedFilesTask = new CheckDeletedFilesTask({ @@ -835,6 +845,11 @@ export class FleetPlugin return new FleetActionsClient(core.elasticsearch.client.asInternalUser, packageName); }, getPackageSpecTagId, + async createOutputClient(request: KibanaRequest) { + const soClient = appContextService.getSavedObjects().getScopedClient(request); + const authz = await getAuthzFromRequest(request); + return new OutputClient(soClient, authz); + }, }; } diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index bcefa56b806e..c51fb9de674c 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -46,6 +46,8 @@ import { fetchAndAssignAgentMetrics } from '../../services/agents/agent_metrics' import { getAgentStatusForAgentPolicy } from '../../services/agents'; import { isAgentInNamespace } from '../../services/spaces/agent_namespaces'; import { getCurrentNamespace } from '../../services/spaces/get_current_namespace'; +import { getPackageInfo } from '../../services/epm/packages'; +import { generateTemplateIndexPattern } from '../../services/epm/elasticsearch/template/template'; import { buildAgentStatusRuntimeField } from '../../services/agents/build_status_runtime_field'; async function verifyNamespace(agent: Agent, namespace?: string) { @@ -308,17 +310,32 @@ export const getAgentDataHandler: RequestHandler< > = async (context, request, response) => { const coreContext = await context.core; const esClient = coreContext.elasticsearch.client.asCurrentUser; - - const returnDataPreview = request.query.previewData; - const agentIds = isStringArray(request.query.agentsIds) + const agentsIds = isStringArray(request.query.agentsIds) ? request.query.agentsIds : [request.query.agentsIds]; + const { pkgName, pkgVersion, previewData: returnDataPreview } = request.query; + + // If a package is specified, get data stream patterns for that package + // and scope incoming data query to that pattern + let dataStreamPattern: string | undefined; + if (pkgName && pkgVersion) { + const packageInfo = await getPackageInfo({ + savedObjectsClient: coreContext.savedObjects.client, + prerelease: true, + pkgName, + pkgVersion, + }); + dataStreamPattern = (packageInfo.data_streams || []) + .map((ds) => generateTemplateIndexPattern(ds)) + .join(','); + } - const { items, dataPreview } = await AgentService.getIncomingDataByAgentsId( + const { items, dataPreview } = await AgentService.getIncomingDataByAgentsId({ esClient, - agentIds, - returnDataPreview - ); + agentsIds, + dataStreamPattern, + returnDataPreview, + }); const body = { items, dataPreview }; diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/index.test.ts b/x-pack/plugins/fleet/server/routes/agent_policy/index.test.ts index 44d15317d616..a8150b26af5f 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/index.test.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/index.test.ts @@ -171,6 +171,10 @@ describe('schema validation', () => { agent_limits_go_max_procs: 1, agent_logging_level: 'info', agent_logging_metrics_period: '30s', + agent_logging_to_files: true, + agent_logging_files_rotateeverybytes: 10000, + agent_logging_files_keepfiles: 10, + agent_logging_files_interval: '7h', }, keep_monitoring_alive: true, supports_agentless: true, diff --git a/x-pack/plugins/fleet/server/routes/package_policy/utils/index.ts b/x-pack/plugins/fleet/server/routes/package_policy/utils/index.ts index 518d8fc3f74c..a174cb65fe3c 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/utils/index.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/utils/index.ts @@ -11,7 +11,7 @@ import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-ser import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { isAgentlessApiEnabled } from '../../../services/utils/agentless'; +import { isAgentlessEnabled } from '../../../services/utils/agentless'; import { getAgentlessAgentPolicyNameFromPackagePolicyName } from '../../../../common/services/agentless_policy_helper'; @@ -65,7 +65,7 @@ export async function renameAgentlessAgentPolicy( packagePolicy: PackagePolicy, name: string ) { - if (!isAgentlessApiEnabled()) { + if (!isAgentlessEnabled()) { return; } // If agentless is enabled for cloud, we need to rename the agent policy diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 706d0686b984..0f196686a471 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -614,11 +614,13 @@ export const getSavedObjectTypes = ( }, secret_references: { properties: { id: { type: 'keyword' } } }, overrides: { type: 'flattened', index: false }, + supports_agentless: { type: 'boolean' }, revision: { type: 'integer' }, updated_at: { type: 'date' }, updated_by: { type: 'keyword' }, created_at: { type: 'date' }, created_by: { type: 'keyword' }, + bump_agent_policy_revision: { type: 'boolean' }, }, }, modelVersions: { @@ -763,6 +765,26 @@ export const getSavedObjectTypes = ( }, ], }, + '15': { + changes: [ + { + type: 'mappings_addition', + addedMappings: { + bump_agent_policy_revision: { type: 'boolean' }, + }, + }, + ], + }, + '16': { + changes: [ + { + type: 'mappings_addition', + addedMappings: { + supports_agentless: { type: 'boolean' }, + }, + }, + ], + }, }, migrations: { '7.10.0': migratePackagePolicyToV7100, @@ -818,11 +840,35 @@ export const getSavedObjectTypes = ( }, secret_references: { properties: { id: { type: 'keyword' } } }, overrides: { type: 'flattened', index: false }, + supports_agentless: { type: 'boolean' }, revision: { type: 'integer' }, updated_at: { type: 'date' }, updated_by: { type: 'keyword' }, created_at: { type: 'date' }, created_by: { type: 'keyword' }, + bump_agent_policy_revision: { type: 'boolean' }, + }, + }, + modelVersions: { + '1': { + changes: [ + { + type: 'mappings_addition', + addedMappings: { + bump_agent_policy_revision: { type: 'boolean' }, + }, + }, + ], + }, + '2': { + changes: [ + { + type: 'mappings_addition', + addedMappings: { + supports_agentless: { type: 'boolean' }, + }, + }, + ], }, }, }, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/bump_agent_policies_task.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/bump_agent_policies_task.test.ts new file mode 100644 index 000000000000..2e7305c5c571 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/bump_agent_policies_task.test.ts @@ -0,0 +1,115 @@ +/* + * 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 { loggingSystemMock } from '@kbn/core/server/mocks'; + +import { agentPolicyService } from '../agent_policy'; + +import { appContextService } from '..'; +import { getPackagePolicySavedObjectType } from '../package_policy'; + +import { _updatePackagePoliciesThatNeedBump } from './bump_agent_policies_task'; + +jest.mock('../app_context'); +jest.mock('../agent_policy'); +jest.mock('../package_policy'); + +const mockedAgentPolicyService = jest.mocked(agentPolicyService); +const mockedAppContextService = jest.mocked(appContextService); +const mockSoClient = { + find: jest.fn(), + bulkUpdate: jest.fn(), +} as any; +const mockGetPackagePolicySavedObjectType = jest.mocked(getPackagePolicySavedObjectType); + +describe('_updatePackagePoliciesThatNeedBump', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockSoClient.find.mockResolvedValue({ + total: 3, + saved_objects: [ + { + id: 'packagePolicy1', + namespaces: ['default'], + attributes: { + policy_ids: ['policy1'], + }, + }, + { + id: 'packagePolicy12', + namespaces: ['default'], + attributes: { + policy_ids: ['policy1'], + }, + }, + { + id: 'packagePolicy2', + namespaces: ['space'], + attributes: { + policy_ids: ['policy2'], + }, + }, + { + id: 'packagePolicy3', + namespaces: ['space'], + attributes: { + policy_ids: ['policy3'], + }, + }, + ], + page: 1, + perPage: 100, + }); + mockedAppContextService.getInternalUserSOClientWithoutSpaceExtension.mockReturnValue( + mockSoClient + ); + mockedAppContextService.getInternalUserSOClientForSpaceId.mockReturnValue(mockSoClient); + mockGetPackagePolicySavedObjectType.mockResolvedValue('fleet-package-policies'); + }); + + it('should update package policy if bump agent policy revision needed', async () => { + const logger = loggingSystemMock.createLogger(); + + await _updatePackagePoliciesThatNeedBump(logger, () => false); + + expect(mockSoClient.bulkUpdate).toHaveBeenCalledWith([ + { + attributes: { bump_agent_policy_revision: false }, + id: 'packagePolicy1', + type: 'fleet-package-policies', + }, + { + attributes: { bump_agent_policy_revision: false }, + id: 'packagePolicy12', + type: 'fleet-package-policies', + }, + ]); + expect(mockSoClient.bulkUpdate).toHaveBeenCalledWith([ + { + attributes: { bump_agent_policy_revision: false }, + id: 'packagePolicy2', + type: 'fleet-package-policies', + }, + { + attributes: { bump_agent_policy_revision: false }, + id: 'packagePolicy3', + type: 'fleet-package-policies', + }, + ]); + + expect(mockedAgentPolicyService.bumpAgentPoliciesByIds).toHaveBeenCalledWith( + expect.anything(), + undefined, + ['policy1'] + ); + expect(mockedAgentPolicyService.bumpAgentPoliciesByIds).toHaveBeenCalledWith( + expect.anything(), + undefined, + ['policy2', 'policy3'] + ); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/bump_agent_policies_task.ts b/x-pack/plugins/fleet/server/services/agent_policies/bump_agent_policies_task.ts new file mode 100644 index 000000000000..d134fdbfae04 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/bump_agent_policies_task.ts @@ -0,0 +1,139 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import type { + ConcreteTaskInstance, + TaskManagerSetupContract, + TaskManagerStartContract, +} from '@kbn/task-manager-plugin/server'; +import { v4 as uuidv4 } from 'uuid'; +import { uniq } from 'lodash'; + +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; + +import { agentPolicyService, appContextService } from '..'; +import { runWithCache } from '../epm/packages/cache'; +import { getPackagePolicySavedObjectType } from '../package_policy'; +import { mapPackagePolicySavedObjectToPackagePolicy } from '../package_policies'; +import type { PackagePolicy, PackagePolicySOAttributes } from '../../types'; +import { normalizeKuery } from '../saved_object'; +import { SO_SEARCH_LIMIT } from '../../constants'; + +const TASK_TYPE = 'fleet:bump_agent_policies'; + +export function registerBumpAgentPoliciesTask(taskManagerSetup: TaskManagerSetupContract) { + taskManagerSetup.registerTaskDefinitions({ + [TASK_TYPE]: { + title: 'Fleet Bump policies', + timeout: '5m', + maxAttempts: 3, + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => { + let cancelled = false; + const isCancelled = () => cancelled; + return { + async run() { + if (isCancelled()) { + throw new Error('Task has been cancelled'); + } + + await runWithCache(async () => { + await _updatePackagePoliciesThatNeedBump(appContextService.getLogger(), isCancelled); + }); + }, + async cancel() { + cancelled = true; + }, + }; + }, + }, + }); +} + +async function getPackagePoliciesToBump(savedObjectType: string) { + const result = await appContextService + .getInternalUserSOClientWithoutSpaceExtension() + .find<PackagePolicySOAttributes>({ + type: savedObjectType, + filter: normalizeKuery(savedObjectType, `${savedObjectType}.bump_agent_policy_revision:true`), + perPage: SO_SEARCH_LIMIT, + namespaces: ['*'], + fields: ['id', 'namespaces', 'policy_ids'], + }); + return { + total: result.total, + items: result.saved_objects.map((so) => + mapPackagePolicySavedObjectToPackagePolicy(so, so.namespaces) + ), + }; +} + +export async function _updatePackagePoliciesThatNeedBump( + logger: Logger, + isCancelled: () => boolean +) { + const savedObjectType = await getPackagePolicySavedObjectType(); + const packagePoliciesToBump = await getPackagePoliciesToBump(savedObjectType); + + logger.info( + `Found ${packagePoliciesToBump.total} package policies that need agent policy revision bump` + ); + + const packagePoliciesIndexedBySpace = packagePoliciesToBump.items.reduce((acc, policy) => { + const spaceId = policy.spaceIds?.[0] ?? DEFAULT_SPACE_ID; + if (!acc[spaceId]) { + acc[spaceId] = []; + } + + acc[spaceId].push(policy); + + return acc; + }, {} as { [k: string]: PackagePolicy[] }); + + const start = Date.now(); + + for (const [spaceId, packagePolicies] of Object.entries(packagePoliciesIndexedBySpace)) { + if (isCancelled()) { + throw new Error('Task has been cancelled'); + } + + const soClient = appContextService.getInternalUserSOClientForSpaceId(spaceId); + const esClient = appContextService.getInternalUserESClient(); + + await soClient.bulkUpdate<PackagePolicySOAttributes>( + packagePolicies.map((item) => ({ + type: savedObjectType, + id: item.id, + attributes: { + bump_agent_policy_revision: false, + }, + })) + ); + + const updatedCount = packagePolicies.length; + + const agentPoliciesToBump = uniq(packagePolicies.map((item) => item.policy_ids).flat()); + + await agentPolicyService.bumpAgentPoliciesByIds(soClient, esClient, agentPoliciesToBump); + + logger.debug( + `Updated ${updatedCount} package policies in space ${spaceId} in ${ + Date.now() - start + }ms, bump ${agentPoliciesToBump.length} agent policies` + ); + } +} + +export async function scheduleBumpAgentPoliciesTask(taskManagerStart: TaskManagerStartContract) { + await taskManagerStart.ensureScheduled({ + id: `${TASK_TYPE}:${uuidv4()}`, + scope: ['fleet'], + params: {}, + taskType: TASK_TYPE, + runAt: new Date(Date.now() + 3 * 1000), + state: {}, + }); +} diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 688320825326..96fd29b9534c 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -888,6 +888,10 @@ describe('getFullAgentPolicy', () => { advanced_settings: { agent_limits_go_max_procs: 2, agent_logging_level: 'debug', + agent_logging_to_files: true, + agent_logging_files_rotateeverybytes: 10000, + agent_logging_files_keepfiles: 10, + agent_logging_files_interval: '7h', }, }); const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); @@ -896,7 +900,11 @@ describe('getFullAgentPolicy', () => { id: 'agent-policy', agent: { limits: { go_max_procs: 2 }, - logging: { level: 'debug' }, + logging: { + level: 'debug', + to_files: true, + files: { rotateeverybytes: 10000, keepfiles: 10, interval: '7h' }, + }, }, }); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index fb3274b6eef7..76d2727b65e8 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -157,7 +157,7 @@ describe('Agent policy', () => { beforeEach(() => { mockedLogger = loggerMock.create(); mockedAppContextService.getLogger.mockReturnValue(mockedLogger); - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ agentless: false } as any); + mockedAppContextService.getExperimentalFeatures.mockReturnValue({} as any); jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); jest .mocked(getPackagePolicySavedObjectType) @@ -315,10 +315,7 @@ describe('Agent policy', () => { ); }); - it('should throw AgentPolicyInvalidError if support_agentless is defined in stateful without agentless feature', async () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: false } as any); + it('should throw AgentPolicyInvalidError if support_agentless is defined in stateful without agentless enabled', async () => { jest .spyOn(appContextService, 'getCloud') .mockReturnValue({ isServerlessEnabled: false } as any); @@ -339,10 +336,7 @@ describe('Agent policy', () => { ); }); - it('should throw AgentPolicyInvalidError if agentless feature flag is disabled in serverless', async () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: false } as any); + it('should throw AgentPolicyInvalidError if agentless is disabled in serverless', async () => { jest .spyOn(appContextService, 'getCloud') .mockReturnValue({ isServerlessEnabled: true } as any); @@ -363,10 +357,10 @@ describe('Agent policy', () => { ); }); - it('should create a policy agentless feature flag is set and in serverless env', async () => { + it('should create an agentless policy when agentless config is set and in serverless env', async () => { jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: true } as any); + .spyOn(appContextService, 'getConfig') + .mockReturnValue({ agentless: { enabled: true } } as any); jest .spyOn(appContextService, 'getCloud') .mockReturnValue({ isServerlessEnabled: true } as any); @@ -1229,13 +1223,13 @@ describe('Agent policy', () => { ).resolves.not.toThrow(); }); - it('should throw AgentPolicyInvalidError if agentless flag is disabled in serverless', async () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: false } as any); + it('should not throw AgentPolicyInvalidError if support_agentless is defined in serverless', async () => { + jest.spyOn(appContextService, 'getConfig').mockReturnValue({ + agentless: { enabled: true }, + } as any); jest .spyOn(appContextService, 'getCloud') - .mockReturnValue({ isServerlessEnabled: true } as any); + .mockReturnValue({ isServerlessEnabled: true, isCloudEnabled: false } as any); const soClient = getAgentPolicyCreateMock(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; @@ -1252,17 +1246,13 @@ describe('Agent policy', () => { namespace: 'default', supports_agentless: true, }) - ).rejects.toThrowError( - new AgentPolicyInvalidError( - 'supports_agentless is only allowed in serverless and cloud environments that support the agentless feature' - ) - ); + ).resolves.not.toThrow(); }); - it('should not throw in serverless if support_agentless is set and agentless feature flag is set', async () => { + it('should not throw in serverless if support_agentless and agentless config is set', async () => { jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: true } as any); + .spyOn(appContextService, 'getConfig') + .mockReturnValue({ agentless: { enabled: true } } as any); jest .spyOn(appContextService, 'getCloud') .mockReturnValue({ isServerlessEnabled: true } as any); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index cada1c8e6445..21614d2a9748 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -1646,6 +1646,27 @@ class AgentPolicyService { ); } + public async bumpAgentPoliciesByIds( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + agentPolicyIds: string[], + options?: { user?: AuthenticatedUser } + ): Promise<SavedObjectsBulkUpdateResponse<AgentPolicy>> { + const internalSoClientWithoutSpaceExtension = + appContextService.getInternalUserSOClientWithoutSpaceExtension(); + const savedObjectType = await getAgentPolicySavedObjectType(); + + const objects = agentPolicyIds.map((id: string) => ({ id, type: savedObjectType })); + const bulkGetResponse = await soClient.bulkGet<AgentPolicySOAttributes>(objects); + + return this._bumpPolicies( + internalSoClientWithoutSpaceExtension, + esClient, + bulkGetResponse.saved_objects, + options + ); + } + public async getInactivityTimeouts(): Promise< Array<{ policyIds: string[]; inactivityTimeout: number }> > { diff --git a/x-pack/plugins/fleet/server/services/agents/agentless_agent.ts b/x-pack/plugins/fleet/server/services/agents/agentless_agent.ts index 314af0ada7bf..6d1945fced80 100644 --- a/x-pack/plugins/fleet/server/services/agents/agentless_agent.ts +++ b/x-pack/plugins/fleet/server/services/agents/agentless_agent.ts @@ -35,7 +35,7 @@ import { appContextService } from '../app_context'; import { listEnrollmentApiKeys } from '../api_keys'; import { listFleetServerHosts } from '../fleet_server_host'; import type { AgentlessConfig } from '../utils/agentless'; -import { prependAgentlessApiBasePathToEndpoint, isAgentlessApiEnabled } from '../utils/agentless'; +import { prependAgentlessApiBasePathToEndpoint, isAgentlessEnabled } from '../utils/agentless'; class AgentlessAgentService { public async createAgentlessAgent( @@ -59,7 +59,7 @@ class AgentlessAgentService { throw new AgentlessAgentConfigError('missing Agentless API configuration in Kibana'); } - if (!isAgentlessApiEnabled()) { + if (!isAgentlessEnabled()) { logger.error( '[Agentless API] Agentless agents are only supported in cloud deployment and serverless projects' ); @@ -179,7 +179,7 @@ class AgentlessAgentService { `[Agentless API] Start deleting agentless agent for agent policy ${requestConfigDebugStatus}` ); - if (!isAgentlessApiEnabled) { + if (!isAgentlessEnabled) { logger.error( '[Agentless API] Agentless API is not supported. Deleting agentless agent is not supported in non-cloud or non-serverless environments' ); @@ -431,45 +431,38 @@ class AgentlessAgentService { 400: { create: { log: '[Agentless API] Creating the agentless agent failed with a status 400, bad request for agentless policy.', - message: - 'the Agentless API could not create the agentless agent. Please delete the agentless policy and try again or contact your administrator.', + message: `the Agentless API could not create the agentless agent. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, delete: { log: '[Agentless API] Deleting the agentless deployment failed with a status 400, bad request for agentless policy', - message: - 'the Agentless API could not create the agentless agent. Please delete the agentless policy try again or contact your administrator.', + message: `the Agentless API could not create the agentless agent. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, }, 401: { create: { log: '[Agentless API] Creating the agentless agent failed with a status 401 unauthorized for agentless policy.', - message: - 'the Agentless API could not create the agentless agent because an unauthorized request was sent. Please delete the agentless policy and try again or contact your administrator.', + message: `the Agentless API could not create the agentless agent because an unauthorized request was sent. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, delete: { log: '[Agentless API] Deleting the agentless deployment failed with a status 401 unauthorized for agentless policy. Check the Kibana Agentless API tls configuration', - message: - 'the Agentless API could not delete the agentless deployment because an unauthorized request was sent. Please try again or contact your administrator.', + message: `the Agentless API could not delete the agentless deployment because an unauthorized request was sent. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, }, 403: { create: { log: '[Agentless API] Creating the agentless agent failed with a status 403 forbidden for agentless policy. Check the Kibana Agentless API configuration and endpoints.', - message: - 'the Agentless API could not create the agentless agent because a forbidden request was sent. Please delete the agentless policy and try again or contact your administrator.', + message: `the Agentless API could not create the agentless agent because a forbidden request was sent. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, delete: { log: '[Agentless API] Deleting the agentless deployment failed with a status 403 forbidden for agentless policy. Check the Kibana Agentless API configuration and endpoints.', - message: - 'the Agentless API could not delete the agentless deployment because a forbidden request was sent. Please try again or contact your administrator.', + message: `the Agentless API could not delete the agentless deployment because a forbidden request was sent. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, }, 404: { // this is likely to happen when creating agentless agents, but covering it in case create: { log: '[Agentless API] Creating the agentless agent failed with a status 404 not found.', - message: - 'the Agentless API could not create the agentless agent because it returned a 404 error not found.', + message: `the Agentless API could not create the agentless agent because it returned a 404 error not found. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, delete: { log: '[Agentless API] Deleting the agentless deployment failed with a status 404 not found', @@ -479,12 +472,11 @@ class AgentlessAgentService { 408: { create: { log: '[Agentless API] Creating the agentless agent failed with a status 408, the request timed out', - message: - 'the Agentless API request timed out waiting for the agentless agent status to respond, please wait a few minutes for the agent to enroll with fleet. If agent fails to enroll with Fleet please delete the agentless policy try again or contact your administrator.', + message: `the Agentless API request timed out waiting for the agentless agent status to respond, please wait a few minutes for the agent to enroll with fleet. If agent fails to enroll with Fleet please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, delete: { log: '[Agentless API] Deleting the agentless deployment failed with a status 408, the request timed out', - message: `the Agentless API could not delete the agentless deployment ${agentlessPolicyId} because the request timed out, please wait a few minutes for the agentless agent deployment to be removed. If it continues to persist please try again or contact your administrator.`, + message: `the Agentless API could not delete the agentless deployment because the request timed out, please wait a few minutes for the agentless agent deployment to be removed. If it continues to persist please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, }, 429: { @@ -503,36 +495,31 @@ class AgentlessAgentService { 500: { create: { log: '[Agentless API] Creating the agentless agent failed with a status 500 internal service error.', - message: - 'the Agentless API could not create the agentless agent because it returned a 500 internal error. Please delete the agentless policy and try again later or contact your administrator.', + message: `the Agentless API could not create the agentless agent because it returned a 500 internal error. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, delete: { log: '[Agentless API] Deleting the agentless deployment failed with a status 500 internal service error.', - message: - 'the Agentless API could not delete the agentless deployment because it returned a 500 internal error. Please try again later or contact your administrator.', + message: `the Agentless API could not delete the agentless deployment because it returned a 500 internal error. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, }, unhandled_response: { create: { log: '[Agentless API] Creating agentless agent failed because the Agentless API responded with an unhandled status code that falls out of the range of 2xx:', - message: - 'the Agentless API could not create the agentless agent due to an unexpected error. Please delete the agentless policy and try again later or contact your administrator.', + message: `the Agentless API could not create the agentless agent due to an unexpected error. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, delete: { log: '[Agentless API] Deleting agentless deployment failed because the Agentless API responded with an unhandled status code that falls out of the range of 2xx:', - message: `the Agentless API could not delete the agentless deployment ${agentlessPolicyId}. Please try again later or contact your administrator.`, + message: `the Agentless API could not delete the agentless deployment. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, }, request_error: { create: { log: '[Agentless API] Creating agentless agent failed with a request error:', - message: - 'the Agentless API could not create the agentless agent due to a request error. Please delete the agentless policy and try again later or contact your administrator.', + message: `the Agentless API could not create the agentless agent due to a request error. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, delete: { log: '[Agentless API] Deleting agentless deployment failed with a request error:', - message: - 'the Agentless API could not delete the agentless deployment due to a request error. Please try again later or contact your administrator.', + message: `the Agentless API could not delete the agentless deployment due to a request error. Please delete the agentless policy ${agentlessPolicyId} and try again or contact your administrator.`, }, }, }; diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index c413ee7e268c..e960a7b955fe 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -177,11 +177,17 @@ export async function getAgentStatusForAgentPolicy( }; } -export async function getIncomingDataByAgentsId( - esClient: ElasticsearchClient, - agentsIds: string[], - returnDataPreview: boolean = false -) { +export async function getIncomingDataByAgentsId({ + esClient, + agentsIds, + dataStreamPattern = DATA_STREAM_INDEX_PATTERN, + returnDataPreview = false, +}: { + esClient: ElasticsearchClient; + agentsIds: string[]; + dataStreamPattern?: string; + returnDataPreview?: boolean; +}) { const logger = appContextService.getLogger(); try { @@ -189,7 +195,7 @@ export async function getIncomingDataByAgentsId( body: { index: [ { - names: [DATA_STREAM_INDEX_PATTERN], + names: [dataStreamPattern], privileges: ['read'], }, ], @@ -203,7 +209,7 @@ export async function getIncomingDataByAgentsId( const searchResult = await retryTransientEsErrors( () => esClient.search({ - index: DATA_STREAM_INDEX_PATTERN, + index: dataStreamPattern, allow_partial_search_results: true, _source: returnDataPreview, timeout: '5s', @@ -244,9 +250,9 @@ export async function getIncomingDataByAgentsId( if (!searchResult.aggregations?.agent_ids) { return { items: agentsIds.map((id) => { - return { items: { [id]: { data: false } } }; + return { [id]: { data: false } }; }), - data: [], + dataPreview: [], }; } diff --git a/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts b/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts index 8888ae75e469..b422660ba78f 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts @@ -9,8 +9,8 @@ import type { CreateRestAPIKeyParams, CreateRestAPIKeyWithKibanaPrivilegesParams, } from '@kbn/security-plugin/server'; -import type { FakeRawRequest, Headers } from '@kbn/core-http-server'; -import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; +import { type FakeRawRequest, type Headers } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; import type { Logger } from '@kbn/logging'; @@ -43,7 +43,7 @@ function createKibanaRequestFromAuth(authorizationHeader: HTTPAuthorizationHeade // Since we're using API keys and accessing elasticsearch can only be done // via a request, we're faking one with the proper authorization headers. - const fakeRequest = CoreKibanaRequest.from(fakeRawRequest); + const fakeRequest = kibanaRequestFactory(fakeRawRequest); return fakeRequest; } diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index da40f8ff7d81..8b49ca1b3232 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -8,16 +8,9 @@ import type { Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs'; import { kibanaPackageJson } from '@kbn/repo-info'; -import type { - ElasticsearchClient, - SavedObjectsServiceStart, - HttpServiceSetup, - Logger, - KibanaRequest, - SecurityServiceStart, -} from '@kbn/core/server'; -import { CoreKibanaRequest } from '@kbn/core/server'; +import type { HttpServiceSetup, KibanaRequest } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; import type { PluginStart as DataPluginStart } from '@kbn/data-plugin/server'; import type { @@ -29,8 +22,12 @@ import type { SecurityPluginStart, SecurityPluginSetup } from '@kbn/security-plu import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server'; +import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; import { SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SecurityServiceStart } from '@kbn/core-security-server'; +import type { Logger } from '@kbn/logging'; import type { FleetConfigType } from '../../common/types'; import { @@ -188,7 +185,7 @@ class AppContextService { return this.savedObjectsTagging; } public getInternalUserSOClientForSpaceId(spaceId?: string) { - const request = CoreKibanaRequest.from({ + const request = kibanaRequestFactory({ headers: {}, path: '/', route: { settings: {} }, diff --git a/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts b/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts index afcba824f1a3..710d862bf3ad 100644 --- a/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts +++ b/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts @@ -55,10 +55,10 @@ describe('form_settings', () => { ).not.toThrow(); }); - it('generate a valid API schema for api_field with default value', () => { + it('generate a valid API schema for api_field with default value but not add the value', () => { const apiSchema = schema.object(_getSettingsAPISchema(TEST_SETTINGS)); const res = apiSchema.validate({ advanced_settings: {} }); - expect(res).toEqual({ advanced_settings: { test_foo_default_value: 'test' } }); + expect(res).toEqual({ advanced_settings: {} }); }); }); diff --git a/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts b/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts index 6f644afca49a..2c625f6bd057 100644 --- a/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts +++ b/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts @@ -24,40 +24,19 @@ export function _getSettingsAPISchema(settings: SettingsConfig[]): Props { if (!setting.api_field) { return; } - const defaultValueRes = setting.schema.safeParse(undefined); - const defaultValue = defaultValueRes.success ? defaultValueRes.data : undefined; - if (defaultValue) { - validations[setting.api_field.name] = schema.oneOf( - [ - schema.any({ - validate: (val: any) => { - const res = setting.schema.safeParse(val); - if (!res.success) { - return stringifyZodError(res.error); - } - }, - }), - schema.literal(null), - ], - { - defaultValue, - } - ); - } else { - validations[setting.api_field.name] = schema.maybe( - schema.oneOf([ - schema.literal(null), - schema.any({ - validate: (val: any) => { - const res = setting.schema.safeParse(val); - if (!res.success) { - return stringifyZodError(res.error); - } - }, - }), - ]) - ); - } + validations[setting.api_field.name] = schema.maybe( + schema.oneOf([ + schema.literal(null), + schema.any({ + validate: (val: any) => { + const res = setting.schema.safeParse(val); + if (!res.success) { + return stringifyZodError(res.error); + } + }, + }), + ]) + ); }); const advancedSettingsValidations: Props = { @@ -90,7 +69,7 @@ export function _getSettingsValuesForAgentPolicy( } const val = agentPolicy.advanced_settings?.[setting.api_field.name]; - if (val) { + if (val !== undefined) { settingsValues[setting.name] = val; } }); diff --git a/x-pack/plugins/fleet/server/services/output_client.mock.ts b/x-pack/plugins/fleet/server/services/output_client.mock.ts new file mode 100644 index 000000000000..ba684a2aff61 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/output_client.mock.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.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 type { OutputClientInterface } from './output_client'; + +export const createOutputClientMock = (): jest.Mocked<OutputClientInterface> => { + return { + getDefaultDataOutputId: jest.fn(), + get: jest.fn(), + }; +}; diff --git a/x-pack/plugins/fleet/server/services/output_client.test.ts b/x-pack/plugins/fleet/server/services/output_client.test.ts new file mode 100644 index 000000000000..f3d4b83bea4b --- /dev/null +++ b/x-pack/plugins/fleet/server/services/output_client.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; + +import { createFleetAuthzMock } from '../../common/mocks'; + +import { OutputClient } from './output_client'; +import { outputService } from './output'; + +jest.mock('./output'); + +const mockedOutputService = outputService as jest.Mocked<typeof outputService>; + +describe('OutputClient', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('getDefaultDataOutputId()', () => { + it('should call output service `getDefaultDataOutputId()` method', async () => { + const soClient = savedObjectsClientMock.create(); + const authz = createFleetAuthzMock(); + const outputClient = new OutputClient(soClient, authz); + await outputClient.getDefaultDataOutputId(); + + expect(mockedOutputService.getDefaultDataOutputId).toHaveBeenCalledWith(soClient); + }); + + it('should throw error when no `fleet.readSettings` and no `fleet.readAgentPolicies` privileges', async () => { + const soClient = savedObjectsClientMock.create(); + const authz = createFleetAuthzMock(); + authz.fleet.readSettings = false; + authz.fleet.readAgentPolicies = false; + const outputClient = new OutputClient(soClient, authz); + + await expect(outputClient.getDefaultDataOutputId()).rejects.toMatchInlineSnapshot( + `[OutputUnauthorizedError]` + ); + expect(mockedOutputService.getDefaultDataOutputId).not.toHaveBeenCalled(); + }); + }); + + describe('get()', () => { + it('should call output service `get()` method', async () => { + const soClient = savedObjectsClientMock.create(); + const authz = createFleetAuthzMock(); + const outputClient = new OutputClient(soClient, authz); + await outputClient.get('default'); + + expect(mockedOutputService.get).toHaveBeenCalledWith(soClient, 'default'); + }); + + it('should throw error when no `fleet.readSettings` and no `fleet.readAgentPolicies` privileges', async () => { + const soClient = savedObjectsClientMock.create(); + const authz = createFleetAuthzMock(); + authz.fleet.readSettings = false; + authz.fleet.readAgentPolicies = false; + const outputClient = new OutputClient(soClient, authz); + + await expect(outputClient.get('default')).rejects.toMatchInlineSnapshot( + `[OutputUnauthorizedError]` + ); + expect(mockedOutputService.get).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/output_client.ts b/x-pack/plugins/fleet/server/services/output_client.ts new file mode 100644 index 000000000000..574446e1f32a --- /dev/null +++ b/x-pack/plugins/fleet/server/services/output_client.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsClientContract } from '@kbn/core/server'; + +import type { FleetAuthz } from '../../common'; + +import { OutputUnauthorizedError } from '../errors'; +import type { Output } from '../types'; + +import { outputService } from './output'; + +export { transformOutputToFullPolicyOutput } from './agent_policies/full_agent_policy'; + +export interface OutputClientInterface { + getDefaultDataOutputId(): Promise<string | null>; + get(outputId: string): Promise<Output>; +} + +export class OutputClient implements OutputClientInterface { + constructor(private soClient: SavedObjectsClientContract, private authz: FleetAuthz) {} + + async getDefaultDataOutputId() { + if (!this.authz.fleet.readSettings && !this.authz.fleet.readAgentPolicies) { + throw new OutputUnauthorizedError(); + } + return outputService.getDefaultDataOutputId(this.soClient); + } + + async get(outputId: string) { + if (!this.authz.fleet.readSettings && !this.authz.fleet.readAgentPolicies) { + throw new OutputUnauthorizedError(); + } + return outputService.get(this.soClient, outputId); + } +} diff --git a/x-pack/plugins/fleet/server/services/package_policies/utils.ts b/x-pack/plugins/fleet/server/services/package_policies/utils.ts index ef59c643a8b3..a27ac5943f80 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/utils.ts +++ b/x-pack/plugins/fleet/server/services/package_policies/utils.ts @@ -28,17 +28,16 @@ import { licenseService } from '../license'; import { outputService } from '../output'; import { appContextService } from '../app_context'; -export const mapPackagePolicySavedObjectToPackagePolicy = ({ - id, - version, - attributes, - namespaces, -}: SavedObject<PackagePolicySOAttributes>): PackagePolicy => { +export const mapPackagePolicySavedObjectToPackagePolicy = ( + { id, version, attributes }: SavedObject<PackagePolicySOAttributes>, + namespaces?: string[] +): PackagePolicy => { + const { bump_agent_policy_revision: bumpAgentPolicyRevision, ...restAttributes } = attributes; return { id, version, - spaceIds: namespaces, - ...attributes, + ...(namespaces ? { spaceIds: namespaces } : {}), + ...restAttributes, }; }; diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 48dc3956d698..eac31bb98025 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -20,6 +20,7 @@ import type { RequestHandlerContext, SavedObjectsBulkCreateObject, SavedObjectsBulkUpdateObject, + SavedObject, } from '@kbn/core/server'; import { SavedObjectsUtils } from '@kbn/core/server'; import { v4 as uuidv4 } from 'uuid'; @@ -446,7 +447,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } } - const createdPackagePolicy = { id: newSo.id, version: newSo.version, ...newSo.attributes }; + const createdPackagePolicy = mapPackagePolicySavedObjectToPackagePolicy(newSo); logger.debug(`Created new package policy with id ${newSo.id} and version ${newSo.version}`); return packagePolicyService.runExternalCallbacks( @@ -668,11 +669,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } logger.debug(`Created new package policies`); return { - created: newSos.map((newSo) => ({ - id: newSo.id, - version: newSo.version, - ...newSo.attributes, - })), + created: newSos.map((newSo) => mapPackagePolicySavedObjectToPackagePolicy(newSo)), failed: failedPolicies, }; } @@ -754,11 +751,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } } - const response = { - id: packagePolicySO.id, - version: packagePolicySO.version, - ...packagePolicySO.attributes, - }; + const response = mapPackagePolicySavedObjectToPackagePolicy(packagePolicySO); // If possible, return the experimental features map for the package policy's `package` field if (experimentalFeatures && response.package) { @@ -788,11 +781,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient { return []; } - const packagePolicies = packagePolicySO.saved_objects.map((so) => ({ - id: so.id, - version: so.version, - ...so.attributes, - })); + const packagePolicies = packagePolicySO.saved_objects.map((so) => + mapPackagePolicySavedObjectToPackagePolicy(so) + ); for (const packagePolicy of packagePolicies) { auditLoggingService.writeCustomSoAuditLog({ @@ -835,11 +826,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } } - return { - id: so.id, - version: so.version, - ...so.attributes, - }; + return mapPackagePolicySavedObjectToPackagePolicy(so); }) .filter((packagePolicy): packagePolicy is PackagePolicy => packagePolicy !== null); @@ -889,12 +876,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } return { - items: packagePolicies?.saved_objects.map((packagePolicySO) => ({ - id: packagePolicySO.id, - version: packagePolicySO.version, - ...packagePolicySO.attributes, - spaceIds: packagePolicySO.namespaces, - })), + items: packagePolicies?.saved_objects.map((so) => + mapPackagePolicySavedObjectToPackagePolicy(so, so.namespaces) + ), total: packagePolicies?.total, page, perPage, @@ -1392,13 +1376,10 @@ class PackagePolicyClientImpl implements PackagePolicyClient { const updatedPoliciesSuccess = updatedPolicies .filter((policy) => !policy.error && policy.attributes) - .map( - (soPolicy) => - ({ - id: soPolicy.id, - version: soPolicy.version, - ...soPolicy.attributes, - } as PackagePolicy) + .map((soPolicy) => + mapPackagePolicySavedObjectToPackagePolicy( + soPolicy as SavedObject<PackagePolicySOAttributes> + ) ); return { updatedPolicies: updatedPoliciesSuccess, failedPolicies }; @@ -1948,6 +1929,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { output_id: newPolicy.output_id, inputs: newPolicy.inputs[0]?.streams ? newPolicy.inputs : inputs, vars: newPolicy.vars || newPP.vars, + supports_agentless: newPolicy.supports_agentless, }; } } @@ -2189,7 +2171,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { perPage: SO_SEARCH_LIMIT, namespaces: ['*'], }) - ).saved_objects.map(mapPackagePolicySavedObjectToPackagePolicy); + ).saved_objects.map((so) => mapPackagePolicySavedObjectToPackagePolicy(so, so.namespaces)); if (packagePolicies.length > 0) { const getPackagePolicyUpdate = (packagePolicy: PackagePolicy) => ({ @@ -2306,7 +2288,10 @@ class PackagePolicyClientImpl implements PackagePolicyClient { savedObjectType, }); - return mapPackagePolicySavedObjectToPackagePolicy(packagePolicySO); + return mapPackagePolicySavedObjectToPackagePolicy( + packagePolicySO, + packagePolicySO.namespaces + ); }); }, }); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index fb2153ff903f..57621238d914 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -306,10 +306,8 @@ jest.mock('./app_context', () => ({ }), getExternalCallbacks: jest.fn(), getCloud: jest.fn(), - getExperimentalFeatures: jest.fn().mockReturnValue({ - agentless: false, - }), getConfig: jest.fn(), + getExperimentalFeatures: jest.fn().mockReturnValue({}), getInternalUserSOClientForSpaceId: jest.fn(), }, })); @@ -1075,10 +1073,6 @@ describe('policy preconfiguration', () => { DEFAULT_SPACE_ID ); - jest.mocked(appContextService.getExperimentalFeatures).mockReturnValue({ - agentless: true, - } as any); - expect(appContextService.getInternalUserSOClientForSpaceId).toBeCalledTimes(1); expect(appContextService.getInternalUserSOClientForSpaceId).toBeCalledWith(TEST_NAMESPACE); diff --git a/x-pack/plugins/fleet/server/services/settings.test.ts b/x-pack/plugins/fleet/server/services/settings.test.ts index f88e735dfcb6..9a75dc72abf3 100644 --- a/x-pack/plugins/fleet/server/services/settings.test.ts +++ b/x-pack/plugins/fleet/server/services/settings.test.ts @@ -143,6 +143,42 @@ describe('getSettings', () => { await getSettings(soClient); }); + + it('should handle null values for space awareness migration fields', async () => { + const soClient = savedObjectsClientMock.create(); + + soClient.find.mockResolvedValueOnce({ + saved_objects: [ + { + id: GLOBAL_SETTINGS_ID, + attributes: { + use_space_awareness_migration_status: null, + use_space_awareness_migration_started_at: null, + }, + references: [], + type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + score: 0, + }, + ], + page: 1, + per_page: 10, + total: 1, + }); + + const settings = await getSettings(soClient); + expect(settings).toEqual({ + delete_unenrolled_agents: undefined, + has_seen_add_data_notice: undefined, + id: 'fleet-default-settings', + output_secret_storage_requirements_met: undefined, + preconfigured_fields: [], + prerelease_integrations_enabled: undefined, + secret_storage_requirements_met: undefined, + use_space_awareness_migration_started_at: undefined, + use_space_awareness_migration_status: undefined, + version: undefined, + }); + }); }); describe('saveSettings', () => { diff --git a/x-pack/plugins/fleet/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts index 3288ec1090e4..5e1948d5d379 100644 --- a/x-pack/plugins/fleet/server/services/settings.ts +++ b/x-pack/plugins/fleet/server/services/settings.ts @@ -6,7 +6,11 @@ */ import Boom from '@hapi/boom'; -import type { SavedObjectsClientContract, SavedObjectsUpdateOptions } from '@kbn/core/server'; +import type { + SavedObject, + SavedObjectsClientContract, + SavedObjectsUpdateOptions, +} from '@kbn/core/server'; import { omit } from 'lodash'; import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_ID } from '../../common/constants'; @@ -18,21 +22,7 @@ import { DeleteUnenrolledAgentsPreconfiguredError } from '../errors'; import { appContextService } from './app_context'; import { auditLoggingService } from './audit_logging'; -export async function getSettings(soClient: SavedObjectsClientContract): Promise<Settings> { - const res = await soClient.find<SettingsSOAttributes>({ - type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, - }); - auditLoggingService.writeCustomSoAuditLog({ - action: 'get', - id: GLOBAL_SETTINGS_ID, - savedObjectType: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, - }); - - if (res.total === 0) { - throw Boom.notFound('Global settings not found'); - } - const settingsSo = res.saved_objects[0]; - +function mapSettingsSO(settingsSo: SavedObject<SettingsSOAttributes>): Settings { return { id: settingsSo.id, version: settingsSo.version, @@ -42,14 +32,30 @@ export async function getSettings(soClient: SavedObjectsClientContract): Promise has_seen_add_data_notice: settingsSo.attributes.has_seen_add_data_notice, prerelease_integrations_enabled: settingsSo.attributes.prerelease_integrations_enabled, use_space_awareness_migration_status: - settingsSo.attributes.use_space_awareness_migration_status, + settingsSo.attributes.use_space_awareness_migration_status ?? undefined, use_space_awareness_migration_started_at: - settingsSo.attributes.use_space_awareness_migration_started_at, + settingsSo.attributes.use_space_awareness_migration_started_at ?? undefined, preconfigured_fields: getConfigFleetServerHosts() ? ['fleet_server_hosts'] : [], delete_unenrolled_agents: settingsSo.attributes.delete_unenrolled_agents, }; } +export async function getSettings(soClient: SavedObjectsClientContract): Promise<Settings> { + const res = await soClient.find<SettingsSOAttributes>({ + type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + }); + auditLoggingService.writeCustomSoAuditLog({ + action: 'get', + id: GLOBAL_SETTINGS_ID, + savedObjectType: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + }); + + if (res.total === 0) { + throw Boom.notFound('Global settings not found'); + } + return mapSettingsSO(res.saved_objects[0]); +} + export async function getSettingsOrUndefined( soClient: SavedObjectsClientContract ): Promise<Settings | undefined> { diff --git a/x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.test.ts b/x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.test.ts index 07ec6593ec9e..9fb30ad75ab7 100644 --- a/x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.test.ts +++ b/x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.test.ts @@ -17,6 +17,7 @@ import { ensureAgentPoliciesFleetServerKeysAndPolicies } from './fleet_server_po jest.mock('../app_context'); jest.mock('../agent_policy'); jest.mock('../api_keys'); +jest.mock('../agent_policies/bump_agent_policies_task'); const mockedEnsureDefaultEnrollmentAPIKeyForAgentPolicy = jest.mocked( ensureDefaultEnrollmentAPIKeyForAgentPolicy diff --git a/x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.ts b/x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.ts index f5ed816d96e6..cd7e91fb81ac 100644 --- a/x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.ts +++ b/x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.ts @@ -13,6 +13,7 @@ import { ensureDefaultEnrollmentAPIKeyForAgentPolicy } from '../api_keys'; import { SO_SEARCH_LIMIT } from '../../constants'; import { appContextService } from '../app_context'; import { scheduleDeployAgentPoliciesTask } from '../agent_policies/deploy_agent_policies_task'; +import { scheduleBumpAgentPoliciesTask } from '../agent_policies/bump_agent_policies_task'; export async function ensureAgentPoliciesFleetServerKeysAndPolicies({ logger, @@ -37,7 +38,6 @@ export async function ensureAgentPoliciesFleetServerKeysAndPolicies({ }); const outdatedAgentPolicyIds: Array<{ id: string; spaceId?: string }> = []; - await pMap( agentPolicies, async (agentPolicy) => { @@ -55,23 +55,23 @@ export async function ensureAgentPoliciesFleetServerKeysAndPolicies({ } ); - if (!outdatedAgentPolicyIds.length) { - return; - } + await scheduleBumpAgentPoliciesTask(appContextService.getTaskManagerStart()!); - if (appContextService.getExperimentalFeatures().asyncDeployPolicies) { - return scheduleDeployAgentPoliciesTask( - appContextService.getTaskManagerStart()!, - outdatedAgentPolicyIds - ); - } else { - return agentPolicyService - .deployPolicies( - soClient, - outdatedAgentPolicyIds.map(({ id }) => id) - ) - .catch((error) => { - logger.warn(`Error deploying policies: ${error.message}`, { error }); - }); + if (outdatedAgentPolicyIds.length) { + if (appContextService.getExperimentalFeatures().asyncDeployPolicies) { + return scheduleDeployAgentPoliciesTask( + appContextService.getTaskManagerStart()!, + outdatedAgentPolicyIds + ); + } else { + return agentPolicyService + .deployPolicies( + soClient, + outdatedAgentPolicyIds.map(({ id }) => id) + ) + .catch((error) => { + logger.warn(`Error deploying policies: ${error.message}`, { error }); + }); + } } } diff --git a/x-pack/plugins/fleet/server/services/utils/agentless.test.ts b/x-pack/plugins/fleet/server/services/utils/agentless.test.ts index 5bf5116128d9..48f186fed553 100644 --- a/x-pack/plugins/fleet/server/services/utils/agentless.test.ts +++ b/x-pack/plugins/fleet/server/services/utils/agentless.test.ts @@ -9,12 +9,7 @@ import { securityMock } from '@kbn/security-plugin/server/mocks'; import { appContextService } from '../app_context'; -import { - isAgentlessApiEnabled, - isAgentlessEnabled, - isDefaultAgentlessPolicyEnabled, - prependAgentlessApiBasePathToEndpoint, -} from './agentless'; +import { isAgentlessEnabled, prependAgentlessApiBasePathToEndpoint } from './agentless'; jest.mock('../app_context'); @@ -23,7 +18,7 @@ mockedAppContextService.getSecuritySetup.mockImplementation(() => ({ ...securityMock.createSetup(), })); -describe('isAgentlessApiEnabled', () => { +describe('isAgentlessEnabled', () => { afterEach(() => { jest.clearAllMocks(); mockedAppContextService.getConfig.mockReset(); @@ -36,7 +31,7 @@ describe('isAgentlessApiEnabled', () => { } as any); jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: false } as any); - expect(isAgentlessApiEnabled()).toBe(false); + expect(isAgentlessEnabled()).toBe(false); }); it('should return false if cloud is enabled but agentless is not', () => { @@ -47,7 +42,7 @@ describe('isAgentlessApiEnabled', () => { } as any); jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any); - expect(isAgentlessApiEnabled()).toBe(false); + expect(isAgentlessEnabled()).toBe(false); }); it('should return true if cloud is enabled and agentless is enabled', () => { @@ -58,109 +53,10 @@ describe('isAgentlessApiEnabled', () => { } as any); jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any); - expect(isAgentlessApiEnabled()).toBe(true); - }); -}); - -describe('isDefaultAgentlessPolicyEnabled', () => { - afterEach(() => { - jest.clearAllMocks(); - mockedAppContextService.getConfig.mockReset(); - }); - - it('should return false if serverless is not enabled', () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: false } as any); - jest - .spyOn(appContextService, 'getCloud') - .mockReturnValue({ isServerlessEnabled: false } as any); - - expect(isDefaultAgentlessPolicyEnabled()).toBe(false); - }); - - it('should return false if serverless is enabled but agentless is not', () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: false } as any); - jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isServerlessEnabled: true } as any); - - expect(isDefaultAgentlessPolicyEnabled()).toBe(false); - }); - - it('should return true if serverless is enabled and agentless is enabled', () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: true } as any); - jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isServerlessEnabled: true } as any); - - expect(isDefaultAgentlessPolicyEnabled()).toBe(true); - }); -}); - -describe('isAgentlessEnabled', () => { - afterEach(() => { - jest.clearAllMocks(); - mockedAppContextService.getConfig.mockReset(); - }); - - it('should return false if cloud and serverless are not enabled', () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: false } as any); - jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: false } as any); - jest - .spyOn(appContextService, 'getCloud') - .mockReturnValue({ isServerlessEnabled: false } as any); - - expect(isAgentlessEnabled()).toBe(false); - }); - - it('should return false if cloud is enabled but agentless is not', () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: false } as any); - jest.spyOn(appContextService, 'getCloud').mockReturnValue({ isCloudEnabled: true } as any); - jest - .spyOn(appContextService, 'getCloud') - .mockReturnValue({ isServerlessEnabled: false } as any); - - expect(isAgentlessEnabled()).toBe(false); - }); - - it('should return false if serverless is enabled but agentless is not', () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: false } as any); - jest - .spyOn(appContextService, 'getCloud') - .mockReturnValue({ isCloudEnabled: false, isServerlessEnabled: true } as any); - - expect(isAgentlessEnabled()).toBe(false); - }); - - it('should return true if cloud is enabled and agentless is enabled', () => { - jest - .spyOn(appContextService, 'getConfig') - .mockReturnValue({ agentless: { enabled: true } } as any); - jest - .spyOn(appContextService, 'getCloud') - .mockReturnValue({ isCloudEnabled: true, isServerlessEnabled: false } as any); - - expect(isAgentlessEnabled()).toBe(true); - }); - - it('should return true if serverless is enabled and agentless is enabled', () => { - jest - .spyOn(appContextService, 'getExperimentalFeatures') - .mockReturnValue({ agentless: true } as any); - jest - .spyOn(appContextService, 'getCloud') - .mockReturnValue({ isCloudEnabled: false, isServerlessEnabled: true } as any); - expect(isAgentlessEnabled()).toBe(true); }); }); + describe('prependAgentlessApiBasePathToEndpoint', () => { afterEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/fleet/server/services/utils/agentless.ts b/x-pack/plugins/fleet/server/services/utils/agentless.ts index 0f5d4e9d1de8..019c035d55e2 100644 --- a/x-pack/plugins/fleet/server/services/utils/agentless.ts +++ b/x-pack/plugins/fleet/server/services/utils/agentless.ts @@ -9,20 +9,11 @@ import { appContextService } from '..'; import type { FleetConfigType } from '../../config'; export { isOnlyAgentlessIntegration } from '../../../common/services/agentless_policy_helper'; -export const isAgentlessApiEnabled = () => { +export const isAgentlessEnabled = () => { const cloudSetup = appContextService.getCloud(); const isHosted = cloudSetup?.isCloudEnabled || cloudSetup?.isServerlessEnabled; return Boolean(isHosted && appContextService.getConfig()?.agentless?.enabled); }; -export const isDefaultAgentlessPolicyEnabled = () => { - const cloudSetup = appContextService.getCloud && appContextService.getCloud(); - return Boolean( - cloudSetup?.isServerlessEnabled && appContextService.getExperimentalFeatures().agentless - ); -}; -export const isAgentlessEnabled = () => { - return isAgentlessApiEnabled() || isDefaultAgentlessPolicyEnabled(); -}; const AGENTLESS_ESS_API_BASE_PATH = '/api/v1/ess'; const AGENTLESS_SERVERLESS_API_BASE_PATH = '/api/v1/serverless'; diff --git a/x-pack/plugins/fleet/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts index e82063e775d7..4f131d00bdf3 100644 --- a/x-pack/plugins/fleet/server/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/agent_policy.ts @@ -377,6 +377,24 @@ export const FullAgentPolicyResponseSchema = schema.object({ signing_key: schema.string(), }) ), + logging: schema.maybe( + schema.object({ + level: schema.maybe(schema.string()), + to_files: schema.maybe(schema.boolean()), + files: schema.maybe( + schema.object({ + rotateeverybytes: schema.maybe(schema.number()), + keepfiles: schema.maybe(schema.number()), + interval: schema.maybe(schema.string()), + }) + ), + }) + ), + limits: schema.maybe( + schema.object({ + go_max_procs: schema.maybe(schema.number()), + }) + ), }) ), secret_references: schema.maybe( diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index c5f3e94868cf..bbbb94b28cd4 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -172,6 +172,16 @@ export const PackagePolicyBaseSchema = { ), ]) ), + supports_agentless: schema.maybe( + schema.nullable( + schema.boolean({ + defaultValue: false, + meta: { + description: 'Indicates whether the package policy belongs to an agentless agent policy.', + }, + }) + ) + ), }; export const NewPackagePolicySchema = schema.object({ @@ -286,6 +296,16 @@ export const SimplifiedPackagePolicyBaseSchema = schema.object({ output_id: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])), vars: schema.maybe(SimplifiedVarsSchema), inputs: SimplifiedPackagePolicyInputsSchema, + supports_agentless: schema.maybe( + schema.nullable( + schema.boolean({ + defaultValue: false, + meta: { + description: 'Indicates whether the package policy belongs to an agentless agent policy.', + }, + }) + ) + ), }); export const SimplifiedPackagePolicyPreconfiguredSchema = SimplifiedPackagePolicyBaseSchema.extends( diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index f9db02b92a65..a5fb3e9e034e 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -527,6 +527,8 @@ export const GetAgentStatusResponseSchema = schema.object({ export const GetAgentDataRequestSchema = { query: schema.object({ agentsIds: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]), + pkgName: schema.maybe(schema.string()), + pkgVersion: schema.maybe(schema.string()), previewData: schema.boolean({ defaultValue: false }), }), }; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts index c40dcc0b6359..64684e51350d 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts @@ -61,7 +61,9 @@ export const SettingsResponseSchema = schema.object({ use_space_awareness_migration_status: schema.maybe( schema.oneOf([schema.literal('pending'), schema.literal('success'), schema.literal('error')]) ), - use_space_awareness_migration_started_at: schema.maybe(schema.string()), + use_space_awareness_migration_started_at: schema.maybe( + schema.oneOf([schema.literal(null), schema.string()]) + ), delete_unenrolled_agents: schema.maybe( schema.object({ enabled: schema.boolean(), diff --git a/x-pack/plugins/fleet/server/types/so_attributes.ts b/x-pack/plugins/fleet/server/types/so_attributes.ts index 31207dda64bc..6f415eea9eb6 100644 --- a/x-pack/plugins/fleet/server/types/so_attributes.ts +++ b/x-pack/plugins/fleet/server/types/so_attributes.ts @@ -137,6 +137,7 @@ export interface PackagePolicySOAttributes { }; agents?: number; overrides?: any | null; + bump_agent_policy_revision?: boolean; } interface OutputSoBaseAttributes { diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 6ebdaffde2a3..7499f980d5eb 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -93,7 +93,6 @@ "@kbn/core-http-request-handler-context-server", "@kbn/shared-ux-router", "@kbn/shared-ux-link-redirect-app", - "@kbn/core-http-router-server-internal", "@kbn/safer-lodash-set", "@kbn/shared-ux-file-types", "@kbn/core-application-browser", @@ -118,5 +117,7 @@ "@kbn/zod", "@kbn/reporting-public", "@kbn/field-formats-plugin", + "@kbn/core-security-server", + "@kbn/core-http-server-utils", ] } diff --git a/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx b/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx index 22790bc7f639..28ee3bd0fd17 100644 --- a/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx +++ b/x-pack/plugins/graph/public/helpers/use_workspace_loader.test.tsx @@ -10,7 +10,7 @@ import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { createMockGraphStore } from '../state_management/mocks'; import { Workspace } from '../types'; -import { renderHook, act, RenderHookOptions } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { ContentClient } from '@kbn/content-management-plugin/public'; jest.mock('react-router-dom', () => { @@ -51,15 +51,16 @@ describe('use_workspace_loader', () => { }; it('should not redirect if outcome is exactMatch', async () => { - await act(async () => { - renderHook( - () => useWorkspaceLoader(defaultProps), - defaultProps as RenderHookOptions<UseWorkspaceLoaderProps> - ); + renderHook((props) => useWorkspaceLoader(props), { + initialProps: defaultProps, + }); + + await waitFor(() => { + expect(defaultProps.spaces?.ui.redirectLegacyUrl).not.toHaveBeenCalled(); + expect(defaultProps.store.dispatch).toHaveBeenCalled(); }); - expect(defaultProps.spaces?.ui.redirectLegacyUrl).not.toHaveBeenCalled(); - expect(defaultProps.store.dispatch).toHaveBeenCalled(); }); + it('should redirect if outcome is aliasMatch', async () => { const props = { ...defaultProps, @@ -77,16 +78,16 @@ describe('use_workspace_loader', () => { }, } as unknown as UseWorkspaceLoaderProps; - await act(async () => { - renderHook( - () => useWorkspaceLoader(props), - props as RenderHookOptions<UseWorkspaceLoaderProps> - ); - }); - expect(props.spaces?.ui.redirectLegacyUrl).toHaveBeenCalledWith({ - path: '#/workspace/aliasTargetId?query={}', - aliasPurpose: 'savedObjectConversion', - objectNoun: 'Graph', + renderHook((_props) => useWorkspaceLoader(_props), { + initialProps: props, }); + + await waitFor(() => + expect(props.spaces?.ui.redirectLegacyUrl).toHaveBeenCalledWith({ + path: '#/workspace/aliasTargetId?query={}', + aliasPurpose: 'savedObjectConversion', + objectNoun: 'Graph', + }) + ); }); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap index ff8d0f4d7caa..83e73494fcb0 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.tsx.snap @@ -2,6 +2,14 @@ exports[`extend index management ilm banner extension should return extension when any index has lifecycle error 1`] = ` Object { + "action": Object { + "buttonLabel": "Retry lifecycle step", + "indexNames": Array [ + "testy3", + ], + "requestMethod": [Function], + "successMessage": "Called retry lifecycle step for: \\"testy3\\"", + }, "filter": Query { "ast": _AST { "_clauses": Array [ diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx index e26000bcacc7..78a2aeddec1c 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx @@ -302,6 +302,24 @@ describe('extend index management', () => { expect(extension).toBeDefined(); expect(extension).toMatchSnapshot(); }); + + test('should return action definition when any index has lifecycle error', () => { + const extension = ilmBannerExtension([ + indexWithoutLifecyclePolicy, + indexWithLifecyclePolicy, + indexWithLifecycleError, + ]); + const { requestMethod, successMessage, buttonLabel } = + retryLifecycleActionExtension({ + indices: [indexWithLifecycleError], + }) ?? {}; + expect(extension?.action).toEqual({ + requestMethod, + successMessage, + buttonLabel, + indexNames: [indexWithLifecycleError.name], + }); + }); }); describe('ilm summary extension', () => { diff --git a/x-pack/plugins/index_lifecycle_management/kibana.jsonc b/x-pack/plugins/index_lifecycle_management/kibana.jsonc index e61210ebbbef..e081aab35d3e 100644 --- a/x-pack/plugins/index_lifecycle_management/kibana.jsonc +++ b/x-pack/plugins/index_lifecycle_management/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/index-lifecycle-management-plugin", "owner": "@elastic/kibana-management", + "group": "platform", + "visibility": "private", "plugin": { "id": "indexLifecycleManagement", "server": true, diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx index 519a0606c36a..2466020deb06 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx @@ -128,12 +128,20 @@ export const ilmBannerExtension = (indices: Index[]) => { if (!numIndicesWithLifecycleErrors) { return null; } + const { requestMethod, successMessage, indexNames, buttonLabel } = + retryLifecycleActionExtension({ indices: indicesWithLifecycleErrors }) ?? {}; return { type: 'warning', filter: Query.parse(`${stepPath}:ERROR`), filterLabel: i18n.translate('xpack.indexLifecycleMgmt.indexMgmtBanner.filterLabel', { defaultMessage: 'Show errors', }), + action: { + buttonLabel, + indexNames: indexNames?.[0] ?? [], + requestMethod, + successMessage, + }, title: i18n.translate('xpack.indexLifecycleMgmt.indexMgmtBanner.errorMessage', { defaultMessage: `{ numIndicesWithLifecycleErrors, number} {numIndicesWithLifecycleErrors, plural, one {index has} other {indices have} } diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 1d7ee65790cf..3bc122ad867f 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -812,7 +812,7 @@ describe('Data Streams tab', () => { const { actions, findDetailPanelIlmPolicyLink } = testBed; await actions.clickNameAt(0); - expect(findDetailPanelIlmPolicyLink().prop('href')).toBe('/test/my_ilm_policy'); + expect(findDetailPanelIlmPolicyLink().prop('data-href')).toBe('/test/my_ilm_policy'); }); test('with an ILM url locator and no ILM policy', async () => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx index 76b8295b6b1f..4badcc04540b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -727,6 +727,11 @@ describe('<IndexDetailsPage />', () => { isActive: true, hasAtLeast: jest.fn((type) => true), }; + const INFERENCE_LOCATOR = 'SEARCH_INFERENCE_ENDPOINTS'; + const createMockLocator = (id: string) => ({ + useUrl: jest.fn().mockReturnValue('https://redirect.me/to/inference_endpoints'), + }); + const mockInferenceManagementLocator = createMockLocator(INFERENCE_LOCATOR); beforeEach(async () => { httpRequestsMockHelpers.setInferenceModels({ data: [ @@ -750,7 +755,9 @@ describe('<IndexDetailsPage />', () => { docLinks: { links: { ml: '', - enterpriseSearch: '', + inferenceManagement: { + inferenceAPIDocumentation: 'https://abc.com/inference-api-create', + }, }, }, core: { @@ -819,6 +826,20 @@ describe('<IndexDetailsPage />', () => { }, }, }, + share: { + url: { + locators: { + get: jest.fn((id) => { + switch (id) { + case INFERENCE_LOCATOR: + return mockInferenceManagementLocator; + default: + throw new Error(`Unknown locator id: ${id}`); + } + }), + }, + }, + }, }, }, }); @@ -836,7 +857,6 @@ describe('<IndexDetailsPage />', () => { testBed.actions.mappings.isReferenceFieldVisible(); testBed.actions.mappings.selectInferenceIdButtonExists(); testBed.actions.mappings.openSelectInferencePopover(); - testBed.actions.mappings.expectDefaultInferenceModelToExists(); testBed.actions.mappings.expectCustomInferenceModelToExists( `custom-inference_${customInferenceModel}` ); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx index ed5af6784051..1c9633f829bc 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx @@ -20,6 +20,11 @@ import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; const createInferenceEndpointMock = jest.fn(); const mockDispatch = jest.fn(); +const INFERENCE_LOCATOR = 'SEARCH_INFERENCE_ENDPOINTS'; +const createMockLocator = (id: string) => ({ + useUrl: jest.fn().mockReturnValue('https://redirect.me/to/inference_endpoints'), +}); +const mockInferenceManagementLocator = createMockLocator(INFERENCE_LOCATOR); jest.mock('../../../public/application/app_context', () => ({ useAppContext: jest.fn().mockReturnValue({ @@ -33,8 +38,8 @@ jest.mock('../../../public/application/app_context', () => ({ }, docLinks: { links: { - enterpriseSearch: { - inferenceApiCreate: 'https://abc.com/inference-api-create', + inferenceManagement: { + inferenceAPIDocumentation: 'https://abc.com/inference-api-create', }, }, }, @@ -47,6 +52,20 @@ jest.mock('../../../public/application/app_context', () => ({ }, }, }, + share: { + url: { + locators: { + get: jest.fn((id) => { + switch (id) { + case INFERENCE_LOCATOR: + return mockInferenceManagementLocator; + default: + throw new Error(`Unknown locator id: ${id}`); + } + }), + }, + }, + }, }, }), })); @@ -71,6 +90,8 @@ jest.mock('../../../public/application/components/mappings_editor/mappings_state jest.mock('../../../public/application/services/api', () => ({ useLoadInferenceEndpoints: jest.fn().mockReturnValue({ data: [ + { inference_id: '.preconfigured-elser', task_type: 'sparse_embedding' }, + { inference_id: '.preconfigured-e5', task_type: 'text_embedding' }, { inference_id: 'endpoint-1', task_type: 'text_embedding' }, { inference_id: 'endpoint-2', task_type: 'sparse_embedding' }, { inference_id: 'endpoint-3', task_type: 'completion' }, @@ -83,7 +104,7 @@ jest.mock('../../../public/application/services/api', () => ({ function getTestForm(Component: React.FC<SelectInferenceIdProps>) { return (defaultProps: SelectInferenceIdProps) => { const { form } = useForm(); - form.setFieldValue('inference_id', 'elser_model_2'); + form.setFieldValue('inference_id', '.preconfigured-elser'); return ( <Form form={form}> <Component {...(defaultProps as any)} /> @@ -125,8 +146,8 @@ describe('SelectInferenceId', () => { it('should display the inference endpoints in the combo', () => { find('inferenceIdButton').simulate('click'); - expect(find('data-inference-endpoint-list').contains('e5')).toBe(true); - expect(find('data-inference-endpoint-list').contains('elser_model_2')).toBe(true); + expect(find('data-inference-endpoint-list').contains('.preconfigured-elser')).toBe(true); + expect(find('data-inference-endpoint-list').contains('.preconfigured-e5')).toBe(true); expect(find('data-inference-endpoint-list').contains('endpoint-1')).toBe(true); expect(find('data-inference-endpoint-list').contains('endpoint-2')).toBe(true); expect(find('data-inference-endpoint-list').contains('endpoint-3')).toBe(false); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index 7ba6832b8833..4992c1391635 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -315,7 +315,7 @@ describe('<TemplateCreate />', () => { expect(exists('indexModeCallout')).toBe(true); expect(find('indexModeCallout').text()).toContain( - 'The index.mode setting has been set to Standard within template Logistics.' + 'The index.mode setting has been set to Standard within the Logistics step.' ); }); diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 1a8276d76b7f..6df01a109a03 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -79,6 +79,7 @@ export interface AppDependencies { docLinks: DocLinksStart; kibanaVersion: SemVer; overlays: OverlayStart; + canUseSyntheticSource: boolean; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_datastreams_rollover/use_datastreams_rollover.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_datastreams_rollover/use_datastreams_rollover.test.tsx index 903a9187a733..d4ac28521b31 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_datastreams_rollover/use_datastreams_rollover.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/component_template_datastreams_rollover/use_datastreams_rollover.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useComponentTemplatesContext } from '../../component_templates_context'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/use_step_from_query_string.test.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/use_step_from_query_string.test.tsx index cb26f40c91ae..6e04cdc37e1f 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/use_step_from_query_string.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_wizard/use_step_from_query_string.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { useStepFromQueryString } from './use_step_from_query_string'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/configuration_form.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/configuration_form.test.tsx index 4c73fd1037dd..5bc28052b73e 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/configuration_form.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/configuration_form.test.tsx @@ -28,6 +28,14 @@ const setup = (props: any = { onUpdate() {} }, appDependencies?: any) => { return testBed; }; +const getContext = (sourceFieldEnabled: boolean = true, canUseSyntheticSource: boolean = true) => + ({ + config: { + enableMappingsSourceFieldSection: sourceFieldEnabled, + }, + canUseSyntheticSource, + } as unknown as AppDependencies); + describe('Mappings editor: configuration form', () => { let testBed: TestBed<TestSubjects>; @@ -49,14 +57,8 @@ describe('Mappings editor: configuration form', () => { describe('_source field', () => { it('renders the _source field when it is enabled', async () => { - const ctx = { - config: { - enableMappingsSourceFieldSection: true, - }, - } as unknown as AppDependencies; - await act(async () => { - testBed = setup({ esNodesPlugins: [] }, ctx); + testBed = setup({ esNodesPlugins: [] }, getContext()); }); testBed.component.update(); const { exists } = testBed; @@ -65,19 +67,37 @@ describe('Mappings editor: configuration form', () => { }); it("doesn't render the _source field when it is disabled", async () => { - const ctx = { - config: { - enableMappingsSourceFieldSection: false, - }, - } as unknown as AppDependencies; - await act(async () => { - testBed = setup({ esNodesPlugins: [] }, ctx); + testBed = setup({ esNodesPlugins: [] }, getContext(false)); }); testBed.component.update(); const { exists } = testBed; expect(exists('sourceField')).toBe(false); }); + + it('has synthetic option if `canUseSyntheticSource` is set to true', async () => { + await act(async () => { + testBed = setup({ esNodesPlugins: [] }, getContext(true, true)); + }); + testBed.component.update(); + const { exists, find } = testBed; + + // Clicking on the field to open the options dropdown + find('sourceValueField').simulate('click'); + expect(exists('syntheticSourceFieldOption')).toBe(true); + }); + + it("doesn't have synthetic option if `canUseSyntheticSource` is set to false", async () => { + await act(async () => { + testBed = setup({ esNodesPlugins: [] }, getContext(true, false)); + }); + testBed.component.update(); + const { exists, find } = testBed; + + // Clicking on the field to open the options dropdown + find('sourceValueField').simulate('click'); + expect(exists('syntheticSourceFieldOption')).toBe(false); + }); }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index ee3b3e72e7c1..cb8a1efbf9c7 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -28,22 +28,9 @@ describe('Mappings editor: core', () => { let onChangeHandler: jest.Mock = jest.fn(); let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); let testBed: MappingsEditorTestBed; - let hasEnterpriseLicense = true; - const mockLicenseCheck = jest.fn((type: any) => hasEnterpriseLicense); const appDependencies = { plugins: { ml: { mlApi: {} }, - licensing: { - license$: { - subscribe: jest.fn((callback: any) => { - callback({ - isActive: true, - hasAtLeast: mockLicenseCheck, - }); - return { unsubscribe: jest.fn() }; - }), - }, - }, }, }; @@ -314,6 +301,7 @@ describe('Mappings editor: core', () => { config: { enableMappingsSourceFieldSection: true, }, + canUseSyntheticSource: true, ...appDependencies, }; @@ -512,8 +500,7 @@ describe('Mappings editor: core', () => { }); ['logsdb', 'time_series'].forEach((indexMode) => { - it(`defaults to 'synthetic' with ${indexMode} index mode prop on enterprise license`, async () => { - hasEnterpriseLicense = true; + it(`defaults to 'synthetic' with ${indexMode} index mode prop when 'canUseSyntheticSource' is set to true`, async () => { await act(async () => { testBed = setup( { @@ -537,8 +524,7 @@ describe('Mappings editor: core', () => { expect(find('sourceValueField').prop('value')).toBe('synthetic'); }); - it(`defaults to 'standard' with ${indexMode} index mode prop on basic license`, async () => { - hasEnterpriseLicense = false; + it(`defaults to 'standard' with ${indexMode} index mode prop when 'canUseSyntheticSource' is set to true`, async () => { await act(async () => { testBed = setup( { @@ -546,7 +532,7 @@ describe('Mappings editor: core', () => { onChange: onChangeHandler, indexMode, }, - ctx + { ...ctx, canUseSyntheticSource: false } ); }); testBed.component.update(); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx index 00ce2d02a1ba..6ddaf8e48fe4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx @@ -39,6 +39,31 @@ interface SerializedSourceField { excludes?: string[]; } +const serializeSourceField = (sourceField: any): SerializedSourceField | undefined => { + if (sourceField?.option === SYNTHETIC_SOURCE_OPTION) { + return { mode: SYNTHETIC_SOURCE_OPTION }; + } + if (sourceField?.option === DISABLED_SOURCE_OPTION) { + return { enabled: false }; + } + if (sourceField?.option === STORED_SOURCE_OPTION) { + return { + mode: 'stored', + includes: sourceField.includes, + excludes: sourceField.excludes, + }; + } + if (sourceField?.includes || sourceField?.excludes) { + // If sourceField?.option is undefined, the user hasn't explicitly selected + // this option, so don't include the `mode` property + return { + includes: sourceField.includes, + excludes: sourceField.excludes, + }; + } + return undefined; +}; + export const formSerializer = (formData: GenericObject) => { const { dynamicMapping, sourceField, metaField, _routing, _size, subobjects } = formData; @@ -48,30 +73,12 @@ export const formSerializer = (formData: GenericObject) => { ? 'strict' : dynamicMapping?.enabled; - const _source = - sourceField?.option === SYNTHETIC_SOURCE_OPTION - ? { mode: SYNTHETIC_SOURCE_OPTION } - : sourceField?.option === DISABLED_SOURCE_OPTION - ? { enabled: false } - : sourceField?.option === STORED_SOURCE_OPTION - ? { - mode: 'stored', - includes: sourceField?.includes, - excludes: sourceField?.excludes, - } - : sourceField?.includes || sourceField?.excludes - ? { - includes: sourceField?.includes, - excludes: sourceField?.excludes, - } - : undefined; - const serialized = { dynamic, numeric_detection: dynamicMapping?.numeric_detection, date_detection: dynamicMapping?.date_detection, dynamic_date_formats: dynamicMapping?.dynamic_date_formats, - _source: _source as SerializedSourceField, + _source: serializeSourceField(sourceField), _meta: metaField, _routing, _size, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx index 2e8f9fb88f87..c1709fa13503 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiLink, EuiSpacer, EuiComboBox, EuiFormRow, EuiCallOut, EuiText } from '@elastic/eui'; -import { useMappingsState } from '../../../mappings_state_context'; +import { useAppContext } from '../../../../../app_context'; import { documentationService } from '../../../../../services/documentation'; import { UseField, FormDataProvider, FormRow, SuperSelectField } from '../../../shared_imports'; import { ComboBoxOption } from '../../../types'; @@ -24,7 +24,7 @@ import { } from './constants'; export const SourceFieldSection = () => { - const state = useMappingsState(); + const { canUseSyntheticSource } = useAppContext(); const renderOptionDropdownDisplay = (option: SourceOptionKey) => ( <Fragment> @@ -44,7 +44,7 @@ export const SourceFieldSection = () => { }, ]; - if (state.hasEnterpriseLicense) { + if (canUseSyntheticSource) { sourceValueOptions.push({ value: SYNTHETIC_SOURCE_OPTION, inputDisplay: sourceOptionLabels[SYNTHETIC_SOURCE_OPTION], diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx index 15d5ee9ba72c..51cd2b5f1478 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx @@ -52,15 +52,6 @@ type SelectInferenceIdContentProps = SelectInferenceIdProps & { value: string; }; -const defaultEndpoints = [ - { - inference_id: 'elser_model_2', - }, - { - inference_id: 'e5', - }, -]; - export const SelectInferenceId: React.FC<SelectInferenceIdProps> = ({ createInferenceEndpoint, 'data-test-subj': dataTestSubj, @@ -89,13 +80,15 @@ const SelectInferenceIdContent: React.FC<SelectInferenceIdContentProps> = ({ value, }) => { const { - core: { application, http }, + core: { application }, docLinks, - plugins: { ml }, + plugins: { ml, share }, } = useAppContext(); const config = getFieldConfig('inference_id'); - const inferenceEndpointsPageLink = `${http.basePath.get()}/app/enterprise_search/relevance/inference_endpoints`; + const inferenceEndpointsPageLink = share?.url.locators + .get('SEARCH_INFERENCE_ENDPOINTS') + ?.useUrl({}); const [isInferenceFlyoutVisible, setIsInferenceFlyoutVisible] = useState<boolean>(false); const [availableTrainedModels, setAvailableTrainedModels] = useState< @@ -134,13 +127,7 @@ const SelectInferenceIdContent: React.FC<SelectInferenceIdContentProps> = ({ endpoint.task_type === 'text_embedding' || endpoint.task_type === 'sparse_embedding' ); - const missingDefaultEndpoints = defaultEndpoints.filter( - (endpoint) => !(filteredEndpoints || []).find((e) => e.inference_id === endpoint.inference_id) - ); - const newOptions: EuiSelectableOption[] = [ - ...(filteredEndpoints || []), - ...missingDefaultEndpoints, - ].map((endpoint) => ({ + const newOptions: EuiSelectableOption[] = [...(filteredEndpoints || [])].map((endpoint) => ({ label: endpoint.inference_id, 'data-test-subj': `custom-inference_${endpoint.inference_id}`, checked: value === endpoint.inference_id ? 'on' : undefined, @@ -239,24 +226,28 @@ const SelectInferenceIdContent: React.FC<SelectInferenceIdContentProps> = ({ panelPaddingSize="m" closePopover={() => setIsInferencePopoverVisible(!isInferencePopoverVisible)} > - <EuiContextMenuPanel> - <EuiContextMenuItem - key="manageInferenceEndpointButton" - icon="gear" - size="s" - data-test-subj="manageInferenceEndpointButton" - onClick={async () => { - application.navigateToUrl(inferenceEndpointsPageLink); - }} - > - {i18n.translate( - 'xpack.idxMgmt.mappingsEditor.parameters.inferenceId.popover.manageInferenceEndpointButton', - { - defaultMessage: 'Manage Inference Endpoints', - } - )} - </EuiContextMenuItem> - </EuiContextMenuPanel> + {inferenceEndpointsPageLink && ( + <EuiContextMenuPanel> + <EuiContextMenuItem + key="manageInferenceEndpointButton" + icon="gear" + size="s" + data-test-subj="manageInferenceEndpointButton" + href={inferenceEndpointsPageLink} + onClick={(e) => { + e.preventDefault(); + application.navigateToUrl(inferenceEndpointsPageLink); + }} + > + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.parameters.inferenceId.popover.manageInferenceEndpointButton', + { + defaultMessage: 'Manage Inference Endpoints', + } + )} + </EuiContextMenuItem> + </EuiContextMenuPanel> + )} <EuiHorizontalRule margin="none" /> <EuiPanel color="transparent" paddingSize="s"> <EuiTitle size="xxxs"> @@ -307,7 +298,7 @@ const SelectInferenceIdContent: React.FC<SelectInferenceIdContentProps> = ({ <EuiHorizontalRule margin="none" /> <EuiContextMenuItem icon={<EuiIcon type="help" color="primary" />} size="s"> <EuiLink - href={docLinks.links.enterpriseSearch.inferenceApiCreate} + href={docLinks.links.inferenceManagement.inferenceAPIDocumentation} target="_blank" data-test-subj="learn-how-to-create-inference-endpoints" > diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts index 65415a287d94..15537b0335e0 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { CustomInferenceEndpointConfig, SemanticTextField } from '../../../../../types'; import { useSemanticText } from './use_semantic_text'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts index 6662c2852ad7..a464a279a8dd 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts @@ -19,6 +19,8 @@ import { useMLModelNotificationToasts } from '../../../../../../../../hooks/use_ import { getInferenceEndpoints } from '../../../../../../../services/api'; import { getFieldByPathName } from '../../../../../lib/utils'; +import { ELSER_PRECONFIGURED_ENDPOINTS } from '../../../../../constants'; + interface UseSemanticTextProps { form: FormHook<Field, Field>; ml?: MlPluginStart; @@ -62,7 +64,7 @@ export function useSemanticText(props: UseSemanticTextProps) { form.setFieldValue('reference_field', referenceField); } if (!form.getFormData().inference_id) { - form.setFieldValue('inference_id', 'elser_model_2'); + form.setFieldValue('inference_id', ELSER_PRECONFIGURED_ENDPOINTS); } } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx index 33c51a3cb644..a67a7df0acb7 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx @@ -19,7 +19,11 @@ import { i18n } from '@kbn/i18n'; import { NormalizedField, NormalizedFields, State } from '../../../types'; import { getTypeLabelFromField } from '../../../lib'; -import { CHILD_FIELD_INDENT_SIZE, LEFT_PADDING_SIZE_FIELD_ITEM_WRAPPER } from '../../../constants'; +import { + CHILD_FIELD_INDENT_SIZE, + ELSER_PRECONFIGURED_ENDPOINTS, + LEFT_PADDING_SIZE_FIELD_ITEM_WRAPPER, +} from '../../../constants'; import { FieldsList } from './fields_list'; import { CreateField } from './create_field'; @@ -105,6 +109,7 @@ function FieldListItemComponent( const indent = treeDepth * CHILD_FIELD_INDENT_SIZE - substractIndentAmount; const isSemanticText = source.type === 'semantic_text'; + const inferenceId: string = (source.inference_id as string) ?? ELSER_PRECONFIGURED_ENDPOINTS; const indentCreateField = (treeDepth + 1) * CHILD_FIELD_INDENT_SIZE + @@ -293,7 +298,7 @@ function FieldListItemComponent( {isSemanticText && ( <EuiFlexItem grow={false}> - <EuiBadge color="hollow">{source.inference_id as string}</EuiBadge> + <EuiBadge color="hollow">{inferenceId}</EuiBadge> </EuiFlexItem> )} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/default_values.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/default_values.ts index b839caf75b24..f8c6da8f7cdd 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/default_values.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/default_values.ts @@ -13,3 +13,9 @@ export const INDEX_DEFAULT = 'index_default'; export const STANDARD = 'standard'; + +/* + This will be repalce once we add default elser inference_id + with the index mapping response. +*/ +export const ELSER_PRECONFIGURED_ENDPOINTS = '.elser-2-elasticsearch'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts index 872c62bc6f7a..58b40293f64f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts @@ -421,7 +421,6 @@ describe('utils', () => { selectedDataTypes: ['Boolean'], }, inferenceToModelIdMap: {}, - hasEnterpriseLicense: true, mappingViewFields: { byId: {}, rootLevelFields: [], aliases: {}, maxNestedDepth: 0 }, }; test('returns list of matching fields with search term', () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx index cc87c3cd614e..9f59f4959bed 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx @@ -9,7 +9,6 @@ import React, { useMemo, useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui'; -import { ILicense } from '@kbn/licensing-plugin/common/types'; import { useAppContext } from '../../app_context'; import { IndexMode } from '../../../../common/types/data_streams'; import { @@ -61,9 +60,7 @@ export interface Props { export const MappingsEditor = React.memo( ({ onChange, value, docLinks, indexSettings, esNodesPlugins, indexMode }: Props) => { - const { - plugins: { licensing }, - } = useAppContext(); + const { canUseSyntheticSource } = useAppContext(); const { parsedDefaultValue, multipleMappingsDeclared } = useMemo<MappingsEditorParsedMetadata>( () => parseMappings(value), [value] @@ -128,39 +125,22 @@ export const MappingsEditor = React.memo( [dispatch] ); - const [isLicenseCheckComplete, setIsLicenseCheckComplete] = useState(false); - useEffect(() => { - const subscription = licensing?.license$.subscribe((license: ILicense) => { - dispatch({ - type: 'hasEnterpriseLicense.update', - value: license.isActive && license.hasAtLeast('enterprise'), - }); - setIsLicenseCheckComplete(true); - }); - - return () => subscription?.unsubscribe(); - }, [dispatch, licensing]); - useEffect(() => { if ( - isLicenseCheckComplete && !state.configuration.defaultValue._source && (indexMode === LOGSDB_INDEX_MODE || indexMode === TIME_SERIES_MODE) ) { - if (state.hasEnterpriseLicense) { + if (canUseSyntheticSource) { + // If the source field is undefined (hasn't been set in the form) + // and if the user has selected a `logsdb` or `time_series` index mode in the Logistics step, + // update the form data with synthetic _source dispatch({ type: 'configuration.save', value: { ...state.configuration.defaultValue, _source: { mode: 'synthetic' } } as any, }); } } - }, [ - indexMode, - dispatch, - state.configuration, - state.hasEnterpriseLicense, - isLicenseCheckComplete, - ]); + }, [indexMode, dispatch, state.configuration, canUseSyntheticSource]); const tabToContentMap = { fields: ( diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state_context.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state_context.tsx index 4fd89ad0e25a..ac19c5395f97 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state_context.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_state_context.tsx @@ -60,7 +60,6 @@ export const StateProvider: React.FC<{ children?: React.ReactNode }> = ({ childr selectedDataTypes: [], }, inferenceToModelIdMap: {}, - hasEnterpriseLicense: false, mappingViewFields: { byId: {}, rootLevelFields: [], aliases: {}, maxNestedDepth: 0 }, }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts index ecb9648c34d0..626ee0e839a8 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts @@ -629,11 +629,5 @@ export const reducer = (state: State, action: Action): State => { inferenceToModelIdMap: action.value.inferenceToModelIdMap, }; } - case 'hasEnterpriseLicense.update': { - return { - ...state, - hasEnterpriseLicense: action.value, - }; - } } }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts index 43b3a7dde3b1..f40fe420eb3b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts @@ -108,7 +108,6 @@ export interface State { }; templates: TemplatesFormState; inferenceToModelIdMap?: InferenceToModelIdMap; - hasEnterpriseLicense: boolean; mappingViewFields: NormalizedFields; // state of the incoming index mappings, separate from the editor state above } @@ -141,7 +140,6 @@ export type Action = | { type: 'fieldsJsonEditor.update'; value: { json: { [key: string]: any }; isValid: boolean } } | { type: 'search:update'; value: string } | { type: 'validity:update'; value: boolean } - | { type: 'filter:update'; value: { selectedOptions: EuiSelectableOption[] } } - | { type: 'hasEnterpriseLicense.update'; value: boolean }; + | { type: 'filter:update'; value: { selectedOptions: EuiSelectableOption[] } }; export type Dispatch = (action: Action) => void; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx index 38a11f03c7ee..cc12cfc988d5 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx @@ -92,7 +92,7 @@ export const StepSettings: React.FunctionComponent<Props> = React.memo( title={ <FormattedMessage id="xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.title" - defaultMessage="The {settingName} setting has been set to {indexMode} within template {logisticsLink}. Any changes to {settingName} set on this page will be overwritten by the Logistics selection." + defaultMessage="The {settingName} setting has been set to {indexMode} within the {logisticsLink}. Any changes to {settingName} set on this page will be overwritten by the Logistics selection." values={{ settingName: ( <EuiCode> @@ -110,7 +110,7 @@ export const StepSettings: React.FunctionComponent<Props> = React.memo( {i18n.translate( 'xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.logisticsLinkLabel', { - defaultMessage: 'Logistics', + defaultMessage: 'Logistics step', } )} </EuiLink> diff --git a/x-pack/plugins/index_management/public/application/hooks/redirect_path.test.tsx b/x-pack/plugins/index_management/public/application/hooks/redirect_path.test.tsx index 5b189ccbc253..83bd06a8dd49 100644 --- a/x-pack/plugins/index_management/public/application/hooks/redirect_path.test.tsx +++ b/x-pack/plugins/index_management/public/application/hooks/redirect_path.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { coreMock } from '@kbn/core/public/mocks'; import { createMemoryHistory } from 'history'; import { useRedirectPath } from './redirect_path'; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index da17bc4706c0..48a45579a01f 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -57,6 +57,7 @@ export function getIndexManagementDependencies({ cloud, startDependencies, uiMetricService, + canUseSyntheticSource, }: { core: CoreStart; usageCollection: UsageCollectionSetup; @@ -68,6 +69,7 @@ export function getIndexManagementDependencies({ cloud?: CloudSetup; startDependencies: StartDependencies; uiMetricService: UiMetricService; + canUseSyntheticSource: boolean; }): AppDependencies { const { docLinks, application, uiSettings, settings } = core; const { url } = startDependencies.share; @@ -100,6 +102,7 @@ export function getIndexManagementDependencies({ docLinks, kibanaVersion, overlays: core.overlays, + canUseSyntheticSource, }; } @@ -112,6 +115,7 @@ export async function mountManagementSection({ kibanaVersion, config, cloud, + canUseSyntheticSource, }: { coreSetup: CoreSetup<StartDependencies>; usageCollection: UsageCollectionSetup; @@ -121,6 +125,7 @@ export async function mountManagementSection({ kibanaVersion: SemVer; config: AppDependencies['config']; cloud?: CloudSetup; + canUseSyntheticSource: boolean; }) { const { element, setBreadcrumbs, history } = params; const [core, startDependencies] = await coreSetup.getStartServices(); @@ -148,6 +153,7 @@ export async function mountManagementSection({ startDependencies, uiMetricService, usageCollection, + canUseSyntheticSource, }); const unmountAppCallback = renderApp(element, { core, dependencies: appDependencies }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index d962305a7147..10ef17c56624 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -131,7 +131,7 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({ const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName); const ilmPolicyLink = useIlmLocator(ILM_PAGES_POLICY_EDIT, dataStream?.ilmPolicyName); - const { history, config } = useAppContext(); + const { history, config, core } = useAppContext(); let indicesLink; let content; @@ -193,7 +193,11 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({ > <> {ilmPolicyLink ? ( - <EuiLink data-test-subj={'ilmPolicyLink'} href={ilmPolicyLink}> + <EuiLink + data-test-subj={'ilmPolicyLink'} + data-href={ilmPolicyLink} + onClick={() => core.application.navigateToUrl(ilmPolicyLink)} + > <EuiTextColor color="subdued">{ilmPolicyName}</EuiTextColor> </EuiLink> ) : ( @@ -204,7 +208,11 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({ ) : ( <> {ilmPolicyLink ? ( - <EuiLink data-test-subj={'ilmPolicyLink'} href={ilmPolicyLink}> + <EuiLink + data-test-subj={'ilmPolicyLink'} + data-href={ilmPolicyLink} + onClick={() => core.application.navigateToUrl(ilmPolicyLink)} + > {ilmPolicyName} </EuiLink> ) : ( @@ -429,7 +437,7 @@ export const DataStreamDetailPanel: React.FunctionComponent<Props> = ({ defaultMessage="To edit data retention for this data stream, you must edit its associated {link}." values={{ link: ( - <EuiLink href={ilmPolicyLink}> + <EuiLink onClick={() => core.application.navigateToUrl(ilmPolicyLink)}> <FormattedMessage id="xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.fullyManagedByILMButtonLabel" defaultMessage="ILM policy" diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx index 2c60e04be31a..b8f259313166 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx @@ -161,6 +161,8 @@ const MixedIndicesCallout = ({ dataStreamName, history, }: MixedIndicesCalloutProps) => { + const { core } = useAppContext(); + return ( <EuiCallOut title={i18n.translate( @@ -177,7 +179,10 @@ const MixedIndicesCallout = ({ defaultMessage="One or more indices are managed by an ILM policy ({viewAllIndicesLink}). Updating data retention for this data stream won't affect these indices. Instead you will have to update the {ilmPolicyLink} policy." values={{ ilmPolicyLink: ( - <EuiLink data-test-subj="viewIlmPolicyLink" href={ilmPolicyLink}> + <EuiLink + data-test-subj="viewIlmPolicyLink" + onClick={() => core.application.navigateToUrl(ilmPolicyLink)} + > {ilmPolicyName} </EuiLink> ), diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx index e2f9cb68ad90..ec5deabc5f1f 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx @@ -63,6 +63,8 @@ import { SemanticTextBanner } from './semantic_text_banner'; import { TrainedModelsDeploymentModal } from './trained_models_deployment_modal'; import { parseMappings } from '../../../../shared/parse_mappings'; +const isInferencePreconfigured = (inferenceId: string) => inferenceId.startsWith('.'); + export const DetailsPageMappingsContent: FunctionComponent<{ index: Index; data: string; @@ -235,7 +237,8 @@ export const DetailsPageMappingsContent: FunctionComponent<{ .filter( (inferenceId: string) => inferenceToModelIdMap?.[inferenceId].trainedModelId && // third-party inference models don't have trainedModelId - !inferenceToModelIdMap?.[inferenceId].isDeployed + !inferenceToModelIdMap?.[inferenceId].isDeployed && + !isInferencePreconfigured(inferenceId) ); setHasSavedFields(true); if (inferenceIdsInPendingList.length === 0) { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.js index 6ea481ae463a..66406576902c 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.js @@ -25,6 +25,7 @@ import { sortChanged, loadIndices, toggleChanged, + performExtensionAction, } from '../../../../store/actions'; import { IndexTable as PresentationComponent } from './index_table'; @@ -63,6 +64,9 @@ const mapDispatchToProps = (dispatch) => { loadIndices: () => { dispatch(loadIndices()); }, + performExtensionAction: (requestMethod, successMessage, indexNames) => { + dispatch(performExtensionAction({ requestMethod, successMessage, indexNames })); + }, }; }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index 1d102419c5bd..ee72f56d2103 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -404,24 +404,47 @@ export class IndexTable extends Component { } renderBanners(extensionsService) { - const { allIndices = [], filterChanged } = this.props; + const { allIndices = [], filterChanged, performExtensionAction } = this.props; return extensionsService.banners.map((bannerExtension, i) => { const bannerData = bannerExtension(allIndices); if (!bannerData) { return null; } - const { type, title, message, filter, filterLabel } = bannerData; + const { type, title, message, filter, filterLabel, action } = bannerData; return ( <Fragment key={`bannerExtension${i}`}> <EuiCallOut color={type} size="m" title={title}> - <EuiText> - {message} - {filter ? ( - <EuiLink onClick={() => filterChanged(filter)}>{filterLabel}</EuiLink> - ) : null} - </EuiText> + {message && <p>{message}</p>} + {action || filter ? ( + <EuiFlexGroup gutterSize="s" alignItems="center"> + {action ? ( + <EuiFlexItem grow={false}> + <EuiButton + color="warning" + fill + onClick={() => { + performExtensionAction( + action.requestMethod, + action.successMessage, + action.indexNames + ); + }} + > + {action.buttonLabel} + </EuiButton> + </EuiFlexItem> + ) : null} + {filter ? ( + <EuiFlexItem grow={false}> + <EuiText> + <EuiLink onClick={() => filterChanged(filter)}>{filterLabel}</EuiLink> + </EuiText> + </EuiFlexItem> + ) : null} + </EuiFlexGroup> + ) : null} </EuiCallOut> <EuiSpacer size="m" /> </Fragment> diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx index 2621f3ec483c..eed3335d0143 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx @@ -63,7 +63,7 @@ export const TabSummary: React.FunctionComponent<Props> = ({ templateDetails }) const numIndexPatterns = indexPatterns.length; - const { history } = useAppContext(); + const { history, core } = useAppContext(); const ilmPolicyLink = useIlmLocator(ILM_PAGES_POLICY_EDIT, ilmPolicy?.name); return ( @@ -171,7 +171,9 @@ export const TabSummary: React.FunctionComponent<Props> = ({ templateDetails }) </EuiDescriptionListTitle> <EuiDescriptionListDescription> {ilmPolicy?.name && ilmPolicyLink ? ( - <EuiLink href={ilmPolicyLink}>{ilmPolicy!.name}</EuiLink> + <EuiLink onClick={() => core.application.navigateToUrl(ilmPolicyLink)}> + {ilmPolicy!.name} + </EuiLink> ) : ( ilmPolicy?.name || i18nTexts.none )} diff --git a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts index 62746791510c..4de34b584aec 100644 --- a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts +++ b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { NormalizedFields } from '../application/components/mappings_editor/types'; import { useDetailsPageMappingsModelManagement } from './use_details_page_mappings_model_management'; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 5d857d8d8ac9..82ba6505696a 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -20,6 +20,7 @@ import { IndexManagementPluginStart, } from '@kbn/index-management-shared-types'; import { IndexManagementLocator } from '@kbn/index-management-shared-types'; +import { Subscription } from 'rxjs'; import { setExtensionsService } from './application/store/selectors/extension_service'; import { ExtensionsService } from './services/extensions_service'; @@ -57,6 +58,8 @@ export class IndexMgmtUIPlugin enableProjectLevelRetentionChecks: boolean; enableSemanticText: boolean; }; + private canUseSyntheticSource: boolean = false; + private licensingSubscription?: Subscription; constructor(ctx: PluginInitializerContext) { // Temporary hack to provide the service instances in module files in order to avoid a big refactor @@ -113,6 +116,7 @@ export class IndexMgmtUIPlugin kibanaVersion: this.kibanaVersion, config: this.config, cloud, + canUseSyntheticSource: this.canUseSyntheticSource, }); }, }); @@ -133,6 +137,11 @@ export class IndexMgmtUIPlugin public start(coreStart: CoreStart, plugins: StartDependencies): IndexManagementPluginStart { const { fleet, usageCollection, cloud, share, console, ml, licensing } = plugins; + + this.licensingSubscription = licensing?.license$.subscribe((next) => { + this.canUseSyntheticSource = next.hasAtLeast('enterprise'); + }); + return { extensionsService: this.extensionsService.setup(), getIndexMappingComponent: (deps: { history: ScopedHistory<unknown> }) => { @@ -213,5 +222,7 @@ export class IndexMgmtUIPlugin }, }; } - public stop() {} + public stop() { + this.licensingSubscription?.unsubscribe(); + } } diff --git a/x-pack/plugins/inference/README.md b/x-pack/plugins/inference/README.md index 935ae31bd6bc..bba5b4cdcfc2 100644 --- a/x-pack/plugins/inference/README.md +++ b/x-pack/plugins/inference/README.md @@ -77,6 +77,25 @@ class MyPlugin { } ``` +### Binding common parameters + +It is also possible to bind a client to its configuration parameters, to avoid passing connectorId +to every call, for example, using the `bindTo` parameter when creating the client. + +```ts +const inferenceClient = myStartDeps.inference.getClient({ + request, + bindTo: { + connectorId: 'my-connector-id', + functionCalling: 'simulated', + } +}); + +const chatResponse = inferenceClient.chatComplete({ + messages: [{ role: MessageRole.User, content: 'Do something' }], +}); +``` + ## APIs ### `chatComplete` API: diff --git a/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.test.ts b/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.test.ts new file mode 100644 index 000000000000..039fd0410d25 --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.test.ts @@ -0,0 +1,126 @@ +/* + * 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 { + BoundChatCompleteOptions, + ChatCompleteAPI, + MessageRole, + UnboundChatCompleteOptions, +} from '@kbn/inference-common'; +import { bindChatComplete } from './bind_chat_complete'; + +describe('bindChatComplete', () => { + let chatComplete: ChatCompleteAPI & jest.MockedFn<ChatCompleteAPI>; + + beforeEach(() => { + chatComplete = jest.fn(); + }); + + it('calls chatComplete with both bound and unbound params', async () => { + const bound: BoundChatCompleteOptions = { + connectorId: 'some-id', + functionCalling: 'native', + }; + + const unbound: UnboundChatCompleteOptions = { + messages: [{ role: MessageRole.User, content: 'hello there' }], + }; + + const boundApi = bindChatComplete(chatComplete, bound); + + await boundApi({ ...unbound }); + + expect(chatComplete).toHaveBeenCalledTimes(1); + expect(chatComplete).toHaveBeenCalledWith({ + ...bound, + ...unbound, + }); + }); + + it('forwards the response from chatComplete', async () => { + const expectedReturnValue = Symbol('something'); + chatComplete.mockResolvedValue(expectedReturnValue as any); + + const boundApi = bindChatComplete(chatComplete, { connectorId: 'my-connector' }); + + const result = await boundApi({ + messages: [{ role: MessageRole.User, content: 'hello there' }], + }); + + expect(result).toEqual(expectedReturnValue); + }); + + it('only passes the expected parameters from the bound param object', async () => { + const bound = { + connectorId: 'some-id', + functionCalling: 'native', + foo: 'bar', + } as BoundChatCompleteOptions; + + const unbound: UnboundChatCompleteOptions = { + messages: [{ role: MessageRole.User, content: 'hello there' }], + }; + + const boundApi = bindChatComplete(chatComplete, bound); + + await boundApi({ ...unbound }); + + expect(chatComplete).toHaveBeenCalledTimes(1); + expect(chatComplete).toHaveBeenCalledWith({ + connectorId: 'some-id', + functionCalling: 'native', + messages: unbound.messages, + }); + }); + + it('ignores mutations of the bound parameters after binding', async () => { + const bound: BoundChatCompleteOptions = { + connectorId: 'some-id', + functionCalling: 'native', + }; + + const unbound: UnboundChatCompleteOptions = { + messages: [{ role: MessageRole.User, content: 'hello there' }], + }; + + const boundApi = bindChatComplete(chatComplete, bound); + + bound.connectorId = 'some-other-id'; + + await boundApi({ ...unbound }); + + expect(chatComplete).toHaveBeenCalledTimes(1); + expect(chatComplete).toHaveBeenCalledWith({ + connectorId: 'some-id', + functionCalling: 'native', + messages: unbound.messages, + }); + }); + + it('does not allow overriding bound parameters with the unbound object', async () => { + const bound: BoundChatCompleteOptions = { + connectorId: 'some-id', + functionCalling: 'native', + }; + + const unbound = { + messages: [{ role: MessageRole.User, content: 'hello there' }], + connectorId: 'overridden', + } as UnboundChatCompleteOptions; + + const boundApi = bindChatComplete(chatComplete, bound); + + await boundApi({ ...unbound }); + + expect(chatComplete).toHaveBeenCalledTimes(1); + expect(chatComplete).toHaveBeenCalledWith({ + connectorId: 'some-id', + functionCalling: 'native', + messages: unbound.messages, + }); + }); +}); diff --git a/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.ts b/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.ts new file mode 100644 index 000000000000..3030dee64122 --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/bind_chat_complete.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + ChatCompleteAPI, + ChatCompleteOptions, + BoundChatCompleteAPI, + BoundChatCompleteOptions, + UnboundChatCompleteOptions, + ToolOptions, +} from '@kbn/inference-common'; + +/** + * Bind chatComplete to the provided parameters, + * returning a bound version of the API. + */ +export function bindChatComplete( + chatComplete: ChatCompleteAPI, + boundParams: BoundChatCompleteOptions +): BoundChatCompleteAPI; +export function bindChatComplete( + chatComplete: ChatCompleteAPI, + boundParams: BoundChatCompleteOptions +) { + const { connectorId, functionCalling } = boundParams; + return (unboundParams: UnboundChatCompleteOptions<ToolOptions, boolean>) => { + const params: ChatCompleteOptions<ToolOptions, boolean> = { + ...unboundParams, + connectorId, + functionCalling, + }; + return chatComplete(params); + }; +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/index.ts b/x-pack/plugins/inference/common/chat_complete/index.ts similarity index 81% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/index.ts rename to x-pack/plugins/inference/common/chat_complete/index.ts index 7d247f755e9d..9eaa850fc819 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/index.ts +++ b/x-pack/plugins/inference/common/chat_complete/index.ts @@ -4,4 +4,5 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export { getTranslateQueryNode } from './translate_query'; + +export { bindChatComplete } from './bind_chat_complete'; diff --git a/x-pack/plugins/inference/common/index.ts b/x-pack/plugins/inference/common/index.ts index 19b24d53a389..79433cbc71a6 100644 --- a/x-pack/plugins/inference/common/index.ts +++ b/x-pack/plugins/inference/common/index.ts @@ -12,6 +12,6 @@ export { export { generateFakeToolCallId } from './utils/generate_fake_tool_call_id'; -export { createOutputApi } from './create_output_api'; +export { createOutputApi } from './output'; export type { ChatCompleteRequestBody, GetConnectorsResponseBody } from './http_apis'; diff --git a/x-pack/plugins/inference/common/output/bind_output.test.ts b/x-pack/plugins/inference/common/output/bind_output.test.ts new file mode 100644 index 000000000000..65741acbd8a3 --- /dev/null +++ b/x-pack/plugins/inference/common/output/bind_output.test.ts @@ -0,0 +1,129 @@ +/* + * 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 { BoundOutputOptions, OutputAPI, UnboundOutputOptions } from '@kbn/inference-common'; +import { bindOutput } from './bind_output'; + +describe('createScopedOutputAPI', () => { + let chatComplete: OutputAPI & jest.MockedFn<OutputAPI>; + + beforeEach(() => { + chatComplete = jest.fn(); + }); + + it('calls chatComplete with both bound and unbound params', async () => { + const bound: BoundOutputOptions = { + connectorId: 'some-id', + functionCalling: 'native', + }; + + const unbound: UnboundOutputOptions = { + id: 'foo', + input: 'hello there', + }; + + const boundApi = bindOutput(chatComplete, bound); + + await boundApi({ ...unbound }); + + expect(chatComplete).toHaveBeenCalledTimes(1); + expect(chatComplete).toHaveBeenCalledWith({ + ...bound, + ...unbound, + }); + }); + + it('forwards the response from chatComplete', async () => { + const expectedReturnValue = Symbol('something'); + chatComplete.mockResolvedValue(expectedReturnValue as any); + + const boundApi = bindOutput(chatComplete, { connectorId: 'my-connector' }); + + const result = await boundApi({ + id: 'foo', + input: 'hello there', + }); + + expect(result).toEqual(expectedReturnValue); + }); + + it('only passes the expected parameters from the bound param object', async () => { + const bound = { + connectorId: 'some-id', + functionCalling: 'native', + foo: 'bar', + } as BoundOutputOptions; + + const unbound: UnboundOutputOptions = { + id: 'foo', + input: 'hello there', + }; + + const boundApi = bindOutput(chatComplete, bound); + + await boundApi({ ...unbound }); + + expect(chatComplete).toHaveBeenCalledTimes(1); + expect(chatComplete).toHaveBeenCalledWith({ + connectorId: 'some-id', + functionCalling: 'native', + id: 'foo', + input: 'hello there', + }); + }); + + it('ignores mutations of the bound parameters after binding', async () => { + const bound: BoundOutputOptions = { + connectorId: 'some-id', + functionCalling: 'native', + }; + + const unbound: UnboundOutputOptions = { + id: 'foo', + input: 'hello there', + }; + + const boundApi = bindOutput(chatComplete, bound); + + bound.connectorId = 'some-other-id'; + + await boundApi({ ...unbound }); + + expect(chatComplete).toHaveBeenCalledTimes(1); + expect(chatComplete).toHaveBeenCalledWith({ + connectorId: 'some-id', + functionCalling: 'native', + id: 'foo', + input: 'hello there', + }); + }); + + it('does not allow overriding bound parameters with the unbound object', async () => { + const bound: BoundOutputOptions = { + connectorId: 'some-id', + functionCalling: 'native', + }; + + const unbound = { + id: 'foo', + input: 'hello there', + connectorId: 'overridden', + } as UnboundOutputOptions; + + const boundApi = bindOutput(chatComplete, bound); + + await boundApi({ ...unbound }); + + expect(chatComplete).toHaveBeenCalledTimes(1); + expect(chatComplete).toHaveBeenCalledWith({ + connectorId: 'some-id', + functionCalling: 'native', + id: 'foo', + input: 'hello there', + }); + }); +}); diff --git a/x-pack/plugins/inference/common/output/bind_output.ts b/x-pack/plugins/inference/common/output/bind_output.ts new file mode 100644 index 000000000000..45ac434d5ffd --- /dev/null +++ b/x-pack/plugins/inference/common/output/bind_output.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + OutputAPI, + OutputOptions, + BoundOutputAPI, + BoundOutputOptions, + UnboundOutputOptions, + ToolSchema, +} from '@kbn/inference-common'; + +/** + * Bind output to the provided parameters, + * returning a bound version of the API. + */ +export function bindOutput( + chatComplete: OutputAPI, + boundParams: BoundOutputOptions +): BoundOutputAPI; +export function bindOutput(chatComplete: OutputAPI, boundParams: BoundOutputOptions) { + const { connectorId, functionCalling } = boundParams; + return (unboundParams: UnboundOutputOptions<string, ToolSchema, boolean>) => { + const params: OutputOptions<string, ToolSchema, boolean> = { + ...unboundParams, + connectorId, + functionCalling, + }; + return chatComplete(params); + }; +} diff --git a/x-pack/plugins/inference/common/create_output_api.test.ts b/x-pack/plugins/inference/common/output/create_output_api.test.ts similarity index 100% rename from x-pack/plugins/inference/common/create_output_api.test.ts rename to x-pack/plugins/inference/common/output/create_output_api.test.ts diff --git a/x-pack/plugins/inference/common/create_output_api.ts b/x-pack/plugins/inference/common/output/create_output_api.ts similarity index 97% rename from x-pack/plugins/inference/common/create_output_api.ts rename to x-pack/plugins/inference/common/output/create_output_api.ts index e5dd2eeda2cb..d263f733bf4e 100644 --- a/x-pack/plugins/inference/common/create_output_api.ts +++ b/x-pack/plugins/inference/common/output/create_output_api.ts @@ -16,7 +16,7 @@ import { withoutTokenCountEvents, } from '@kbn/inference-common'; import { isObservable, map } from 'rxjs'; -import { ensureMultiTurn } from './utils/ensure_multi_turn'; +import { ensureMultiTurn } from '../utils/ensure_multi_turn'; export function createOutputApi(chatCompleteApi: ChatCompleteAPI): OutputAPI; export function createOutputApi(chatCompleteApi: ChatCompleteAPI) { diff --git a/x-pack/plugins/inference/common/output/index.ts b/x-pack/plugins/inference/common/output/index.ts new file mode 100644 index 000000000000..4c6f053d6ed8 --- /dev/null +++ b/x-pack/plugins/inference/common/output/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 { createOutputApi } from './create_output_api'; +export { bindOutput } from './bind_output'; diff --git a/x-pack/plugins/inference/public/plugin.tsx b/x-pack/plugins/inference/public/plugin.tsx index f1023bc9c254..614c2107c0a0 100644 --- a/x-pack/plugins/inference/public/plugin.tsx +++ b/x-pack/plugins/inference/public/plugin.tsx @@ -7,7 +7,7 @@ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import type { Logger } from '@kbn/logging'; -import { createOutputApi } from '../common/create_output_api'; +import { createOutputApi } from '../common/output'; import type { GetConnectorsResponseBody } from '../common/http_apis'; import { createChatCompleteApi } from './chat_complete'; import type { diff --git a/x-pack/plugins/inference/scripts/util/kibana_client.ts b/x-pack/plugins/inference/scripts/util/kibana_client.ts index ad6c21cf4b24..ef6f1c4fdcdc 100644 --- a/x-pack/plugins/inference/scripts/util/kibana_client.ts +++ b/x-pack/plugins/inference/scripts/util/kibana_client.ts @@ -28,7 +28,7 @@ import { } from '@kbn/inference-common'; import type { ChatCompleteRequestBody } from '../../common/http_apis'; import type { InferenceConnector } from '../../common/connectors'; -import { createOutputApi } from '../../common/create_output_api'; +import { createOutputApi } from '../../common/output/create_output_api'; import { eventSourceStreamIntoObservable } from '../../server/util/event_source_stream_into_observable'; // eslint-disable-next-line spaced-comment diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/openai/openai_adapter.test.ts b/x-pack/plugins/inference/server/chat_complete/adapters/openai/openai_adapter.test.ts index ff1bbc71a876..2d0154313b63 100644 --- a/x-pack/plugins/inference/server/chat_complete/adapters/openai/openai_adapter.test.ts +++ b/x-pack/plugins/inference/server/chat_complete/adapters/openai/openai_adapter.test.ts @@ -21,17 +21,19 @@ function createOpenAIChunk({ delta, usage, }: { - delta: OpenAI.ChatCompletionChunk['choices'][number]['delta']; + delta?: OpenAI.ChatCompletionChunk['choices'][number]['delta']; usage?: OpenAI.ChatCompletionChunk['usage']; }): OpenAI.ChatCompletionChunk { return { - choices: [ - { - finish_reason: null, - index: 0, - delta, - }, - ], + choices: delta + ? [ + { + finish_reason: null, + index: 0, + delta, + }, + ] + : [], created: new Date().getTime(), id: v4(), model: 'gpt-4o', @@ -313,7 +315,7 @@ describe('openAIAdapter', () => { ]); }); - it('emits token events', async () => { + it('emits chunk events with tool calls', async () => { const response$ = openAIAdapter.chatComplete({ ...defaultArgs, messages: [ @@ -375,5 +377,55 @@ describe('openAIAdapter', () => { }, ]); }); + + it('emits token count events', async () => { + const response$ = openAIAdapter.chatComplete({ + ...defaultArgs, + messages: [ + { + role: MessageRole.User, + content: 'Hello', + }, + ], + }); + + source$.next( + createOpenAIChunk({ + delta: { + content: 'chunk', + }, + }) + ); + + source$.next( + createOpenAIChunk({ + usage: { + prompt_tokens: 50, + completion_tokens: 100, + total_tokens: 150, + }, + }) + ); + + source$.complete(); + + const allChunks = await lastValueFrom(response$.pipe(toArray())); + + expect(allChunks).toEqual([ + { + type: ChatCompletionEventType.ChatCompletionChunk, + content: 'chunk', + tool_calls: [], + }, + { + type: ChatCompletionEventType.ChatCompletionTokenCount, + tokens: { + prompt: 50, + completion: 100, + total: 150, + }, + }, + ]); + }); }); }); diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/openai/openai_adapter.ts b/x-pack/plugins/inference/server/chat_complete/adapters/openai/openai_adapter.ts index 121ba96ab115..fa412f335800 100644 --- a/x-pack/plugins/inference/server/chat_complete/adapters/openai/openai_adapter.ts +++ b/x-pack/plugins/inference/server/chat_complete/adapters/openai/openai_adapter.ts @@ -5,7 +5,7 @@ * 2.0. */ -import OpenAI from 'openai'; +import type OpenAI from 'openai'; import type { ChatCompletionAssistantMessageParam, ChatCompletionMessageParam, @@ -13,22 +13,33 @@ import type { ChatCompletionToolMessageParam, ChatCompletionUserMessageParam, } from 'openai/resources'; -import { filter, from, map, switchMap, tap, throwError, identity } from 'rxjs'; -import { Readable, isReadable } from 'stream'; +import { + filter, + from, + identity, + map, + mergeMap, + Observable, + switchMap, + tap, + throwError, +} from 'rxjs'; +import { isReadable, Readable } from 'stream'; import { ChatCompletionChunkEvent, ChatCompletionEventType, + ChatCompletionTokenCountEvent, + createInferenceInternalError, Message, MessageRole, ToolOptions, - createInferenceInternalError, } from '@kbn/inference-common'; import { createTokenLimitReachedError } from '../../errors'; import { eventSourceStreamIntoObservable } from '../../../util/event_source_stream_into_observable'; import type { InferenceConnectorAdapter } from '../../types'; import { - wrapWithSimulatedFunctionCalling, parseInlineFunctionCalls, + wrapWithSimulatedFunctionCalling, } from '../../simulated_function_calling'; export const openAIAdapter: InferenceConnectorAdapter = { @@ -92,34 +103,57 @@ export const openAIAdapter: InferenceConnectorAdapter = { throw createTokenLimitReachedError(); } }), - filter( - (line): line is OpenAI.ChatCompletionChunk => - 'object' in line && line.object === 'chat.completion.chunk' && line.choices.length > 0 - ), - map((chunk): ChatCompletionChunkEvent => { - const delta = chunk.choices[0].delta; - - return { - type: ChatCompletionEventType.ChatCompletionChunk, - content: delta.content ?? '', - tool_calls: - delta.tool_calls?.map((toolCall) => { - return { - function: { - name: toolCall.function?.name ?? '', - arguments: toolCall.function?.arguments ?? '', - }, - toolCallId: toolCall.id ?? '', - index: toolCall.index, - }; - }) ?? [], - }; + filter((line): line is OpenAI.ChatCompletionChunk => { + return 'object' in line && line.object === 'chat.completion.chunk'; + }), + mergeMap((chunk): Observable<ChatCompletionChunkEvent | ChatCompletionTokenCountEvent> => { + const events: Array<ChatCompletionChunkEvent | ChatCompletionTokenCountEvent> = []; + if (chunk.usage) { + events.push(tokenCountFromOpenAI(chunk.usage)); + } + if (chunk.choices?.length) { + events.push(chunkFromOpenAI(chunk)); + } + return from(events); }), simulatedFunctionCalling ? parseInlineFunctionCalls({ logger }) : identity ); }, }; +function chunkFromOpenAI(chunk: OpenAI.ChatCompletionChunk): ChatCompletionChunkEvent { + const delta = chunk.choices[0].delta; + + return { + type: ChatCompletionEventType.ChatCompletionChunk, + content: delta.content ?? '', + tool_calls: + delta.tool_calls?.map((toolCall) => { + return { + function: { + name: toolCall.function?.name ?? '', + arguments: toolCall.function?.arguments ?? '', + }, + toolCallId: toolCall.id ?? '', + index: toolCall.index, + }; + }) ?? [], + }; +} + +function tokenCountFromOpenAI( + completionUsage: OpenAI.CompletionUsage +): ChatCompletionTokenCountEvent { + return { + type: ChatCompletionEventType.ChatCompletionTokenCount, + tokens: { + completion: completionUsage.completion_tokens, + prompt: completionUsage.prompt_tokens, + total: completionUsage.total_tokens, + }, + }; +} + function toolsToOpenAI(tools: ToolOptions['tools']): OpenAI.ChatCompletionCreateParams['tools'] { return tools ? Object.entries(tools).map(([toolName, { description, schema }]) => { diff --git a/x-pack/plugins/inference/server/chat_complete/api.ts b/x-pack/plugins/inference/server/chat_complete/api.ts index cf325e72ddf3..13b1c8d87270 100644 --- a/x-pack/plugins/inference/server/chat_complete/api.ts +++ b/x-pack/plugins/inference/server/chat_complete/api.ts @@ -16,14 +16,14 @@ import { type ToolOptions, ChatCompleteOptions, } from '@kbn/inference-common'; -import type { InferenceStartDependencies } from '../types'; +import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; import { getConnectorById } from '../util/get_connector_by_id'; import { getInferenceAdapter } from './adapters'; import { createInferenceExecutor, chunksIntoMessage, streamToResponse } from './utils'; interface CreateChatCompleteApiOptions { request: KibanaRequest; - actions: InferenceStartDependencies['actions']; + actions: ActionsPluginStart; logger: Logger; } diff --git a/x-pack/plugins/inference/server/index.ts b/x-pack/plugins/inference/server/index.ts index 60ce870020fe..128e90a58308 100644 --- a/x-pack/plugins/inference/server/index.ts +++ b/x-pack/plugins/inference/server/index.ts @@ -15,7 +15,7 @@ import type { } from './types'; import { InferencePlugin } from './plugin'; -export type { InferenceClient } from './types'; +export type { InferenceClient, BoundInferenceClient } from './inference_client'; export type { InferenceServerSetup, InferenceServerStart }; export { naturalLanguageToEsql } from './tasks/nl_to_esql'; diff --git a/x-pack/plugins/inference/server/inference_client/bind_client.ts b/x-pack/plugins/inference/server/inference_client/bind_client.ts new file mode 100644 index 000000000000..4600ed1364ed --- /dev/null +++ b/x-pack/plugins/inference/server/inference_client/bind_client.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { BoundChatCompleteOptions } from '@kbn/inference-common'; +import { bindChatComplete } from '../../common/chat_complete'; +import { bindOutput } from '../../common/output'; +import type { InferenceClient, BoundInferenceClient } from './types'; + +export const bindClient = ( + unboundClient: InferenceClient, + boundParams: BoundChatCompleteOptions +): BoundInferenceClient => { + return { + ...unboundClient, + chatComplete: bindChatComplete(unboundClient.chatComplete, boundParams), + output: bindOutput(unboundClient.output, boundParams), + }; +}; diff --git a/x-pack/plugins/inference/server/inference_client/create_client.test.ts b/x-pack/plugins/inference/server/inference_client/create_client.test.ts new file mode 100644 index 000000000000..98f5502cdfa5 --- /dev/null +++ b/x-pack/plugins/inference/server/inference_client/create_client.test.ts @@ -0,0 +1,129 @@ +/* + * 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 { createClient } from './create_client'; +import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; +import { httpServerMock } from '@kbn/core/server/mocks'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; + +jest.mock('./inference_client'); +jest.mock('./bind_client'); +import { createInferenceClient } from './inference_client'; +import { bindClient } from './bind_client'; + +const bindClientMock = bindClient as jest.MockedFn<typeof bindClient>; +const createInferenceClientMock = createInferenceClient as jest.MockedFn< + typeof createInferenceClient +>; + +describe('createClient', () => { + let logger: MockedLogger; + let actions: ReturnType<typeof actionsMock.createStart>; + let request: ReturnType<typeof httpServerMock.createKibanaRequest>; + + beforeEach(() => { + logger = loggerMock.create(); + actions = actionsMock.createStart(); + request = httpServerMock.createKibanaRequest(); + }); + + afterEach(() => { + bindClientMock.mockReset(); + createInferenceClientMock.mockReset(); + }); + + describe('when `bindTo` is not specified', () => { + it('calls createInferenceClient and return the client', () => { + const expectedResult = Symbol('expected') as any; + createInferenceClientMock.mockReturnValue(expectedResult); + + const result = createClient({ + request, + actions, + logger, + }); + + expect(createInferenceClientMock).toHaveBeenCalledTimes(1); + expect(createInferenceClientMock).toHaveBeenCalledWith({ request, actions, logger }); + + expect(bindClientMock).not.toHaveBeenCalled(); + + expect(result).toBe(expectedResult); + }); + + it('return a client with the expected type', async () => { + createInferenceClientMock.mockReturnValue({ + chatComplete: jest.fn(), + } as any); + + const client = createClient({ + request, + actions, + logger, + }); + + // type check on client.chatComplete + await client.chatComplete({ + messages: [], + connectorId: '.foo', + }); + }); + }); + + describe('when `bindTo` is specified', () => { + it('calls createInferenceClient and bindClient and forward the expected value', () => { + const createInferenceResult = Symbol('createInferenceResult') as any; + createInferenceClientMock.mockReturnValue(createInferenceResult); + + const bindClientResult = Symbol('bindClientResult') as any; + bindClientMock.mockReturnValue(bindClientResult); + + const result = createClient({ + request, + actions, + logger, + bindTo: { + connectorId: '.my-connector', + }, + }); + + expect(createInferenceClientMock).toHaveBeenCalledTimes(1); + expect(createInferenceClientMock).toHaveBeenCalledWith({ + request, + actions, + logger, + }); + + expect(bindClientMock).toHaveBeenCalledTimes(1); + expect(bindClientMock).toHaveBeenCalledWith(createInferenceResult, { + connectorId: '.my-connector', + }); + + expect(result).toBe(bindClientResult); + }); + + it('return a client with the expected type', async () => { + bindClientMock.mockReturnValue({ + chatComplete: jest.fn(), + } as any); + + const client = createClient({ + request, + actions, + logger, + bindTo: { + connectorId: '.foo', + }, + }); + + // type check on client.chatComplete + await client.chatComplete({ + messages: [], + }); + }); + }); +}); diff --git a/x-pack/plugins/inference/server/inference_client/create_client.ts b/x-pack/plugins/inference/server/inference_client/create_client.ts new file mode 100644 index 000000000000..3507dd7fef8a --- /dev/null +++ b/x-pack/plugins/inference/server/inference_client/create_client.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/logging'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; +import type { BoundChatCompleteOptions } from '@kbn/inference-common'; +import type { BoundInferenceClient, InferenceClient } from './types'; +import { createInferenceClient } from './inference_client'; +import { bindClient } from './bind_client'; + +interface UnboundOptions { + request: KibanaRequest; + actions: ActionsPluginStart; + logger: Logger; +} + +interface BoundOptions extends UnboundOptions { + bindTo: BoundChatCompleteOptions; +} + +export function createClient(options: UnboundOptions): InferenceClient; +export function createClient(options: BoundOptions): BoundInferenceClient; +export function createClient( + options: UnboundOptions | BoundOptions +): BoundInferenceClient | InferenceClient { + const { actions, request, logger } = options; + const client = createInferenceClient({ request, actions, logger }); + if ('bindTo' in options) { + return bindClient(client, options.bindTo); + } else { + return client; + } +} diff --git a/x-pack/plugins/inference/server/inference_client/index.ts b/x-pack/plugins/inference/server/inference_client/index.ts index 03da0e3da200..9d56ebe7ff61 100644 --- a/x-pack/plugins/inference/server/inference_client/index.ts +++ b/x-pack/plugins/inference/server/inference_client/index.ts @@ -5,28 +5,5 @@ * 2.0. */ -import type { Logger } from '@kbn/logging'; -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { InferenceClient, InferenceStartDependencies } from '../types'; -import { createChatCompleteApi } from '../chat_complete'; -import { createOutputApi } from '../../common/create_output_api'; -import { getConnectorById } from '../util/get_connector_by_id'; - -export function createInferenceClient({ - request, - actions, - logger, -}: { request: KibanaRequest; logger: Logger } & Pick< - InferenceStartDependencies, - 'actions' ->): InferenceClient { - const chatComplete = createChatCompleteApi({ request, actions, logger }); - return { - chatComplete, - output: createOutputApi(chatComplete), - getConnectorById: async (connectorId: string) => { - const actionsClient = await actions.getActionsClientWithRequest(request); - return await getConnectorById({ connectorId, actionsClient }); - }, - }; -} +export { createClient } from './create_client'; +export type { InferenceClient, BoundInferenceClient } from './types'; diff --git a/x-pack/plugins/inference/server/inference_client/inference_client.ts b/x-pack/plugins/inference/server/inference_client/inference_client.ts new file mode 100644 index 000000000000..f4c64ebdcce5 --- /dev/null +++ b/x-pack/plugins/inference/server/inference_client/inference_client.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/logging'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; +import type { InferenceClient } from './types'; +import { createChatCompleteApi } from '../chat_complete'; +import { createOutputApi } from '../../common/output/create_output_api'; +import { getConnectorById } from '../util/get_connector_by_id'; + +export function createInferenceClient({ + request, + actions, + logger, +}: { + request: KibanaRequest; + logger: Logger; + actions: ActionsPluginStart; +}): InferenceClient { + const chatComplete = createChatCompleteApi({ request, actions, logger }); + return { + chatComplete, + output: createOutputApi(chatComplete), + getConnectorById: async (connectorId: string) => { + const actionsClient = await actions.getActionsClientWithRequest(request); + return await getConnectorById({ connectorId, actionsClient }); + }, + }; +} diff --git a/x-pack/plugins/inference/server/inference_client/types.ts b/x-pack/plugins/inference/server/inference_client/types.ts new file mode 100644 index 000000000000..193ce83f6d7b --- /dev/null +++ b/x-pack/plugins/inference/server/inference_client/types.ts @@ -0,0 +1,58 @@ +/* + * 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 { + BoundChatCompleteAPI, + ChatCompleteAPI, + BoundOutputAPI, + OutputAPI, +} from '@kbn/inference-common'; +import type { InferenceConnector } from '../../common/connectors'; + +/** + * An inference client, scoped to a request, that can be used to interact with LLMs. + */ +export interface InferenceClient { + /** + * `chatComplete` requests the LLM to generate a response to + * a prompt or conversation, which might be plain text + * or a tool call, or a combination of both. + */ + chatComplete: ChatCompleteAPI; + /** + * `output` asks the LLM to generate a structured (JSON) + * response based on a schema and a prompt or conversation. + */ + output: OutputAPI; + /** + * `getConnectorById` returns an inference connector by id. + * Non-inference connectors will throw an error. + */ + getConnectorById: (id: string) => Promise<InferenceConnector>; +} + +/** + * A version of the {@link InferenceClient} that is pre-bound to a set of parameters. + */ +export interface BoundInferenceClient { + /** + * `chatComplete` requests the LLM to generate a response to + * a prompt or conversation, which might be plain text + * or a tool call, or a combination of both. + */ + chatComplete: BoundChatCompleteAPI; + /** + * `output` asks the LLM to generate a structured (JSON) + * response based on a schema and a prompt or conversation. + */ + output: BoundOutputAPI; + /** + * `getConnectorById` returns an inference connector by id. + * Non-inference connectors will throw an error. + */ + getConnectorById: (id: string) => Promise<InferenceConnector>; +} diff --git a/x-pack/plugins/inference/server/plugin.ts b/x-pack/plugins/inference/server/plugin.ts index 2b1a7be0a165..0f7090f48333 100644 --- a/x-pack/plugins/inference/server/plugin.ts +++ b/x-pack/plugins/inference/server/plugin.ts @@ -7,10 +7,16 @@ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { createInferenceClient } from './inference_client'; +import { + type BoundInferenceClient, + createClient as createInferenceClient, + type InferenceClient, +} from './inference_client'; import { registerRoutes } from './routes'; import type { InferenceConfig } from './config'; -import type { +import { + InferenceBoundClientCreateOptions, + InferenceClientCreateOptions, InferenceServerSetup, InferenceServerStart, InferenceSetupDependencies, @@ -48,12 +54,12 @@ export class InferencePlugin start(core: CoreStart, pluginsStart: InferenceStartDependencies): InferenceServerStart { return { - getClient: ({ request }) => { + getClient: <T extends InferenceClientCreateOptions>(options: T) => { return createInferenceClient({ - request, + ...options, actions: pluginsStart.actions, logger: this.logger.get('client'), - }); + }) as T extends InferenceBoundClientCreateOptions ? BoundInferenceClient : InferenceClient; }, }; } diff --git a/x-pack/plugins/inference/server/routes/chat_complete.ts b/x-pack/plugins/inference/server/routes/chat_complete.ts index e4e078e58c15..b363c8835299 100644 --- a/x-pack/plugins/inference/server/routes/chat_complete.ts +++ b/x-pack/plugins/inference/server/routes/chat_complete.ts @@ -15,7 +15,7 @@ import type { } from '@kbn/core/server'; import { MessageRole, ToolCall, ToolChoiceType } from '@kbn/inference-common'; import type { ChatCompleteRequestBody } from '../../common/http_apis'; -import { createInferenceClient } from '../inference_client'; +import { createClient as createInferenceClient } from '../inference_client'; import { InferenceServerStart, InferenceStartDependencies } from '../types'; import { observableIntoEventSourceStream } from '../util/observable_into_event_source_stream'; diff --git a/x-pack/plugins/inference/server/tasks/nl_to_esql/types.ts b/x-pack/plugins/inference/server/tasks/nl_to_esql/types.ts index ce45d9a15e4b..db3ac3b49348 100644 --- a/x-pack/plugins/inference/server/tasks/nl_to_esql/types.ts +++ b/x-pack/plugins/inference/server/tasks/nl_to_esql/types.ts @@ -14,7 +14,7 @@ import type { ToolOptions, OutputCompleteEvent, } from '@kbn/inference-common'; -import type { InferenceClient } from '../../types'; +import type { InferenceClient } from '../../inference_client'; export type NlToEsqlTaskEvent<TToolOptions extends ToolOptions> = | OutputCompleteEvent< diff --git a/x-pack/plugins/inference/server/types.ts b/x-pack/plugins/inference/server/types.ts index f538448372e3..8d6d1413f306 100644 --- a/x-pack/plugins/inference/server/types.ts +++ b/x-pack/plugins/inference/server/types.ts @@ -10,8 +10,8 @@ import type { PluginSetupContract as ActionsPluginSetup, } from '@kbn/actions-plugin/server'; import type { KibanaRequest } from '@kbn/core-http-server'; -import { ChatCompleteAPI, OutputAPI } from '@kbn/inference-common'; -import { InferenceConnector } from '../common/connectors'; +import type { BoundChatCompleteOptions } from '@kbn/inference-common'; +import type { InferenceClient, BoundInferenceClient } from './inference_client'; /* eslint-disable @typescript-eslint/no-empty-interface*/ @@ -23,37 +23,74 @@ export interface InferenceStartDependencies { actions: ActionsPluginStart; } +/** + * Setup contract of the inference plugin. + */ export interface InferenceServerSetup {} -export interface InferenceClient { - /** - * `chatComplete` requests the LLM to generate a response to - * a prompt or conversation, which might be plain text - * or a tool call, or a combination of both. - */ - chatComplete: ChatCompleteAPI; +/** + * Options to create an inference client using the {@link InferenceServerStart.getClient} API. + */ +export interface InferenceUnboundClientCreateOptions { /** - * `output` asks the LLM to generate a structured (JSON) - * response based on a schema and a prompt or conversation. + * The request to scope the client to. */ - output: OutputAPI; + request: KibanaRequest; +} + +/** + * Options to create a bound inference client using the {@link InferenceServerStart.getClient} API. + */ +export interface InferenceBoundClientCreateOptions extends InferenceUnboundClientCreateOptions { /** - * `getConnectorById` returns an inference connector by id. - * Non-inference connectors will throw an error. + * The parameters to bind the client to. */ - getConnectorById: (id: string) => Promise<InferenceConnector>; + bindTo: BoundChatCompleteOptions; } -interface InferenceClientCreateOptions { - request: KibanaRequest; -} +/** + * Options to create an inference client using the {@link InferenceServerStart.getClient} API. + */ +export type InferenceClientCreateOptions = + | InferenceUnboundClientCreateOptions + | InferenceBoundClientCreateOptions; +/** + * Start contract of the inference plugin, exposing APIs to interact with LLMs. + */ export interface InferenceServerStart { /** - * Creates an inference client, scoped to a request. + * Creates an {@link InferenceClient}, scoped to a request. + * + * @example + * ```ts + * const inferenceClient = myStartDeps.inference.getClient({ request }); + * + * const chatResponse = inferenceClient.chatComplete({ + * connectorId: 'my-connector-id', + * messages: [{ role: MessageRole.User, content: 'Do something' }], + * }); + * ``` + * + * It is also possible to bind a client to its configuration parameters, to avoid passing connectorId + * to every call, for example. Defining the `bindTo` parameter will return a {@link BoundInferenceClient} + * + * @example + * ```ts + * const inferenceClient = myStartDeps.inference.getClient({ + * request, + * bindTo: { + * connectorId: 'my-connector-id', + * functionCalling: 'simulated', + * } + * }); * - * @param options {@link InferenceClientCreateOptions} - * @returns {@link InferenceClient} + * const chatResponse = inferenceClient.chatComplete({ + * messages: [{ role: MessageRole.User, content: 'Do something' }], + * }); + * ``` */ - getClient: (options: InferenceClientCreateOptions) => InferenceClient; + getClient: <T extends InferenceClientCreateOptions>( + options: T + ) => T extends InferenceBoundClientCreateOptions ? BoundInferenceClient : InferenceClient; } diff --git a/x-pack/plugins/ingest_pipelines/public/application/hooks/redirect_path.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/hooks/redirect_path.test.tsx index a387661fc1b5..25729f5ba255 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/hooks/redirect_path.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/hooks/redirect_path.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { useRedirectPath } from './redirect_path'; import { useKibana } from '../../shared_imports'; diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts index 67e6c7d8f6a5..8b905ccd64f9 100644 --- a/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts +++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { EcsMappingState } from '../../server/types'; import { SamplesFormatName } from '../../common'; export const ecsMappingExpectedResults = { @@ -480,3 +481,383 @@ export const ecsTestState = { combinedSamples: '{"test1": "test1"}', additionalProcessors: [], }; + +export const ecsPipelineState: EcsMappingState = { + lastExecutedChain: 'validateMappings', + rawSamples: [], + additionalProcessors: [], + prefixedSamples: [ + '{"xdfsfs":{"ds":{"ei":0,"event":"cert.create","uid":"efd326fc-dd13-4df8-erre-3102c2d717d3","code":"TC000I","time":"2024-02-24T06:56:50.648137154Z","cluster_name":"teleport.ericbeahan.com","cert_type":"user","identity":{"user":"teleport-admin","roles":["access","editor"],"logins":["root","ubuntu","ec2-user","-teleport-internal-join"],"expires":"2024-02-24T06:56:50.648137154Z","route_to_cluster":"teleport.ericbeahan.com","traits":{"aws_role_arns":null,"azure_identities":null,"db_names":null,"db_roles":null,"db_users":null,"gcp_service_accounts":null,"host_user_gid":[""],"host_user_uid":[""],"kubernetes_groups":null,"kubernetes_users":null,"logins":["root","ubuntu","ec2-user"],"windows_logins":null},"teleport_cluster":"teleport.ericbeahan.com","client_ip":"1.2.3.4","prev_identity_expires":"0001-01-01T00:00:00Z","private_key_policy":"none"}}}}', + '{"xdfsfs":{"ds":{"ei":0,"event":"session.start","uid":"fff30583-13be-49e8-b159-32952c6ea34f","code":"T2000I","time":"2024-02-23T18:56:57.648137154Z","cluster_name":"teleport.ericbeahan.com","user":"teleport-admin","login":"ec2-user","user_kind":1,"sid":"293fda2d-2266-4d4d-b9d1-bd5ea9dd9fc3","private_key_policy":"none","namespace":"default","server_id":"face0091-2bf1-54er-a16a-f1514b4119f4","server_hostname":"ip-172-31-8-163.us-east-2.compute.internal","server_labels":{"hostname":"ip-172-31-8-163.us-east-2.compute.internal","teleport.internal/resource-id":"dccb2999-9fb8-4169-aded-ec7a1c0a26de"},"addr.remote":"1.2.3.4:50339","proto":"ssh","size":"80:25","initial_command":[""],"session_recording":"node"}}}', + ], + combinedSamples: + '{\n "xdfsfs": {\n "ds": {\n "identity": {\n "client_ip": "1.2.3.4",\n "prev_identity_expires": "0001-01-01T00:00:00Z",\n "private_key_policy": "none"\n },\n "user": "teleport-admin",\n "login": "ec2-user",\n "user_kind": 1,\n "sid": "293fda2d-2266-4d4d-b9d1-bd5ea9dd9fc3",\n "private_key_policy": "none",\n "namespace": "default",\n "server_id": "face0091-2bf1-43fd-a16a-f1514b4119f4",\n "server_hostname": "ip-172-31-8-163.us-east-2.compute.internal",\n "server_labels": {\n "hostname": "ip-172-31-8-163.us-east-2.compute.internal",\n "teleport.internal/resource-id": "dccb2999-9fb8-4169-aded-ec7a1c0a26de"\n },\n "addr.remote": "1.2.3.4:50339",\n "proto": "ssh",\n "size": "80:25",\n "initial_command": [\n ""\n ],\n "session_recording": "node"\n }\n }\n}', + sampleChunks: [], + exAnswer: + '{\n "crowdstrike": {\n "falcon": {\n "metadata": {\n "customerIDString": null,\n "offset": null,\n "eventType": {\n "target": "event.code",\n "confidence": 0.94,\n "type": "string",\n "date_formats": []\n },\n "eventCreationTime": {\n "target": "event.created",\n "confidence": 0.85,\n "type": "date",\n "date_formats": [\n "UNIX"\n ]\n },\n "version": null,\n "event": {\n "DeviceId": null,\n "CustomerId": null,\n "Ipv": {\n "target": "network.type",\n "confidence": 0.99,\n "type": "string",\n "date_formats": []\n }\n }\n }\n }\n }\n}', + packageName: 'xdfsfs', + dataStreamName: 'ds', + finalized: false, + currentMapping: { + xdfsfs: { + ds: { + identity: { + client_ip: { + target: 'client.ip', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + prev_identity_expires: { + target: 'event.end', + confidence: 0.7, + type: 'date', + date_formats: ["yyyy-MM-dd'T'HH:mm:ss'Z'"], + }, + private_key_policy: null, + }, + user: { + target: 'user.name', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + login: { + target: 'user.id', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + user_kind: null, + sid: { + target: 'event.id', + confidence: 0.85, + type: 'string', + date_formats: [], + }, + private_key_policy: null, + namespace: null, + server_id: { + target: 'host.id', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + server_hostname: { + target: 'host.hostname', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + server_labels: { + hostname: null, + 'teleport.internal/resource-id': null, + }, + 'addr.remote': { + target: 'source.address', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + proto: { + target: 'network.protocol', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + size: null, + initial_command: null, + session_recording: null, + }, + }, + }, + chunkMapping: { + xdfsfs: { + ds: { + ei: null, + event: { + target: 'event.action', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + uid: { + target: 'event.id', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + code: { + target: 'event.code', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + time: { + target: 'event.created', + confidence: 0.95, + type: 'date', + date_formats: ["yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'"], + }, + cluster_name: { + target: 'cloud.account.name', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + cert_type: null, + identity: { + user: { + target: 'user.name', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + roles: { + target: 'user.roles', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + logins: null, + expires: { + target: 'user.changes.name', + confidence: 0.7, + type: 'date', + date_formats: ["yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'"], + }, + route_to_cluster: null, + traits: { + aws_role_arns: null, + azure_identities: null, + db_names: null, + db_roles: null, + db_users: null, + gcp_service_accounts: null, + host_user_gid: null, + host_user_uid: null, + kubernetes_groups: null, + kubernetes_users: null, + logins: null, + windows_logins: null, + }, + teleport_cluster: null, + client_ip: { + target: 'client.ip', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + prev_identity_expires: { + target: 'event.end', + confidence: 0.7, + type: 'date', + date_formats: ["yyyy-MM-dd'T'HH:mm:ss'Z'"], + }, + private_key_policy: null, + }, + user: { + target: 'user.name', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + login: { + target: 'user.id', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + user_kind: null, + sid: { + target: 'event.id', + confidence: 0.85, + type: 'string', + date_formats: [], + }, + private_key_policy: null, + namespace: null, + server_id: { + target: 'host.id', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + server_hostname: { + target: 'host.hostname', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + server_labels: { + hostname: null, + 'teleport.internal/resource-id': null, + }, + 'addr.remote': { + target: 'source.address', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + proto: { + target: 'network.protocol', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + size: null, + initial_command: null, + session_recording: null, + }, + }, + }, + finalMapping: { + xdfsfs: { + ds: { + ei: null, + event: { + target: 'event.action', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + uid: { + target: 'event.id', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + code: { + target: 'event.code', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + '@timestamp': { + target: '@timestamp', + confidence: 0.95, + type: 'date', + date_formats: ["yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'"], + }, + cluster_name: { + target: 'cloud.account.name', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + cert_type: null, + identity: { + user: { + target: 'user.name', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + roles: { + target: 'user.roles', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + logins: null, + expires: { + target: 'user.changes.name', + confidence: 0.7, + type: 'date', + date_formats: ["yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'"], + }, + route_to_cluster: null, + traits: { + aws_role_arns: null, + azure_identities: null, + db_names: null, + db_roles: null, + db_users: null, + gcp_service_accounts: null, + host_user_gid: null, + host_user_uid: null, + kubernetes_groups: null, + kubernetes_users: null, + logins: null, + windows_logins: null, + }, + teleport_cluster: null, + client_ip: { + target: 'client.ip', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + prev_identity_expires: { + target: 'event.end', + confidence: 0.7, + type: 'date', + date_formats: ["yyyy-MM-dd'T'HH:mm:ss'Z'"], + }, + private_key_policy: null, + }, + user: { + target: 'user.name', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + login: { + target: 'user.id', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + user_kind: null, + sid: null, + private_key_policy: null, + namespace: null, + server_id: { + target: 'host.id', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + server_hostname: { + target: 'host.hostname', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + server_labels: { + hostname: null, + 'teleport.internal/resource-id': null, + }, + 'addr.remote': { + target: 'source.address', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + proto: { + target: 'network.protocol', + confidence: 0.95, + type: 'string', + date_formats: [], + }, + size: null, + initial_command: null, + session_recording: null, + }, + }, + }, + useFinalMapping: true, + hasTriedOnce: true, + currentPipeline: {}, + duplicateFields: [], + missingKeys: [], + invalidEcsFields: [], + results: {}, + samplesFormat: { + name: 'json', + json_path: [], + }, + ecsVersion: '8.11.0', + ecs: '', + chunkSize: 0, +}; diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.test.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.test.ts new file mode 100644 index 000000000000..3dda9208c309 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.test.ts @@ -0,0 +1,306 @@ +/* + * 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 { ecsPipelineState } from '../../../__jest__/fixtures/ecs_mapping'; +import type { EcsMappingState } from '../../types'; +import { createPipeline } from './pipeline'; + +const state: EcsMappingState = ecsPipelineState; + +describe('Testing pipeline templates', () => { + it('handle pipeline creation', async () => { + const pipeline = createPipeline(state); + expect(pipeline.processors).toEqual([ + { + set: { field: 'ecs.version', tag: 'set_ecs_version', value: '8.11.0' }, + }, + { + set: { + field: 'originalMessage', + copy_from: 'message', + tag: 'copy_original_message', + }, + }, + { + rename: { + field: 'originalMessage', + target_field: 'event.original', + tag: 'rename_message', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'originalMessage', + ignore_missing: true, + tag: 'remove_copied_message', + if: 'ctx.event?.original != null', + }, + }, + { + remove: { field: 'message', ignore_missing: true, tag: 'remove_message' }, + }, + { + json: { + field: 'event.original', + tag: 'json_original', + target_field: 'xdfsfs.ds', + }, + }, + { + rename: { + field: 'xdfsfs.ds.event', + target_field: 'event.action', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.uid', + target_field: 'event.id', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.code', + target_field: 'event.code', + ignore_missing: true, + }, + }, + { + date: { + field: 'xdfsfs.ds.@timestamp', + target_field: '@timestamp', + formats: ["yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'", 'ISO8601'], + tag: 'date_processor_xdfsfs.ds.@timestamp', + if: 'ctx.xdfsfs?.ds?.@timestamp != null', + }, + }, + { + rename: { + field: 'xdfsfs.ds.cluster_name', + target_field: 'cloud.account.name', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.identity.user', + target_field: 'user.name', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.identity.roles', + target_field: 'user.roles', + ignore_missing: true, + }, + }, + { + script: { + description: 'Ensures the date processor does not receive an array value.', + tag: 'script_convert_array_to_string', + lang: 'painless', + source: + 'if (ctx.xdfsfs?.ds?.identity?.expires != null &&\n' + + ' ctx.xdfsfs.ds.identity.expires instanceof ArrayList){\n' + + ' ctx.xdfsfs.ds.identity.expires = ctx.xdfsfs.ds.identity.expires[0];\n' + + '}\n', + }, + }, + { + date: { + field: 'xdfsfs.ds.identity.expires', + target_field: 'user.changes.name', + formats: ["yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'", 'ISO8601'], + tag: 'date_processor_xdfsfs.ds.identity.expires', + if: 'ctx.xdfsfs?.ds?.identity?.expires != null', + }, + }, + { + convert: { + field: 'xdfsfs.ds.identity.client_ip', + target_field: 'client.ip', + ignore_missing: true, + ignore_failure: true, + type: 'ip', + }, + }, + { + script: { + description: 'Ensures the date processor does not receive an array value.', + tag: 'script_convert_array_to_string', + lang: 'painless', + source: + 'if (ctx.xdfsfs?.ds?.identity?.prev_identity_expires != null &&\n' + + ' ctx.xdfsfs.ds.identity.prev_identity_expires instanceof ArrayList){\n' + + ' ctx.xdfsfs.ds.identity.prev_identity_expires = ctx.xdfsfs.ds.identity.prev_identity_expires[0];\n' + + '}\n', + }, + }, + { + date: { + field: 'xdfsfs.ds.identity.prev_identity_expires', + target_field: 'event.end', + formats: ["yyyy-MM-dd'T'HH:mm:ss'Z'", 'ISO8601'], + tag: 'date_processor_xdfsfs.ds.identity.prev_identity_expires', + if: 'ctx.xdfsfs?.ds?.identity?.prev_identity_expires != null', + }, + }, + { + rename: { + field: 'xdfsfs.ds.user', + target_field: 'user.name', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.login', + target_field: 'user.id', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.server_id', + target_field: 'host.id', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.server_hostname', + target_field: 'host.hostname', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.addr.remote', + target_field: 'source.address', + ignore_missing: true, + }, + }, + { + rename: { + field: 'xdfsfs.ds.proto', + target_field: 'network.protocol', + ignore_missing: true, + }, + }, + { + script: { + description: 'Drops null/empty values recursively.', + tag: 'script_drop_null_empty_values', + lang: 'painless', + source: + 'boolean dropEmptyFields(Object object) {\n' + + ' if (object == null || object == "") {\n' + + ' return true;\n' + + ' } else if (object instanceof Map) {\n' + + ' ((Map) object).values().removeIf(value -> dropEmptyFields(value));\n' + + ' return (((Map) object).size() == 0);\n' + + ' } else if (object instanceof List) {\n' + + ' ((List) object).removeIf(value -> dropEmptyFields(value));\n' + + ' return (((List) object).length == 0);\n' + + ' }\n' + + ' return false;\n' + + '}\n' + + 'dropEmptyFields(ctx);\n', + }, + }, + { + geoip: { + field: 'source.ip', + tag: 'geoip_source_ip', + target_field: 'source.geo', + ignore_missing: true, + }, + }, + { + geoip: { + ignore_missing: true, + database_file: 'GeoLite2-ASN.mmdb', + field: 'source.ip', + tag: 'geoip_source_asn', + target_field: 'source.as', + properties: ['asn', 'organization_name'], + }, + }, + { + rename: { + field: 'source.as.asn', + tag: 'rename_source_as_asn', + target_field: 'source.as.number', + ignore_missing: true, + }, + }, + { + rename: { + field: 'source.as.organization_name', + tag: 'rename_source_as_organization_name', + target_field: 'source.as.organization.name', + ignore_missing: true, + }, + }, + { + geoip: { + field: 'destination.ip', + tag: 'geoip_destination_ip', + target_field: 'destination.geo', + ignore_missing: true, + }, + }, + { + geoip: { + database_file: 'GeoLite2-ASN.mmdb', + field: 'destination.ip', + tag: 'geoip_destination_asn', + target_field: 'destination.as', + properties: ['asn', 'organization_name'], + ignore_missing: true, + }, + }, + { + rename: { + field: 'destination.as.asn', + tag: 'rename_destination_as_asn', + target_field: 'destination.as.number', + ignore_missing: true, + }, + }, + { + rename: { + field: 'destination.as.organization_name', + tag: 'rename_destination_as_organization_name', + target_field: 'destination.as.organization.name', + ignore_missing: true, + }, + }, + { + remove: { + field: ['xdfsfs.ds.identity.client_ip'], + ignore_missing: true, + tag: 'remove_fields', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ]); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts index b5a6e23000d3..dda48c97bdf9 100644 --- a/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts @@ -186,6 +186,9 @@ export function createPipeline(state: EcsMappingState): IngestPipeline { const env = new Environment(new FileSystemLoader(templatesPath), { autoescape: false, }); + env.addFilter('includes', function (str, substr) { + return str.includes(substr); + }); env.addFilter('startswith', function (str, prefix) { return str.startsWith(prefix); }); diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.test.ts b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.test.ts index 419e287e23bf..d7ce89e2e8f6 100644 --- a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.test.ts +++ b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.test.ts @@ -277,6 +277,13 @@ describe('renderPackageManifestYAML', () => { expect(manifest.name).toBe(integration.name); expect(manifest.type).toBe('integration'); expect(manifest.description).toBe(integration.description); - expect(manifest.icons).toBeTruthy(); + expect(Array.isArray(manifest.icons)).toBe(true); + expect((manifest.icons as object[]).length).toBe(1); + expect((manifest.icons as object[])[0]).toEqual({ + src: '/img/logo.svg', + title: 'Sample Integration Logo', + size: '32x32', + type: 'image/svg+xml', + }); }); }); diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts index 8743ada38bdb..bf2e9b6b9d5a 100644 --- a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts +++ b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts @@ -185,12 +185,14 @@ function createPackageManifestDict( }; if (package_logo !== undefined && package_logo !== '') { - data.icons = { - src: '/img/logo.svg', - title: `${package_title} Logo`, - size: '32x32', - type: 'image/svg+xml', - }; + data.icons = [ + { + src: '/img/logo.svg', + title: `${package_title} Logo`, + size: '32x32', + type: 'image/svg+xml', + }, + ]; } return data; } diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/http_endpoint_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/http_endpoint_manifest.yml.njk index b62a582a1cfc..b35471ad4a63 100644 --- a/x-pack/plugins/integration_assistant/server/templates/manifest/http_endpoint_manifest.yml.njk +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/http_endpoint_manifest.yml.njk @@ -42,12 +42,6 @@ The Ingest Node pipeline ID to be used by the integration. required: false show_user: true - - name: preserve_original_event - type: bool - title: Preserve Original Event - description: This option copies the raw unmodified body of the incoming request to the event.original field as a string before sending the event to Elasticsearch. - required: false - show_user: true - name: prefix type: text title: Prefix diff --git a/x-pack/plugins/integration_assistant/server/templates/pipeline.yml.njk b/x-pack/plugins/integration_assistant/server/templates/pipeline.yml.njk index ba846dc50fba..116d5cc66719 100644 --- a/x-pack/plugins/integration_assistant/server/templates/pipeline.yml.njk +++ b/x-pack/plugins/integration_assistant/server/templates/pipeline.yml.njk @@ -35,6 +35,7 @@ processors: target_field: {% if value.target_field | startswith('@') %}"{{ value.target_field }}"{% else %}{{ value.target_field }}{% endif %} ignore_missing: true{% endif %} {% if key == 'date' %} + {% if not value.field | includes('.@') %} {# Leaving fields of type 'test.log.@timestamp' #} - script: description: Ensures the date processor does not receive an array value. tag: script_convert_array_to_string @@ -43,7 +44,7 @@ processors: if (ctx.{% endraw %}{{ value.field.replaceAll('.', '?.') }}{% raw %} != null && ctx.{% endraw %}{{ value.field }}{% raw %} instanceof ArrayList){ ctx.{% endraw %}{{ value.field }}{% raw %} = ctx.{% endraw %}{{ value.field }}{% raw %}[0]; - }{% endraw %} + }{% endraw %}{% endif %} - {{ key }}: field: {{ value.field }} target_field: {% if value.target_field | startswith('@') %}"{{ value.target_field }}"{% else %}{{ value.target_field }}{% endif %} diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts index 955d260abe8a..b8154c4bc543 100644 --- a/x-pack/plugins/lens/common/constants.ts +++ b/x-pack/plugins/lens/common/constants.ts @@ -10,13 +10,17 @@ import type { RefreshInterval, TimeRange } from '@kbn/data-plugin/common/query'; import type { Filter } from '@kbn/es-query'; export const PLUGIN_ID = 'lens'; -export const APP_ID = 'lens'; -export const LENS_APP_NAME = 'lens'; -export const LENS_EMBEDDABLE_TYPE = 'lens'; +export const APP_ID = PLUGIN_ID; export const DOC_TYPE = 'lens'; +export const LENS_APP_NAME = APP_ID; +export const LENS_EMBEDDABLE_TYPE = DOC_TYPE; export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; export const BASE_API_URL = '/api/lens'; export const LENS_EDIT_BY_VALUE = 'edit_by_value'; +export const LENS_ICON = 'lensApp'; +export const STAGE_ID = 'production'; + +export const INDEX_PATTERN_TYPE = 'index-pattern'; export const PieChartTypes = { PIE: 'pie', diff --git a/x-pack/plugins/lens/common/embeddable_factory/index.ts b/x-pack/plugins/lens/common/embeddable_factory/index.ts index 68e6c77e9dae..62cd68e15e9d 100644 --- a/x-pack/plugins/lens/common/embeddable_factory/index.ts +++ b/x-pack/plugins/lens/common/embeddable_factory/index.ts @@ -6,47 +6,52 @@ */ import { cloneDeep } from 'lodash'; -import type { SerializableRecord, Serializable } from '@kbn/utility-types'; +import type { SerializableRecord } from '@kbn/utility-types'; import type { SavedObjectReference } from '@kbn/core/types'; -import type { - EmbeddableStateWithType, +import { EmbeddableRegistryDefinition, + EmbeddableStateWithType, } from '@kbn/embeddable-plugin/common'; +import type { LensRuntimeState } from '../../public'; export type LensEmbeddablePersistableState = EmbeddableStateWithType & { attributes: SerializableRecord; }; -export const inject: EmbeddableRegistryDefinition['inject'] = (state, references) => { - // We need to clone the state because we can not modify the original state object. - const typedState = cloneDeep(state) as LensEmbeddablePersistableState; +export const inject: NonNullable<EmbeddableRegistryDefinition['inject']> = ( + state, + references +): EmbeddableStateWithType => { + const typedState = cloneDeep(state) as unknown as LensRuntimeState; - if ('attributes' in typedState && typedState.attributes !== undefined) { - // match references based on name, so only references associated with this lens panel are injected. - const matchedReferences: SavedObjectReference[] = []; - - if (Array.isArray(typedState.attributes.references)) { - typedState.attributes.references.forEach((serializableRef) => { - const internalReference = serializableRef as unknown as SavedObjectReference; - const matchedReference = references.find( - (reference) => reference.name === internalReference.name - ); - if (matchedReference) matchedReferences.push(matchedReference); - }); - } - - typedState.attributes.references = matchedReferences as unknown as Serializable[]; + if (typedState.savedObjectId) { + return typedState as unknown as EmbeddableStateWithType; } - return typedState; + // match references based on name, so only references associated with this lens panel are injected. + const matchedReferences: SavedObjectReference[] = []; + + if (Array.isArray(typedState.attributes.references)) { + typedState.attributes.references.forEach((serializableRef) => { + const internalReference = serializableRef; + const matchedReference = references.find( + (reference) => reference.name === internalReference.name + ); + if (matchedReference) matchedReferences.push(matchedReference); + }); + } + + typedState.attributes.references = matchedReferences; + + return typedState as unknown as EmbeddableStateWithType; }; -export const extract: EmbeddableRegistryDefinition['extract'] = (state) => { +export const extract: NonNullable<EmbeddableRegistryDefinition['extract']> = (state) => { let references: SavedObjectReference[] = []; - const typedState = state as LensEmbeddablePersistableState; + const typedState = state as unknown as LensRuntimeState; if ('attributes' in typedState && typedState.attributes !== undefined) { - references = typedState.attributes.references as unknown as SavedObjectReference[]; + references = typedState.attributes.references; } return { state, references }; diff --git a/x-pack/plugins/lens/common/locator/locator.ts b/x-pack/plugins/lens/common/locator/locator.ts index ea0e54136ffc..7b0b12416f14 100644 --- a/x-pack/plugins/lens/common/locator/locator.ts +++ b/x-pack/plugins/lens/common/locator/locator.ts @@ -9,7 +9,7 @@ import rison from '@kbn/rison'; import type { SerializableRecord } from '@kbn/utility-types'; import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/common'; -import type { Filter, Query } from '@kbn/es-query'; +import type { AggregateQuery, Filter, Query } from '@kbn/es-query'; import type { DataViewSpec, SavedQuery } from '@kbn/data-plugin/common'; import { SavedObjectReference } from '@kbn/core-saved-objects-common'; import type { DateRange } from '../types'; @@ -26,7 +26,7 @@ interface LensShareableState { /** * Optionally set a query. */ - query?: Query; + query?: Query | AggregateQuery; /** * Optionally set the date range in the date picker. @@ -88,7 +88,7 @@ export interface LensAppLocatorParams extends SerializableRecord { /** * Optionally set a query. */ - query?: Query; + query?: Query | AggregateQuery; /** * Optionally set the date range in the date picker. diff --git a/x-pack/plugins/lens/kibana.jsonc b/x-pack/plugins/lens/kibana.jsonc index 4b0b14141474..012a077abb12 100644 --- a/x-pack/plugins/lens/kibana.jsonc +++ b/x-pack/plugins/lens/kibana.jsonc @@ -45,6 +45,7 @@ "expressionLegacyMetricVis", "expressionPartitionVis", "usageCollection", + "embeddableEnhanced", "taskManager", "globalSearch", "savedObjectsTagging", diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 8aebc4778e20..73fb52bbe668 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -5,23 +5,20 @@ * 2.0. */ -import React, { PropsWithChildren } from 'react'; +import React from 'react'; import { Observable, Subject } from 'rxjs'; -import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { App } from './app'; import { LensAppProps, LensAppServices } from './types'; -import { EditorFrameInstance, EditorFrameProps } from '../types'; -import { Document, SavedObjectIndexStore } from '../persistence'; +import { LensDocument, SavedObjectIndexStore } from '../persistence'; import { visualizationMap, datasourceMap, makeDefaultServices, - mountWithProvider, + renderWithReduxStore, mockStoreDeps, + defaultDoc, } from '../mocks'; -import { I18nProvider } from '@kbn/i18n-react'; -import { SavedObjectSaveModal } from '@kbn/saved-objects-plugin/public'; import { checkForDuplicateTitle } from '../persistence'; import { createMemoryHistory } from 'history'; import type { Query } from '@kbn/es-query'; @@ -29,55 +26,52 @@ import { FilterManager } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { buildExistsFilter, FilterStateStore } from '@kbn/es-query'; import type { FieldSpec } from '@kbn/data-plugin/common'; -import { TopNavMenuData } from '@kbn/navigation-plugin/public'; -import { LensByValueInput } from '../embeddable/embeddable'; -import { SavedObjectReference } from '@kbn/core/types'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { serverlessMock } from '@kbn/serverless/public/mocks'; +import { cloneDeep } from 'lodash'; import moment from 'moment'; - import { setState, LensAppState } from '../state_management'; import { coreMock } from '@kbn/core/public/mocks'; -jest.mock('../editor_frame_service/editor_frame/expression_helpers'); -jest.mock('@kbn/core/public'); +import { LensSerializedState } from '..'; +import { createMockedField, createMockedIndexPattern } from '../datasources/form_based/mocks'; +import faker from 'faker'; +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { VisualizeEditorContext } from '../types'; +import { setMockedPresentationUtilServices } from '@kbn/presentation-util-plugin/public/mocks'; + jest.mock('../persistence/saved_objects_utils/check_for_duplicate_title', () => ({ checkForDuplicateTitle: jest.fn(), })); +jest.mock('lodash', () => ({ + ...jest.requireActual('lodash'), + debounce: (fn: unknown) => fn, +})); -jest.mock('lodash', () => { - const original = jest.requireActual('lodash'); - - return { - ...original, - debounce: (fn: unknown) => fn, - }; -}); +const defaultSavedObjectId: string = faker.random.uuid(); -// const navigationStartMock = navigationPluginMock.createStartContract(); +const waitToLoad = async () => + await act(async () => new Promise((resolve) => setTimeout(resolve, 0))); -const sessionIdSubject = new Subject<string>(); +function getLensDocumentMock(propsOverrides?: Partial<LensDocument>) { + return cloneDeep({ ...defaultDoc, ...propsOverrides }); +} describe('Lens App', () => { - let defaultDoc: Document; - let defaultSavedObjectId: string; - - function createMockFrame(): jest.Mocked<EditorFrameInstance> { - return { - EditorFrameContainer: jest.fn((props: EditorFrameProps) => <div />), - datasourceMap, - visualizationMap, - }; - } - - const navMenuItems = { - expectedSaveButton: { emphasize: true, testId: 'lnsApp_saveButton' }, - expectedSaveAsButton: { emphasize: false, testId: 'lnsApp_saveButton' }, - expectedSaveAndReturnButton: { emphasize: true, testId: 'lnsApp_saveAndReturnButton' }, - }; + let props: jest.Mocked<LensAppProps>; + let services: jest.Mocked<LensAppServices> = makeDefaultServices( + new Subject<string>(), + 'sessionId-1' + ); + beforeAll(() => setMockedPresentationUtilServices()); - function makeDefaultProps(): jest.Mocked<LensAppProps> { - return { - editorFrame: createMockFrame(), + beforeEach(() => { + props = { + editorFrame: { + EditorFrameContainer: jest.fn((_) => <div>Editor frame</div>), + datasourceMap, + visualizationMap, + }, history: createMemoryHistory(), redirectTo: jest.fn(), redirectToOrigin: jest.fn(), @@ -94,93 +88,60 @@ describe('Lens App', () => { search: jest.fn(), } as unknown as SavedObjectIndexStore, }; - } - const makeDefaultServicesForApp = () => makeDefaultServices(sessionIdSubject, 'sessionId-1'); + services = makeDefaultServices(new Subject<string>(), 'sessionId-1'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); - async function mountWith({ - props = makeDefaultProps(), - services = makeDefaultServicesForApp(), + async function renderApp({ preloadedState, }: { - props?: jest.Mocked<LensAppProps>; - services?: jest.Mocked<LensAppServices>; preloadedState?: Partial<LensAppState>; - }) { - const wrappingComponent: React.FC<PropsWithChildren<{}>> = ({ children }) => { - return ( - <I18nProvider> - <KibanaContextProvider services={services}>{children}</KibanaContextProvider> - </I18nProvider> - ); - }; - const storeDeps = mockStoreDeps({ lensServices: services }); - const { instance, lensStore } = await mountWithProvider( + } = {}) { + const Wrapper = ({ children }: { children: React.ReactNode }) => ( + <KibanaContextProvider services={services}>{children}</KibanaContextProvider> + ); + + const { + store, + render: renderRtl, + rerender, + ...rest + } = renderWithReduxStore( <App {...props} />, + { wrapper: Wrapper }, { - storeDeps, + storeDeps: mockStoreDeps({ lensServices: services }), preloadedState, - }, - { wrappingComponent } + } ); - const frame = props.editorFrame as ReturnType<typeof createMockFrame>; - lensStore.dispatch(setState({ ...preloadedState })); - return { instance, frame, props, services, lensStore }; - } + const rerenderWithProps = (newProps: Partial<LensAppProps>) => { + rerender(<App {...props} {...newProps} />, { + wrapper: Wrapper, + }); + }; - beforeEach(() => { - defaultSavedObjectId = '1234'; - defaultDoc = { - savedObjectId: defaultSavedObjectId, - visualizationType: 'testVis', - type: 'lens', - title: 'An extremely cool default document!', - expression: 'definitely a valid expression', - state: { - query: 'lucene', - filters: [{ query: { match_phrase: { src: 'test' } }, meta: { index: 'index-pattern-0' } }], - }, - references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], - } as unknown as Document; - }); + await act(async () => await store.dispatch(setState({ ...preloadedState }))); + return { props, lensStore: store, rerender: rerenderWithProps, ...rest }; + } it('renders the editor frame', async () => { - const { frame } = await mountWith({}); - expect(frame.EditorFrameContainer).toHaveBeenLastCalledWith( - { - indexPatternService: expect.any(Object), - getUserMessages: expect.any(Function), - addUserMessages: expect.any(Function), - lensInspector: { - adapters: { - expression: expect.any(Object), - requests: expect.any(Object), - tables: expect.any(Object), - }, - close: expect.any(Function), - inspect: expect.any(Function), - }, - showNoDataPopover: expect.any(Function), - }, - {} - ); + await renderApp(); + expect(screen.getByText('Editor frame')).toBeInTheDocument(); }); it('updates global filters with store state', async () => { - const services = makeDefaultServicesForApp(); - const indexPattern = { id: 'index1', isPersisted: () => true } as unknown as DataView; - const pinnedField = { name: 'pinnedField' } as unknown as FieldSpec; + const pinnedField = createMockedField({ name: 'pinnedField', type: '' }); + const indexPattern = createMockedIndexPattern({ id: 'index1' }, [pinnedField]); const pinnedFilter = buildExistsFilter(pinnedField, indexPattern); - services.data.query.filterManager.getFilters = jest.fn().mockImplementation(() => { - return []; - }); - services.data.query.filterManager.getGlobalFilters = jest.fn().mockImplementation(() => { - return [pinnedFilter]; - }); - const { instance, lensStore } = await mountWith({ services }); + services.data.query.filterManager.getFilters = jest.fn().mockReturnValue([]); + services.data.query.filterManager.getGlobalFilters = jest.fn().mockReturnValue([pinnedFilter]); + const { lensStore } = await renderApp(); - instance.update(); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ query: { query: '', language: 'lucene' }, @@ -198,22 +159,19 @@ describe('Lens App', () => { describe('extra nav menu entries', () => { it('shows custom menu entry', async () => { const runFn = jest.fn(); - const { instance, services } = await mountWith({ - props: { - ...makeDefaultProps(), - topNavMenuEntryGenerators: [ - () => ({ - label: 'My entry', - run: runFn, - }), - ], - }, - }); - const navigationComponent = services.navigation.ui - .AggregateQueryTopNavMenu as unknown as React.ReactElement; - const extraEntry = instance.find(navigationComponent).prop('config')[0]; - expect(extraEntry.label).toEqual('My entry'); - expect(extraEntry.run).toBe(runFn); + props.topNavMenuEntryGenerators = [ + () => ({ + label: 'My entry', + run: runFn, + }), + ]; + await renderApp(); + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( + expect.objectContaining({ + config: expect.arrayContaining([{ label: 'My entry', run: runFn }]), + }), + {} + ); }); it('passes current state, filter, query timerange and initial context into getter', async () => { @@ -244,15 +202,12 @@ describe('Lens App', () => { }, ], }; - await mountWith({ - props: { - ...makeDefaultProps(), - topNavMenuEntryGenerators: [getterFn], - initialContext: { - fieldName: 'a', - dataViewSpec: { id: '1' }, - }, - }, + props.topNavMenuEntryGenerators = [getterFn]; + props.initialContext = { + fieldName: 'a', + dataViewSpec: { id: '1' }, + }; + await renderApp({ preloadedState, }); @@ -278,19 +233,14 @@ describe('Lens App', () => { }); describe('breadcrumbs', () => { - const breadcrumbDocSavedObjectId = defaultSavedObjectId; - const breadcrumbDoc = { + const breadcrumbDocSavedObjectId = faker.random.uuid(); + const breadcrumbDoc = getLensDocumentMock({ savedObjectId: breadcrumbDocSavedObjectId, title: 'Daaaaaaadaumching!', - state: { - query: 'fake query', - filters: [], - }, - references: [], - } as unknown as Document; + }); it('sets breadcrumbs when the document title changes', async () => { - const { instance, services, lensStore } = await mountWith({}); + const { lensStore } = await renderApp(); expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ { @@ -302,8 +252,7 @@ describe('Lens App', () => { ]); await act(async () => { - instance.setProps({ initialInput: { savedObjectId: breadcrumbDocSavedObjectId } }); - lensStore.dispatch( + await lensStore.dispatch( setState({ persistedDoc: breadcrumbDoc, }) @@ -321,17 +270,10 @@ describe('Lens App', () => { }); it('sets originatingApp breadcrumb when the document title changes', async () => { - const props = makeDefaultProps(); - const services = makeDefaultServicesForApp(); - props.incomingState = { originatingApp: 'coolContainer' }; + props.incomingState = { originatingApp: 'dashboards' }; services.getOriginatingAppName = jest.fn(() => 'The Coolest Container Ever Made'); - - const { instance, lensStore } = await mountWith({ - props, - services, - preloadedState: { - isLinkedToOriginatingApp: false, - }, + const { lensStore, rerender } = await renderApp({ + preloadedState: { isLinkedToOriginatingApp: false }, }); expect(services.chrome.setBreadcrumbs).toHaveBeenCalledWith([ @@ -344,12 +286,7 @@ describe('Lens App', () => { ]); await act(async () => { - instance.setProps({ - initialInput: { savedObjectId: breadcrumbDocSavedObjectId }, - preloadedState: { - isLinkedToOriginatingApp: true, - }, - }); + await rerender({ initialInput: { savedObjectId: breadcrumbDocSavedObjectId } }); lensStore.dispatch( setState({ @@ -370,17 +307,13 @@ describe('Lens App', () => { it('sets serverless breadcrumbs when the document title changes when serverless service is available', async () => { const serverless = serverlessMock.createStart(); - const { instance, services, lensStore } = await mountWith({ - services: { - ...makeDefaultServices(), - serverless, - }, - }); + services.serverless = serverless; + const { lensStore, rerender } = await renderApp(); expect(services.chrome.setBreadcrumbs).not.toHaveBeenCalled(); expect(serverless.setBreadcrumbs).toHaveBeenCalledWith({ text: 'Create' }); await act(async () => { - instance.setProps({ initialInput: { savedObjectId: breadcrumbDocSavedObjectId } }); + rerender({ initialInput: { savedObjectId: breadcrumbDocSavedObjectId } }); lensStore.dispatch( setState({ persistedDoc: breadcrumbDoc, @@ -395,43 +328,40 @@ describe('Lens App', () => { describe('TopNavMenu#showDatePicker', () => { it('shows date picker if any used index pattern isTimeBased', async () => { - const customServices = makeDefaultServicesForApp(); - customServices.dataViews.get = jest + services.dataViews.get = jest .fn() - .mockImplementation((id) => - Promise.resolve({ id, isTimeBased: () => true, isPersisted: () => true } as DataView) + .mockImplementation( + async (id) => ({ id, isTimeBased: () => true, isPersisted: () => true } as DataView) ); - const { services } = await mountWith({ services: customServices }); + await renderApp(); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showDatePicker: true }), {} ); }); it('shows date picker if active datasource isTimeBased', async () => { - const customServices = makeDefaultServicesForApp(); - customServices.dataViews.get = jest + services.dataViews.get = jest .fn() - .mockImplementation((id) => - Promise.resolve({ id, isTimeBased: () => true, isPersisted: () => true } as DataView) + .mockImplementation( + async (id) => ({ id, isTimeBased: () => true, isPersisted: () => true } as DataView) ); - const customProps = makeDefaultProps(); - customProps.datasourceMap.testDatasource.isTimeBased = () => true; - const { services } = await mountWith({ props: customProps, services: customServices }); + + props.datasourceMap.testDatasource.isTimeBased = () => true; + await renderApp(); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showDatePicker: true }), {} ); }); it('does not show date picker if index pattern nor active datasource is not time based', async () => { - const customServices = makeDefaultServicesForApp(); - customServices.dataViews.get = jest + services.dataViews.get = jest .fn() - .mockImplementation((id) => - Promise.resolve({ id, isTimeBased: () => true, isPersisted: () => true } as DataView) + .mockImplementation( + async (id) => ({ id, isTimeBased: () => true, isPersisted: () => true } as DataView) ); - const customProps = makeDefaultProps(); - customProps.datasourceMap.testDatasource.isTimeBased = () => false; - const { services } = await mountWith({ props: customProps, services: customServices }); + + props.datasourceMap.testDatasource.isTimeBased = () => false; + await renderApp(); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ showDatePicker: false }), {} @@ -441,7 +371,7 @@ describe('Lens App', () => { describe('TopNavMenu#dataViewPickerProps', () => { it('calls the nav component with the correct dataview picker props if permissions are given', async () => { - const { instance, lensStore, services } = await mountWith({ preloadedState: {} }); + const { lensStore } = await renderApp(); services.dataViewEditor.userPermissions.editDataView = () => true; const document = { savedObjectId: defaultSavedObjectId, @@ -450,8 +380,9 @@ describe('Lens App', () => { filters: [{ query: { match_phrase: { src: 'test' } } }], }, references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], - } as unknown as Document; + } as unknown as LensDocument; + (services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock).mockClear(); act(() => { lensStore.dispatch( setState({ @@ -460,46 +391,44 @@ describe('Lens App', () => { }) ); }); - instance.update(); - const props = instance - .find('[data-test-subj="lnsApp_topNav"]') - .prop('dataViewPickerComponentProps') as TopNavMenuData[]; - expect(props).toEqual( + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ - currentDataViewId: 'mockip', - onChangeDataView: expect.any(Function), - onDataViewCreated: expect.any(Function), - onAddField: expect.any(Function), - }) + dataViewPickerComponentProps: expect.objectContaining({ + currentDataViewId: 'mockip', + onChangeDataView: expect.any(Function), + onDataViewCreated: expect.any(Function), + onAddField: expect.any(Function), + }), + }), + {} ); }); }); describe('persistence', () => { it('passes query and indexPatterns to TopNavMenu', async () => { - const { instance, lensStore, services } = await mountWith({ preloadedState: {} }); - const document = { + const { lensStore } = await renderApp(); + const query = { query: 'fake query', language: 'kuery' }; + const document = getLensDocumentMock({ savedObjectId: defaultSavedObjectId, state: { - query: 'fake query', - filters: [{ query: { match_phrase: { src: 'test' } } }], + ...defaultDoc.state, + query, + filters: [{ query: { match_phrase: { src: 'test' } }, meta: {} }], }, references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], - } as unknown as Document; - - act(() => { - lensStore.dispatch( - setState({ - query: 'fake query' as unknown as Query, - persistedDoc: document, - }) - ); }); - instance.update(); + + await lensStore.dispatch( + setState({ + query, + persistedDoc: document, + }) + ); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ - query: 'fake query', + query, indexPatterns: [ { id: 'mockip', @@ -514,240 +443,155 @@ describe('Lens App', () => { ); }); it('handles rejected index pattern', async () => { - const customServices = makeDefaultServicesForApp(); - customServices.dataViews.get = jest + services.dataViews.get = jest .fn() - .mockImplementation((id) => Promise.reject({ reason: 'Could not locate that data view' })); - const customProps = makeDefaultProps(); - const { services } = await mountWith({ props: customProps, services: customServices }); + .mockResolvedValue(Promise.reject({ reason: 'Could not locate that data view' })); + await renderApp(); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ indexPatterns: [] }), {} ); }); - describe('save buttons', () => { - interface SaveProps { - newCopyOnSave: boolean; - returnToOrigin?: boolean; - newTitle: string; - } - function getButton(inst: ReactWrapper): TopNavMenuData { - return ( - inst.find('[data-test-subj="lnsApp_topNav"]').prop('config') as TopNavMenuData[] - ).find((button) => button.testId === 'lnsApp_saveButton')!; - } + describe('save buttons', () => { + const querySaveButton = () => screen.queryByTestId('lnsApp_saveButton'); + const clickSaveButton = async () => + await act(async () => await userEvent.click(screen.getByTestId('lnsApp_saveButton'))); - async function testSave(inst: ReactWrapper, saveProps: SaveProps) { - getButton(inst).run(inst.getDOMNode()); - // wait a tick since SaveModalContainer initializes asynchronously - await new Promise(process.nextTick); - const handler = inst.update().find('SavedObjectSaveModalOrigin').prop('onSave') as ( - p: unknown - ) => void; - handler(saveProps); - } + const querySaveAndReturnButton = () => screen.queryByTestId('lnsApp_saveAndReturnButton'); + const waitForModalVisible = async () => + await waitFor(() => screen.getByTestId('savedObjectTitle')); async function save({ preloadedState, - initialSavedObjectId, - ...saveProps - }: SaveProps & { + savedObjectId = defaultSavedObjectId, + prevSavedObjectId = undefined, + newTitle = 'hello there', + newCopyOnSave = false, + comesFromDashboard = true, + switchToAddToDashboardNone = false, + }: { + newCopyOnSave?: boolean; + newTitle?: string; preloadedState?: Partial<LensAppState>; - initialSavedObjectId?: string; + prevSavedObjectId?: string; + savedObjectId?: string; + comesFromDashboard?: boolean; + switchToAddToDashboardNone?: boolean; }) { - const props = { - ...makeDefaultProps(), - initialInput: initialSavedObjectId - ? { savedObjectId: initialSavedObjectId, id: '5678' } - : undefined, - }; - - props.incomingState = { - originatingApp: 'ultraDashboard', - }; - - const services = makeDefaultServicesForApp(); - services.attributeService.wrapAttributes = jest - .fn() - .mockImplementation(async ({ savedObjectId }) => ({ - savedObjectId: savedObjectId || 'aaa', - })); - services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({ - metaInfo: { - sharingSavedObjectProps: { - outcome: 'exactMatch', - }, + services.attributeService.saveToLibrary = jest.fn().mockResolvedValue(savedObjectId); + services.attributeService.loadFromLibrary = jest.fn().mockResolvedValue({ + sharingSavedObjectProps: { + outcome: 'exactMatch', }, attributes: { - savedObjectId: initialSavedObjectId ?? 'aaa', + savedObjectId, references: [], state: { - query: 'fake query', + query: { query: 'fake query', language: 'kuery' }, filters: [], }, }, - } as jest.ResolvedValue<Document>); + managed: false, + }); + + props = { + ...props, + initialInput: prevSavedObjectId ? { savedObjectId: prevSavedObjectId } : undefined, + }; - const { frame, instance, lensStore } = await mountWith({ - services, - props, + if (comesFromDashboard) { + props.incomingState = { originatingApp: 'dashboards' }; + } + + const { lensStore } = await renderApp({ preloadedState: { isSaveable: true, - isLinkedToOriginatingApp: true, + isLinkedToOriginatingApp: comesFromDashboard, ...preloadedState, }, }); - expect(getButton(instance).disableButton).toEqual(false); - await act(async () => { - testSave(instance, { ...saveProps }); + await clickSaveButton(); + await waitForModalVisible(); + if (newCopyOnSave) { + await userEvent.click(screen.getByTestId('saveAsNewCheckbox')); + } + if (switchToAddToDashboardNone) { + await userEvent.click(screen.getByLabelText('None')); + } + await waitFor(async () => { + await userEvent.clear(screen.getByTestId('savedObjectTitle')); + expect(screen.getByTestId('savedObjectTitle')).toHaveValue(''); }); - return { props, services, instance, frame, lensStore }; + await userEvent.type(screen.getByTestId('savedObjectTitle'), `${newTitle}`); + await userEvent.click(screen.getByTestId('confirmSaveSavedObjectButton')); + await waitToLoad(); + return { props, lensStore }; } it('shows a disabled save button when the user does not have permissions', async () => { - const services = makeDefaultServicesForApp(); - services.application = { - ...services.application, - capabilities: { - ...services.application.capabilities, - visualize: { save: false, saveQuery: false, show: true }, + services.application.capabilities = { + ...services.application.capabilities, + visualize: { save: false, saveQuery: false, show: true }, + dashboard: { + showWriteControls: false, }, }; - const { instance, lensStore } = await mountWith({ services }); - expect(getButton(instance).disableButton).toEqual(true); - act(() => { - lensStore.dispatch( - setState({ - isSaveable: true, - }) - ); - }); - instance.update(); - expect(getButton(instance).disableButton).toEqual(true); + await renderApp({ preloadedState: { isSaveable: true } }); + expect(querySaveButton()).toBeDisabled(); }); it('shows a save button that is enabled when the frame has provided its state and does not show save and return or save as', async () => { - const { instance, lensStore, services } = await mountWith({}); - expect(getButton(instance).disableButton).toEqual(true); - act(() => { - lensStore.dispatch( - setState({ - isSaveable: true, - }) - ); + await renderApp({ + preloadedState: { isSaveable: true }, }); - instance.update(); - expect(getButton(instance).disableButton).toEqual(false); - await act(async () => { - const topNavMenuConfig = instance - .find(services.navigation.ui.AggregateQueryTopNavMenu) - .prop('config'); - expect(topNavMenuConfig).not.toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) - ); - expect(topNavMenuConfig).not.toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveAsButton) - ); - expect(topNavMenuConfig).toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveButton) - ); - }); + expect(querySaveButton()).toHaveTextContent('Save'); + expect(querySaveAndReturnButton()).toBeFalsy(); }); - it('Shows Save and Return and Save As buttons in create by value mode with originating app', async () => { - const props = makeDefaultProps(); - const services = makeDefaultServicesForApp(); + it('Shows Save and Return and Save to library buttons in create by value mode with originating app', async () => { props.incomingState = { - originatingApp: 'ultraDashboard', + originatingApp: 'dashboards', valueInput: { id: 'whatchaGonnaDoWith', attributes: { title: 'whatcha gonna do with all these references? All these references in your value Input', - references: [] as SavedObjectReference[], + references: [], }, - } as LensByValueInput, + } as unknown as LensSerializedState, }; - - const { instance } = await mountWith({ - props, - services, + await renderApp({ preloadedState: { isLinkedToOriginatingApp: true, + isSaveable: true, }, }); - await act(async () => { - const topNavMenuConfig = instance - .find(services.navigation.ui.AggregateQueryTopNavMenu) - .prop('config'); - expect(topNavMenuConfig).toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) - ); - expect(topNavMenuConfig).toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveAsButton) - ); - expect(topNavMenuConfig).not.toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveButton) - ); - }); + expect(querySaveAndReturnButton()).toBeEnabled(); + expect(querySaveButton()).toHaveTextContent('Save to library'); }); it('Shows Save and Return and Save As buttons in edit by reference mode', async () => { - const props = makeDefaultProps(); - props.initialInput = { savedObjectId: defaultSavedObjectId, id: '5678' }; props.incomingState = { - originatingApp: 'ultraDashboard', + originatingApp: 'dashboards', }; - - const { instance, services } = await mountWith({ - props, + props.initialInput = { savedObjectId: defaultSavedObjectId, id: '5678' }; + await renderApp({ preloadedState: { + isSaveable: true, isLinkedToOriginatingApp: true, }, }); - await act(async () => { - const topNavMenuConfig = instance - .find(services.navigation.ui.AggregateQueryTopNavMenu) - .prop('config'); - expect(topNavMenuConfig).toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveAndReturnButton) - ); - expect(topNavMenuConfig).toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveAsButton) - ); - expect(topNavMenuConfig).not.toContainEqual( - expect.objectContaining(navMenuItems.expectedSaveButton) - ); - }); - }); - - it('saves new docs', async () => { - const { props, services } = await save({ - initialSavedObjectId: undefined, - newCopyOnSave: false, - newTitle: 'hello there', - }); - expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( - expect.objectContaining({ - savedObjectId: undefined, - title: 'hello there', - }), - true, - undefined - ); - expect(props.redirectTo).toHaveBeenCalledWith('aaa'); - expect(services.notifications.toasts.addSuccess).toHaveBeenCalledWith( - "Saved 'hello there'" - ); + expect(querySaveAndReturnButton()).toBeEnabled(); + expect(querySaveButton()).toHaveTextContent('Save as'); }); it('applies all changes on-save', async () => { const { lensStore } = await save({ - initialSavedObjectId: undefined, + savedObjectId: undefined, newCopyOnSave: false, newTitle: 'hello there', preloadedState: { @@ -756,120 +600,91 @@ describe('Lens App', () => { }); expect(lensStore.getState().lens.applyChangesCounter).toBe(1); }); - it('adds to the recently accessed list on save', async () => { - const { services } = await save({ - initialSavedObjectId: undefined, - newCopyOnSave: false, - newTitle: 'hello there', - }); + const savedObjectId = faker.random.uuid(); + await save({ savedObjectId, prevSavedObjectId: 'prevId', comesFromDashboard: false }); expect(services.chrome.recentlyAccessed.add).toHaveBeenCalledWith( - '/app/lens#/edit/aaa', + `/app/lens#/edit/${savedObjectId}`, 'hello there', - 'aaa' + savedObjectId ); }); - it('saves the latest doc as a copy', async () => { - const { props, services, instance } = await save({ - initialSavedObjectId: defaultSavedObjectId, - newCopyOnSave: true, + it('saves new docs', async () => { + await save({ + prevSavedObjectId: undefined, + savedObjectId: defaultSavedObjectId, newTitle: 'hello there', - preloadedState: { persistedDoc: defaultDoc }, + comesFromDashboard: false, + switchToAddToDashboardNone: true, }); - expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( + expect(services.attributeService.saveToLibrary).toHaveBeenCalledWith( expect.objectContaining({ title: 'hello there', }), - true, + // from mocks + [ + { + id: 'mockip', + name: 'mockip', + type: 'index-pattern', + }, + ], undefined ); expect(props.redirectTo).toHaveBeenCalledWith(defaultSavedObjectId); - await act(async () => { - instance.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); - }); - expect(services.attributeService.wrapAttributes).toHaveBeenCalledTimes(1); expect(services.notifications.toasts.addSuccess).toHaveBeenCalledWith( "Saved 'hello there'" ); }); - it('saves existing docs', async () => { - const { props, services, instance } = await save({ - initialSavedObjectId: defaultSavedObjectId, - newCopyOnSave: false, + it('saves existing docs as a copy', async () => { + const doc = getLensDocumentMock(); + await save({ + savedObjectId: doc.savedObjectId, + newCopyOnSave: true, newTitle: 'hello there', - preloadedState: { persistedDoc: defaultDoc }, + preloadedState: { persistedDoc: doc }, + prevSavedObjectId: 'prevId', + comesFromDashboard: false, }); - expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( + expect(services.attributeService.saveToLibrary).toHaveBeenCalledWith( expect.objectContaining({ - savedObjectId: defaultSavedObjectId, title: 'hello there', }), - true, - { id: '5678', savedObjectId: defaultSavedObjectId } + [{ id: 'mockip', name: 'mockip', type: 'index-pattern' }], + undefined ); - expect(props.redirectTo).not.toHaveBeenCalled(); - await act(async () => { - instance.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); - }); + // new copy gets a new SO id + expect(props.redirectTo).toHaveBeenCalledWith(doc.savedObjectId); + expect(services.attributeService.saveToLibrary).toHaveBeenCalledTimes(1); expect(services.notifications.toasts.addSuccess).toHaveBeenCalledWith( "Saved 'hello there'" ); }); - it('handles save failure by showing a warning, but still allows another save', async () => { - const mockedConsoleDir = jest.spyOn(console, 'dir'); // mocked console.dir to avoid messages in the console when running tests - mockedConsoleDir.mockImplementation(() => {}); - - const props = makeDefaultProps(); - - props.incomingState = { - originatingApp: 'ultraDashboard', - }; - - const services = makeDefaultServicesForApp(); - services.attributeService.wrapAttributes = jest - .fn() - .mockRejectedValue({ message: 'failed' }); - const { instance } = await mountWith({ - props, - services, - preloadedState: { - isSaveable: true, - isLinkedToOriginatingApp: true, - }, - }); - - await act(async () => { - testSave(instance, { newCopyOnSave: false, newTitle: 'hello there' }); - }); - expect(props.redirectTo).not.toHaveBeenCalled(); - expect(getButton(instance).disableButton).toEqual(false); - // eslint-disable-next-line no-console - expect(console.dir).toHaveBeenCalledTimes(1); - mockedConsoleDir.mockRestore(); - }); - - it('saves new doc and redirects to originating app', async () => { - const { props, services } = await save({ - initialSavedObjectId: undefined, - returnToOrigin: true, + it('saves existing docs', async () => { + await save({ + savedObjectId: defaultSavedObjectId, + prevSavedObjectId: defaultSavedObjectId, newCopyOnSave: false, newTitle: 'hello there', + comesFromDashboard: false, + preloadedState: { + persistedDoc: getLensDocumentMock({ savedObjectId: defaultSavedObjectId }), + }, }); - expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( + expect(services.attributeService.saveToLibrary).toHaveBeenCalledWith( expect.objectContaining({ - savedObjectId: undefined, title: 'hello there', }), - true, - undefined + [{ id: 'mockip', name: 'mockip', type: 'index-pattern' }], + defaultSavedObjectId + ); + expect(props.redirectTo).not.toHaveBeenCalled(); + expect(services.notifications.toasts.addSuccess).toHaveBeenCalledWith( + "Saved 'hello there'" ); - expect(props.redirectToOrigin).toHaveBeenCalledWith({ - input: { savedObjectId: 'aaa' }, - isCopied: false, - }); }); it('saves app filters and does not save pinned filters', async () => { @@ -881,229 +696,227 @@ describe('Lens App', () => { await act(async () => { FilterManager.setFiltersStore([pinned], FilterStateStore.GLOBAL_STATE); }); - const { services } = await save({ - initialSavedObjectId: defaultSavedObjectId, - newCopyOnSave: false, - newTitle: 'hello there2', + + await save({ + savedObjectId: defaultSavedObjectId, + prevSavedObjectId: defaultSavedObjectId, preloadedState: { - persistedDoc: defaultDoc, + isSaveable: true, + persistedDoc: getLensDocumentMock({ savedObjectId: defaultSavedObjectId }), + isLinkedToOriginatingApp: true, filters: [pinned, unpinned], }, }); const { state: expectedFilters } = services.data.query.filterManager.extract([unpinned]); - expect(services.attributeService.wrapAttributes).toHaveBeenCalledWith( + expect(services.attributeService.saveToLibrary).toHaveBeenCalledWith( expect.objectContaining({ - savedObjectId: defaultSavedObjectId, - title: 'hello there2', + title: 'hello there', state: expect.objectContaining({ filters: expectedFilters }), }), - true, - { id: '5678', savedObjectId: defaultSavedObjectId } + [{ id: 'mockip', name: 'mockip', type: 'index-pattern' }], + undefined ); }); it('checks for duplicate title before saving', async () => { - const props = makeDefaultProps(); - props.incomingState = { originatingApp: 'coolContainer' }; - const services = makeDefaultServicesForApp(); - services.attributeService.wrapAttributes = jest - .fn() - .mockReturnValue(Promise.resolve({ savedObjectId: '123' })); - const { instance } = await mountWith({ - props, - services, + await save({ + savedObjectId: defaultSavedObjectId, + prevSavedObjectId: defaultSavedObjectId, preloadedState: { isSaveable: true, - persistedDoc: { savedObjectId: '123' } as unknown as Document, + persistedDoc: { savedObjectId: defaultSavedObjectId } as unknown as LensDocument, isLinkedToOriginatingApp: true, }, }); - await act(async () => { - instance.setProps({ initialInput: { savedObjectId: '123' } }); - getButton(instance).run(instance.getDOMNode()); - }); - instance.update(); - const onTitleDuplicate = jest.fn(); - await act(async () => { - instance.find(SavedObjectSaveModal).prop('onSave')({ - onTitleDuplicate, - isTitleDuplicateConfirmed: false, - newCopyOnSave: false, - newDescription: '', - newTitle: 'test', - }); - }); + expect(checkForDuplicateTitle).toHaveBeenCalledWith( - expect.objectContaining({ id: '123', isTitleDuplicateConfirmed: false }), - onTitleDuplicate, + { + copyOnSave: true, + displayName: 'Lens visualization', + isTitleDuplicateConfirmed: false, + lastSavedTitle: '', + title: 'hello there', + }, + expect.any(Function), expect.anything() ); }); + it('saves new doc and redirects to originating app', async () => { + await save({ + savedObjectId: undefined, + newCopyOnSave: false, + newTitle: 'hello there', + }); + expect(services.attributeService.saveToLibrary).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'hello there', + }), + [{ id: 'mockip', name: 'mockip', type: 'index-pattern' }], + undefined + ); + expect(props.redirectToOrigin).toHaveBeenCalledWith({ + state: expect.objectContaining({ savedObjectId: defaultSavedObjectId }), + isCopied: false, + }); + }); + + it('handles save failure by showing a warning, but still allows another save', async () => { + const mockedConsoleDir = jest.spyOn(console, 'dir').mockImplementation(() => {}); // mocked console.dir to avoid messages in the console when running tests + + services.attributeService.saveToLibrary = jest + .fn() + .mockRejectedValue({ message: 'failed' }); + + props.incomingState = { + originatingApp: 'dashboards', + }; + + await renderApp({ + preloadedState: { + isSaveable: true, + isLinkedToOriginatingApp: true, + }, + }); + await clickSaveButton(); + await userEvent.type(screen.getByTestId('savedObjectTitle'), 'hello there'); + await userEvent.click(screen.getByTestId('confirmSaveSavedObjectButton')); + await waitToLoad(); + + expect(props.redirectTo).not.toHaveBeenCalled(); + expect(services.attributeService.saveToLibrary).toHaveBeenCalled(); + // eslint-disable-next-line no-console + expect(console.dir).toHaveBeenCalledTimes(1); + mockedConsoleDir.mockRestore(); + }); + it('does not show the copy button on first save', async () => { - const props = makeDefaultProps(); - props.incomingState = { originatingApp: 'coolContainer' }; - const { instance } = await mountWith({ - props, + props.incomingState = { + originatingApp: 'dashboards', + }; + + await renderApp({ preloadedState: { isSaveable: true, isLinkedToOriginatingApp: true }, }); - await act(async () => getButton(instance).run(instance.getDOMNode())); - instance.update(); - expect(instance.find(SavedObjectSaveModal).prop('showCopyOnSave')).toEqual(false); + await clickSaveButton(); + await waitForModalVisible(); + expect(screen.queryByTestId('saveAsNewCheckbox')).not.toBeInTheDocument(); }); it('enables Save Query UI when user has app-level permissions', async () => { - const services = makeDefaultServicesForApp(); - services.application = { - ...services.application, - capabilities: { - ...services.application.capabilities, - visualize: { saveQuery: true }, - }, + services.application.capabilities = { + ...services.application.capabilities, + visualize: { saveQuery: true }, }; - const { instance } = await mountWith({ services }); - await act(async () => { - const topNavMenu = instance.find(services.navigation.ui.AggregateQueryTopNavMenu); - expect(topNavMenu.props().saveQueryMenuVisibility).toBe('allowed_by_app_privilege'); - }); + + await renderApp(); + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenLastCalledWith( + expect.objectContaining({ saveQueryMenuVisibility: 'allowed_by_app_privilege' }), + {} + ); }); it('checks global save query permission when user does not have app-level permissions', async () => { - const services = makeDefaultServicesForApp(); - services.application = { - ...services.application, - capabilities: { - ...services.application.capabilities, - visualize: { saveQuery: false }, - }, + services.application.capabilities = { + ...services.application.capabilities, + visualize: { saveQuery: false }, }; - const { instance } = await mountWith({ services }); - await act(async () => { - const topNavMenu = instance.find(services.navigation.ui.AggregateQueryTopNavMenu); - expect(topNavMenu.props().saveQueryMenuVisibility).toBe('globally_managed'); - }); + await renderApp(); + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenLastCalledWith( + expect.objectContaining({ saveQueryMenuVisibility: 'globally_managed' }), + {} + ); }); }); }); describe('share button', () => { - function getShareButton(inst: ReactWrapper): TopNavMenuData { - return ( - inst.find('[data-test-subj="lnsApp_topNav"]').prop('config') as TopNavMenuData[] - ).find((button) => button.testId === 'lnsApp_shareButton')!; - } + const getShareButton = () => screen.getByTestId('lnsApp_shareButton'); it('should be disabled when no data is available', async () => { - const { instance } = await mountWith({ preloadedState: { isSaveable: true } }); - expect(getShareButton(instance).disableButton).toEqual(true); + await renderApp({ preloadedState: { isSaveable: true } }); + expect(getShareButton()).toBeDisabled(); }); it('should not disable share when not saveable', async () => { - const { instance } = await mountWith({ + await renderApp({ preloadedState: { isSaveable: false, activeData: { layer1: { type: 'datatable', columns: [], rows: [] } }, }, }); - expect(getShareButton(instance).disableButton).toEqual(false); + expect(getShareButton()).toBeEnabled(); }); it('should still be enabled even if the user is missing save permissions', async () => { - const services = makeDefaultServicesForApp(); - services.application = { - ...services.application, - capabilities: { - ...services.application.capabilities, - visualize: { save: false, saveQuery: false, show: true, createShortUrl: true }, - }, + services.application.capabilities = { + ...services.application.capabilities, + visualize: { save: false, saveQuery: false, show: true, createShortUrl: true }, }; - const { instance } = await mountWith({ - services, + await renderApp({ preloadedState: { isSaveable: true, activeData: { layer1: { type: 'datatable', columns: [], rows: [] } }, }, }); - expect(getShareButton(instance).disableButton).toEqual(false); + expect(getShareButton()).toBeEnabled(); }); it('should still be enabled even if the user is missing shortUrl permissions', async () => { - const services = makeDefaultServicesForApp(); - services.application = { - ...services.application, - capabilities: { - ...services.application.capabilities, - visualize: { save: true, saveQuery: false, show: true, createShortUrl: false }, - }, + services.application.capabilities = { + ...services.application.capabilities, + visualize: { save: true, saveQuery: false, show: true, createShortUrl: false }, }; - const { instance } = await mountWith({ - services, + await renderApp({ preloadedState: { isSaveable: true, activeData: { layer1: { type: 'datatable', columns: [], rows: [] } }, }, }); - expect(getShareButton(instance).disableButton).toEqual(false); + + expect(getShareButton()).toBeEnabled(); }); it('should be disabled if the user is missing shortUrl permissions and visualization is not saveable', async () => { - const services = makeDefaultServicesForApp(); - services.application = { - ...services.application, - capabilities: { - ...services.application.capabilities, - visualize: { save: false, saveQuery: false, show: true, createShortUrl: false }, - }, + services.application.capabilities = { + ...services.application.capabilities, + visualize: { save: false, saveQuery: false, show: true, createShortUrl: false }, }; - const { instance } = await mountWith({ - services, + await renderApp({ preloadedState: { isSaveable: false, activeData: { layer1: { type: 'datatable', columns: [], rows: [] } }, }, }); - expect(getShareButton(instance).disableButton).toEqual(true); + expect(getShareButton()).toBeDisabled(); }); }); describe('inspector', () => { - function getButton(inst: ReactWrapper): TopNavMenuData { - return ( - inst.find('[data-test-subj="lnsApp_topNav"]').prop('config') as TopNavMenuData[] - ).find((button) => button.testId === 'lnsApp_inspectButton')!; - } - - async function runInspect(inst: ReactWrapper) { - await getButton(inst).run(inst.getDOMNode()); - await inst.update(); - } - it('inspector button should be available', async () => { - const { instance } = await mountWith({ preloadedState: { isSaveable: true } }); - const button = getButton(instance); - - expect(button.disableButton).toEqual(false); + await renderApp({ + preloadedState: { isSaveable: true }, + }); + expect(screen.getByTestId('lnsApp_inspectButton')).toBeEnabled(); }); - it('should open inspect panel', async () => { - const services = makeDefaultServicesForApp(); - const { instance } = await mountWith({ services, preloadedState: { isSaveable: true } }); - - await runInspect(instance); - + await renderApp({ + preloadedState: { isSaveable: true }, + }); + await userEvent.click(screen.getByTestId('lnsApp_inspectButton')); expect(services.inspector.inspect).toHaveBeenCalledTimes(1); }); }); describe('query bar state management', () => { it('uses the default time and query language settings', async () => { - const { lensStore, services } = await mountWith({}); + const { lensStore } = await renderApp(); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: '', language: 'lucene' }, @@ -1125,18 +938,20 @@ describe('Lens App', () => { }); it('updates the editor frame when the user changes query or time in the search bar', async () => { - const { instance, services, lensStore } = await mountWith({}); + const { lensStore } = await renderApp(); (services.data.query.timefilter.timefilter.calculateBounds as jest.Mock).mockReturnValue({ min: moment('2021-01-09T04:00:00.000Z'), max: moment('2021-01-09T08:00:00.000Z'), }); + const onQuerySubmit = (services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock).mock + .calls[0][0].onQuerySubmit; await act(async () => - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ + onQuerySubmit({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) ); - instance.update(); + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: 'new', language: 'lucene' }, @@ -1162,7 +977,7 @@ describe('Lens App', () => { }); it('updates the filters when the user changes them', async () => { - const { instance, services, lensStore } = await mountWith({}); + const { lensStore } = await renderApp(); const indexPattern = { id: 'index1', isPersisted: () => true } as unknown as DataView; const field = { name: 'myfield' } as unknown as FieldSpec; expect(lensStore.getState()).toEqual({ @@ -1170,10 +985,9 @@ describe('Lens App', () => { filters: [], }), }); - act(() => - services.data.query.filterManager.setFilters([buildExistsFilter(field, indexPattern)]) - ); - instance.update(); + + services.data.query.filterManager.setFilters([buildExistsFilter(field, indexPattern)]); + expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ filters: [buildExistsFilter(field, indexPattern)], @@ -1182,7 +996,7 @@ describe('Lens App', () => { }); it('updates the searchSessionId when the user changes query or time in the search bar', async () => { - const { instance, services, lensStore } = await mountWith({}); + const { lensStore } = await renderApp(); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ @@ -1190,13 +1004,14 @@ describe('Lens App', () => { }), }); + const AggregateQueryTopNavMenu = services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock; + const onQuerySubmit = AggregateQueryTopNavMenu.mock.calls[0][0].onQuerySubmit; act(() => - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ + onQuerySubmit({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: '', language: 'lucene' }, }) ); - instance.update(); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ @@ -1205,12 +1020,12 @@ describe('Lens App', () => { }); // trigger again, this time changing just the query act(() => - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ + onQuerySubmit({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) ); - instance.update(); + expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ searchSessionId: `sessionId-3`, @@ -1221,7 +1036,7 @@ describe('Lens App', () => { act(() => services.data.query.filterManager.setFilters([buildExistsFilter(field, indexPattern)]) ); - instance.update(); + expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ searchSessionId: `sessionId-4`, @@ -1232,15 +1047,11 @@ describe('Lens App', () => { describe('saved query handling', () => { it('does not allow saving when the user is missing the saveQuery permission', async () => { - const services = makeDefaultServicesForApp(); - services.application = { - ...services.application, - capabilities: { - ...services.application.capabilities, - visualize: { save: false, saveQuery: false, show: true }, - }, + services.application.capabilities = { + ...services.application.capabilities, + visualize: { save: false, saveQuery: false, show: true }, }; - await mountWith({ services }); + await renderApp(); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ saveQueryMenuVisibility: 'globally_managed' }), {} @@ -1248,7 +1059,8 @@ describe('Lens App', () => { }); it('persists the saved query ID when the query is saved', async () => { - const { instance, services } = await mountWith({}); + await renderApp(); + expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ saveQueryMenuVisibility: 'allowed_by_app_privilege', @@ -1259,8 +1071,11 @@ describe('Lens App', () => { }), {} ); + + const onSaved = (services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock).mock + .calls[0][0].onSaved; act(() => { - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSaved')!({ + onSaved({ id: '1', attributes: { title: '', @@ -1287,9 +1102,12 @@ describe('Lens App', () => { }); it('changes the saved query ID when the query is updated', async () => { - const { instance, services } = await mountWith({}); + await renderApp(); + const { onSaved, onSavedQueryUpdated } = ( + services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock + ).mock.calls[0][0]; act(() => { - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSaved')!({ + onSaved({ id: '1', attributes: { title: '', @@ -1300,17 +1118,15 @@ describe('Lens App', () => { }); }); act(() => { - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSavedQueryUpdated')!( - { - id: '2', - attributes: { - title: 'new title', - description: '', - query: { query: '', language: 'lucene' }, - }, - namespaces: ['default'], - } - ); + onSavedQueryUpdated({ + id: '2', + attributes: { + title: 'new title', + description: '', + query: { query: '', language: 'lucene' }, + }, + namespaces: ['default'], + }); }); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ @@ -1329,19 +1145,19 @@ describe('Lens App', () => { }); it('updates the query if saved query is selected', async () => { - const { instance, services } = await mountWith({}); + await renderApp(); + const { onSavedQueryUpdated } = (services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock) + .mock.calls[0][0]; act(() => { - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSavedQueryUpdated')!( - { - id: '2', - attributes: { - title: 'new title', - description: '', - query: { query: 'abc:def', language: 'lucene' }, - }, - namespaces: ['default'], - } - ); + onSavedQueryUpdated({ + id: '2', + attributes: { + title: 'new title', + description: '', + query: { query: 'abc:def', language: 'lucene' }, + }, + namespaces: ['default'], + }); }); expect(services.navigation.ui.AggregateQueryTopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ @@ -1352,9 +1168,12 @@ describe('Lens App', () => { }); it('clears all existing unpinned filters when the active saved query is cleared', async () => { - const { instance, services, lensStore } = await mountWith({}); + const { lensStore } = await renderApp(); + const { onQuerySubmit, onClearSavedQuery } = ( + services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock + ).mock.calls[0][0]; act(() => - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ + onQuerySubmit({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) @@ -1366,11 +1185,7 @@ describe('Lens App', () => { const pinned = buildExistsFilter(pinnedField, indexPattern); FilterManager.setFiltersStore([pinned], FilterStateStore.GLOBAL_STATE); act(() => services.data.query.filterManager.setFilters([pinned, unpinned])); - instance.update(); - act(() => - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onClearSavedQuery')!() - ); - instance.update(); + act(() => onClearSavedQuery()); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ filters: [pinned], @@ -1381,9 +1196,12 @@ describe('Lens App', () => { describe('search session id management', () => { it('updates the searchSessionId when the query is updated', async () => { - const { instance, lensStore, services } = await mountWith({}); + const { lensStore } = await renderApp(); + const { onSaved, onSavedQueryUpdated } = ( + services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock + ).mock.calls[0][0]; act(() => { - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSaved')!({ + onSaved({ id: '1', attributes: { title: '', @@ -1394,19 +1212,16 @@ describe('Lens App', () => { }); }); act(() => { - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onSavedQueryUpdated')!( - { - id: '2', - attributes: { - title: 'new title', - description: '', - query: { query: '', language: 'lucene' }, - }, - namespaces: ['default'], - } - ); + onSavedQueryUpdated({ + id: '2', + attributes: { + title: 'new title', + description: '', + query: { query: '', language: 'lucene' }, + }, + namespaces: ['default'], + }); }); - instance.update(); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ searchSessionId: `sessionId-2`, @@ -1415,9 +1230,12 @@ describe('Lens App', () => { }); it('updates the searchSessionId when the active saved query is cleared', async () => { - const { instance, services, lensStore } = await mountWith({}); + const { lensStore } = await renderApp(); + const { onQuerySubmit, onClearSavedQuery } = ( + services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock + ).mock.calls[0][0]; act(() => - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ + onQuerySubmit({ dateRange: { from: 'now-14d', to: 'now-7d' }, query: { query: 'new', language: 'lucene' }, }) @@ -1429,11 +1247,7 @@ describe('Lens App', () => { const pinned = buildExistsFilter(pinnedField, indexPattern); FilterManager.setFiltersStore([pinned], FilterStateStore.GLOBAL_STATE); act(() => services.data.query.filterManager.setFilters([pinned, unpinned])); - instance.update(); - act(() => - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onClearSavedQuery')!() - ); - instance.update(); + act(() => onClearSavedQuery()); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ searchSessionId: `sessionId-4`, @@ -1442,14 +1256,14 @@ describe('Lens App', () => { }); it('dispatches update to searchSessionId and dateRange when the user hits refresh', async () => { - const { instance, services, lensStore } = await mountWith({}); + const { lensStore } = await renderApp(); + const { onQuerySubmit } = (services.navigation.ui.AggregateQueryTopNavMenu as jest.Mock).mock + .calls[0][0]; act(() => - instance.find(services.navigation.ui.AggregateQueryTopNavMenu).prop('onQuerySubmit')!({ + onQuerySubmit({ dateRange: { from: 'now-7d', to: 'now' }, }) ); - - instance.update(); expect(lensStore.dispatch).toHaveBeenCalledWith({ type: 'lens/setState', payload: { @@ -1464,15 +1278,12 @@ describe('Lens App', () => { it('updates the state if session id changes from the outside', async () => { const sessionIdS = new Subject<string>(); - const services = makeDefaultServices(sessionIdS, 'sessionId-1'); - const { lensStore } = await mountWith({ props: undefined, services }); + services = makeDefaultServices(sessionIdS, 'sessionId-1'); + const { lensStore } = await renderApp(); - act(() => { - sessionIdS.next('new-session-id'); - }); - await act(async () => { - await new Promise((r) => setTimeout(r, 0)); - }); + act(() => sessionIdS.next('new-session-id')); + + await waitToLoad(); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ searchSessionId: `new-session-id`, @@ -1481,7 +1292,7 @@ describe('Lens App', () => { }); it('does not update the searchSessionId when the state changes', async () => { - const { lensStore } = await mountWith({ preloadedState: { isSaveable: true } }); + const { lensStore } = await renderApp({ preloadedState: { isSaveable: true } }); expect(lensStore.getState()).toEqual({ lens: expect.objectContaining({ searchSessionId: `sessionId-1`, @@ -1491,40 +1302,37 @@ describe('Lens App', () => { }); describe('showing a confirm message when leaving', () => { - let defaultLeave: jest.Mock; - let confirmLeave: jest.Mock; + const defaultLeave = jest.fn(); + const confirmLeave = jest.fn(); beforeEach(() => { - defaultLeave = jest.fn(); - confirmLeave = jest.fn(); + jest.clearAllMocks(); }); it('should not show a confirm message if there is no expression to save', async () => { - const { props } = await mountWith({}); - const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; + await renderApp(); + const lastCall = (props.onAppLeave as jest.Mock).mock.lastCall![0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); it('does not confirm if the user is missing save permissions', async () => { - const services = makeDefaultServicesForApp(); - services.application = { - ...services.application, - capabilities: { - ...services.application.capabilities, - visualize: { save: false, saveQuery: false, show: true }, - }, + services.application.capabilities = { + ...services.application.capabilities, + visualize: { save: false, saveQuery: false, show: true }, }; - const { props } = await mountWith({ services, preloadedState: { isSaveable: true } }); - const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; + await renderApp({ + preloadedState: { isSaveable: true }, + }); + const lastCall = (props.onAppLeave as jest.Mock).mock.lastCall![0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); it('should confirm when leaving with an unsaved doc', async () => { - const { props } = await mountWith({ + await renderApp({ preloadedState: { visualization: { activeId: 'testVis', @@ -1533,16 +1341,18 @@ describe('Lens App', () => { isSaveable: true, }, }); - const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; + const lastCall = (props.onAppLeave as jest.Mock).mock.calls[ + (props.onAppLeave as jest.Mock).mock.calls.length - 1 + ][0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); it('should confirm when leaving with unsaved changes to an existing doc', async () => { - const { props } = await mountWith({ + await renderApp({ preloadedState: { - persistedDoc: defaultDoc, + persistedDoc: getLensDocumentMock(), visualization: { activeId: 'testVis', state: {}, @@ -1550,73 +1360,45 @@ describe('Lens App', () => { isSaveable: true, }, }); - const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; + const lastCall = (props.onAppLeave as jest.Mock).mock.lastCall![0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); it('should confirm when leaving from a context initial doc with changes made in lens', async () => { - const initialProps = { - ...makeDefaultProps(), - contextOriginatingApp: 'TSVB', - initialContext: { - layers: [ - { - indexPatternId: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', - xFieldName: 'order_date', - xMode: 'date_histogram', - chartType: 'area', - axisPosition: 'left', - palette: { - type: 'palette', - name: 'default', - }, - metrics: [ - { - agg: 'count', - isFullReference: false, - fieldName: 'document', - params: {}, - color: '#68BC00', - }, - ], - timeInterval: 'auto', - }, - ], - type: 'lnsXY', - configuration: { - fill: 0.5, - legend: { - isVisible: true, - position: 'right', - shouldTruncate: true, - maxLines: 1, - }, - gridLinesVisibility: { - x: true, - yLeft: true, - yRight: true, + props.contextOriginatingApp = 'TSVB'; + props.initialContext = { + layers: [ + { + indexPatternId: 'indexPatternId', + chartType: 'area', + axisPosition: 'left', + palette: { + type: 'palette', + name: 'default', }, - extents: { - yLeftExtent: { - mode: 'full', - }, - yRightExtent: { - mode: 'full', + metrics: [ + { + agg: 'count', + isFullReference: false, + fieldName: 'document', + params: {}, + color: '#68BC00', }, - }, + ], + timeInterval: 'auto', }, - savedObjectId: '', - vizEditorOriginatingAppUrl: '#/tsvb-link', - isVisualizeAction: true, - }, - }; + ], + type: 'lnsXY', + savedObjectId: '', + vizEditorOriginatingAppUrl: '#/tsvb-link', + isVisualizeAction: true, + } as unknown as VisualizeEditorContext; - const mountedApp = await mountWith({ - props: initialProps as unknown as jest.Mocked<LensAppProps>, + await renderApp({ preloadedState: { - persistedDoc: defaultDoc, + persistedDoc: getLensDocumentMock(), visualization: { activeId: 'testVis', state: {}, @@ -1624,76 +1406,72 @@ describe('Lens App', () => { isSaveable: true, }, }); - const lastCall = - mountedApp.props.onAppLeave.mock.calls[ - mountedApp.props.onAppLeave.mock.calls.length - 1 - ][0]; + const lastCall = (props.onAppLeave as jest.Mock).mock.lastCall![0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); expect(defaultLeave).not.toHaveBeenCalled(); expect(confirmLeave).toHaveBeenCalled(); }); it('should not confirm when changes are saved', async () => { + const localDoc = getLensDocumentMock(); const preloadedState = { persistedDoc: { - ...defaultDoc, + ...localDoc, state: { - ...defaultDoc.state, - datasourceStates: { testDatasource: {} }, + ...localDoc.state, + datasourceStates: { + testDatasource: 'datasource', + }, visualization: {}, }, }, isSaveable: true, - ...(defaultDoc.state as Partial<LensAppState>), + ...(localDoc.state as Partial<LensAppState>), visualization: { activeId: 'testVis', state: {}, }, }; - const customProps = makeDefaultProps(); - customProps.datasourceMap.testDatasource.isEqual = () => true; // if this returns false, the documents won't be accounted equal + props.datasourceMap.testDatasource.isEqual = jest.fn().mockReturnValue(true); // if this returns false, the documents won't be accounted equal - const { props } = await mountWith({ preloadedState, props: customProps }); + await renderApp({ preloadedState }); - const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; - lastCall({ default: defaultLeave, confirm: confirmLeave }); + const lastCallArg = props.onAppLeave.mock.lastCall![0]; + lastCallArg?.({ default: defaultLeave, confirm: confirmLeave }); expect(defaultLeave).toHaveBeenCalled(); expect(confirmLeave).not.toHaveBeenCalled(); }); - // not sure how to test it it('should confirm when the latest doc is invalid', async () => { - const { lensStore, props } = await mountWith({}); - act(() => { - lensStore.dispatch( + const { lensStore } = await renderApp(); + await act(async () => { + await lensStore.dispatch( setState({ - persistedDoc: defaultDoc, + persistedDoc: getLensDocumentMock(), isSaveable: true, }) ); }); - const lastCall = props.onAppLeave.mock.calls[props.onAppLeave.mock.calls.length - 1][0]; + const lastCall = (props.onAppLeave as jest.Mock).mock.lastCall![0]; lastCall({ default: defaultLeave, confirm: confirmLeave }); expect(confirmLeave).toHaveBeenCalled(); expect(defaultLeave).not.toHaveBeenCalled(); }); }); + it('should display a conflict callout if saved object conflicts', async () => { const history = createMemoryHistory(); - const { services } = await mountWith({ - props: { - ...makeDefaultProps(), - history: { - ...history, - location: { - ...history.location, - search: '?_g=test', - }, - }, + props.history = { + ...history, + location: { + ...history.location, + search: '?_g=test', }, + }; + await renderApp({ preloadedState: { - persistedDoc: defaultDoc, + persistedDoc: getLensDocumentMock({ savedObjectId: defaultSavedObjectId }), sharingSavedObjectProps: { outcome: 'conflict', aliasTargetId: '2', @@ -1701,7 +1479,7 @@ describe('Lens App', () => { }, }); expect(services.spaces?.ui.components.getLegacyUrlConflict).toHaveBeenCalledWith({ - currentObjectId: '1234', + currentObjectId: defaultSavedObjectId, objectNoun: 'Lens visualization', otherObjectId: '2', otherObjectPath: '#/edit/2?_g=test', diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 60e1b0dfdb66..b8903bde1af0 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -9,16 +9,14 @@ import './app.scss'; import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { i18n } from '@kbn/i18n'; import type { TimeRange } from '@kbn/es-query'; -import { EuiBreadcrumb, EuiConfirmModal } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { useExecutionContext, useKibana } from '@kbn/kibana-react-plugin/public'; import { OnSaveProps } from '@kbn/saved-objects-plugin/public'; import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; -import type { LensAppLocatorParams } from '../../common/locator/locator'; import { LensAppProps, LensAppServices } from './types'; import { LensTopNavMenu } from './lens_top_nav'; -import { LensByReferenceInput } from '../embeddable'; -import { AddUserMessages, EditorFrameInstance, UserMessagesGetter } from '../types'; -import { Document } from '../persistence/saved_object_store'; +import { AddUserMessages, EditorFrameInstance, Simplify, UserMessagesGetter } from '../types'; +import { LensDocument } from '../persistence/saved_object_store'; import { setState, @@ -43,15 +41,24 @@ import { import { replaceIndexpattern } from '../state_management/lens_slice'; import { useApplicationUserMessages } from './get_application_user_messages'; import { trackSaveUiCounterEvents } from '../lens_ui_telemetry'; - -export type SaveProps = Omit<OnSaveProps, 'onTitleDuplicate' | 'newDescription'> & { - returnToOrigin: boolean; - dashboardId?: string | null; - onTitleDuplicate?: OnSaveProps['onTitleDuplicate']; - newDescription?: string; - newTags?: string[]; - panelTimeRange?: TimeRange; -}; +import { + getCurrentTitle, + isLegacyEditorEmbeddable, + setBreadcrumbsTitle, + useNavigateBackToApp, + useShortUrlService, +} from './app_helpers'; + +export type SaveProps = Simplify< + Omit<OnSaveProps, 'onTitleDuplicate' | 'newDescription'> & { + returnToOrigin: boolean; + dashboardId?: string | null; + onTitleDuplicate?: OnSaveProps['onTitleDuplicate']; + newDescription?: string; + newTags?: string[]; + panelTimeRange?: TimeRange; + } +>; export function App({ history, @@ -127,18 +134,26 @@ export function App({ selectSavedObjectFormat(state, selectorDependencies) ); - const shortUrls = useMemo(() => share?.url.shortUrls.get(null), [share]); - // Used to show a popover that guides the user towards changing the date range when no data is available. const [indicateNoData, setIndicateNoData] = useState(false); const [isSaveModalVisible, setIsSaveModalVisible] = useState(false); - const [lastKnownDoc, setLastKnownDoc] = useState<Document | undefined>(undefined); - const [initialDocFromContext, setInitialDocFromContext] = useState<Document | undefined>( + const [lastKnownDoc, setLastKnownDoc] = useState<LensDocument | undefined>(undefined); + const [initialDocFromContext, setInitialDocFromContext] = useState<LensDocument | undefined>( undefined ); - const [isGoBackToVizEditorModalVisible, setIsGoBackToVizEditorModalVisible] = useState(false); const [shouldCloseAndSaveTextBasedQuery, setShouldCloseAndSaveTextBasedQuery] = useState(false); - const savedObjectId = (initialInput as LensByReferenceInput)?.savedObjectId; + const savedObjectId = initialInput?.savedObjectId; + + const isFromLegacyEditorEmbeddable = isLegacyEditorEmbeddable(initialContext); + const legacyEditorAppName = + initialContext && 'originatingApp' in initialContext + ? initialContext.originatingApp + : undefined; + const legacyEditorAppUrl = + initialContext && 'vizEditorOriginatingAppUrl' in initialContext + ? initialContext.vizEditorOriginatingAppUrl + : undefined; + const initialContextIsEmbedded = Boolean(legacyEditorAppName); useEffect(() => { if (currentDoc) { @@ -167,18 +182,27 @@ export function App({ [isLinkedToOriginatingApp, savedObjectId] ); + // Wrap the isEqual call to avoid to carry all the static references + // around all the time. + const isLensEqualWrapper = useCallback( + (refDoc: LensDocument | undefined) => { + return isLensEqual( + refDoc, + lastKnownDoc, + data.query.filterManager.inject.bind(data.query.filterManager), + datasourceMap, + visualizationMap, + annotationGroups + ); + }, + [annotationGroups, data.query.filterManager, datasourceMap, lastKnownDoc, visualizationMap] + ); + useEffect(() => { onAppLeave((actions) => { if ( application.capabilities.visualize.save && - !isLensEqual( - persistedDoc, - lastKnownDoc, - data.query.filterManager.inject.bind(data.query.filterManager), - datasourceMap, - visualizationMap, - annotationGroups - ) && + !isLensEqualWrapper(persistedDoc) && (isSaveable || persistedDoc) ) { return actions.confirm( @@ -208,6 +232,7 @@ export function App({ datasourceMap, visualizationMap, annotationGroups, + isLensEqualWrapper, ]); const getLegacyUrlConflictCallout = useCallback(() => { @@ -235,66 +260,17 @@ export function App({ // Sync Kibana breadcrumbs any time the saved document's title changes useEffect(() => { const isByValueMode = getIsByValueMode(); - const comesFromVizEditorDashboard = - initialContext && 'originatingApp' in initialContext && initialContext.originatingApp; - const breadcrumbs: EuiBreadcrumb[] = []; - if ( - (isLinkedToOriginatingApp || comesFromVizEditorDashboard) && - getOriginatingAppName() && - redirectToOrigin - ) { - breadcrumbs.push({ - onClick: () => { - redirectToOrigin(); - }, - text: getOriginatingAppName(), - }); - } - if (!isByValueMode) { - breadcrumbs.push({ - href: application.getUrlForApp('visualize'), - onClick: (e) => { - application.navigateToApp('visualize', { path: '/' }); - e.preventDefault(); - }, - text: i18n.translate('xpack.lens.breadcrumbsTitle', { - defaultMessage: 'Visualize Library', - }), - }); - } - let currentDocTitle = i18n.translate('xpack.lens.breadcrumbsCreate', { - defaultMessage: 'Create', - }); - if (persistedDoc) { - currentDocTitle = isByValueMode - ? i18n.translate('xpack.lens.breadcrumbsByValue', { defaultMessage: 'Edit visualization' }) - : persistedDoc.title; - } - if ( - !persistedDoc?.title && - initialContext && - 'isEmbeddable' in initialContext && - initialContext.isEmbeddable - ) { - currentDocTitle = i18n.translate('xpack.lens.breadcrumbsEditInLensFromDashboard', { - defaultMessage: 'Converting {title} visualization', - values: { - title: initialContext.title ? `"${initialContext.title}"` : initialContext.visTypeTitle, - }, - }); - } - - const currentDocBreadcrumb: EuiBreadcrumb = { text: currentDocTitle }; - breadcrumbs.push(currentDocBreadcrumb); - if (serverless?.setBreadcrumbs) { - // TODO: https://github.com/elastic/kibana/issues/163488 - // for now, serverless breadcrumbs only set the title, - // the rest of the breadcrumbs are handled by the serverless navigation - // the serverless navigation is not yet aware of the byValue/originatingApp context - serverless.setBreadcrumbs(currentDocBreadcrumb); - } else { - chrome.setBreadcrumbs(breadcrumbs); - } + const currentDocTitle = getCurrentTitle(persistedDoc, isByValueMode, initialContext); + setBreadcrumbsTitle( + { application, chrome, serverless }, + { + isByValueMode, + currentDocTitle, + redirectToOrigin, + isFromLegacyEditor: Boolean(isLinkedToOriginatingApp || legacyEditorAppName), + originatingAppName: getOriginatingAppName(), + } + ); }, [ getOriginatingAppName, redirectToOrigin, @@ -303,8 +279,10 @@ export function App({ chrome, isLinkedToOriginatingApp, persistedDoc, - initialContext, + isFromLegacyEditorEmbeddable, + legacyEditorAppName, serverless, + initialContext, ]); const switchDatasource = useCallback(() => { @@ -314,12 +292,13 @@ export function App({ }, []); const runSave = useCallback( - (saveProps: SaveProps, options: { saveToLibrary: boolean }) => { + async (saveProps: SaveProps, options: { saveToLibrary: boolean }) => { dispatch(applyChanges()); const prevVisState = persistedDoc?.visualizationType === visualization.activeId ? persistedDoc?.state.visualization : undefined; + const telemetryEvents = activeVisualization?.getTelemetryEventsOnSave?.( visualization.state, prevVisState @@ -327,36 +306,33 @@ export function App({ if (telemetryEvents && telemetryEvents.length) { trackSaveUiCounterEvents(telemetryEvents); } - return runSaveLensVisualization( - { - lastKnownDoc, - getIsByValueMode, - savedObjectsTagging, - initialInput, - redirectToOrigin, - persistedDoc, - onAppLeave, - redirectTo, - switchDatasource, - originatingApp: incomingState?.originatingApp, - textBasedLanguageSave: shouldCloseAndSaveTextBasedQuery, - ...lensAppServices, - }, - saveProps, - options - ).then( - (newState) => { - if (newState) { - dispatchSetState(newState); - setIsSaveModalVisible(false); - setShouldCloseAndSaveTextBasedQuery(false); - } - }, - () => { - // error is handled inside the modal - // so ignoring it here + try { + const newState = await runSaveLensVisualization( + { + lastKnownDoc, + savedObjectsTagging, + initialInput, + redirectToOrigin, + persistedDoc, + onAppLeave, + redirectTo, + switchDatasource, + originatingApp: incomingState?.originatingApp, + textBasedLanguageSave: shouldCloseAndSaveTextBasedQuery, + ...lensAppServices, + }, + saveProps, + options + ); + if (newState) { + dispatchSetState(newState); + setIsSaveModalVisible(false); + setShouldCloseAndSaveTextBasedQuery(false); } - ); + } catch (e) { + // error is handled inside the modal + // so ignoring it here + } }, [ visualization.activeId, @@ -364,7 +340,6 @@ export function App({ activeVisualization, dispatch, lastKnownDoc, - getIsByValueMode, savedObjectsTagging, initialInput, redirectToOrigin, @@ -386,67 +361,20 @@ export function App({ } }, [lastKnownDoc, initialDocFromContext]); - // if users comes to Lens from the Viz editor, they should have the option to navigate back - const goBackToOriginatingApp = useCallback(() => { - if ( - initialContext && - 'vizEditorOriginatingAppUrl' in initialContext && - initialContext.vizEditorOriginatingAppUrl - ) { - const [initialDocFromContextUnchanged, currentDocHasBeenSavedInLens] = [ - initialDocFromContext, - persistedDoc, - ].map((refDoc) => - isLensEqual( - refDoc, - lastKnownDoc, - data.query.filterManager.inject, - datasourceMap, - visualizationMap, - annotationGroups - ) - ); - if (initialDocFromContextUnchanged || currentDocHasBeenSavedInLens) { - onAppLeave((actions) => { - return actions.default(); - }); - application.navigateToApp('visualize', { path: initialContext.vizEditorOriginatingAppUrl }); - } else { - setIsGoBackToVizEditorModalVisible(true); - } - } - }, [ - annotationGroups, + const { + shouldShowGoBackToVizEditorModal, + goBackToOriginatingApp, + navigateToVizEditor, + closeGoBackToVizEditorModal, + } = useNavigateBackToApp({ application, - data.query.filterManager.inject, - datasourceMap, - initialContext, - initialDocFromContext, - lastKnownDoc, onAppLeave, + legacyEditorAppName, + legacyEditorAppUrl, + initialDocFromContext, persistedDoc, - visualizationMap, - ]); - - const navigateToVizEditor = useCallback(() => { - setIsGoBackToVizEditorModalVisible(false); - if ( - initialContext && - 'vizEditorOriginatingAppUrl' in initialContext && - initialContext.vizEditorOriginatingAppUrl - ) { - onAppLeave((actions) => { - return actions.default(); - }); - application.navigateToApp('visualize', { path: initialContext.vizEditorOriginatingAppUrl }); - } - }, [application, initialContext, onAppLeave]); - - const initialContextIsEmbedded = useMemo(() => { - return Boolean( - initialContext && 'originatingApp' in initialContext && initialContext.originatingApp - ); - }, [initialContext]); + isLensEqual: isLensEqualWrapper, + }); const indexPatternService = useMemo( () => @@ -471,35 +399,12 @@ export function App({ [dataViews, uiActions, http, notifications, uiSettings, initialContext, dispatch] ); - // remember latest URL based on the configuration - // url_panel_content has a similar logic - const shareURLCache = useRef({ params: '', url: '' }); - - const shortUrlService = useCallback( - async (params: LensAppLocatorParams) => { - const cacheKey = JSON.stringify(params); - if (shareURLCache.current.params === cacheKey) { - return shareURLCache.current.url; - } - if (locator && shortUrls) { - // This is a stripped down version of what the share URL plugin is doing - const shortUrl = await shortUrls.createWithLocator({ locator, params }); - const absoluteShortUrl = await shortUrl.locator.getUrl(shortUrl.params, { absolute: true }); - shareURLCache.current = { params: cacheKey, url: absoluteShortUrl }; - return absoluteShortUrl; - } - return ''; - }, - [locator, shortUrls] - ); + const shortUrlService = useShortUrlService(locator, share); const isManaged = useLensSelector(selectIsManaged); const returnToOriginSwitchLabelForContext = - initialContext && - 'isEmbeddable' in initialContext && - initialContext.isEmbeddable && - !persistedDoc + isFromLegacyEditorEmbeddable && !persistedDoc ? i18n.translate('xpack.lens.app.replacePanel', { defaultMessage: 'Replace panel on {originatingApp}', values: { @@ -547,16 +452,7 @@ export function App({ title={persistedDoc?.title} lensInspector={lensInspector} currentDoc={currentDoc} - isCurrentStateDirty={ - !isLensEqual( - persistedDoc, - lastKnownDoc, - data.query.filterManager.inject.bind(data.query.filterManager), - datasourceMap, - visualizationMap, - annotationGroups - ) - } + isCurrentStateDirty={!isLensEqualWrapper(persistedDoc)} goBackToOriginatingApp={goBackToOriginatingApp} contextOriginatingApp={contextOriginatingApp} initialContextIsEmbedded={initialContextIsEmbedded} @@ -612,13 +508,13 @@ export function App({ } /> )} - {isGoBackToVizEditorModalVisible && ( + {shouldShowGoBackToVizEditorModal && ( <EuiConfirmModal maxWidth={600} title={i18n.translate('xpack.lens.app.unsavedWorkTitle', { defaultMessage: 'Unsaved changes', })} - onCancel={() => setIsGoBackToVizEditorModalVisible(false)} + onCancel={closeGoBackToVizEditorModal} onConfirm={navigateToVizEditor} cancelButtonText={i18n.translate('xpack.lens.app.goBackModalCancelBtn', { defaultMessage: 'Cancel', diff --git a/x-pack/plugins/lens/public/app_plugin/app_helpers.test.ts b/x-pack/plugins/lens/public/app_plugin/app_helpers.test.ts new file mode 100644 index 000000000000..7dc4e8cfda78 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/app_helpers.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import faker from 'faker'; +import { UseNavigateBackToAppProps, useNavigateBackToApp } from './app_helpers'; +import { defaultDoc, makeDefaultServices } from '../mocks/services_mock'; +import { cloneDeep } from 'lodash'; +import { LensDocument } from '../persistence'; + +function getLensDocumentMock(someProps?: Partial<LensDocument>) { + return cloneDeep({ ...defaultDoc, ...someProps }); +} + +const getApplicationMock = () => makeDefaultServices().application; + +describe('App helpers', () => { + function getDefaultProps( + someProps?: Partial<UseNavigateBackToAppProps> + ): UseNavigateBackToAppProps { + return { + application: getApplicationMock(), + onAppLeave: jest.fn(), + legacyEditorAppName: faker.lorem.word(), + legacyEditorAppUrl: faker.internet.url(), + isLensEqual: jest.fn(() => true), + initialDocFromContext: undefined, + persistedDoc: getLensDocumentMock(), + ...someProps, + }; + } + describe('useNavigateBackToApp', () => { + it('navigates back to originating app if documents has not changed', () => { + const props = getDefaultProps(); + const { result } = renderHook(() => useNavigateBackToApp(props)); + + act(() => { + result.current.goBackToOriginatingApp(); + }); + + expect(props.application.navigateToApp).toHaveBeenCalledWith(props.legacyEditorAppName, { + path: props.legacyEditorAppUrl, + }); + }); + + it('shows modal if documents are not equal', () => { + const props = getDefaultProps({ isLensEqual: jest.fn().mockReturnValue(false) }); + const { result } = renderHook(() => useNavigateBackToApp(props)); + + act(() => { + result.current.goBackToOriginatingApp(); + }); + + expect(props.application.navigateToApp).not.toHaveBeenCalled(); + expect(result.current.shouldShowGoBackToVizEditorModal).toBe(true); + }); + + it('navigateToVizEditor hides modal and navigates back to Viz editor', () => { + const props = getDefaultProps(); + const { result } = renderHook(() => useNavigateBackToApp(props)); + + act(() => { + result.current.navigateToVizEditor(); + }); + + expect(result.current.shouldShowGoBackToVizEditorModal).toBe(false); + expect(props.application.navigateToApp).toHaveBeenCalledWith(props.legacyEditorAppName, { + path: props.legacyEditorAppUrl, + }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/app_plugin/app_helpers.ts b/x-pack/plugins/lens/public/app_plugin/app_helpers.ts new file mode 100644 index 000000000000..4e240ac17159 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/app_helpers.ts @@ -0,0 +1,207 @@ +/* + * 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 { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; +import { EuiBreadcrumb } from '@elastic/eui'; +import { AppLeaveHandler, ApplicationStart } from '@kbn/core-application-browser'; +import { ChromeStart } from '@kbn/core-chrome-browser'; +import { ServerlessPluginStart } from '@kbn/serverless/public'; +import { useRef, useCallback, useMemo, useState } from 'react'; +import { SharePublicStart } from '@kbn/share-plugin/public/plugin'; +import { LensAppLocator, LensAppLocatorParams } from '../../common/locator/locator'; +import { VisualizeEditorContext } from '../types'; +import { LensDocument } from '../persistence'; +import { RedirectToOriginProps } from './types'; + +const VISUALIZE_APP_ID = 'visualize'; + +export function isLegacyEditorEmbeddable( + initialContext: VisualizeEditorContext | VisualizeFieldContext | undefined +): initialContext is VisualizeEditorContext { + return Boolean(initialContext && 'isEmbeddable' in initialContext && initialContext.isEmbeddable); +} + +export function getCurrentTitle( + persistedDoc: LensDocument | undefined, + isByValueMode: boolean, + initialContext: VisualizeEditorContext | VisualizeFieldContext | undefined +) { + if (persistedDoc) { + if (isByValueMode) { + return i18n.translate('xpack.lens.breadcrumbsByValue', { + defaultMessage: 'Edit visualization', + }); + } + if (persistedDoc.title) { + return persistedDoc.title; + } + } + if (!persistedDoc?.title && isLegacyEditorEmbeddable(initialContext)) { + return i18n.translate('xpack.lens.breadcrumbsEditInLensFromDashboard', { + defaultMessage: 'Converting {title} visualization', + values: { + title: initialContext.title ? `"${initialContext.title}"` : initialContext.visTypeTitle, + }, + }); + } + return i18n.translate('xpack.lens.breadcrumbsCreate', { + defaultMessage: 'Create', + }); +} + +export function setBreadcrumbsTitle( + { + application, + serverless, + chrome, + }: { + application: ApplicationStart; + serverless: ServerlessPluginStart | undefined; + chrome: ChromeStart; + }, + { + isByValueMode, + originatingAppName, + redirectToOrigin, + isFromLegacyEditor, + currentDocTitle, + }: { + isByValueMode: boolean; + originatingAppName: string | undefined; + redirectToOrigin: ((props?: RedirectToOriginProps | undefined) => void) | undefined; + isFromLegacyEditor: boolean; + currentDocTitle: string; + } +) { + const breadcrumbs: EuiBreadcrumb[] = []; + if (isFromLegacyEditor && originatingAppName && redirectToOrigin) { + breadcrumbs.push({ + onClick: () => { + redirectToOrigin(); + }, + text: originatingAppName, + }); + } + if (!isByValueMode) { + breadcrumbs.push({ + href: application.getUrlForApp(VISUALIZE_APP_ID), + onClick: (e) => { + application.navigateToApp(VISUALIZE_APP_ID, { path: '/' }); + e.preventDefault(); + }, + text: i18n.translate('xpack.lens.breadcrumbsTitle', { + defaultMessage: 'Visualize Library', + }), + }); + } + + const currentDocBreadcrumb: EuiBreadcrumb = { text: currentDocTitle }; + breadcrumbs.push(currentDocBreadcrumb); + if (serverless?.setBreadcrumbs) { + // TODO: https://github.com/elastic/kibana/issues/163488 + // for now, serverless breadcrumbs only set the title, + // the rest of the breadcrumbs are handled by the serverless navigation + // the serverless navigation is not yet aware of the byValue/originatingApp context + serverless.setBreadcrumbs(currentDocBreadcrumb); + } else { + chrome.setBreadcrumbs(breadcrumbs); + } +} + +export function useShortUrlService( + locator: LensAppLocator | undefined, + share: SharePublicStart | undefined +) { + const shortUrls = useMemo(() => share?.url.shortUrls.get(null), [share]); + // remember latest URL based on the configuration + // url_panel_content has a similar logic + const shareURLCache = useRef({ params: '', url: '' }); + + return useCallback( + async (params: LensAppLocatorParams) => { + const cacheKey = JSON.stringify(params); + if (shareURLCache.current.params === cacheKey) { + return shareURLCache.current.url; + } + if (locator && shortUrls) { + // This is a stripped down version of what the share URL plugin is doing + const shortUrl = await shortUrls.createWithLocator({ locator, params }); + const absoluteShortUrl = await shortUrl.locator.getUrl(shortUrl.params, { absolute: true }); + shareURLCache.current = { params: cacheKey, url: absoluteShortUrl }; + return absoluteShortUrl; + } + return ''; + }, + [locator, shortUrls] + ); +} + +export interface UseNavigateBackToAppProps { + application: ApplicationStart; + onAppLeave: (handler: AppLeaveHandler) => void; + legacyEditorAppName: string | undefined; + legacyEditorAppUrl: string | undefined; + initialDocFromContext: LensDocument | undefined; + persistedDoc: LensDocument | undefined; + isLensEqual: (refDoc: LensDocument | undefined) => boolean; +} + +export function useNavigateBackToApp({ + application, + onAppLeave, + legacyEditorAppName, + legacyEditorAppUrl, + initialDocFromContext, + persistedDoc, + isLensEqual, +}: UseNavigateBackToAppProps) { + const [shouldShowGoBackToVizEditorModal, setIsGoBackToVizEditorModalVisible] = useState(false); + /** Shared logic to navigate back to the originating viz editor app */ + const navigateBackToVizEditor = useCallback(() => { + if (legacyEditorAppUrl) { + onAppLeave((actions) => { + return actions.default(); + }); + application.navigateToApp(legacyEditorAppName || VISUALIZE_APP_ID, { + path: legacyEditorAppUrl, + }); + } + }, [application, legacyEditorAppName, legacyEditorAppUrl, onAppLeave]); + + // if users comes to Lens from the Viz editor, they should have the option to navigate back + // used for TopNavMenu + const goBackToOriginatingApp = useCallback(() => { + if (legacyEditorAppUrl) { + if ([initialDocFromContext, persistedDoc].some(isLensEqual)) { + navigateBackToVizEditor(); + } else { + setIsGoBackToVizEditorModalVisible(true); + } + } + }, [ + legacyEditorAppUrl, + initialDocFromContext, + persistedDoc, + isLensEqual, + navigateBackToVizEditor, + setIsGoBackToVizEditorModalVisible, + ]); + + // Used for Saving Modal + const navigateToVizEditor = useCallback(() => { + setIsGoBackToVizEditorModalVisible(false); + navigateBackToVizEditor(); + }, [navigateBackToVizEditor, setIsGoBackToVizEditorModalVisible]); + + return { + shouldShowGoBackToVizEditorModal, + goBackToOriginatingApp, + navigateToVizEditor, + closeGoBackToVizEditorModal: () => setIsGoBackToVizEditorModalVisible(false), + }; +} diff --git a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx index 1afa1974de35..a470cf41cf83 100644 --- a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx @@ -15,9 +15,12 @@ import { UserMessageGetterProps, filterAndSortUserMessages, getApplicationUserMessages, + handleMessageOverwriteFromConsumer, } from './get_application_user_messages'; import { cleanup, render, screen } from '@testing-library/react'; import { I18nProvider } from '@kbn/i18n-react'; +import { FIELD_NOT_FOUND, FIELD_WRONG_TYPE } from '../user_messages_ids'; +import { LensPublicCallbacks } from '../react_embeddable/types'; import { getLongMessage } from '../user_messages_utils'; jest.mock('@kbn/shared-ux-link-redirect-app', () => { @@ -388,4 +391,100 @@ describe('filtering user messages', () => { ] `); }); + + describe('override messages with custom callback', () => { + it('should override embeddableBadge message', async () => { + const getBadgeMessage = jest.fn( + (): ReturnType<NonNullable<LensPublicCallbacks['onBeforeBadgesRender']>> => [ + { + uniqueId: FIELD_NOT_FOUND, + severity: 'warning', + fixableInEditor: true, + displayLocations: [ + { id: 'embeddableBadge' }, + { id: 'dimensionButton', dimensionId: '1' }, + ], + longMessage: 'custom', + shortMessage: '', + hidePopoverIcon: true, + }, + ] + ); + + expect( + handleMessageOverwriteFromConsumer( + [ + { + uniqueId: FIELD_NOT_FOUND, + severity: 'error', + fixableInEditor: true, + displayLocations: [ + { id: 'embeddableBadge' }, + { id: 'dimensionButton', dimensionId: '1' }, + ], + longMessage: 'original', + shortMessage: '', + }, + { + uniqueId: FIELD_WRONG_TYPE, + severity: 'error', + fixableInEditor: true, + displayLocations: [{ id: 'visualization' }], + longMessage: 'original', + shortMessage: '', + }, + ], + getBadgeMessage + ) + ).toEqual( + expect.arrayContaining([ + { + uniqueId: FIELD_WRONG_TYPE, + severity: 'error', + fixableInEditor: true, + displayLocations: [{ id: 'visualization' }], + longMessage: 'original', + shortMessage: '', + }, + { + uniqueId: FIELD_NOT_FOUND, + severity: 'warning', + fixableInEditor: true, + displayLocations: [ + { id: 'embeddableBadge' }, + { id: 'dimensionButton', dimensionId: '1' }, + ], + longMessage: 'custom', + shortMessage: '', + hidePopoverIcon: true, + }, + ]) + ); + }); + + it('should not override embeddableBadge message if callback is not provided', async () => { + const messages: UserMessage[] = [ + { + uniqueId: FIELD_NOT_FOUND, + severity: 'error', + fixableInEditor: true, + displayLocations: [ + { id: 'embeddableBadge' }, + { id: 'dimensionButton', dimensionId: '1' }, + ], + longMessage: 'original', + shortMessage: '', + }, + { + uniqueId: FIELD_WRONG_TYPE, + severity: 'error', + fixableInEditor: true, + displayLocations: [{ id: 'visualization' }], + longMessage: 'original', + shortMessage: '', + }, + ]; + expect(handleMessageOverwriteFromConsumer(messages)).toEqual(messages); + }); + }); }); diff --git a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx index d7d04a837e08..b2755a411e71 100644 --- a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx +++ b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx @@ -11,6 +11,7 @@ import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { FormattedMessage } from '@kbn/i18n-react'; import type { CoreStart } from '@kbn/core/public'; import { Dispatch } from '@reduxjs/toolkit'; +import { partition } from 'lodash'; import { updateDatasourceState, type DataViewsState, @@ -35,6 +36,8 @@ import { EDITOR_UNKNOWN_DATASOURCE_TYPE, EDITOR_UNKNOWN_VIS_TYPE, } from '../user_messages_ids'; +import { nonNullable } from '../utils'; +import type { LensPublicCallbacks } from '../react_embeddable/types'; export interface UserMessageGetterProps { visualizationType: string | null | undefined; @@ -203,21 +206,38 @@ function getMissingIndexPatternsErrors( ]; } +export const handleMessageOverwriteFromConsumer = ( + messages: UserMessage[], + onBeforeBadgesRender?: LensPublicCallbacks['onBeforeBadgesRender'] +) => { + if (onBeforeBadgesRender) { + // we need something else to better identify those errors + const [messagesToHandle, originalMessages] = partition(messages, (message) => + message.displayLocations.some((location) => location.id === 'embeddableBadge') + ); + + if (messagesToHandle.length > 0) { + const customBadgeMessages = onBeforeBadgesRender(messagesToHandle); + return originalMessages.concat(customBadgeMessages); + } + } + + return messages; +}; + export const filterAndSortUserMessages = ( userMessages: UserMessage[], locationId?: UserMessagesDisplayLocationId | UserMessagesDisplayLocationId[], { dimensionId, severity }: UserMessageFilters = {} ) => { - const locationIds = Array.isArray(locationId) - ? locationId - : typeof locationId === 'string' - ? [locationId] - : []; + const locationIds = new Set( + (Array.isArray(locationId) ? locationId : [locationId]).filter(nonNullable) + ); const filteredMessages = userMessages.filter((message) => { - if (locationIds.length) { + if (locationIds.size) { const hasMatch = message.displayLocations.some((location) => { - if (!locationIds.includes(location.id)) { + if (!locationIds.has(location.id)) { return false; } @@ -229,11 +249,7 @@ export const filterAndSortUserMessages = ( } } - if (severity && message.severity !== severity) { - return false; - } - - return true; + return !severity || message.severity === severity; }); return filteredMessages.sort(bySeverity); @@ -329,7 +345,7 @@ export const useApplicationUserMessages = ({ const getUserMessages: UserMessagesGetter = (locationId, filterArgs) => filterAndSortUserMessages( - [...userMessages, ...Object.values(additionalUserMessages)], + userMessages.concat(Object.values(additionalUserMessages)), locationId, filterArgs ?? {} ); diff --git a/x-pack/plugins/lens/public/app_plugin/lens_document_equality.test.ts b/x-pack/plugins/lens/public/app_plugin/lens_document_equality.test.ts index babde51e39f2..8371a77793ea 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_document_equality.test.ts +++ b/x-pack/plugins/lens/public/app_plugin/lens_document_equality.test.ts @@ -7,7 +7,7 @@ import { Filter, FilterStateStore } from '@kbn/es-query'; import { isLensEqual } from './lens_document_equality'; -import { Document } from '../persistence/saved_object_store'; +import { LensDocument } from '../persistence/saved_object_store'; import { AnnotationGroups, Datasource, @@ -18,7 +18,7 @@ import { const visualizationType = 'lnsSomeVis'; -const defaultDoc: Document = { +const defaultDoc: LensDocument = { title: 'some-title', visualizationType, state: { @@ -105,7 +105,7 @@ describe('lens document equality', () => { expect( isLensEqual( undefined, - {} as Document, + {} as LensDocument, mockInjectFilterReferences, {}, mockVisualizationMap, @@ -114,7 +114,7 @@ describe('lens document equality', () => { ).toBeFalsy(); expect( isLensEqual( - {} as Document, + {} as LensDocument, undefined, mockInjectFilterReferences, {}, diff --git a/x-pack/plugins/lens/public/app_plugin/lens_document_equality.ts b/x-pack/plugins/lens/public/app_plugin/lens_document_equality.ts index 60316802ca5e..4fc97882fd92 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_document_equality.ts +++ b/x-pack/plugins/lens/public/app_plugin/lens_document_equality.ts @@ -7,7 +7,7 @@ import { isEqual, intersection, union } from 'lodash'; import { FilterManager } from '@kbn/data-plugin/public'; -import { Document } from '../persistence/saved_object_store'; +import { LensDocument } from '../persistence/saved_object_store'; import { AnnotationGroups, DatasourceMap, VisualizationMap } from '../types'; import { removePinnedFilters } from './save_modal_container'; @@ -15,8 +15,8 @@ const removeNonSerializable = (obj: Parameters<JSON['stringify']>[0]) => JSON.parse(JSON.stringify(obj)); export const isLensEqual = ( - doc1In: Document | undefined, - doc2In: Document | undefined, + doc1In: LensDocument | undefined, + doc2In: LensDocument | undefined, injectFilterReferences: FilterManager['inject'], datasourceMap: DatasourceMap, visualizationMap: VisualizationMap, @@ -54,6 +54,7 @@ export const isLensEqual = ( } })() : isEqual(doc1.state.visualization, doc2.state.visualization); + if (!visualizationStateIsEqual) { return false; } @@ -68,16 +69,14 @@ export const isLensEqual = ( if (datasourcesEqual) { // equal so far, so actually check - datasourcesEqual = availableDatasourceTypes1 - .map((type) => - datasourceMap[type].isEqual( - doc1.state.datasourceStates[type], - [...doc1.references, ...(doc1.state.internalReferences || [])], - doc2.state.datasourceStates[type], - [...doc2.references, ...(doc2.state.internalReferences || [])] - ) + datasourcesEqual = availableDatasourceTypes1.every((type) => + datasourceMap[type].isEqual( + doc1.state.datasourceStates[type], + doc1.references.concat(doc1.state.internalReferences || []), + doc2.state.datasourceStates[type], + doc2.references.concat(doc2.state.internalReferences || []) ) - .every((res) => res); + ); } if (!datasourcesEqual) { @@ -96,7 +95,7 @@ export const isLensEqual = ( function injectDocFilterReferences( injectFilterReferences: FilterManager['inject'], - doc?: Document + doc?: LensDocument ) { if (!doc) return undefined; return { diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 399c849b6ebc..cf76df44eefc 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -37,7 +37,6 @@ import { } from '../utils'; import { combineQueryAndFilters, getLayerMetaInfo } from './show_underlying_data'; import { changeIndexPattern } from '../state_management/lens_slice'; -import { LensByReferenceInput } from '../embeddable'; import { DEFAULT_LENS_LAYOUT_DIMENSIONS, getShareURL } from './share_action'; import { getDatasourceLayers } from '../state_management/utils'; @@ -291,7 +290,6 @@ export const LensTopNavMenu = ({ navigation, uiSettings, application, - attributeService, share, dataViewFieldEditor, dataViewEditor, @@ -529,11 +527,9 @@ export const LensTopNavMenu = ({ const topNavConfig = useMemo(() => { const showReplaceInDashboard = - initialContext?.originatingApp === 'dashboards' && - !(initialInput as LensByReferenceInput)?.savedObjectId; + initialContext?.originatingApp === 'dashboards' && !initialInput?.savedObjectId; const showReplaceInCanvas = - initialContext?.originatingApp === 'canvas' && - !(initialInput as LensByReferenceInput)?.savedObjectId; + initialContext?.originatingApp === 'canvas' && !initialInput?.savedObjectId; const contextFromEmbeddable = initialContext && 'isEmbeddable' in initialContext && initialContext.isEmbeddable; @@ -690,8 +686,7 @@ export const LensTopNavMenu = ({ panelTimeRange: contextFromEmbeddable ? initialContext.panelTimeRange : undefined, }, { - saveToLibrary: - (initialInput && attributeService.inputIsRefType(initialInput)) ?? false, + saveToLibrary: Boolean(initialInput?.savedObjectId), } ); } @@ -801,7 +796,6 @@ export const LensTopNavMenu = ({ defaultLensTitle, onAppLeave, runSave, - attributeService, setIsSaveModalVisible, goBackToOriginatingApp, redirectToOrigin, diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 7f91943eade3..c431f48f0c40 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -33,12 +33,7 @@ import { EditorFrameStart, LensTopNavMenuEntryGenerator, VisualizeEditorContext import { addHelpMenuToAppChrome } from '../help_menu_util'; import { LensPluginStartDependencies } from '../plugin'; import { LENS_EMBEDDABLE_TYPE, LENS_EDIT_BY_VALUE, APP_ID } from '../../common/constants'; -import { - LensEmbeddableInput, - LensByReferenceInput, - LensByValueInput, -} from '../embeddable/embeddable'; -import { LensAttributeService } from '../lens_attribute_service'; +import { LensAttributesService } from '../lens_attribute_service'; import { LensAppServices, RedirectToOriginProps, HistoryLocationState } from './types'; import { makeConfigureStore, @@ -55,6 +50,7 @@ import { MainHistoryLocationState, } from '../../common/locator/locator'; import { SavedObjectIndexStore } from '../persistence'; +import { LensSerializedState } from '../react_embeddable/types'; function getInitialContext(history: AppMountParameters['history']) { const historyLocationState = history.location.state as @@ -83,7 +79,7 @@ function getInitialContext(history: AppMountParameters['history']) { export async function getLensServices( coreStart: CoreStart, startDependencies: LensPluginStartDependencies, - attributeService: LensAttributeService, + attributeService: LensAttributesService, initialContext?: VisualizeFieldContext | VisualizeEditorContext, locator?: LensAppLocator ): Promise<LensAppServices> { @@ -146,7 +142,7 @@ export async function mountApp( params: AppMountParameters, mountProps: { createEditorFrame: EditorFrameStart['createInstance']; - attributeService: LensAttributeService; + attributeService: LensAttributesService; topNavMenuEntryGenerators: LensTopNavMenuEntryGenerator[]; locator?: LensAppLocator; } @@ -188,12 +184,12 @@ export async function mountApp( i18n.translate('xpack.lens.pageTitle', { defaultMessage: 'Lens' }) ); - const getInitialInput = (id?: string, editByValue?: boolean): LensEmbeddableInput | undefined => { + const getInitialInput = (id?: string, editByValue?: boolean): LensSerializedState | undefined => { if (editByValue) { - return embeddableEditorIncomingState?.valueInput as LensByValueInput; + return embeddableEditorIncomingState?.valueInput as LensSerializedState; } if (id) { - return { savedObjectId: id } as LensByReferenceInput; + return { savedObjectId: id } as LensSerializedState; } }; @@ -220,14 +216,14 @@ export async function mountApp( if (initialContext && 'embeddableId' in initialContext) { embeddableId = initialContext.embeddableId; } - if (stateTransfer && props?.input) { - const { input, isCopied } = props; + if (stateTransfer && props?.state) { + const { state, isCopied } = props; stateTransfer.navigateToWithEmbeddablePackage(mergedOriginatingApp, { path: embeddableEditorIncomingState?.originatingPath, state: { embeddableId: isCopied ? undefined : embeddableId, type: LENS_EMBEDDABLE_TYPE, - input, + input: { ...state, savedObject: state.savedObjectId }, searchSessionId: data.search.session.getSessionId(), }, }); @@ -426,7 +422,7 @@ export async function mountApp( return () => { data.search.session.clear(); unmountComponentAtNode(params.element); - lensServices.inspector.close(); + lensServices.inspector.closeInspector(); unlistenParentHistory(); lensStore.dispatch(navigateAway()); stateTransfer.clearEditorState?.(APP_ID); diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.test.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.test.tsx new file mode 100644 index 000000000000..987b320b3abf --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.test.tsx @@ -0,0 +1,407 @@ +/* + * 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 { SaveProps } from './app'; +import { type SaveVisualizationProps, runSaveLensVisualization } from './save_modal_container'; +import { defaultDoc, makeDefaultServices } from '../mocks'; +import faker from 'faker'; +import { makeAttributeService } from '../mocks/services_mock'; + +jest.mock('../persistence/saved_objects_utils/check_for_duplicate_title', () => ({ + checkForDuplicateTitle: jest.fn(async () => false), +})); + +describe('runSaveLensVisualization', () => { + // Need to call reset here as makeDefaultServices() reuses some mocks from core + const resetMocks = () => + beforeEach(() => { + jest.resetAllMocks(); + }); + + function getDefaultArgs( + servicesOverrides: Partial<SaveVisualizationProps> = {}, + { saveToLibrary, ...propsOverrides }: Partial<SaveProps & { saveToLibrary: boolean }> = {} + ) { + const redirectToOrigin = jest.fn(); + const redirectTo = jest.fn(); + const onAppLeave = jest.fn(); + const switchDatasource = jest.fn(); + const props: SaveVisualizationProps = { + ...makeDefaultServices(), + // start with both the initial input and lastKnownDoc synced + lastKnownDoc: defaultDoc, + initialInput: { attributes: defaultDoc, savedObjectId: defaultDoc.savedObjectId }, + redirectToOrigin, + redirectTo, + onAppLeave, + switchDatasource, + ...servicesOverrides, + }; + const saveProps: SaveProps = { + newTitle: faker.lorem.word(), + newDescription: faker.lorem.sentence(), + newTags: [faker.lorem.word(), faker.lorem.word()], + isTitleDuplicateConfirmed: false, + returnToOrigin: false, + dashboardId: undefined, + newCopyOnSave: false, + ...propsOverrides, + }; + const options = { + saveToLibrary: Boolean(saveToLibrary), + }; + + return { + props, + saveProps, + options, + // convenience shortcuts + /** + * This function will be called when a fresh chart is saved + * and in the modal the user chooses to add the chart into a specific dashboard. Make sure to pass the "dashboardId" prop as well to simulate this scenario. + * This is used to test indirectly the redirectToDashboard call + */ + redirectToDashboardFn: props.stateTransfer.navigateToWithEmbeddablePackage, + /** + * This function will be called before reloading the editor after saving a a new document/new copy of the document + */ + cleanupEditor: props.stateTransfer.clearEditorState, + saveToLibraryFn: props.attributeService.saveToLibrary, + toasts: props.notifications.toasts, + }; + } + + describe('from dashboard', () => { + describe('as by value', () => { + const defaultByValueDoc = { ...defaultDoc, savedObjectId: undefined }; + + describe('Save and return', () => { + resetMocks(); + + // Test the "Save and return" button + it('should get back to dashboard', async () => { + const { props, saveProps, options, redirectToDashboardFn, saveToLibraryFn } = + getDefaultArgs( + { + lastKnownDoc: defaultByValueDoc, + initialInput: { attributes: defaultByValueDoc }, + }, + { returnToOrigin: true } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(props.onAppLeave).toHaveBeenCalled(); + expect(props.redirectToOrigin).toHaveBeenCalled(); + + // callback not called + expect(redirectToDashboardFn).not.toHaveBeenCalled(); + expect(saveToLibraryFn).not.toHaveBeenCalled(); + expect(props.notifications.toasts.addSuccess).not.toHaveBeenCalled(); + }); + + it('should get back to dashboard preserving the original panel settings', async () => { + const { props, saveProps, options } = getDefaultArgs( + { + lastKnownDoc: defaultByValueDoc, + initialInput: { + attributes: defaultByValueDoc, + title: 'blah', + timeRange: { from: 'now-7d', to: 'now' }, + }, + }, + { returnToOrigin: true } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(props.onAppLeave).toHaveBeenCalled(); + expect(props.redirectToOrigin).toHaveBeenCalledWith( + expect.objectContaining({ + state: expect.objectContaining({ + title: 'blah', + timeRange: { from: 'now-7d', to: 'now' }, + }), + }) + ); + }); + }); + + describe('Save to library', () => { + resetMocks(); + + // Test the "Save to library" flow + it('should save to library without redirect', async () => { + const { props, saveProps, options, redirectToDashboardFn, saveToLibraryFn } = + getDefaultArgs( + { + lastKnownDoc: defaultByValueDoc, + initialInput: { attributes: defaultByValueDoc }, + }, + { + saveToLibrary: true, + // do not get back at dashboard once saved + returnToOrigin: false, + } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(saveToLibraryFn).toHaveBeenCalled(); + expect(props.notifications.toasts.addSuccess).toHaveBeenCalled(); + + // not called + expect(props.onAppLeave).not.toHaveBeenCalled(); + expect(props.redirectToOrigin).not.toHaveBeenCalled(); + expect(redirectToDashboardFn).not.toHaveBeenCalled(); + }); + + it('should save to library and redirect', async () => { + const { props, saveProps, options, redirectToDashboardFn, saveToLibraryFn } = + getDefaultArgs( + { + lastKnownDoc: defaultByValueDoc, + initialInput: { attributes: defaultByValueDoc }, + }, + { + saveToLibrary: true, + // return to dashboard once saved + returnToOrigin: true, + } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(props.onAppLeave).toHaveBeenCalled(); + expect(props.redirectToOrigin).toHaveBeenCalled(); + expect(saveToLibraryFn).toHaveBeenCalled(); + + // not called + expect(redirectToDashboardFn).not.toHaveBeenCalled(); + expect(props.notifications.toasts.addSuccess).not.toHaveBeenCalled(); + }); + }); + }); + + describe('as by reference', () => { + resetMocks(); + // There are 4 possibilities here: + // save the current document overwriting the existing one + it('should overwrite and show a success toast', async () => { + const { props, saveProps, options, redirectToDashboardFn, saveToLibraryFn, toasts } = + getDefaultArgs( + { + // defaultDoc is by reference + }, + { newCopyOnSave: false, saveToLibrary: true } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(saveToLibraryFn).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + defaultDoc.savedObjectId + ); + expect(toasts.addSuccess).toHaveBeenCalled(); + + // not called + expect(props.onAppLeave).not.toHaveBeenCalled(); + expect(props.redirectToOrigin).not.toHaveBeenCalled(); + expect(redirectToDashboardFn).not.toHaveBeenCalled(); + }); + + // save the current document as a new by-ref copy in the library + it('should save as a new copy and show a success toast', async () => { + const { props, saveProps, options, redirectToDashboardFn, saveToLibraryFn, toasts } = + getDefaultArgs( + { + // defaultDoc is by reference + }, + { newCopyOnSave: true, saveToLibrary: true } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(saveToLibraryFn).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + undefined + ); + expect(toasts.addSuccess).toHaveBeenCalled(); + + // not called + expect(props.onAppLeave).not.toHaveBeenCalled(); + expect(props.redirectToOrigin).not.toHaveBeenCalled(); + expect(redirectToDashboardFn).not.toHaveBeenCalled(); + }); + // save the current document as a new by-value copy and add it to a dashboard + it('should save as a new by-value copy and redirect to the dashboard', async () => { + const dashboardId = faker.random.uuid(); + const { props, saveProps, options, redirectToDashboardFn, saveToLibraryFn, toasts } = + getDefaultArgs( + { + // defaultDoc is by reference + }, + { newCopyOnSave: true, saveToLibrary: false, dashboardId } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(props.onAppLeave).toHaveBeenCalled(); + + // not called + expect(props.redirectToOrigin).not.toHaveBeenCalled(); + expect(redirectToDashboardFn).toHaveBeenCalledWith( + 'dashboards', + // make sure the new savedObject id is removed from the new input + expect.objectContaining({ + state: expect.objectContaining({ + input: expect.objectContaining({ savedObjectId: undefined }), + }), + }) + ); + expect(saveToLibraryFn).not.toHaveBeenCalled(); + expect(toasts.addSuccess).not.toHaveBeenCalled(); + }); + + // save the current document as a new by-ref copy and add it to a dashboard + it('should save as a new by-ref copy and redirect to the dashboard', async () => { + const dashboardId = faker.random.uuid(); + const { props, saveProps, options, redirectToDashboardFn, saveToLibraryFn, toasts } = + getDefaultArgs( + { + // defaultDoc is by reference + }, + { newCopyOnSave: true, saveToLibrary: true, dashboardId } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(props.onAppLeave).toHaveBeenCalled(); + expect(redirectToDashboardFn).toHaveBeenCalledWith( + 'dashboards', + // make sure the new savedObject id is passed with the new input + expect.objectContaining({ + state: expect.objectContaining({ + input: expect.objectContaining({ savedObjectId: '1234' }), + }), + }) + ); + expect(saveToLibraryFn).toHaveBeenCalled(); + + // not called + expect(props.redirectToOrigin).not.toHaveBeenCalled(); + expect(toasts.addSuccess).not.toHaveBeenCalled(); + }); + }); + }); + + describe('fresh editor start', () => { + resetMocks(); + + it('should reload the editor if it has been saved as new copy', async () => { + const { props, saveProps, options, saveToLibraryFn, cleanupEditor, toasts } = getDefaultArgs( + {}, + { + saveToLibrary: true, + newCopyOnSave: true, + } + ); + const result = await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(saveToLibraryFn).toHaveBeenCalled(); + expect(toasts.addSuccess).toHaveBeenCalled(); + expect(cleanupEditor).toHaveBeenCalled(); + expect(props.redirectTo).toHaveBeenCalledWith(defaultDoc.savedObjectId); + expect(result?.isLinkedToOriginatingApp).toBeFalsy(); + + // not called + expect(props.onAppLeave).not.toHaveBeenCalled(); + }); + + it('should show a notification toast and reload as first save of the document', async () => { + const { props, saveProps, options, saveToLibraryFn, toasts } = getDefaultArgs( + { + lastKnownDoc: { ...defaultDoc, savedObjectId: undefined }, + persistedDoc: undefined, + initialInput: undefined, + }, + { saveToLibrary: true } + ); + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(saveToLibraryFn).toHaveBeenCalled(); + expect(toasts.addSuccess).toHaveBeenCalled(); + expect(props.redirectTo).toHaveBeenCalled(); + + // not called + expect(props.application.navigateToApp).not.toHaveBeenCalledWith('lens', { path: '/' }); + expect(props.redirectToOrigin).not.toHaveBeenCalled(); + }); + + it('should throw if something goes wrong when saving', async () => { + const attributeServiceMock = { + ...makeAttributeService(defaultDoc), + saveToLibrary: jest.fn().mockImplementation(() => Promise.reject(Error('failed to save'))), + }; + const { props, saveProps, options, toasts } = getDefaultArgs( + { + lastKnownDoc: { ...defaultDoc, savedObjectId: undefined }, + attributeService: attributeServiceMock, + }, + { saveToLibrary: true } + ); + try { + await runSaveLensVisualization(props, saveProps, options); + } catch (error) { + expect(toasts.addDanger).toHaveBeenCalled(); + expect(toasts.addSuccess).not.toHaveBeenCalled(); + expect(error.message).toEqual('failed to save'); + } + }); + }); + + // While this is technically a virtual option as for now, it's still worth testing to not break it in the future + describe('Textbased version', () => { + resetMocks(); + + it('should have a dedicated flow for textbased saving by-ref', async () => { + // simulate a new save + const attributeServiceMock = makeAttributeService({ + ...defaultDoc, + savedObjectId: faker.random.uuid(), + }); + + const { props, saveProps, options, saveToLibraryFn, cleanupEditor } = getDefaultArgs( + { + textBasedLanguageSave: true, + attributeService: attributeServiceMock, + // give a document without a savedObjectId + lastKnownDoc: { ...defaultDoc, savedObjectId: undefined }, + persistedDoc: undefined, + // simulate a fresh start in the editor + initialInput: undefined, + }, + { + saveToLibrary: true, + } + ); + + await runSaveLensVisualization(props, saveProps, options); + + // callback called + expect(saveToLibraryFn).toHaveBeenCalled(); + expect(cleanupEditor).toHaveBeenCalled(); + expect(props.switchDatasource).toHaveBeenCalled(); + expect(props.redirectTo).not.toHaveBeenCalled(); + expect(props.application.navigateToApp).toHaveBeenCalledWith('lens', { path: '/' }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index 354bf0888259..f1ccacc37db5 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -11,25 +11,29 @@ import { isFilterPinned } from '@kbn/es-query'; import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import type { SavedObjectReference } from '@kbn/core/public'; import { EuiLoadingSpinner } from '@elastic/eui'; +import { omit } from 'lodash'; import { SaveModal } from './save_modal'; import type { LensAppProps, LensAppServices } from './types'; import type { SaveProps } from './app'; -import { Document, checkForDuplicateTitle, SavedObjectIndexStore } from '../persistence'; -import type { LensByReferenceInput, LensEmbeddableInput } from '../embeddable'; +import { checkForDuplicateTitle, SavedObjectIndexStore, LensDocument } from '../persistence'; import { APP_ID, getFullPath } from '../../common/constants'; import type { LensAppState } from '../state_management'; -import { getPersisted } from '../state_management/init_middleware/load_initial'; -import { VisualizeEditorContext } from '../types'; +import { getFromPreloaded } from '../state_management/init_middleware/load_initial'; +import { Simplify, VisualizeEditorContext } from '../types'; import { redirectToDashboard } from './save_modal_container_helpers'; +import { LensSerializedState } from '../react_embeddable/types'; +import { isLegacyEditorEmbeddable } from './app_helpers'; -type ExtraProps = Pick<LensAppProps, 'initialInput'> & - Partial<Pick<LensAppProps, 'redirectToOrigin' | 'redirectTo' | 'onAppLeave'>>; +type ExtraProps = Simplify< + Pick<LensAppProps, 'initialInput'> & + Partial<Pick<LensAppProps, 'redirectToOrigin' | 'redirectTo' | 'onAppLeave'>> +>; export type SaveModalContainerProps = { originatingApp?: string; getOriginatingPath?: (dashboardId: string) => string; - persistedDoc?: Document; - lastKnownDoc?: Document; + persistedDoc?: LensDocument; + lastKnownDoc?: LensDocument; returnToOriginSwitchLabel?: string; onClose: () => void; onSave?: (saveProps: SaveProps) => void; @@ -78,19 +82,14 @@ export function SaveModalContainer({ let description; let savedObjectId; const [initializing, setInitializing] = useState(true); - const [lastKnownDoc, setLastKnownDoc] = useState<Document | undefined>(initLastKnownDoc); + const [lastKnownDoc, setLastKnownDoc] = useState<LensDocument | undefined>(initLastKnownDoc); if (lastKnownDoc) { title = lastKnownDoc.title; description = lastKnownDoc.description; savedObjectId = lastKnownDoc.savedObjectId; } - if ( - !lastKnownDoc?.title && - initialContext && - 'isEmbeddable' in initialContext && - initialContext.isEmbeddable - ) { + if (!lastKnownDoc?.title && isLegacyEditorEmbeddable(initialContext)) { title = i18n.translate('xpack.lens.app.convertedLabel', { defaultMessage: '{title} (converted)', values: { @@ -109,7 +108,7 @@ export function SaveModalContainer({ let isMounted = true; if (initialInput) { - getPersisted({ + getFromPreloaded({ initialInput, lensServices, }) @@ -133,12 +132,13 @@ export function SaveModalContainer({ ? savedObjectsTagging.ui.getTagIdsFromReferences(persistedDoc.references) : []; - const runLensSave = (saveProps: SaveProps, options: { saveToLibrary: boolean }) => { + const runLensSave = async (saveProps: SaveProps, options: { saveToLibrary: boolean }) => { if (runSave) { // inside lens, we use the function that's passed to it - runSave(saveProps, options); - } else if (attributeService && lastKnownDoc) { - runSaveLensVisualization( + return runSave(saveProps, options); + } + if (attributeService && lastKnownDoc) { + await runSaveLensVisualization( { ...lensServices, lastKnownDoc, @@ -147,16 +147,14 @@ export function SaveModalContainer({ redirectToOrigin, originatingApp, getOriginatingPath, - getIsByValueMode: () => false, onAppLeave: () => {}, ...lensServices, }, saveProps, options - ).then(() => { - onSave?.(saveProps); - onClose(); - }); + ); + onSave?.(saveProps); + onClose(); } }; @@ -188,11 +186,24 @@ export function SaveModalContainer({ ); } +function fromDocumentToSerializedState( + doc: LensDocument, + panelSettings: Partial<LensSerializedState>, + originalInput?: LensAppProps['initialInput'] +): LensSerializedState { + return { + ...originalInput, + attributes: omit(doc, 'savedObjectId'), + savedObjectId: doc.savedObjectId, + ...panelSettings, + }; +} + const getDocToSave = ( - lastKnownDoc: Document, + lastKnownDoc: LensDocument, saveProps: SaveProps, references: SavedObjectReference[] -) => { +): LensDocument => { const docToSave = { ...removePinnedFilters(lastKnownDoc)!, references, @@ -209,11 +220,10 @@ const getDocToSave = ( return docToSave; }; -export const runSaveLensVisualization = async ( - props: { - lastKnownDoc?: Document; - getIsByValueMode: () => boolean; - persistedDoc?: Document; +export type SaveVisualizationProps = Simplify< + { + lastKnownDoc?: LensDocument; + persistedDoc?: LensDocument; originatingApp?: string; getOriginatingPath?: (dashboardId: string) => string; textBasedLanguageSave?: boolean; @@ -232,7 +242,11 @@ export const runSaveLensVisualization = async ( | 'stateTransfer' | 'attributeService' | 'savedObjectsTagging' - >, + > +>; + +export const runSaveLensVisualization = async ( + props: SaveVisualizationProps, saveProps: SaveProps, options: { saveToLibrary: boolean } ): Promise<Partial<LensAppState> | undefined> => { @@ -245,7 +259,6 @@ export const runSaveLensVisualization = async ( stateTransfer, attributeService, savedObjectsTagging, - getIsByValueMode, redirectToOrigin, onAppLeave, redirectTo, @@ -262,7 +275,7 @@ export const runSaveLensVisualization = async ( return; } - let references = lastKnownDoc.references; + let references = lastKnownDoc.references || initialInput?.attributes?.references; if (savedObjectsTagging) { const tagsIds = @@ -277,68 +290,90 @@ export const runSaveLensVisualization = async ( const docToSave = getDocToSave(lastKnownDoc, saveProps, references); - // Required to serialize filters in by value mode until - // https://github.com/elastic/kibana/issues/77588 is fixed - if (getIsByValueMode()) { - docToSave.state.filters.forEach((filter) => { - if (typeof filter.meta.value === 'function') { - delete filter.meta.value; - } - }); - } - const originalInput = saveProps.newCopyOnSave ? undefined : initialInput; - const originalSavedObjectId = (originalInput as LensByReferenceInput)?.savedObjectId; + const originalSavedObjectId = originalInput?.savedObjectId; if (options.saveToLibrary) { - try { - await checkForDuplicateTitle( - { - id: originalSavedObjectId, - title: docToSave.title, - displayName: i18n.translate('xpack.lens.app.saveModalType', { - defaultMessage: 'Lens visualization', - }), - lastSavedTitle: lastKnownDoc.title, - copyOnSave: saveProps.newCopyOnSave, - isTitleDuplicateConfirmed: saveProps.isTitleDuplicateConfirmed, - }, - saveProps.onTitleDuplicate, - { - client: savedObjectStore, - ...startServices, - } - ); - } catch (e) { - // ignore duplicate title failure, user notified in save modal - throw e; - } + // this is a lower level call that the Lens attribute service one + // @TODO: check if it's worth to replace it witht he attribute service one + await checkForDuplicateTitle( + { + id: originalSavedObjectId, + title: docToSave.title, + displayName: i18n.translate('xpack.lens.app.saveModalType', { + defaultMessage: 'Lens visualization', + }), + lastSavedTitle: lastKnownDoc.title, + copyOnSave: saveProps.newCopyOnSave, + isTitleDuplicateConfirmed: saveProps.isTitleDuplicateConfirmed, + }, + saveProps.onTitleDuplicate, + { + client: savedObjectStore, + ...startServices, + } + ); + // ignore duplicate title failure, user notified in save modal } + try { - let newInput = (await attributeService.wrapAttributes( + // wrap the doc into a serializable state + const newDoc = fromDocumentToSerializedState( docToSave, - options.saveToLibrary, + { + timeRange: saveProps.panelTimeRange ?? originalInput?.timeRange, + savedObjectId: options.saveToLibrary ? originalSavedObjectId : undefined, + }, originalInput - )) as LensEmbeddableInput; - if (saveProps.panelTimeRange) { - newInput = { - ...newInput, - timeRange: saveProps.panelTimeRange, - }; + ); + + let savedObjectId: string | undefined; + try { + savedObjectId = + newDoc.attributes && options.saveToLibrary + ? await attributeService.saveToLibrary( + newDoc.attributes, + newDoc.attributes.references || [], + originalSavedObjectId + ) + : undefined; + } catch (error) { + notifications.toasts.addDanger({ + title: i18n.translate('xpack.lens.app.saveVisualization.errorNotificationText', { + defaultMessage: `An error occurred while saving. Error: {errorMessage}`, + values: { + errorMessage: error.message, + }, + }), + }); + // trigger a reject to jump to the final catch clause + throw error; } - if (saveProps.returnToOrigin && redirectToOrigin) { + + const shouldNavigateBackToOrigin = saveProps.returnToOrigin && redirectToOrigin; + const hasRedirect = shouldNavigateBackToOrigin || saveProps.dashboardId; + + // if a redirect was set, prevent the validation on app leave + if (hasRedirect) { // disabling the validation on app leave because the document has been saved. onAppLeave?.((actions) => { return actions.default(); }); - redirectToOrigin({ input: newInput, isCopied: saveProps.newCopyOnSave }); - return; - } else if (saveProps.dashboardId) { - // disabling the validation on app leave because the document has been saved. - onAppLeave?.((actions) => { - return actions.default(); + } + + if (shouldNavigateBackToOrigin) { + redirectToOrigin({ + state: { ...newDoc, savedObjectId }, + isCopied: saveProps.newCopyOnSave, }); + return; + } + // should we make it more robust here and better check the context of the saving + // or keep the responsability of the consumer of the function to provide the right set + // of args here in case the user is within a by value chart AND want's to save it in the library + // without redirect? + if (saveProps.dashboardId) { redirectToDashboard({ - embeddableInput: newInput, + embeddableInput: { ...newDoc, savedObjectId }, dashboardId: saveProps.dashboardId, stateTransfer, originatingApp: props.originatingApp, @@ -356,15 +391,8 @@ export const runSaveLensVisualization = async ( }) ); - if ( - attributeService.inputIsRefType(newInput) && - newInput.savedObjectId !== originalSavedObjectId - ) { - chrome.recentlyAccessed.add( - getFullPath(newInput.savedObjectId), - docToSave.title, - newInput.savedObjectId - ); + if (savedObjectId && savedObjectId !== originalSavedObjectId) { + chrome.recentlyAccessed.add(getFullPath(savedObjectId), docToSave.title, savedObjectId); // remove editor state so the connection is still broken after reload stateTransfer.clearEditorState?.(APP_ID); @@ -372,18 +400,13 @@ export const runSaveLensVisualization = async ( switchDatasource?.(); application.navigateToApp('lens', { path: '/' }); } else { - redirectTo?.(newInput.savedObjectId); + redirectTo?.(savedObjectId); } return { isLinkedToOriginatingApp: false }; } - const newDoc = { - ...docToSave, - ...newInput, - }; - return { - persistedDoc: newDoc, + persistedDoc: newDoc.attributes, isLinkedToOriginatingApp: false, }; } catch (e) { @@ -393,7 +416,7 @@ export const runSaveLensVisualization = async ( } }; -export function removePinnedFilters(doc?: Document) { +export function removePinnedFilters(doc?: LensDocument) { if (!doc) return undefined; return { ...doc, diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container_helpers.test.ts b/x-pack/plugins/lens/public/app_plugin/save_modal_container_helpers.test.ts index 1f4e255c5441..9415ab2e323c 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container_helpers.test.ts +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container_helpers.test.ts @@ -5,14 +5,14 @@ * 2.0. */ import { makeDefaultServices } from '../mocks'; -import type { LensEmbeddableInput } from '../embeddable'; import type { LensAppServices } from './types'; import { redirectToDashboard } from './save_modal_container_helpers'; +import { LensSerializedState } from '..'; describe('redirectToDashboard', () => { const embeddableInput = { test: 'test', - } as unknown as LensEmbeddableInput; + } as unknown as LensSerializedState; const mockServices = makeDefaultServices(); it('should call the navigateToWithEmbeddablePackage with the correct args if originatingApp is given', () => { diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container_helpers.ts b/x-pack/plugins/lens/public/app_plugin/save_modal_container_helpers.ts index 98b2d0bdc2ab..44b879c7f27c 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container_helpers.ts +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container_helpers.ts @@ -6,8 +6,8 @@ */ import type { LensAppServices } from './types'; -import type { LensEmbeddableInput } from '../embeddable'; import { LENS_EMBEDDABLE_TYPE } from '../../common/constants'; +import { LensSerializedState } from '../react_embeddable/types'; export const redirectToDashboard = ({ embeddableInput, @@ -16,7 +16,7 @@ export const redirectToDashboard = ({ getOriginatingPath, stateTransfer, }: { - embeddableInput: LensEmbeddableInput; + embeddableInput: LensSerializedState; dashboardId: string; originatingApp?: string; getOriginatingPath?: (dashboardId: string) => string | undefined; diff --git a/x-pack/plugins/lens/public/app_plugin/share_action.ts b/x-pack/plugins/lens/public/app_plugin/share_action.ts index c9ec3a11ef5e..dbb5d9d61eda 100644 --- a/x-pack/plugins/lens/public/app_plugin/share_action.ts +++ b/x-pack/plugins/lens/public/app_plugin/share_action.ts @@ -11,7 +11,7 @@ import { DataViewSpec } from '@kbn/data-views-plugin/common'; import type { LensAppLocatorParams } from '../../common/locator/locator'; import type { LensAppState } from '../state_management'; import type { LensAppServices } from './types'; -import type { Document } from '../persistence/saved_object_store'; +import type { LensDocument } from '../persistence/saved_object_store'; import type { DatasourceMap, VisualizationMap } from '../types'; import { extractReferencesFromState, getResolvedDateRange } from '../utils'; import { getEditPath } from '../../common/constants'; @@ -23,7 +23,7 @@ interface ShareableConfiguration > { datasourceMap: DatasourceMap; visualizationMap: VisualizationMap; - currentDoc: Document | undefined; + currentDoc: LensDocument | undefined; adHocDataViews?: DataViewSpec[]; } @@ -37,7 +37,7 @@ export const DEFAULT_LENS_LAYOUT_DIMENSIONS = { function getShareURLForSavedObject( { application, data }: Pick<LensAppServices, 'application' | 'data'>, - currentDoc: Document | undefined + currentDoc: LensDocument | undefined ) { return new URL( `${application.getUrlForApp('lens', { absolute: true })}${ @@ -89,7 +89,7 @@ export function getLocatorParams( const serializableDatasourceStates = datasourceStates as LensAppState['datasourceStates'] & SerializableRecord; - const snapshotParams = { + const snapshotParams: LensAppLocatorParams = { filters, query, resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx index 205aa74aaee2..dedd34c24cb5 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration.tsx @@ -16,6 +16,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { isEqual } from 'lodash'; import { RootDragDropProvider } from '@kbn/dom-drag-drop'; +import { TypedLensSerializedState } from '../../../react_embeddable/types'; import type { LensPluginStartDependencies } from '../../../plugin'; import { makeConfigureStore, @@ -28,8 +29,7 @@ import { generateId } from '../../../id_generator'; import type { DatasourceMap, VisualizationMap } from '../../../types'; import { LensEditConfigurationFlyout } from './lens_configuration_flyout'; import type { EditConfigPanelProps } from './types'; -import { SavedObjectIndexStore, type Document } from '../../../persistence'; -import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; +import { SavedObjectIndexStore, type LensDocument } from '../../../persistence'; import { DOC_TYPE } from '../../../../common/constants'; export type EditLensConfigurationProps = Omit< @@ -87,6 +87,41 @@ export const updatingMiddleware = } }; +const MaybeWrapper = ({ + wrapInFlyout, + closeFlyout, + children, +}: { + wrapInFlyout?: boolean; + children: JSX.Element; + closeFlyout?: () => void; +}) => { + if (!wrapInFlyout) { + return children; + } + return ( + <EuiFlyout + data-test-subj="lnsEditOnFlyFlyout" + type="push" + ownFocus + paddingSize="m" + onClose={() => { + closeFlyout?.(); + }} + aria-labelledby={i18n.translate('xpack.lens.config.editLabel', { + defaultMessage: 'Edit configuration', + })} + size="s" + hideCloseButton + css={css` + clip-path: polygon(-100% 0, 100% 0, 100% 100%, -100% 100%); + `} + > + {children} + </EuiFlyout> + ); +}; + export async function getEditLensConfiguration( coreStart: CoreStart, startDependencies: LensPluginStartDependencies, @@ -109,30 +144,29 @@ export async function getEditLensConfiguration( datasourceId, panelId, savedObjectId, - output$, + dataLoading$, lensAdapters, updateByRefInput, navigateToLensEditor, displayFlyoutHeader, canEditTextBasedQuery, isNewPanel, - deletePanel, hidesSuggestions, - onApplyCb, - onCancelCb, + onApply, + onCancel, hideTimeFilterInfo, }: EditLensConfigurationProps) => { if (!lensServices || !datasourceMap || !visualizationMap) { return <LoadingSpinnerWithOverlay />; } const [currentAttributes, setCurrentAttributes] = - useState<TypedLensByValueInput['attributes']>(attributes); + useState<TypedLensSerializedState['attributes']>(attributes); /** * During inline editing of a by reference panel, the panel is converted to a by value one. * When the user applies the changes we save them to the Lens SO */ const saveByRef = useCallback( - async (attrs: Document) => { + async (attrs: LensDocument) => { const savedObjectStore = new SavedObjectIndexStore(lensServices.contentManagement); await savedObjectStore.save({ ...attrs, @@ -167,34 +201,6 @@ export async function getEditLensConfiguration( }) ); - const getWrapper = (children: JSX.Element) => { - if (wrapInFlyout) { - return ( - <EuiFlyout - data-test-subj="lnsEditOnFlyFlyout" - type="push" - ownFocus - paddingSize="m" - onClose={() => { - closeFlyout?.(); - }} - aria-labelledby={i18n.translate('xpack.lens.config.editLabel', { - defaultMessage: 'Edit configuration', - })} - size="s" - hideCloseButton - css={css` - clip-path: polygon(-100% 0, 100% 0, 100% 100%, -100% 100%); - `} - > - {children} - </EuiFlyout> - ); - } else { - return children; - } - }; - const configPanelProps = { attributes: currentAttributes, updatePanelState, @@ -204,7 +210,7 @@ export async function getEditLensConfiguration( coreStart, startDependencies, visualizationMap, - output$, + dataLoading$, lensAdapters, datasourceMap, saveByRef, @@ -216,22 +222,23 @@ export async function getEditLensConfiguration( hidesSuggestions, setCurrentAttributes, isNewPanel, - deletePanel, - onApplyCb, - onCancelCb, + onApply, + onCancel, hideTimeFilterInfo, }; - return getWrapper( - <Provider store={lensStore}> - <KibanaRenderContextProvider {...coreStart}> - <KibanaContextProvider services={lensServices}> - <RootDragDropProvider> - <LensEditConfigurationFlyout {...configPanelProps} /> - </RootDragDropProvider> - </KibanaContextProvider> - </KibanaRenderContextProvider> - </Provider> + return ( + <MaybeWrapper wrapInFlyout={wrapInFlyout} closeFlyout={closeFlyout}> + <Provider store={lensStore}> + <KibanaRenderContextProvider {...coreStart}> + <KibanaContextProvider services={lensServices}> + <RootDragDropProvider> + <LensEditConfigurationFlyout {...configPanelProps} /> + </RootDragDropProvider> + </KibanaContextProvider> + </KibanaRenderContextProvider> + </Provider> + </MaybeWrapper> ); }; } diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts index 1274008d0de8..c0280af59504 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts @@ -18,7 +18,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { getTime } from '@kbn/data-plugin/common'; import { type DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; +import { TypedLensSerializedState } from '../../../react_embeddable/types'; import type { LensPluginStartDependencies } from '../../../plugin'; import type { DatasourceMap, VisualizationMap } from '../../../types'; import { suggestionsApi } from '../../../lens_suggestions_api'; @@ -123,7 +123,7 @@ export const getSuggestions = async ( query, suggestion: firstSuggestion, dataView, - }) as TypedLensByValueInput['attributes']; + }) as TypedLensSerializedState['attributes']; return attrs; } catch (e) { setErrors([e]); diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx index 85c7036a3e9d..474d5cc69c18 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.test.tsx @@ -13,9 +13,9 @@ import { coreMock } from '@kbn/core/public/mocks'; import { mockVisualizationMap, mockDatasourceMap, mockDataPlugin } from '../../../mocks'; import type { LensPluginStartDependencies } from '../../../plugin'; import { createMockStartDependencies } from '../../../editor_frame_service/mocks'; -import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; import { LensEditConfigurationFlyout } from './lens_configuration_flyout'; import type { EditConfigPanelProps } from './types'; +import { TypedLensSerializedState } from '../../../react_embeddable/types'; jest.mock('@kbn/esql-utils', () => { return { @@ -93,7 +93,7 @@ const lensAttributes = { esql: 'from index1 | limit 10', }, references: [], -} as unknown as TypedLensByValueInput['attributes']; +} as unknown as TypedLensSerializedState['attributes']; const mockStartDependencies = createMockStartDependencies() as unknown as LensPluginStartDependencies; @@ -139,6 +139,8 @@ describe('LensEditConfigurationFlyout', () => { visualizationMap={visualizationMap} closeFlyout={jest.fn()} datasourceId={'testDatasource' as EditConfigPanelProps['datasourceId']} + onApply={jest.fn()} + onCancel={jest.fn()} {...propsOverrides} />, {}, @@ -234,7 +236,7 @@ describe('LensEditConfigurationFlyout', () => { await renderConfigFlyout( { closeFlyout: jest.fn(), - onApplyCb: onApplyCbSpy, + onApply: onApplyCbSpy, }, { esql: 'from index1 | limit 10' } ); diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx index fd3bcdc8bed8..8c8693cd7c76 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx @@ -30,6 +30,7 @@ import { import type { AggregateQuery, Query } from '@kbn/es-query'; import { ESQLLangEditor } from '@kbn/esql/public'; import { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; +import type { TypedLensSerializedState } from '../../../react_embeddable/types'; import { buildExpression } from '../../../editor_frame_service/editor_frame/expression_helpers'; import { MAX_NUM_OF_COLUMNS } from '../../../datasources/text_based/utils'; import { @@ -38,7 +39,6 @@ import { onActiveDataChange, useLensDispatch, } from '../../../state_management'; -import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; import { EXPRESSION_BUILD_ERROR_ID, extractReferencesFromState, @@ -67,20 +67,19 @@ export function LensEditConfigurationFlyout({ saveByRef, savedObjectId, updateByRefInput, - output$, + dataLoading$, lensAdapters, navigateToLensEditor, displayFlyoutHeader, canEditTextBasedQuery, isNewPanel, - deletePanel, hidesSuggestions, - onApplyCb, - onCancelCb, + onApply: onApplyCallback, + onCancel: onCancelCallback, hideTimeFilterInfo, }: EditConfigPanelProps) { const euiTheme = useEuiTheme(); - const previousAttributes = useRef<TypedLensByValueInput['attributes']>(attributes); + const previousAttributes = useRef<TypedLensSerializedState['attributes']>(attributes); const previousAdapters = useRef<Partial<DefaultInspectorAdapters> | undefined>(lensAdapters); const prevQuery = useRef<AggregateQuery | Query>(attributes.state.query); const [query, setQuery] = useState<AggregateQuery | Query>(attributes.state.query); @@ -117,7 +116,11 @@ export function LensEditConfigurationFlyout({ const dispatch = useLensDispatch(); useEffect(() => { - const s = output$?.subscribe(() => { + const s = dataLoading$?.subscribe((isDataLoading) => { + // go thru only when the loading is complete + if (isDataLoading) { + return; + } const activeData: Record<string, Datatable> = {}; const adaptersTables = previousAdapters.current?.tables?.tables; const [table] = Object.values(adaptersTables || {}); @@ -134,7 +137,7 @@ export function LensEditConfigurationFlyout({ } }); return () => s?.unsubscribe(); - }, [dispatch, output$, layers]); + }, [dispatch, dataLoading$, layers]); useEffect(() => { const abortController = new AbortController(); @@ -217,16 +220,10 @@ export function LensEditConfigurationFlyout({ updateByRefInput?.(savedObjectId); } } - // for a newly created chart, I want cancelling to also remove the panel - if (isNewPanel && deletePanel) { - deletePanel(); - } - onCancelCb?.(); + onCancelCallback?.(); closeFlyout?.(); }, [ attributesChanged, - isNewPanel, - deletePanel, closeFlyout, visualization.activeId, savedObjectId, @@ -235,7 +232,7 @@ export function LensEditConfigurationFlyout({ updatePanelState, updateSuggestion, updateByRefInput, - onCancelCb, + onCancelCallback, ]); const textBasedMode = useMemo( @@ -244,6 +241,9 @@ export function LensEditConfigurationFlyout({ ); const onApply = useCallback(() => { + if (visualization.activeId == null) { + return; + } const dsStates = Object.fromEntries( Object.entries(datasourceStates).map(([id, ds]) => { const dsState = ds.state; @@ -265,7 +265,7 @@ export function LensEditConfigurationFlyout({ activeVisualization, }) : []; - const attrs = { + const attrs: TypedLensSerializedState['attributes'] = { ...attributes, state: { ...attributes.state, @@ -293,18 +293,18 @@ export function LensEditConfigurationFlyout({ trackSaveUiCounterEvents(telemetryEvents); } - onApplyCb?.(attrs as TypedLensByValueInput['attributes']); + onApplyCallback?.(attrs); closeFlyout?.(); }, [ + visualization.activeId, + savedObjectId, + closeFlyout, + onApplyCallback, datasourceStates, textBasedMode, visualization.state, - visualization.activeId, activeVisualization, attributes, - savedObjectId, - onApplyCb, - closeFlyout, datasourceMap, saveByRef, updateByRefInput, diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/types.ts b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/types.ts index d2aceb323773..d31a518cf80e 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/types.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Observable } from 'rxjs'; import type { CoreStart } from '@kbn/core/public'; -import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; +import type { PublishingSubject } from '@kbn/presentation-publishing'; +import type { TypedLensSerializedState } from '../../../react_embeddable/types'; import type { LensPluginStartDependencies } from '../../../plugin'; import type { DatasourceMap, @@ -14,9 +14,8 @@ import type { FramePublicAPI, UserMessagesGetter, } from '../../../types'; -import type { LensEmbeddableOutput } from '../../../embeddable'; import type { LensInspector } from '../../../lens_inspector_service'; -import type { Document } from '../../../persistence'; +import type { LensDocument } from '../../../persistence'; export interface FlyoutWrapperProps { children: JSX.Element; @@ -37,22 +36,22 @@ export interface EditConfigPanelProps { visualizationMap: VisualizationMap; datasourceMap: DatasourceMap; /** The attributes of the Lens embeddable */ - attributes: TypedLensByValueInput['attributes']; + attributes: TypedLensSerializedState['attributes']; /** Callback for updating the visualization and datasources state.*/ updatePanelState: ( datasourceState: unknown, visualizationState: unknown, - visualizationType?: string + visualizationId?: string ) => void; - updateSuggestion?: (attrs: TypedLensByValueInput['attributes']) => void; + updateSuggestion?: (attrs: TypedLensSerializedState['attributes']) => void; /** Set the attributes state */ - setCurrentAttributes?: (attrs: TypedLensByValueInput['attributes']) => void; + setCurrentAttributes?: (attrs: TypedLensSerializedState['attributes']) => void; /** Lens visualizations can be either created from ESQL (textBased) or from dataviews (formBased) */ datasourceId: 'formBased' | 'textBased'; /** Embeddable output observable, useful for dashboard flyout */ - output$?: Observable<LensEmbeddableOutput>; + dataLoading$?: PublishingSubject<boolean | undefined>; /** Contains the active data, necessary for some panel configuration such as coloring */ - lensAdapters?: LensInspector['adapters']; + lensAdapters?: ReturnType<LensInspector['getInspectorAdapters']>; /** Optional callback called when updating the by reference embeddable */ updateByRefInput?: (soId: string) => void; /** Callback for closing the edit flyout */ @@ -69,7 +68,7 @@ export interface EditConfigPanelProps { */ savedObjectId?: string; /** Callback for saving the embeddable as a SO */ - saveByRef?: (attrs: Document) => void; + saveByRef?: (attrs: LensDocument) => void; /** Optional callback for navigation from the header of the flyout */ navigateToLensEditor?: () => void; /** If set to true it displays a header on the flyout */ @@ -78,21 +77,19 @@ export interface EditConfigPanelProps { canEditTextBasedQuery?: boolean; /** The flyout is used for adding a new panel by scratch */ isNewPanel?: boolean; - /** Handler for deleting the embeddable, used in case a user cancels a newly created chart */ - deletePanel?: () => void; /** If set to true the layout changes to accordion and the text based query (i.e. ES|QL) can be edited */ hidesSuggestions?: boolean; - /** Optional callback for apply flyout button */ - onApplyCb?: (input: TypedLensByValueInput['attributes']) => void; - /** Optional callback for cancel flyout button */ - onCancelCb?: () => void; + /** Apply button handler */ + onApply?: (attrs: TypedLensSerializedState['attributes']) => void; + /** Cancel button handler */ + onCancel?: () => void; // in cases where the embeddable is not filtered by time // (e.g. through unified search) set this property to true hideTimeFilterInfo?: boolean; } export interface LayerConfigurationProps { - attributes: TypedLensByValueInput['attributes']; + attributes: TypedLensSerializedState['attributes']; coreStart: CoreStart; startDependencies: LensPluginStartDependencies; visualizationMap: VisualizationMap; diff --git a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts index fa9268c0374e..f35443a51014 100644 --- a/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts +++ b/x-pack/plugins/lens/public/app_plugin/show_underlying_data.ts @@ -16,6 +16,7 @@ import { EsQueryConfig, isOfQueryType, AggregateQuery, + isOfAggregateQueryType, } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { RecursiveReadonly } from '@kbn/utility-types'; @@ -219,8 +220,9 @@ export function combineQueryAndFilters( }; const allQueries = Array.isArray(query) ? query : query && isOfQueryType(query) ? [query] : []; - const nonEmptyQueries = allQueries.filter((q) => - Boolean(typeof q.query === 'string' ? q.query.trim() : q.query) + const nonEmptyQueries = allQueries.filter( + (q) => + !isOfAggregateQueryType(q) && Boolean(typeof q.query === 'string' ? q.query.trim() : q.query) ); [queries.lucene, queries.kuery] = partition(nonEmptyQueries, (q) => q.language === 'lucene'); diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index 317efd5be507..4791dc89d446 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -55,15 +55,15 @@ import type { UserMessagesGetter, StartServices, } from '../types'; -import type { LensAttributeService } from '../lens_attribute_service'; -import type { LensEmbeddableInput } from '../embeddable/embeddable'; +import type { LensAttributesService } from '../lens_attribute_service'; import type { LensInspector } from '../lens_inspector_service'; import type { IndexPatternServiceAPI } from '../data_views_service/service'; -import type { Document, SavedObjectIndexStore } from '../persistence/saved_object_store'; +import type { LensDocument, SavedObjectIndexStore } from '../persistence/saved_object_store'; import type { LensAppLocator, LensAppLocatorParams } from '../../common/locator/locator'; +import { LensSerializedState } from '../react_embeddable/types'; export interface RedirectToOriginProps { - input?: LensEmbeddableInput; + state?: LensSerializedState; isCopied?: boolean; } @@ -76,7 +76,7 @@ export interface LensAppProps { redirectToOrigin?: (props?: RedirectToOriginProps) => void; // The initial input passed in by the container when editing. Can be either by reference or by value. - initialInput?: LensEmbeddableInput; + initialInput?: LensSerializedState; // State passed in by the container which is used to determine the id of the Originating App. incomingState?: EmbeddableEditorState; @@ -110,7 +110,7 @@ export interface LensTopNavMenuProps { redirectToOrigin?: (props?: RedirectToOriginProps) => void; // The initial input passed in by the container when editing. Can be either by reference or by value. - initialInput?: LensEmbeddableInput; + initialInput?: LensSerializedState; getIsByValueMode: () => boolean; indicateNoData: boolean; setIsSaveModalVisible: React.Dispatch<React.SetStateAction<boolean>>; @@ -124,7 +124,7 @@ export interface LensTopNavMenuProps { initialContextIsEmbedded?: boolean; topNavMenuEntryGenerators: LensTopNavMenuEntryGenerator[]; initialContext?: VisualizeFieldContext | VisualizeEditorContext; - currentDoc: Document | undefined; + currentDoc: LensDocument | undefined; indexPatternService: IndexPatternServiceAPI; getUserMessages: UserMessagesGetter; shortUrlService: (params: LensAppLocatorParams) => Promise<string>; @@ -156,7 +156,7 @@ export interface LensAppServices extends StartServices { usageCollection?: UsageCollectionStart; stateTransfer: EmbeddableStateTransfer; navigation: NavigationPublicPluginStart; - attributeService: LensAttributeService; + attributeService: LensAttributesService; contentManagement: ContentManagementPublicStart; savedObjectsTagging?: SavedObjectTaggingPluginStart; getOriginatingAppName: () => string | undefined; diff --git a/x-pack/plugins/lens/public/async_services.ts b/x-pack/plugins/lens/public/async_services.ts index 28becae5e607..e5523b38b525 100644 --- a/x-pack/plugins/lens/public/async_services.ts +++ b/x-pack/plugins/lens/public/async_services.ts @@ -43,13 +43,11 @@ export * from './lens_ui_telemetry'; export * from './lens_ui_errors'; export * from './editor_frame_service/editor_frame'; export * from './editor_frame_service'; -export * from './embeddable'; export * from './app_plugin/mounter'; export * from './lens_attribute_service'; export * from './app_plugin/save_modal_container'; export * from './chart_info_api'; export * from './trigger_actions/open_in_discover_helpers'; -export * from './trigger_actions/open_lens_config/edit_action_helpers'; export * from './trigger_actions/open_lens_config/create_action_helpers'; export * from './trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers'; diff --git a/x-pack/plugins/lens/public/chart_info_api.test.ts b/x-pack/plugins/lens/public/chart_info_api.test.ts index c302d4e934eb..f647e2289c5b 100644 --- a/x-pack/plugins/lens/public/chart_info_api.test.ts +++ b/x-pack/plugins/lens/public/chart_info_api.test.ts @@ -6,9 +6,9 @@ */ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; -import type { EditorFrameService } from './editor_frame_service'; import { createChartInfoApi } from './chart_info_api'; -import type { LensSavedObjectAttributes } from '.'; +import { LensDocument } from './persistence'; +import { DatasourceMap, VisualizationMap } from './types'; const mockGetVisualizationInfo = jest.fn().mockReturnValue({ layers: [ @@ -37,18 +37,19 @@ const mockGetDatasourceInfo = jest.fn().mockResolvedValue([ describe('createChartInfoApi', () => { const dataViews = dataViewPluginMocks.createStartContract(); test('get correct chart info', async () => { - const chartInfoApi = await createChartInfoApi(dataViews, { - loadVisualizations: () => ({ + const chartInfoApi = await createChartInfoApi( + dataViews, + { lnsXY: { getVisualizationInfo: mockGetVisualizationInfo, }, - }), - loadDatasources: () => ({ + } as unknown as VisualizationMap, + { from_based: { getDatasourceInfo: mockGetDatasourceInfo, }, - }), - } as unknown as EditorFrameService); + } as unknown as DatasourceMap + ); const vis = { title: 'xy', visualizationType: 'lnsXY', @@ -69,7 +70,7 @@ describe('createChartInfoApi', () => { query: '', }, references: [], - } as LensSavedObjectAttributes; + } as LensDocument; const chartInfo = await chartInfoApi.getChartInfo(vis); diff --git a/x-pack/plugins/lens/public/chart_info_api.ts b/x-pack/plugins/lens/public/chart_info_api.ts index d2661226cdf1..ace9ab445dba 100644 --- a/x-pack/plugins/lens/public/chart_info_api.ts +++ b/x-pack/plugins/lens/public/chart_info_api.ts @@ -5,23 +5,22 @@ * 2.0. */ -import type { Filter, Query } from '@kbn/es-query'; +import type { AggregateQuery, Filter, Query } from '@kbn/es-query'; import type { IconType } from '@elastic/eui/src/components/icon/icon'; import type { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { getActiveDatasourceIdFromDoc } from './utils'; -import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; -import type { OperationDescriptor } from './types'; -import type { LensSavedObjectAttributes } from '.'; +import type { DatasourceMap, OperationDescriptor, VisualizationMap } from './types'; +import { LensDocument } from './persistence'; export type ChartInfoApi = Promise<{ - getChartInfo: (vis: LensSavedObjectAttributes) => Promise<ChartInfo | undefined>; + getChartInfo: (vis: LensDocument) => Promise<ChartInfo | undefined>; }>; export interface ChartInfo { layers: ChartLayerDescriptor[]; visualizationType: string; filters: Filter[]; - query: Query; + query: Query | AggregateQuery; } export interface ChartLayerDescriptor { @@ -42,17 +41,14 @@ export interface ChartLayerDescriptor { export const createChartInfoApi = async ( dataViews: DataViewsPublicPluginStart, - editorFrameService?: EditorFrameServiceType + visualizationMap: VisualizationMap, + datasourceMap: DatasourceMap ): ChartInfoApi => { - const [visualizationMap, datasourceMap] = await Promise.all([ - editorFrameService!.loadVisualizations(), - editorFrameService!.loadDatasources(), - ]); return { - async getChartInfo(vis: LensSavedObjectAttributes): Promise<ChartInfo | undefined> { + async getChartInfo(vis: LensDocument): Promise<ChartInfo | undefined> { const lensVis = vis; const activeDatasourceId = getActiveDatasourceIdFromDoc(lensVis); - if (!activeDatasourceId || !lensVis?.visualizationType) { + if (!activeDatasourceId || lensVis?.visualizationType == null) { return undefined; } diff --git a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx index e356a59956f0..ff51014f548d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx @@ -12,6 +12,7 @@ import { EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { CoreStart } from '@kbn/core/public'; +import { Query } from '@kbn/es-query'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { type DataView, DataViewField, FieldSpec } from '@kbn/data-plugin/common'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -42,7 +43,7 @@ import { IndexPatternServiceAPI } from '../../data_views_service/service'; import { FieldItem } from '../common/field_item'; export type FormBasedDataPanelProps = Omit< - DatasourceDataPanelProps<FormBasedPrivateState>, + DatasourceDataPanelProps<FormBasedPrivateState, Query>, 'core' | 'onChangeIndexPattern' > & { data: DataPublicPluginStart; @@ -185,7 +186,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ showNoDataPopover, activeIndexPatterns, }: Omit< - DatasourceDataPanelProps, + DatasourceDataPanelProps<unknown, Query>, 'state' | 'setState' | 'core' | 'onChangeIndexPattern' | 'usedIndexPatterns' > & { data: DataPublicPluginStart; diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts index b399f8eaa7b5..cd26abe0fdd8 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts @@ -51,6 +51,7 @@ import { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; import { filterAndSortUserMessages } from '../../app_plugin/get_application_user_messages'; import { createMockFramePublicAPI } from '../../mocks'; import { createMockDataViewsState } from '../../data_views_service/mocks'; +import { Query } from '@kbn/es-query'; jest.mock('./loader'); jest.mock('../../id_generator'); @@ -193,7 +194,7 @@ const dateRange = { describe('IndexPattern Data Source', () => { let baseState: FormBasedPrivateState; - let FormBasedDatasource: Datasource<FormBasedPrivateState, FormBasedPersistedState>; + let FormBasedDatasource: Datasource<FormBasedPrivateState, FormBasedPersistedState, Query>; beforeEach(() => { const data = dataPluginMock.createStartContract(); diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx index da893707ab2b..ebe98c56adeb 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type { CoreStart, SavedObjectReference } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { TimeRange } from '@kbn/es-query'; +import { Query, TimeRange } from '@kbn/es-query'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { flatten, isEqual } from 'lodash'; @@ -28,7 +28,6 @@ import memoizeOne from 'memoize-one'; import type { DatasourceDimensionEditorProps, DatasourceDimensionTriggerProps, - DatasourceDataPanelProps, DatasourceLayerPanelProps, PublicAPIProps, OperationDescriptor, @@ -40,6 +39,7 @@ import type { UserMessage, StateSetter, IndexPatternMap, + DatasourceDataPanelProps, } from '../../types'; import { changeIndexPattern, @@ -217,7 +217,7 @@ export function getFormBasedDatasource({ const ALIAS_IDS = ['indexpattern']; // Not stateful. State is persisted to the frame - const formBasedDatasource: Datasource<FormBasedPrivateState, FormBasedPersistedState> = { + const formBasedDatasource: Datasource<FormBasedPrivateState, FormBasedPersistedState, Query> = { id: DATASOURCE_ID, alias: ALIAS_IDS, @@ -464,7 +464,7 @@ export function getFormBasedDatasource({ LayerSettingsComponent(props) { return <LayerSettingsPanel {...props} />; }, - DataPanelComponent(props: DatasourceDataPanelProps<FormBasedPrivateState>) { + DataPanelComponent(props: DatasourceDataPanelProps<FormBasedPrivateState, Query>) { const { onChangeIndexPattern, ...otherProps } = props; const layerFields = formBasedDatasource?.getSelectedFields?.(props.state); return ( @@ -869,13 +869,11 @@ export function getFormBasedDatasource({ getDatasourceInfo: async (state, references, dataViewsService) => { const layers = references ? injectReferences(state, references).layers : state.layers; - const indexPatterns: DataView[] = []; - for (const { indexPatternId } of Object.values(layers)) { - const dataView = await dataViewsService?.get(indexPatternId); - if (dataView) { - indexPatterns.push(dataView); - } - } + const indexPatterns: DataView[] = await Promise.all( + Object.values(layers) + .map(({ indexPatternId }) => dataViewsService?.get(indexPatternId)) + .filter(nonNullable) + ); return Object.entries(layers).reduce<DataSourceInfo[]>((acc, [key, layer]) => { const dataView = indexPatterns?.find( (indexPattern) => indexPattern.id === layer.indexPatternId diff --git a/x-pack/plugins/lens/public/datasources/form_based/mocks.ts b/x-pack/plugins/lens/public/datasources/form_based/mocks.ts index fcefa97ecd4b..f98107eebbcc 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/mocks.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/mocks.ts @@ -8,101 +8,83 @@ import { getFieldByNameFactory } from './pure_helpers'; import type { IndexPattern, IndexPatternField } from '../../types'; +export function createMockedField( + someProps: Partial<IndexPatternField> & Pick<IndexPatternField, 'name' | 'type'> +) { + return { + displayName: someProps.name, + aggregatable: true, + searchable: true, + ...someProps, + }; +} + export const createMockedIndexPattern = ( someProps?: Partial<IndexPattern>, customFields: IndexPatternField[] = [] ): IndexPattern => { const fields = [ - { + createMockedField({ name: 'timestamp', displayName: 'timestampLabel', type: 'date', - aggregatable: true, - searchable: true, - }, - { + }), + createMockedField({ name: 'start_date', - displayName: 'start_date', type: 'date', - aggregatable: true, - searchable: true, - }, - { + }), + createMockedField({ name: 'bytes', - displayName: 'bytes', type: 'number', - aggregatable: true, - searchable: true, - }, - { + }), + createMockedField({ name: 'memory', - displayName: 'memory', type: 'number', - aggregatable: true, - searchable: true, esTypes: ['float'], - }, - { + }), + createMockedField({ name: 'source', - displayName: 'source', type: 'string', - aggregatable: true, - searchable: true, esTypes: ['keyword'], - }, - { + }), + createMockedField({ name: 'unsupported', - displayName: 'unsupported', type: 'geo', - aggregatable: true, - searchable: true, - }, - { + }), + createMockedField({ name: 'dest', - displayName: 'dest', type: 'string', - aggregatable: true, - searchable: true, esTypes: ['keyword'], - }, - { + }), + createMockedField({ name: 'geo.src', - displayName: 'geo.src', type: 'string', - aggregatable: true, - searchable: true, esTypes: ['keyword'], - }, - { + }), + createMockedField({ name: 'scripted', displayName: 'Scripted', type: 'string', - searchable: true, - aggregatable: true, scripted: true, lang: 'painless' as const, script: '1234', - }, - { + }), + createMockedField({ name: 'runtime-keyword', displayName: 'Runtime keyword field', type: 'string', - searchable: true, - aggregatable: true, runtime: true, lang: 'painless' as const, script: 'emit("123")', - }, - { + }), + createMockedField({ name: 'runtime-number', displayName: 'Runtime number field', type: 'number', - searchable: true, - aggregatable: true, runtime: true, lang: 'painless' as const, script: 'emit(123)', - }, + }), ...(customFields || []), ]; return { @@ -120,31 +102,23 @@ export const createMockedIndexPattern = ( export const createMockedRestrictedIndexPattern = () => { const fields = [ - { + createMockedField({ name: 'timestamp', displayName: 'timestampLabel', type: 'date', - aggregatable: true, - searchable: true, - }, - { + }), + createMockedField({ name: 'bytes', - displayName: 'bytes', type: 'number', - aggregatable: true, - searchable: true, - }, - { + }), + createMockedField({ name: 'source', - displayName: 'source', type: 'string', - aggregatable: true, - searchable: true, scripted: true, esTypes: ['keyword'], lang: 'painless' as const, script: '1234', - }, + }), ]; return { id: '2', diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx index 411583d88ef1..6a9471e174e8 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx @@ -362,7 +362,7 @@ export function getTextBasedDatasource({ getUsedDataViews: (state) => { return Object.values(state.layers) .map(({ index }) => index) - .filter((index) => index !== undefined) as string[]; + .filter(nonNullable); }, getPersistableState({ layers }: TextBasedPrivateState) { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/easteregg/index.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/easteregg/index.tsx index 7bfd7c666079..3372625ff283 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/easteregg/index.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/easteregg/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import type { Query } from '@kbn/es-query'; +import { AggregateQuery, isOfAggregateQueryType, Query } from '@kbn/es-query'; import { EuiErrorBoundary } from '@elastic/eui'; const Bee = React.lazy(() => import('./bee')); @@ -34,11 +34,14 @@ function Bees({ query }: { query?: Query }) { ); } -export function Easteregg(props: { query?: Query }) { +export function Easteregg(props: { query?: Query | AggregateQuery }) { + if (isOfAggregateQueryType(props.query)) { + return null; + } return ( // Do not break Lens for an easteregg <EuiErrorBoundary style={{ display: 'none' }}> - <Bees {...props} /> + <Bees query={props.query} /> </EuiErrorBoundary> ); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index 466773ec1c6b..efe3ccc84f56 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -33,7 +33,7 @@ import type { SuggestionRequest, } from '../../types'; import { buildExpression } from './expression_helpers'; -import { Document } from '../../persistence/saved_object_store'; +import { LensDocument } from '../../persistence/saved_object_store'; import { getActiveDatasourceIdFromDoc, sortDataViewRefs } from '../../utils'; import type { DatasourceState, DatasourceStates, VisualizationState } from '../../state_management'; import { readFromStorage } from '../../settings_storage'; @@ -353,12 +353,13 @@ export interface DocumentToExpressionReturnType { indexPatterns: IndexPatternMap; indexPatternRefs: IndexPatternRef[]; activeVisualizationState: unknown; + activeDatasourceState: unknown; } export async function persistedStateToExpression( datasourceMap: DatasourceMap, visualizations: VisualizationMap, - doc: Document, + doc: LensDocument, services: { uiSettings: IUiSettingsClient; storage: IStorageWrapper; @@ -381,7 +382,13 @@ export async function persistedStateToExpression( description, } = doc; if (!visualizationType) { - return { ast: null, indexPatterns: {}, indexPatternRefs: [], activeVisualizationState: null }; + return { + ast: null, + indexPatterns: {}, + indexPatternRefs: [], + activeVisualizationState: null, + activeDatasourceState: null, + }; } const annotationGroups = await initializeEventAnnotationGroups( @@ -435,6 +442,7 @@ export async function persistedStateToExpression( indexPatterns, indexPatternRefs, activeVisualizationState, + activeDatasourceState: null, }; } @@ -454,6 +462,7 @@ export async function persistedStateToExpression( nowInstant: services.nowProvider.get(), }), activeVisualizationState, + activeDatasourceState: datasourceStates[datasourceId]?.state, indexPatterns, indexPatternRefs, }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index b32d7456bd2b..5775748da8ce 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -248,7 +248,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ const removeExpressionBuildErrorsRef = useRef<() => void>(); const onData$ = useCallback( - (_data: unknown, adapters?: Partial<DefaultInspectorAdapters>) => { + (_data: unknown, adapters?: DefaultInspectorAdapters) => { if (renderDeps.current) { dataReceivedTime.current = performance.now(); @@ -283,10 +283,11 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ dispatchLens( onActiveDataChange({ activeData: Object.entries(adapters.tables?.tables).reduce<Record<string, Datatable>>( - (acc, [key, value], _index, tables) => ({ - ...acc, - [tables.length === 1 ? defaultLayerId : key]: value, - }), + (acc, [key, value], _index, tables) => { + const id = tables.length === 1 ? defaultLayerId : key; + acc[id] = value as Datatable; + return acc; + }, {} ), }) @@ -726,7 +727,7 @@ export const VisualizationWrapper = ({ ExpressionRendererComponent: ReactExpressionRendererType; core: CoreStart; onRender$: () => void; - onData$: (data: unknown, adapters?: Partial<DefaultInspectorAdapters>) => void; + onData$: (data: unknown, adapters?: DefaultInspectorAdapters) => void; onComponentRendered: () => void; displayOptions: VisualizationDisplayOptions | undefined; }) => { @@ -788,7 +789,7 @@ export const VisualizationWrapper = ({ // @ts-expect-error upgrade typescript v4.9.5 onData$={onData$} onRender$={onRenderHandler} - inspectorAdapters={lensInspector.adapters} + inspectorAdapters={lensInspector.getInspectorAdapters()} executionContext={executionContext} renderMode="edit" renderError={(errorMessage?: string | null, error?: ExpressionRenderError | null) => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 71cf62d02d38..a677e0c6105b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -24,7 +24,7 @@ import { DataViewsPublicPluginStart, } from '@kbn/data-views-plugin/public'; import { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'; -import { Document } from '../persistence/saved_object_store'; +import { LensDocument } from '../persistence/saved_object_store'; import { Datasource, Visualization, @@ -93,7 +93,7 @@ export class EditorFrameService { * This is an asynchronous process. * @param doc parsed Lens saved object */ - public documentToExpression = async (doc: Document, services: EditorFramePlugins) => { + public documentToExpression = async (doc: LensDocument, services: EditorFramePlugins) => { const [resolvedDatasources, resolvedVisualizations] = await Promise.all([ this.loadDatasources(), this.loadVisualizations(), diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx deleted file mode 100644 index 3dda0daf2576..000000000000 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ /dev/null @@ -1,1373 +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 React from 'react'; -import { - Embeddable, - LensByValueInput, - LensUnwrapMetaInfo, - LensEmbeddableInput, - LensByReferenceInput, - LensSavedObjectAttributes, - LensUnwrapResult, - LensEmbeddableDeps, -} from './embeddable'; -import { ReactExpressionRendererProps } from '@kbn/expressions-plugin/public'; -import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; -import { Filter, Query, TimeRange } from '@kbn/es-query'; -import { FilterManager } from '@kbn/data-plugin/public'; -import type { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { Document } from '../persistence'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public/embeddable'; -import { coreMock, httpServiceMock } from '@kbn/core/public/mocks'; -import { IBasePath, IUiSettingsClient } from '@kbn/core/public'; -import { AttributeService, ViewMode } from '@kbn/embeddable-plugin/public'; -import { LensAttributeService } from '../lens_attribute_service'; -import { OnSaveProps } from '@kbn/saved-objects-plugin/public/save_modal'; -import { act } from 'react-dom/test-utils'; -import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks'; -import { Visualization } from '../types'; -import { createMockDatasource, createMockVisualization } from '../mocks'; -import { FIELD_NOT_FOUND, FIELD_WRONG_TYPE } from '../user_messages_ids'; - -jest.mock('@kbn/inspector-plugin/public', () => ({ - isAvailable: false, - open: false, -})); - -const defaultVisualizationId = 'lnsSomeVisType'; -const defaultDatasourceId = 'someDatasource'; - -const savedVis: Document = { - state: { - visualization: { activeId: defaultVisualizationId }, - datasourceStates: { [defaultDatasourceId]: {} }, - query: { query: '', language: 'lucene' }, - filters: [], - }, - references: [], - title: 'My title', - visualizationType: defaultVisualizationId, -}; - -const defaultVisualizationMap = { - [defaultVisualizationId]: createMockVisualization(), -}; - -const defaultDatasourceMap = { - [defaultDatasourceId]: createMockDatasource(defaultDatasourceId), -}; - -const defaultSaveMethod = ( - _testAttributes: LensSavedObjectAttributes, - _savedObjectId?: string -): Promise<{ id: string }> => { - return new Promise(() => { - return { id: '123' }; - }); -}; -const defaultUnwrapMethod = ( - _savedObjectId: string -): Promise<{ attributes: LensSavedObjectAttributes }> => { - return new Promise(() => { - return { attributes: { ...savedVis } }; - }); -}; -const defaultCheckForDuplicateTitle = (_props: OnSaveProps): Promise<true> => { - return new Promise(() => { - return true; - }); -}; -const options = { - saveMethod: defaultSaveMethod, - unwrapMethod: defaultUnwrapMethod, - checkForDuplicateTitle: defaultCheckForDuplicateTitle, -}; - -const mockInjectFilterReferences: FilterManager['inject'] = (filters, _references) => { - return filters.map((filter) => ({ - ...filter, - meta: { - ...filter.meta, - index: 'injected!', - }, - })); -}; - -const attributeServiceMockFromSavedVis = (document: Document): LensAttributeService => { - const core = coreMock.createStart(); - const service = new AttributeService< - LensSavedObjectAttributes, - LensByValueInput, - LensByReferenceInput, - LensUnwrapMetaInfo - >('lens', core.notifications.toasts, options); - service.unwrapAttributes = jest.fn((_input: LensByValueInput | LensByReferenceInput) => { - return Promise.resolve({ - attributes: { - ...document, - }, - metaInfo: { - sharingSavedObjectProps: { - outcome: 'exactMatch', - }, - }, - } as LensUnwrapResult); - }); - service.wrapAttributes = jest.fn(); - return service; -}; - -const dataMock = dataPluginMock.createStartContract(); - -describe('embeddable', () => { - const coreStart = coreMock.createStart(); - - let mountpoint: HTMLDivElement; - let expressionRenderer: jest.Mock<null, [ReactExpressionRendererProps]>; - let getTrigger: jest.Mock; - let trigger: { exec: jest.Mock }; - let basePath: IBasePath; - let attributeService: AttributeService< - LensSavedObjectAttributes, - LensByValueInput, - LensByReferenceInput, - LensUnwrapMetaInfo - >; - - beforeEach(() => { - mountpoint = document.createElement('div'); - expressionRenderer = jest.fn((_props) => null); - trigger = { exec: jest.fn() }; - getTrigger = jest.fn(() => trigger); - attributeService = attributeServiceMockFromSavedVis(savedVis); - const http = httpServiceMock.createSetupContract({ basePath: '/test' }); - basePath = http.basePath; - }); - - afterEach(() => { - mountpoint.remove(); - }); - - function getEmbeddableProps(props: Partial<LensEmbeddableDeps> = {}): LensEmbeddableDeps { - return { - timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, - attributeService, - data: dataMock, - uiSettings: { get: () => undefined } as unknown as IUiSettingsClient, - inspector: inspectorPluginMock.createStartContract(), - expressionRenderer, - coreStart, - basePath, - dataViews: { - get: (id: string) => Promise.resolve({ id, isTimeBased: () => false }), - } as unknown as DataViewsContract, - capabilities: { - canSaveDashboards: true, - canSaveVisualizations: true, - canOpenVisualizations: true, - discover: {}, - navLinks: {}, - }, - getTrigger, - visualizationMap: defaultVisualizationMap, - datasourceMap: defaultDatasourceMap, - injectFilterReferences: jest.fn(mockInjectFilterReferences), - documentToExpression: () => - Promise.resolve({ - ast: { - type: 'expression', - chain: [ - { type: 'function', function: 'my', arguments: {} }, - { type: 'function', function: 'expression', arguments: {} }, - ], - }, - indexPatterns: {}, - indexPatternRefs: [], - activeVisualizationState: null, - }), - ...props, - }; - } - - it('should render expression once with expression renderer', async () => { - const embeddable = new Embeddable(getEmbeddableProps(), { - timeRange: { - from: 'now-15m', - to: 'now', - }, - } as LensEmbeddableInput); - embeddable.render(mountpoint); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - expect(expressionRenderer.mock.calls[0][0]!.expression).toEqual(`my -| expression`); - }); - - it('should not throw if render is called after destroy', async () => { - const embeddable = new Embeddable(getEmbeddableProps(), { - timeRange: { - from: 'now-15m', - to: 'now', - }, - } as LensEmbeddableInput); - let renderCalled = false; - let renderThrew = false; - // destroying completes output synchronously which might make a synchronous render call - this shouldn't throw - embeddable.getOutput$().subscribe(undefined, undefined, () => { - try { - embeddable.render(mountpoint); - } catch (e) { - renderThrew = true; - } finally { - renderCalled = true; - } - }); - embeddable.destroy(); - expect(renderCalled).toBe(true); - expect(renderThrew).toBe(false); - }); - - it('should render once even if reload is called before embeddable is fully initialized', async () => { - const embeddable = new Embeddable(getEmbeddableProps(), { - timeRange: { - from: 'now-15m', - to: 'now', - }, - } as LensEmbeddableInput); - embeddable.reload(); - expect(expressionRenderer).toHaveBeenCalledTimes(0); - embeddable.render(mountpoint); - expect(expressionRenderer).toHaveBeenCalledTimes(0); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - }); - - it('should not render the visualization if any error arises', async () => { - const embeddable = new Embeddable(getEmbeddableProps(), {} as LensEmbeddableInput); - - jest.spyOn(embeddable, 'getUserMessages').mockReturnValue([ - { - uniqueId: 'error', - severity: 'error', - fixableInEditor: true, - displayLocations: [{ id: 'visualization' }], - longMessage: 'lol', - shortMessage: 'lol', - }, - ]); - - await embeddable.initializeSavedVis({} as LensEmbeddableInput); - embeddable.render(mountpoint); - - expect(expressionRenderer).toHaveBeenCalledTimes(0); - }); - - it('should override embeddableBadge message', async () => { - const getBadgeMessage = jest.fn( - (): ReturnType<NonNullable<LensEmbeddableInput['onBeforeBadgesRender']>> => [ - { - uniqueId: FIELD_NOT_FOUND, - severity: 'warning', - fixableInEditor: true, - displayLocations: [ - { id: 'embeddableBadge' }, - { id: 'dimensionButton', dimensionId: '1' }, - ], - longMessage: 'custom', - shortMessage: '', - hidePopoverIcon: true, - }, - ] - ); - - const embeddable = new Embeddable( - getEmbeddableProps({ - datasourceMap: { - ...defaultDatasourceMap, - [defaultDatasourceId]: { - ...defaultDatasourceMap[defaultDatasourceId], - getUserMessages: jest.fn(() => [ - { - uniqueId: FIELD_NOT_FOUND, - severity: 'error', - fixableInEditor: true, - displayLocations: [ - { id: 'embeddableBadge' }, - { id: 'dimensionButton', dimensionId: '1' }, - ], - longMessage: 'original', - shortMessage: '', - }, - { - uniqueId: FIELD_WRONG_TYPE, - severity: 'error', - fixableInEditor: true, - displayLocations: [{ id: 'visualization' }], - longMessage: 'original', - shortMessage: '', - }, - ]), - }, - }, - }), - { - onBeforeBadgesRender: getBadgeMessage as LensEmbeddableInput['onBeforeBadgesRender'], - } as LensEmbeddableInput - ); - - const getUserMessagesSpy = jest.spyOn(embeddable, 'getUserMessages'); - await embeddable.initializeSavedVis({} as LensEmbeddableInput); - - embeddable.render(mountpoint); - - expect(getUserMessagesSpy.mock.results.flatMap((r) => r.value)).toEqual( - expect.arrayContaining([ - { - uniqueId: FIELD_WRONG_TYPE, - severity: 'error', - fixableInEditor: true, - displayLocations: [{ id: 'visualization' }], - longMessage: 'original', - shortMessage: '', - }, - { - uniqueId: FIELD_NOT_FOUND, - severity: 'warning', - fixableInEditor: true, - displayLocations: [ - { id: 'embeddableBadge' }, - { id: 'dimensionButton', dimensionId: '1' }, - ], - longMessage: 'custom', - shortMessage: '', - hidePopoverIcon: true, - }, - ]) - ); - }); - - it('should not render the vis if loaded saved object conflicts', async () => { - attributeService.unwrapAttributes = jest.fn( - (_input: LensByValueInput | LensByReferenceInput) => { - return Promise.resolve({ - attributes: { - ...savedVis, - }, - metaInfo: { - sharingSavedObjectProps: { - outcome: 'conflict', - sourceId: '1', - aliasTargetId: '2', - }, - }, - } as LensUnwrapResult); - } - ); - const spacesPluginStart = spacesPluginMock.createStartContract(); - spacesPluginStart.ui.components.getEmbeddableLegacyUrlConflict = jest.fn(() => ( - <>getEmbeddableLegacyUrlConflict</> - )); - const embeddable = new Embeddable( - getEmbeddableProps({ - spaces: spacesPluginStart, - attributeService, - }), - {} as LensEmbeddableInput - ); - await embeddable.initializeSavedVis({} as LensEmbeddableInput); - embeddable.render(mountpoint); - expect(expressionRenderer).toHaveBeenCalledTimes(0); - expect(spacesPluginStart.ui.components.getEmbeddableLegacyUrlConflict).toHaveBeenCalled(); - }); - - it('should not render if timeRange prop is not passed when a referenced data view is time based', async () => { - const embeddable = new Embeddable( - getEmbeddableProps({ - attributeService: attributeServiceMockFromSavedVis({ - ...savedVis, - references: [ - { type: 'index-pattern', id: '123', name: 'abc' }, - { type: 'index-pattern', id: '123', name: 'def' }, - { type: 'index-pattern', id: '456', name: 'ghi' }, - ], - }), - dataViews: { - get: (id: string) => Promise.resolve({ id, isTimeBased: () => true }), - } as unknown as DataViewsContract, - }), - {} as LensEmbeddableInput - ); - await embeddable.initializeSavedVis({} as LensEmbeddableInput); - embeddable.render(mountpoint); - expect(expressionRenderer).toHaveBeenCalledTimes(0); - }); - - it('should initialize output with deduped list of index patterns', async () => { - const embeddable = new Embeddable( - getEmbeddableProps({ - attributeService: attributeServiceMockFromSavedVis({ - ...savedVis, - references: [ - { type: 'index-pattern', id: '123', name: 'abc' }, - { type: 'index-pattern', id: '123', name: 'def' }, - { type: 'index-pattern', id: '456', name: 'ghi' }, - ], - }), - }), - {} as LensEmbeddableInput - ); - - await embeddable.initializeSavedVis({} as LensEmbeddableInput); - const outputIndexPatterns = embeddable.getOutput().indexPatterns!; - expect(outputIndexPatterns.length).toEqual(2); - expect(outputIndexPatterns[0].id).toEqual('123'); - expect(outputIndexPatterns[1].id).toEqual('456'); - }); - - it('should re-render once on filter change', async () => { - const embeddable = new Embeddable( - { - timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, - attributeService, - data: dataMock, - uiSettings: { get: () => undefined } as unknown as IUiSettingsClient, - expressionRenderer, - coreStart, - basePath, - inspector: inspectorPluginMock.createStartContract(), - dataViews: {} as DataViewsContract, - capabilities: { - canSaveDashboards: true, - canSaveVisualizations: true, - canOpenVisualizations: true, - discover: {}, - navLinks: {}, - }, - getTrigger, - visualizationMap: defaultVisualizationMap, - datasourceMap: defaultDatasourceMap, - injectFilterReferences: jest.fn(mockInjectFilterReferences), - documentToExpression: () => - Promise.resolve({ - ast: { - type: 'expression', - chain: [ - { type: 'function', function: 'my', arguments: {} }, - { type: 'function', function: 'expression', arguments: {} }, - ], - }, - indexPatterns: {}, - indexPatternRefs: [], - activeVisualizationState: null, - }), - }, - { id: '123' } as LensEmbeddableInput - ); - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - - embeddable.updateInput({ - filters: [{ meta: { alias: 'test', negate: false, disabled: false } }], - }); - - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(2); - }); - - it('should re-render once on search session change', async () => { - const embeddable = new Embeddable( - { - timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, - attributeService, - data: dataMock, - uiSettings: { get: () => undefined } as unknown as IUiSettingsClient, - expressionRenderer, - coreStart, - basePath, - inspector: inspectorPluginMock.createStartContract(), - dataViews: {} as DataViewsContract, - capabilities: { - canSaveDashboards: true, - canSaveVisualizations: true, - canOpenVisualizations: true, - discover: {}, - navLinks: {}, - }, - getTrigger, - visualizationMap: defaultVisualizationMap, - datasourceMap: defaultDatasourceMap, - injectFilterReferences: jest.fn(mockInjectFilterReferences), - documentToExpression: () => - Promise.resolve({ - ast: { - type: 'expression', - chain: [ - { type: 'function', function: 'my', arguments: {} }, - { type: 'function', function: 'expression', arguments: {} }, - ], - }, - indexPatterns: {}, - indexPatternRefs: [], - activeVisualizationState: null, - }), - }, - { id: '123', searchSessionId: 'firstSession' } as LensEmbeddableInput - ); - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - - embeddable.updateInput({ - searchSessionId: 'nextSession', - }); - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(2); - }); - - it('should re-render when dashboard view/edit mode changes if dynamic actions are set', async () => { - const sampleInput = { - id: '123', - enhancements: { - dynamicActions: {}, - }, - } as unknown as LensEmbeddableInput; - const embeddable = new Embeddable(getEmbeddableProps(), { id: '123' } as LensEmbeddableInput); - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - - embeddable.updateInput({ - viewMode: ViewMode.VIEW, - }); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - - embeddable.updateInput({ - ...sampleInput, - viewMode: ViewMode.VIEW, - }); - - expect(expressionRenderer).toHaveBeenCalledTimes(2); - }); - - it('should re-render when dynamic actions input changes', async () => { - const embeddable = new Embeddable(getEmbeddableProps(), { id: '123' } as LensEmbeddableInput); - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - - embeddable.updateInput({ - enhancements: { - dynamicActions: {}, - }, - }); - - expect(expressionRenderer).toHaveBeenCalledTimes(2); - }); - - it('should pass context to embeddable', async () => { - const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; - const query: Query = { language: 'kquery', query: '' }; - const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }]; - - const input = { - savedObjectId: '123', - timeRange, - query, - filters, - searchSessionId: 'searchSessionId', - } as LensEmbeddableInput; - - const embeddable = new Embeddable(getEmbeddableProps(), input); - await embeddable.initializeSavedVis(input); - embeddable.render(mountpoint); - - expect(expressionRenderer.mock.calls[0][0].searchContext).toEqual( - expect.objectContaining({ - timeRange, - query: [query, savedVis.state.query], - filters, - }) - ); - - expect(expressionRenderer.mock.calls[0][0].searchSessionId).toBe(input.searchSessionId); - }); - - it('should pass render mode to expression', async () => { - const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; - const query: Query = { language: 'kquery', query: '' }; - const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: false } }]; - - const input = { - savedObjectId: '123', - timeRange, - query, - filters, - renderMode: 'view', - disableTriggers: true, - } as LensEmbeddableInput; - - const embeddable = new Embeddable(getEmbeddableProps(), input); - await embeddable.initializeSavedVis(input); - embeddable.render(mountpoint); - - expect(expressionRenderer.mock.calls[0][0]).toEqual( - expect.objectContaining({ - interactive: false, - renderMode: 'view', - }) - ); - }); - - it('should merge external context with query and filters of the saved object', async () => { - const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; - const query: Query = { language: 'kquery', query: 'external query' }; - const filters: Filter[] = [ - { meta: { alias: 'external filter', negate: false, disabled: false } }, - ]; - - const newSavedVis = { - ...savedVis, - state: { - ...savedVis.state, - query: { language: 'kquery', query: 'saved filter' }, - filters: [{ meta: { alias: 'test', negate: false, disabled: false, index: 'filter-0' } }], - }, - references: [{ type: 'index-pattern', name: 'filter-0', id: 'my-index-pattern-id' }], - }; - - const input = { savedObjectId: '123', timeRange, query, filters } as LensEmbeddableInput; - - const embeddable = new Embeddable( - getEmbeddableProps({ attributeService: attributeServiceMockFromSavedVis(newSavedVis) }), - input - ); - await embeddable.initializeSavedVis(input); - embeddable.render(mountpoint); - - const expectedFilters = [ - ...input.filters!, - ...mockInjectFilterReferences(newSavedVis.state.filters, []), - ]; - expect(expressionRenderer.mock.calls[0][0].searchContext?.timeRange).toEqual(timeRange); - expect(expressionRenderer.mock.calls[0][0].searchContext?.filters).toEqual(expectedFilters); - expect(expressionRenderer.mock.calls[0][0].searchContext?.query).toEqual([ - query, - { language: 'kquery', query: 'saved filter' }, - ]); - }); - - it('should execute trigger on event from expression renderer', async () => { - const embeddable = new Embeddable(getEmbeddableProps(), { id: '123' } as LensEmbeddableInput); - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - const onEvent = expressionRenderer.mock.calls[0][0].onEvent!; - - const eventData = { myData: true, table: { rows: [], columns: [] }, column: 0 }; - onEvent({ name: 'brush', data: eventData }); - - expect(getTrigger).toHaveBeenCalledWith(VIS_EVENT_TO_TRIGGER.brush); - expect(trigger.exec).toHaveBeenCalledWith( - expect.objectContaining({ - data: { ...eventData, timeFieldName: undefined }, - embeddable: expect.anything(), - }) - ); - }); - - it('should execute trigger on row click event from expression renderer', async () => { - const embeddable = new Embeddable(getEmbeddableProps(), { id: '123' } as LensEmbeddableInput); - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - const onEvent = expressionRenderer.mock.calls[0][0].onEvent!; - - onEvent({ name: 'tableRowContextMenuClick', data: {} }); - - expect(getTrigger).toHaveBeenCalledWith(VIS_EVENT_TO_TRIGGER.tableRowContextMenuClick); - }); - - it('should not re-render if only change is in disabled filter', async () => { - const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; - const query: Query = { language: 'kquery', query: '' }; - const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }]; - - const embeddable = new Embeddable(getEmbeddableProps(), { - id: '123', - timeRange, - query, - filters, - } as LensEmbeddableInput); - await embeddable.initializeSavedVis({ - id: '123', - timeRange, - query, - filters, - } as LensEmbeddableInput); - embeddable.render(mountpoint); - - act(() => { - embeddable.updateInput({ - timeRange, - query, - filters: [{ meta: { alias: 'test', negate: true, disabled: true } }], - }); - }); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - }); - - it('should call onload after rerender and onData$ call ', async () => { - const onDataTimeout = 10; - const onLoad = jest.fn(); - const adapters = { tables: {} }; - - expressionRenderer = jest.fn(({ onData$ }) => { - setTimeout(() => { - onData$?.({}, adapters); - }, onDataTimeout); - - return null; - }); - - const embeddable = new Embeddable(getEmbeddableProps({ expressionRenderer }), { - id: '123', - onLoad, - } as unknown as LensEmbeddableInput); - - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - expect(onLoad).toHaveBeenCalledWith(true); - expect(onLoad).toHaveBeenCalledTimes(1); - - await new Promise((resolve) => setTimeout(resolve, onDataTimeout * 1.5)); - - // loading should become false - expect(onLoad).toHaveBeenCalledTimes(2); - expect(onLoad).toHaveBeenNthCalledWith(2, false, adapters, embeddable.getOutput$()); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - - embeddable.updateInput({ - searchSessionId: 'newSession', - }); - - await new Promise((resolve) => setTimeout(resolve, 0)); - - // loading should become again true - expect(onLoad).toHaveBeenCalledTimes(3); - expect(onLoad).toHaveBeenNthCalledWith(3, true); - expect(expressionRenderer).toHaveBeenCalledTimes(2); - - await new Promise((resolve) => setTimeout(resolve, onDataTimeout * 1.5)); - - // loading should again become false - expect(onLoad).toHaveBeenCalledTimes(4); - expect(onLoad).toHaveBeenNthCalledWith(4, false, adapters, embeddable.getOutput$()); - }); - - it('should call onFilter event on filter call ', async () => { - const onFilter = jest.fn(); - - expressionRenderer = jest.fn(({ onEvent }) => { - setTimeout(() => { - onEvent?.({ - name: 'filter', - data: { pings: false, table: { rows: [], columns: [] }, column: 0 }, - }); - }, 10); - - return null; - }); - - const embeddable = new Embeddable(getEmbeddableProps({ expressionRenderer }), { - id: '123', - onFilter, - } as unknown as LensEmbeddableInput); - - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - await new Promise((resolve) => setTimeout(resolve, 20)); - - expect(onFilter).toHaveBeenCalledWith(expect.objectContaining({ pings: false })); - expect(onFilter).toHaveBeenCalledTimes(1); - }); - - it('should prevent the onFilter trigger when calling preventDefault', async () => { - const onFilter = jest.fn(({ preventDefault }) => preventDefault()); - - expressionRenderer = jest.fn(({ onEvent }) => { - setTimeout(() => { - onEvent?.({ - name: 'filter', - data: { pings: false, table: { rows: [], columns: [] }, column: 0 }, - }); - }, 10); - - return null; - }); - - const embeddable = new Embeddable(getEmbeddableProps({ expressionRenderer }), { - id: '123', - onFilter, - } as unknown as LensEmbeddableInput); - - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - await new Promise((resolve) => setTimeout(resolve, 20)); - - expect(getTrigger).not.toHaveBeenCalled(); - }); - - it('should call onBrush event on brushing', async () => { - const onBrushEnd = jest.fn(); - - expressionRenderer = jest.fn(({ onEvent }) => { - setTimeout(() => { - onEvent?.({ - name: 'brush', - data: { range: [0, 1], table: { rows: [], columns: [] }, column: 0 }, - }); - }, 10); - - return null; - }); - - const embeddable = new Embeddable(getEmbeddableProps({ expressionRenderer }), { - id: '123', - onBrushEnd, - } as unknown as LensEmbeddableInput); - - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - await new Promise((resolve) => setTimeout(resolve, 20)); - - expect(onBrushEnd).toHaveBeenCalledWith(expect.objectContaining({ range: [0, 1] })); - expect(onBrushEnd).toHaveBeenCalledTimes(1); - }); - - it('should prevent the onBrush trigger when calling preventDefault', async () => { - const onBrushEnd = jest.fn(({ preventDefault }) => preventDefault()); - - expressionRenderer = jest.fn(({ onEvent }) => { - setTimeout(() => { - onEvent?.({ - name: 'brush', - data: { range: [0, 1], table: { rows: [], columns: [] }, column: 0 }, - }); - }, 10); - - return null; - }); - - const embeddable = new Embeddable(getEmbeddableProps({ expressionRenderer }), { - id: '123', - onBrushEnd, - } as unknown as LensEmbeddableInput); - - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - await new Promise((resolve) => setTimeout(resolve, 20)); - - expect(getTrigger).not.toHaveBeenCalled(); - }); - - it('should call onTableRowClick event ', async () => { - const onTableRowClick = jest.fn(); - - expressionRenderer = jest.fn(({ onEvent }) => { - setTimeout(() => { - onEvent?.({ name: 'tableRowContextMenuClick', data: { name: 'test' } }); - }, 10); - - return null; - }); - - const embeddable = new Embeddable(getEmbeddableProps({ expressionRenderer }), { - id: '123', - onTableRowClick, - } as unknown as LensEmbeddableInput); - - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - await new Promise((resolve) => setTimeout(resolve, 20)); - - expect(onTableRowClick).toHaveBeenCalledWith(expect.objectContaining({ name: 'test' })); - expect(onTableRowClick).toHaveBeenCalledTimes(1); - }); - - it('should prevent onTableRowClick trigger when calling preventDefault ', async () => { - const onTableRowClick = jest.fn(({ preventDefault }) => preventDefault()); - - expressionRenderer = jest.fn(({ onEvent }) => { - setTimeout(() => { - onEvent?.({ name: 'tableRowContextMenuClick', data: { name: 'test' } }); - }, 10); - - return null; - }); - - const embeddable = new Embeddable(getEmbeddableProps({ expressionRenderer }), { - id: '123', - onTableRowClick, - } as unknown as LensEmbeddableInput); - - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - await new Promise((resolve) => setTimeout(resolve, 20)); - - expect(getTrigger).not.toHaveBeenCalled(); - }); - - it('handles edit actions ', async () => { - const editedVisualizationState = { value: 'edited' }; - const onEditActionMock = jest.fn().mockReturnValue(editedVisualizationState); - const documentToExpressionMock = jest.fn().mockImplementation(async (document) => { - const isStateEdited = document.state.visualization.value === 'edited'; - return { - ast: { - type: 'expression', - chain: [ - { - type: 'function', - function: isStateEdited ? 'edited' : 'not_edited', - arguments: {}, - }, - ], - }, - indexPatterns: {}, - indexPatternRefs: [], - }; - }); - - const visDocument: Document = { - state: { - visualization: {}, - datasourceStates: { [defaultDatasourceId]: {} }, - query: { query: '', language: 'lucene' }, - filters: [], - }, - references: [], - title: 'My title', - visualizationType: 'lensDatatable', - }; - - const embeddable = new Embeddable( - getEmbeddableProps({ - attributeService: attributeServiceMockFromSavedVis(visDocument), - visualizationMap: { - [visDocument.visualizationType as string]: { - onEditAction: onEditActionMock, - initialize: () => {}, - } as unknown as Visualization, - }, - documentToExpression: documentToExpressionMock, - }), - { id: '123' } as unknown as LensEmbeddableInput - ); - - // SETUP FRESH STATE - await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); - embeddable.render(mountpoint); - - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - expect(expressionRenderer.mock.calls[0][0]!.expression).toBe(`not_edited`); - - // TEST EDIT EVENT - await embeddable.handleEvent({ name: 'edit' }); - - expect(onEditActionMock).toHaveBeenCalledTimes(1); - expect(documentToExpressionMock).toHaveBeenCalled(); - - const docToExpCalls = documentToExpressionMock.mock.calls; - const editedVisDocument = docToExpCalls[docToExpCalls.length - 1][0]; - expect(editedVisDocument.state.visualization).toEqual(editedVisualizationState); - - expect(expressionRenderer).toHaveBeenCalledTimes(2); - expect(expressionRenderer.mock.calls[1][0]!.expression).toBe(`edited`); - }); - - it('should override noPadding in the display options if noPadding is set in the embeddable input', async () => { - expressionRenderer = jest.fn((_) => null); - - const createEmbeddable = (displayOptions?: { noPadding: boolean }, noPadding?: boolean) => { - return new Embeddable( - { - timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, - attributeService: attributeServiceMockFromSavedVis(savedVis), - data: dataMock, - expressionRenderer, - coreStart, - basePath, - dataViews: {} as DataViewsContract, - capabilities: { - canSaveDashboards: true, - canSaveVisualizations: true, - canOpenVisualizations: true, - discover: {}, - navLinks: {}, - }, - inspector: inspectorPluginMock.createStartContract(), - getTrigger, - visualizationMap: { - [savedVis.visualizationType as string]: { - getDisplayOptions: displayOptions ? () => displayOptions : undefined, - initialize: () => {}, - } as unknown as Visualization, - }, - datasourceMap: defaultDatasourceMap, - injectFilterReferences: jest.fn(mockInjectFilterReferences), - documentToExpression: () => - Promise.resolve({ - ast: { - type: 'expression', - chain: [ - { type: 'function', function: 'my', arguments: {} }, - { type: 'function', function: 'expression', arguments: {} }, - ], - }, - indexPatterns: {}, - indexPatternRefs: [], - activeVisualizationState: null, - }), - uiSettings: { get: () => undefined } as unknown as IUiSettingsClient, - }, - { - timeRange: { - from: 'now-15m', - to: 'now', - }, - noPadding, - } as LensEmbeddableInput - ); - }; - - // no display options and no override - let embeddable = createEmbeddable(); - embeddable.render(mountpoint); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - expect(expressionRenderer.mock.calls[0][0]!.padding).toBe('s'); - - // display options and no override - embeddable = createEmbeddable({ noPadding: true }); - embeddable.render(mountpoint); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(2); - expect(expressionRenderer.mock.calls[1][0]!.padding).toBe(undefined); - - // no display options and override - embeddable = createEmbeddable(undefined, true); - embeddable.render(mountpoint); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(3); - expect(expressionRenderer.mock.calls[1][0]!.padding).toBe(undefined); - - // display options and override - embeddable = createEmbeddable({ noPadding: false }, true); - embeddable.render(mountpoint); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(4); - expect(expressionRenderer.mock.calls[1][0]!.padding).toBe(undefined); - }); - - it('should reload only once when the attributes or savedObjectId and the search context change at the same time', async () => { - const createEmbeddable = async () => { - const currentExpressionRenderer = jest.fn((_props) => null); - const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; - const query: Query = { language: 'kquery', query: '' }; - const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }]; - const embeddable = new Embeddable( - { - timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, - attributeService, - data: dataMock, - uiSettings: { get: () => undefined } as unknown as IUiSettingsClient, - expressionRenderer: currentExpressionRenderer, - coreStart, - basePath, - inspector: inspectorPluginMock.createStartContract(), - dataViews: {} as DataViewsContract, - capabilities: { - canSaveDashboards: true, - canSaveVisualizations: true, - canOpenVisualizations: true, - discover: {}, - navLinks: {}, - }, - getTrigger, - visualizationMap: defaultVisualizationMap, - datasourceMap: defaultDatasourceMap, - injectFilterReferences: jest.fn(mockInjectFilterReferences), - documentToExpression: () => - Promise.resolve({ - ast: { - type: 'expression', - chain: [ - { type: 'function', function: 'my', arguments: {} }, - { type: 'function', function: 'expression', arguments: {} }, - ], - }, - indexPatterns: {}, - indexPatternRefs: [], - activeVisualizationState: null, - }), - }, - { id: '123', timeRange, query, filters } as LensEmbeddableInput - ); - const reload = jest.spyOn(embeddable, 'reload'); - const initializeSavedVis = jest.spyOn(embeddable, 'initializeSavedVis'); - - await embeddable.initializeSavedVis({ - id: '123', - timeRange, - query, - filters, - } as LensEmbeddableInput); - - embeddable.render(mountpoint); - - return { - embeddable, - reload, - initializeSavedVis, - expressionRenderer: currentExpressionRenderer, - }; - }; - - let test = await createEmbeddable(); - - expect(test.reload).toHaveBeenCalledTimes(1); - expect(test.initializeSavedVis).toHaveBeenCalledTimes(1); - expect(test.expressionRenderer).toHaveBeenCalledTimes(1); - - // Test with savedObjectId and searchSessionId change - act(() => { - test.embeddable.updateInput({ savedObjectId: '123', searchSessionId: '456' }); - }); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(test.reload).toHaveBeenCalledTimes(2); - expect(test.initializeSavedVis).toHaveBeenCalledTimes(2); - expect(test.expressionRenderer).toHaveBeenCalledTimes(2); - - test = await createEmbeddable(); - - expect(test.reload).toHaveBeenCalledTimes(1); - expect(test.initializeSavedVis).toHaveBeenCalledTimes(1); - expect(test.expressionRenderer).toHaveBeenCalledTimes(1); - - // Test with attributes and timeRange change - act(() => { - test.embeddable.updateInput({ - attributes: { foo: 'bar' } as unknown as LensSavedObjectAttributes, - timeRange: { from: 'now-30d', to: 'now' }, - }); - }); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(test.reload).toHaveBeenCalledTimes(2); - expect(test.initializeSavedVis).toHaveBeenCalledTimes(2); - expect(test.expressionRenderer).toHaveBeenCalledTimes(2); - }); - - it('should get full attributes', async () => { - const createEmbeddable = async () => { - const currentExpressionRenderer = jest.fn((_props) => null); - const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; - const query: Query = { language: 'kquery', query: '' }; - const filters: Filter[] = [{ meta: { alias: 'test', negate: false, disabled: true } }]; - const embeddable = new Embeddable( - { - timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, - attributeService, - data: dataMock, - uiSettings: { get: () => undefined } as unknown as IUiSettingsClient, - expressionRenderer: currentExpressionRenderer, - coreStart, - basePath, - inspector: inspectorPluginMock.createStartContract(), - dataViews: {} as DataViewsContract, - capabilities: { - canSaveDashboards: true, - canSaveVisualizations: true, - canOpenVisualizations: true, - discover: {}, - navLinks: {}, - }, - getTrigger, - visualizationMap: defaultVisualizationMap, - datasourceMap: defaultDatasourceMap, - injectFilterReferences: jest.fn(mockInjectFilterReferences), - documentToExpression: () => - Promise.resolve({ - ast: { - type: 'expression', - chain: [ - { type: 'function', function: 'my', arguments: {} }, - { type: 'function', function: 'expression', arguments: {} }, - ], - }, - indexPatterns: {}, - indexPatternRefs: [], - activeVisualizationState: null, - }), - }, - { id: '123', timeRange, query, filters } as LensEmbeddableInput - ); - const reload = jest.spyOn(embeddable, 'reload'); - const initializeSavedVis = jest.spyOn(embeddable, 'initializeSavedVis'); - - await embeddable.initializeSavedVis({ - id: '123', - timeRange, - query, - filters, - } as LensEmbeddableInput); - - embeddable.render(mountpoint); - - return { - embeddable, - reload, - initializeSavedVis, - expressionRenderer: currentExpressionRenderer, - }; - }; - - const test = await createEmbeddable(); - - expect(test.embeddable.getFullAttributes()).toEqual(savedVis); - }); - - it('should pass over the overrides as variables', async () => { - const embeddable = new Embeddable( - { - timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, - attributeService, - data: dataMock, - expressionRenderer, - coreStart, - basePath, - dataViews: {} as DataViewsContract, - capabilities: { - canSaveDashboards: true, - canSaveVisualizations: true, - canOpenVisualizations: true, - discover: {}, - navLinks: {}, - }, - inspector: inspectorPluginMock.createStartContract(), - getTrigger, - visualizationMap: defaultVisualizationMap, - datasourceMap: defaultDatasourceMap, - injectFilterReferences: jest.fn(mockInjectFilterReferences), - documentToExpression: () => - Promise.resolve({ - ast: { - type: 'expression', - chain: [ - { type: 'function', function: 'my', arguments: {} }, - { type: 'function', function: 'expression', arguments: {} }, - ], - }, - indexPatterns: {}, - indexPatternRefs: [], - activeVisualizationState: null, - }), - uiSettings: { get: () => undefined } as unknown as IUiSettingsClient, - }, - { - timeRange: { - from: 'now-15m', - to: 'now', - }, - overrides: { - settings: { - onBrushEnd: 'ignore', - }, - }, - } as LensEmbeddableInput - ); - embeddable.render(mountpoint); - - // wait one tick to give embeddable time to initialize - await new Promise((resolve) => setTimeout(resolve, 0)); - - expect(expressionRenderer).toHaveBeenCalledTimes(1); - expect(expressionRenderer.mock.calls[0][0]!.variables).toEqual( - expect.objectContaining({ - overrides: { - settings: { - onBrushEnd: 'ignore', - }, - }, - }) - ); - }); - - it('should not be editable for no visualize library privileges', async () => { - const embeddable = new Embeddable( - getEmbeddableProps({ - capabilities: { - canSaveDashboards: false, - canSaveVisualizations: true, - canOpenVisualizations: false, - discover: {}, - navLinks: {}, - }, - }), - { - timeRange: { - from: 'now-15m', - to: 'now', - }, - } as LensEmbeddableInput - ); - expect(embeddable.getOutput().editable).toBeUndefined(); - }); -}); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx deleted file mode 100644 index ce86b896d5fa..000000000000 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ /dev/null @@ -1,1719 +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 { partition, uniqBy } from 'lodash'; -import React from 'react'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { css } from '@emotion/react'; -import { i18n } from '@kbn/i18n'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { ENABLE_ESQL } from '@kbn/esql-utils'; -import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; -import { - DataViewBase, - EsQueryConfig, - Filter, - Query, - AggregateQuery, - TimeRange, - isOfQueryType, - getAggregateQueryMode, - ExecutionContextSearch, - getLanguageDisplayName, - isOfAggregateQueryType, -} from '@kbn/es-query'; -import type { PaletteOutput } from '@kbn/coloring'; -import { - DataPublicPluginStart, - TimefilterContract, - FilterManager, - getEsQueryConfig, - mapAndFlattenFilters, -} from '@kbn/data-plugin/public'; -import type { Start as InspectorStart } from '@kbn/inspector-plugin/public'; - -import { merge, Subscription, switchMap } from 'rxjs'; -import { toExpression } from '@kbn/interpreter'; -import { - Datatable, - DefaultInspectorAdapters, - ErrorLike, - RenderMode, -} from '@kbn/expressions-plugin/common'; -import { map, distinctUntilChanged, skip, debounceTime } from 'rxjs'; -import fastIsEqual from 'fast-deep-equal'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { - ExpressionRendererEvent, - ReactExpressionRendererType, -} from '@kbn/expressions-plugin/public'; -import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; - -import { - EmbeddableStateTransfer, - Embeddable as AbstractEmbeddable, - EmbeddableInput, - EmbeddableOutput, - IContainer, - SavedObjectEmbeddableInput, - ReferenceOrValueEmbeddable, - SelfStyledEmbeddable, - FilterableEmbeddable, - cellValueTrigger, - CELL_VALUE_TRIGGER, - type CellValueContext, - shouldFetch$, -} from '@kbn/embeddable-plugin/public'; -import type { Action, UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import type { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; -import type { - Capabilities, - CoreStart, - IBasePath, - IUiSettingsClient, - KibanaExecutionContext, -} from '@kbn/core/public'; -import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import { - BrushTriggerEvent, - ClickTriggerEvent, - MultiClickTriggerEvent, -} from '@kbn/charts-plugin/public'; -import { DataViewSpec } from '@kbn/data-views-plugin/common'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useEuiFontSize, useEuiTheme, EuiEmptyPrompt } from '@elastic/eui'; -import { canTrackContentfulRender } from '@kbn/presentation-containers'; -import { getSuccessfulRequestTimings } from '../report_performance_metric_util'; -import { getExecutionContextEvents, trackUiCounterEvents } from '../lens_ui_telemetry'; -import { Document } from '../persistence'; -import { ExpressionWrapper, ExpressionWrapperProps } from './expression_wrapper'; -import { - isLensBrushEvent, - isLensFilterEvent, - isLensMultiFilterEvent, - isLensEditEvent, - isLensTableRowContextMenuClickEvent, - LensTableRowContextMenuEvent, - VisualizationMap, - Visualization, - DatasourceMap, - Datasource, - IndexPatternMap, - GetCompatibleCellValueActions, - UserMessage, - IndexPatternRef, - FramePublicAPI, - AddUserMessages, - UserMessagesGetter, - UserMessagesDisplayLocationId, -} from '../types'; - -import type { - AllowedChartOverrides, - AllowedPartitionOverrides, - AllowedSettingsOverrides, - AllowedGaugeOverrides, - AllowedXYOverrides, -} from '../../common/types'; -import { getEditPath, DOC_TYPE, APP_ID } from '../../common/constants'; -import { LensAttributeService } from '../lens_attribute_service'; -import type { TableInspectorAdapter } from '../editor_frame_service/types'; -import { getLensInspectorService, LensInspector } from '../lens_inspector_service'; -import { SharingSavedObjectProps, VisualizationDisplayOptions } from '../types'; -import { - getActiveDatasourceIdFromDoc, - getActiveVisualizationIdFromDoc, - getIndexPatternsObjects, - getSearchWarningMessages, - inferTimeField, - extractReferencesFromState, -} from '../utils'; -import { getLayerMetaInfo, combineQueryAndFilters } from '../app_plugin/show_underlying_data'; -import { - filterAndSortUserMessages, - getApplicationUserMessages, -} from '../app_plugin/get_application_user_messages'; -import { MessageList } from '../editor_frame_service/editor_frame/workspace_panel/message_list'; -import type { DocumentToExpressionReturnType } from '../editor_frame_service/editor_frame'; -import type { TypedLensByValueInput } from './embeddable_component'; -import type { LensPluginStartDependencies } from '../plugin'; -import { EmbeddableFeatureBadge } from './embeddable_info_badges'; -import { getDatasourceLayers } from '../state_management/utils'; -import type { EditLensConfigurationProps } from '../app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; -import { TextBasedPersistedState } from '../datasources/text_based/types'; -import { getLongMessage } from '../user_messages_utils'; - -export type LensSavedObjectAttributes = Omit<Document, 'savedObjectId' | 'type'>; - -export interface LensUnwrapMetaInfo { - sharingSavedObjectProps?: SharingSavedObjectProps; - managed?: boolean; -} - -export interface LensUnwrapResult { - attributes: LensSavedObjectAttributes; - metaInfo?: LensUnwrapMetaInfo; -} - -interface PreventableEvent { - preventDefault(): void; -} - -export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {}; - -export interface LensBaseEmbeddableInput extends EmbeddableInput { - filters?: Filter[]; - query?: Query; - timeRange?: TimeRange; - timeslice?: [number, number]; - palette?: PaletteOutput; - renderMode?: RenderMode; - style?: React.CSSProperties; - className?: string; - noPadding?: boolean; - onBrushEnd?: (data: Simplify<BrushTriggerEvent['data'] & PreventableEvent>) => void; - onLoad?: ( - isLoading: boolean, - adapters?: Partial<DefaultInspectorAdapters>, - output$?: Observable<LensEmbeddableOutput> - ) => void; - onFilter?: ( - data: Simplify<(ClickTriggerEvent['data'] | MultiClickTriggerEvent['data']) & PreventableEvent> - ) => void; - onTableRowClick?: ( - data: Simplify<LensTableRowContextMenuEvent['data'] & PreventableEvent> - ) => void; - abortController?: AbortController; - onBeforeBadgesRender?: (userMessages: UserMessage[]) => UserMessage[]; -} - -export type LensByValueInput = { - attributes: LensSavedObjectAttributes; - /** - * Overrides can tweak the style of the final embeddable and are executed at the end of the Lens rendering pipeline. - * Each visualization type offers various type of overrides, per component (i.e. 'setting', 'axisX', 'partition', etc...) - * - * While it is not possible to pass function/callback/handlers to the renderer, it is possible to overwrite - * the current behaviour by passing the "ignore" string to the override prop (i.e. onBrushEnd: "ignore" to stop brushing) - */ - overrides?: - | AllowedChartOverrides - | AllowedSettingsOverrides - | AllowedXYOverrides - | AllowedPartitionOverrides - | AllowedGaugeOverrides; -} & LensBaseEmbeddableInput; - -export type LensByReferenceInput = SavedObjectEmbeddableInput & LensBaseEmbeddableInput; -export type LensEmbeddableInput = LensByValueInput | LensByReferenceInput; - -export interface LensEmbeddableOutput extends EmbeddableOutput { - indexPatterns?: DataView[]; -} - -export interface LensEmbeddableDeps { - attributeService: LensAttributeService; - data: DataPublicPluginStart; - documentToExpression: (doc: Document) => Promise<DocumentToExpressionReturnType>; - injectFilterReferences: FilterManager['inject']; - visualizationMap: VisualizationMap; - datasourceMap: DatasourceMap; - dataViews: DataViewsContract; - expressionRenderer: ReactExpressionRendererType; - timefilter: TimefilterContract; - basePath: IBasePath; - inspector: InspectorStart; - getTrigger?: UiActionsStart['getTrigger'] | undefined; - getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions']; - capabilities: { - canSaveVisualizations: boolean; - canOpenVisualizations: boolean; - canSaveDashboards: boolean; - navLinks: Capabilities['navLinks']; - discover: Capabilities['discover']; - }; - coreStart: CoreStart; - usageCollection?: UsageCollectionSetup; - spaces?: SpacesPluginStart; - uiSettings: IUiSettingsClient; -} - -export interface ViewUnderlyingDataArgs { - dataViewSpec: DataViewSpec; - timeRange: TimeRange; - filters: Filter[]; - query: Query | AggregateQuery | undefined; - columns: string[]; -} - -function VisualizationErrorPanel({ errors, canEdit }: { errors: UserMessage[]; canEdit: boolean }) { - const firstError = errors.at(0); - const canFixInLens = canEdit && errors.some(({ fixableInEditor }) => fixableInEditor); - return ( - <div className="lnsEmbeddedError"> - <EuiEmptyPrompt - iconType="warning" - iconColor="danger" - data-test-subj="embeddable-lens-failure" - body={ - <> - {firstError ? ( - <> - <p>{getLongMessage(firstError)}</p> - {errors.length > 1 && !canFixInLens ? ( - <p> - <FormattedMessage - id="xpack.lens.embeddable.moreErrors" - defaultMessage="Edit in Lens editor to see more errors" - /> - </p> - ) : null} - {canFixInLens ? ( - <p> - <FormattedMessage - id="xpack.lens.embeddable.fixErrors" - defaultMessage="Edit in Lens editor to fix the error" - /> - </p> - ) : null} - </> - ) : ( - <p> - <FormattedMessage - id="xpack.lens.embeddable.failure" - defaultMessage="Visualization couldn't be displayed" - /> - </p> - )} - </> - } - /> - </div> - ); -} - -const getExpressionFromDocument = async ( - document: Document, - documentToExpression: LensEmbeddableDeps['documentToExpression'] -) => { - const { ast, indexPatterns, indexPatternRefs, activeVisualizationState } = - await documentToExpression(document); - return { - ast: ast ? toExpression(ast) : null, - indexPatterns, - indexPatternRefs, - activeVisualizationState, - }; -}; - -function getViewUnderlyingDataArgs({ - activeDatasource, - activeDatasourceState, - activeVisualization, - activeVisualizationState, - activeData, - dataViews, - capabilities, - query, - filters, - timeRange, - esQueryConfig, - indexPatternsCache, -}: { - activeDatasource: Datasource; - activeDatasourceState: unknown; - activeVisualization: Visualization; - activeVisualizationState: unknown; - activeData: TableInspectorAdapter | undefined; - dataViews: DataViewBase[] | undefined; - capabilities: LensEmbeddableDeps['capabilities']; - query: ExecutionContextSearch['query']; - filters: Filter[]; - timeRange: TimeRange; - esQueryConfig: EsQueryConfig; - indexPatternsCache: IndexPatternMap; -}) { - const { error, meta } = getLayerMetaInfo( - activeDatasource, - activeDatasourceState, - activeVisualization, - activeVisualizationState, - activeData, - indexPatternsCache, - timeRange, - capabilities - ); - - if (error || !meta) { - return; - } - const luceneOrKuery: Query[] = []; - const aggregateQuery: AggregateQuery[] = []; - - if (Array.isArray(query)) { - query.forEach((q) => { - if (isOfQueryType(q)) { - luceneOrKuery.push(q); - } else { - aggregateQuery.push(q); - } - }); - } - - const { filters: newFilters, query: newQuery } = combineQueryAndFilters( - luceneOrKuery.length > 0 ? luceneOrKuery : (query as Query), - filters, - meta, - dataViews, - esQueryConfig - ); - - const dataViewSpec = indexPatternsCache[meta.id]!.spec; - - return { - dataViewSpec, - timeRange, - filters: newFilters, - query: aggregateQuery.length > 0 ? aggregateQuery[0] : newQuery, - columns: meta.columns, - }; -} - -const EmbeddableMessagesPopover = ({ messages }: { messages: UserMessage[] }) => { - const { euiTheme } = useEuiTheme(); - const xsFontSize = useEuiFontSize('xs').fontSize; - - if (!messages.length) { - return null; - } - - return ( - <MessageList - messages={messages} - customButtonStyles={css` - block-size: ${euiTheme.size.l}; - font-size: ${xsFontSize}; - padding: 0 ${euiTheme.size.xs}; - & > * { - gap: ${euiTheme.size.xs}; - } - `} - /> - ); -}; - -const blockingMessageDisplayLocations: UserMessagesDisplayLocationId[] = [ - 'visualization', - 'visualizationOnEmbeddable', -]; - -const MessagesBadge = ({ onMount }: { onMount: (el: HTMLDivElement) => void }) => ( - <div - css={css({ - position: 'absolute', - zIndex: 2, - left: 0, - bottom: 0, - })} - ref={(el) => { - if (el) { - onMount(el); - } - }} - /> -); - -export class Embeddable - extends AbstractEmbeddable<LensEmbeddableInput, LensEmbeddableOutput> - implements - ReferenceOrValueEmbeddable<LensByValueInput, LensByReferenceInput>, - SelfStyledEmbeddable, - FilterableEmbeddable -{ - type = DOC_TYPE; - - deferEmbeddableLoad = true; - - private expressionRenderer: ReactExpressionRendererType; - private savedVis: Document | undefined; - private expression: string | undefined | null; - private domNode: HTMLElement | Element | undefined; - private isInitialized = false; - private inputReloadSubscriptions: Subscription[]; - private isDestroyed?: boolean; - private lensInspector: LensInspector; - - private logError(type: 'runtime' | 'validation') { - trackUiCounterEvents( - type === 'runtime' ? 'embeddable_runtime_error' : 'embeddable_validation_error', - this.getExecutionContext() - ); - } - - private activeData?: TableInspectorAdapter; - - private internalDataViews: DataView[] = []; - - private viewUnderlyingDataArgs?: ViewUnderlyingDataArgs; - - private activeVisualizationState?: unknown; - - constructor( - private deps: LensEmbeddableDeps, - initialInput: LensEmbeddableInput, - parent?: IContainer - ) { - super( - initialInput, - { - editApp: 'lens', - }, - parent - ); - - this.lensInspector = getLensInspectorService(deps.inspector); - this.expressionRenderer = deps.expressionRenderer; - this.initializeSavedVis(initialInput) - .then(() => { - this.reload(); - }) - .catch((e) => this.onFatalError(e)); - - const input$ = this.getInput$(); - - this.inputReloadSubscriptions = []; - - // Lens embeddable does not re-render when embeddable input changes in - // general, to improve performance. This line makes sure the Lens embeddable - // re-renders when anything in ".dynamicActions" (e.g. drilldowns) changes. - this.inputReloadSubscriptions.push( - input$ - .pipe( - map((input) => input.enhancements?.dynamicActions), - distinctUntilChanged((a, b) => fastIsEqual(a, b)), - skip(1) - ) - .subscribe((_input) => { - this.reload(); - }) - ); - - // Lens embeddable does not re-render when embeddable input changes in - // general, to improve performance. This line makes sure the Lens embeddable - // re-renders when dashboard view mode switches between "view/edit". This is - // needed to see the changes to ".dynamicActions" (e.g. drilldowns) when - // dashboard's mode is toggled. - this.inputReloadSubscriptions.push( - input$ - .pipe( - map((input) => input.viewMode), - distinctUntilChanged(), - skip(1) - ) - .subscribe((_input) => { - // only reload if drilldowns are set - if (this.getInput().enhancements?.dynamicActions) { - this.reload(); - } - }) - ); - - // Use a trigger to distinguish between observables in the subscription - const withTrigger = (trigger: 'attributesOrSavedObjectId' | 'searchContext') => - map((input: LensEmbeddableInput) => ({ trigger, input })); - - // Re-initialize the visualization if either the attributes or the saved object id changes - const attributesOrSavedObjectId$ = input$.pipe( - distinctUntilChanged((a, b) => - fastIsEqual( - [ - 'attributes' in a && a.attributes, - 'savedObjectId' in a && a.savedObjectId, - 'overrides' in a && a.overrides, - 'disableTriggers' in a && a.disableTriggers, - ], - [ - 'attributes' in b && b.attributes, - 'savedObjectId' in b && b.savedObjectId, - 'overrides' in b && b.overrides, - 'disableTriggers' in b && b.disableTriggers, - ] - ) - ), - skip(1), - withTrigger('attributesOrSavedObjectId') - ); - - // Update search context and reload on changes related to search - const searchContext$ = shouldFetch$<LensEmbeddableInput>(input$, () => this.getInput()).pipe( - withTrigger('searchContext') - ); - - // Merge and debounce the observables to avoid multiple reloads - this.inputReloadSubscriptions.push( - merge(searchContext$, attributesOrSavedObjectId$) - .pipe( - debounceTime(0), - switchMap(async ({ trigger, input }) => { - if (trigger === 'attributesOrSavedObjectId') { - await this.initializeSavedVis(input); - } - - // reset removable messages - // Dashboard search/context changes are detected here - this.additionalUserMessages = {}; - - this.reload(); - }) - ) - .subscribe() - ); - } - - private get activeDatasourceId() { - return getActiveDatasourceIdFromDoc(this.savedVis); - } - - private get activeDatasource() { - if (!this.activeDatasourceId) return; - return this.deps.datasourceMap[this.activeDatasourceId]; - } - - private get activeVisualizationId() { - return getActiveVisualizationIdFromDoc(this.savedVis); - } - - private get activeVisualization() { - if (!this.activeVisualizationId) return; - return this.deps.visualizationMap[this.activeVisualizationId]; - } - - private indexPatterns: IndexPatternMap = {}; - - private indexPatternRefs: IndexPatternRef[] = []; - - // TODO - consider getting this from the persistedStateToExpression function - // where it is already computed - private get activeDatasourceState(): undefined | unknown { - if (!this.activeDatasourceId || !this.activeDatasource) return; - - const docDatasourceState = this.savedVis?.state.datasourceStates[this.activeDatasourceId]; - - return this.activeDatasource.initialize( - docDatasourceState, - [...(this.savedVis?.references || []), ...(this.savedVis?.state.internalReferences || [])], - undefined, - undefined, - this.indexPatterns - ); - } - - private fullAttributes: LensSavedObjectAttributes | undefined; - - private handleExternalUserMessage = (messages: UserMessage[]) => { - if (this.input.onBeforeBadgesRender) { - // we need something else to better identify those errors - const [messagesToHandle, originalMessages] = partition(messages, (message) => - message.displayLocations.some((location) => location.id === 'embeddableBadge') - ); - - if (messagesToHandle.length > 0) { - const customBadgeMessages = this.input.onBeforeBadgesRender(messagesToHandle); - return [...originalMessages, ...customBadgeMessages]; - } - } - - return messages; - }; - - public getUserMessages: UserMessagesGetter = (locationId, filters) => { - const userMessages: UserMessage[] = []; - userMessages.push( - ...getApplicationUserMessages({ - visualizationType: this.savedVis?.visualizationType, - visualizationState: { - state: this.activeVisualizationState, - activeId: this.activeVisualizationId, - }, - visualization: - this.activeVisualizationId && this.deps.visualizationMap[this.activeVisualizationId] - ? this.deps.visualizationMap[this.activeVisualizationId] - : undefined, - activeDatasource: this.activeDatasource, - activeDatasourceState: { - isLoading: !this.activeDatasourceState, - state: this.activeDatasourceState, - }, - dataViews: { - indexPatterns: this.indexPatterns, - indexPatternRefs: this.indexPatternRefs, // TODO - are these actually used? - }, - core: this.deps.coreStart, - }) - ); - - if (!this.savedVis) { - return this.handleExternalUserMessage(userMessages); - } - - const mergedSearchContext = this.getMergedSearchContext(); - - const framePublicAPI: FramePublicAPI = { - dataViews: { - indexPatterns: this.indexPatterns, - indexPatternRefs: this.indexPatternRefs, - }, - datasourceLayers: getDatasourceLayers( - { - [this.activeDatasourceId!]: { - isLoading: !this.activeDatasourceState, - state: this.activeDatasourceState, - }, - }, - this.deps.datasourceMap, - this.indexPatterns - ), - query: this.savedVis.state.query, - filters: mergedSearchContext.filters ?? [], - dateRange: { - fromDate: mergedSearchContext.timeRange?.from ?? '', - toDate: mergedSearchContext.timeRange?.to ?? '', - }, - absDateRange: { - fromDate: mergedSearchContext.timeRange?.from ?? '', - toDate: mergedSearchContext.timeRange?.to ?? '', - }, - activeData: this.activeData, - }; - - userMessages.push( - ...(this.activeDatasource?.getUserMessages(this.activeDatasourceState, { - setState: () => {}, - frame: framePublicAPI, - visualizationInfo: this.activeVisualization?.getVisualizationInfo?.( - this.activeVisualizationState, - framePublicAPI - ), - }) ?? []), - ...(this.activeVisualization?.getUserMessages?.(this.activeVisualizationState, { - frame: framePublicAPI, - }) ?? []) - ); - - return this.handleExternalUserMessage( - filterAndSortUserMessages( - [...userMessages, ...Object.values(this.additionalUserMessages)], - locationId, - filters ?? {} - ) - ); - }; - - private additionalUserMessages: Record<string, UserMessage> = {}; - - // used to add warnings and errors from elsewhere in the embeddable - private addUserMessages: AddUserMessages = (messages) => { - const newMessageMap = { - ...this.additionalUserMessages, - }; - - const addedMessageIds: string[] = []; - messages.forEach((message) => { - if (!newMessageMap[message.uniqueId]) { - addedMessageIds.push(message.uniqueId); - newMessageMap[message.uniqueId] = message; - } - }); - - if (addedMessageIds.length) { - this.additionalUserMessages = newMessageMap; - this.renderUserMessages(); - } - - return () => { - messages.forEach(({ uniqueId }) => { - delete this.additionalUserMessages[uniqueId]; - }); - }; - }; - - public reportsEmbeddableLoad() { - return true; - } - - public supportedTriggers() { - if (!this.savedVis || !this.savedVis.visualizationType) { - return []; - } - - return this.deps.visualizationMap[this.savedVis.visualizationType]?.triggers || []; - } - - public getInspectorAdapters() { - return this.lensInspector.adapters; - } - - public getFullAttributes() { - return this.fullAttributes; - } - - public isTextBasedLanguage() { - if (!this.savedVis) { - return; - } - const query = this.savedVis.state.query; - return !isOfQueryType(query); - } - - public getTextBasedLanguage(): string | undefined { - if (!this.isTextBasedLanguage() || !this.savedVis?.state.query) { - return; - } - const query = this.savedVis?.state.query as unknown as AggregateQuery; - const language = getAggregateQueryMode(query); - return getLanguageDisplayName(language).toUpperCase(); - } - - /** - * Gets the Lens embeddable's datasource and visualization states - * updates the embeddable input - */ - async updateVisualization( - datasourceState: unknown, - visualizationState: unknown, - visualizationType?: string - ) { - const viz = this.savedVis; - const activeDatasourceId = (this.activeDatasourceId ?? - 'formBased') as EditLensConfigurationProps['datasourceId']; - if (viz?.state) { - const datasourceStates = { - ...viz.state.datasourceStates, - [activeDatasourceId]: datasourceState, - }; - const references = - activeDatasourceId === 'formBased' - ? extractReferencesFromState({ - activeDatasources: Object.keys(datasourceStates).reduce( - (acc, datasourceId) => ({ - ...acc, - [datasourceId]: this.deps.datasourceMap[datasourceId], - }), - {} - ), - datasourceStates: Object.fromEntries( - Object.entries(datasourceStates).map(([id, state]) => [ - id, - { isLoading: false, state }, - ]) - ), - visualizationState, - activeVisualization: this.activeVisualizationId - ? this.deps.visualizationMap[visualizationType ?? this.activeVisualizationId] - : undefined, - }) - : []; - const attrs = { - ...viz, - state: { - ...viz.state, - visualization: visualizationState, - datasourceStates, - }, - references, - visualizationType: visualizationType ?? viz.visualizationType, - }; - - /** - * SavedObjectId is undefined for by value panels and defined for the by reference ones. - * Here we are converting the by reference panels to by value when user is inline editing - */ - this.updateInput({ attributes: attrs, savedObjectId: undefined }); - /** - * Should load again the user messages, - * otherwise the embeddable state is stuck in an error state - */ - this.renderUserMessages(); - } - } - - async updateSuggestion(attrs: LensSavedObjectAttributes) { - const viz = this.savedVis; - const newViz = { - ...viz, - ...attrs, - }; - this.updateInput({ attributes: newViz }); - } - - /** - * Callback which allows the navigation to the editor. - * Used for the Edit in Lens link inside the inline editing flyout. - */ - private async navigateToLensEditor() { - const appContext = this.getAppContext(); - /** - * The origininating app variable is very important for the Save and Return button - * of the editor to work properly. - */ - const transferState = { - originatingApp: appContext?.currentAppId ?? 'dashboards', - originatingPath: appContext?.getCurrentPath?.(), - valueInput: this.getExplicitInput(), - embeddableId: this.id, - searchSessionId: this.getInput().searchSessionId, - }; - const transfer = new EmbeddableStateTransfer( - this.deps.coreStart.application.navigateToApp, - this.deps.coreStart.application.currentAppId$ - ); - if (transfer) { - await transfer.navigateToEditor(APP_ID, { - path: this.output.editPath, - state: transferState, - skipAppLeave: true, - }); - } - } - - public updateByRefInput(savedObjectId: string) { - const attrs = this.savedVis; - this.updateInput({ attributes: attrs, savedObjectId }); - } - - async openConfigPanel( - startDependencies: LensPluginStartDependencies, - isNewPanel?: boolean, - deletePanel?: () => void - ) { - const { getEditLensConfiguration } = await import('../async_services'); - const Component = await getEditLensConfiguration( - this.deps.coreStart, - startDependencies, - this.deps.visualizationMap, - this.deps.datasourceMap - ); - - const datasourceId = (this.activeDatasourceId ?? - 'formBased') as EditLensConfigurationProps['datasourceId']; - - const attributes = this.savedVis as TypedLensByValueInput['attributes']; - if (attributes) { - return ( - <Component - attributes={attributes} - updatePanelState={this.updateVisualization.bind(this)} - updateSuggestion={this.updateSuggestion.bind(this)} - datasourceId={datasourceId} - lensAdapters={this.lensInspector.adapters} - output$={this.getOutput$()} - panelId={this.id} - savedObjectId={this.savedVis?.savedObjectId} - updateByRefInput={this.updateByRefInput.bind(this)} - navigateToLensEditor={ - !this.isTextBasedLanguage() ? this.navigateToLensEditor.bind(this) : undefined - } - displayFlyoutHeader - canEditTextBasedQuery={this.isTextBasedLanguage()} - isNewPanel={isNewPanel} - deletePanel={deletePanel} - /> - ); - } - return null; - } - - async initializeSavedVis(input: LensEmbeddableInput) { - const unwrapResult: LensUnwrapResult | false = await this.deps.attributeService - .unwrapAttributes(input) - .catch((e: Error) => { - this.onFatalError(e); - return false; - }); - if (!unwrapResult || this.isDestroyed) { - return; - } - - const { metaInfo, attributes } = unwrapResult; - this.fullAttributes = attributes; - this.savedVis = { - ...attributes, - type: this.type, - savedObjectId: (input as LensByReferenceInput)?.savedObjectId, - }; - - if (this.isTextBasedLanguage()) { - this.updateInput({ - disabledActions: ['OPEN_FLYOUT_ADD_DRILLDOWN'], - }); - } - - try { - const { ast, indexPatterns, indexPatternRefs, activeVisualizationState } = - await getExpressionFromDocument(this.savedVis, this.deps.documentToExpression); - - this.expression = ast; - this.indexPatterns = indexPatterns; - this.indexPatternRefs = indexPatternRefs; - this.activeVisualizationState = activeVisualizationState; - } catch { - // nothing, errors should be reported via getUserMessages - } - - if (metaInfo?.sharingSavedObjectProps?.outcome === 'conflict' && !!this.deps.spaces) { - this.addUserMessages([ - { - uniqueId: 'url-conflict', - severity: 'error', - displayLocations: [{ id: 'visualization' }], - shortMessage: i18n.translate('xpack.lens.embeddable.legacyURLConflict.shortMessage', { - defaultMessage: `You've encountered a URL conflict`, - }), - longMessage: ( - <this.deps.spaces.ui.components.getEmbeddableLegacyUrlConflict - targetType={DOC_TYPE} - sourceId={metaInfo?.sharingSavedObjectProps?.sourceId!} - /> - ), - fixableInEditor: false, - }, - ]); - } - - await this.initializeOutput(); - - // deferred loading of this embeddable is complete - this.setInitializationFinished(); - - this.isInitialized = true; - } - - private getSearchWarningMessages(adapters?: Partial<DefaultInspectorAdapters>): UserMessage[] { - if (!this.activeDatasource || !this.activeDatasourceId || !adapters?.requests) { - return []; - } - - const docDatasourceState = this.savedVis?.state.datasourceStates[this.activeDatasourceId]; - - const requestWarnings = getSearchWarningMessages( - adapters.requests, - this.activeDatasource, - docDatasourceState, - { - searchService: this.deps.data.search, - } - ); - - return requestWarnings; - } - - private removeActiveDataWarningMessages: () => void = () => {}; - private updateActiveData: ExpressionWrapperProps['onData$'] = (data, adapters) => { - if (this.input.onLoad) { - // once onData$ is get's called from expression renderer, loading becomes false - this.input.onLoad(false, adapters, this.getOutput$()); - } - - const { type, error } = data as { type: string; error: ErrorLike }; - this.updateOutput({ - loading: false, - error: type === 'error' ? error : undefined, - }); - - const newActiveData = adapters?.tables?.tables; - - this.removeActiveDataWarningMessages(); - const searchWarningMessages = this.getSearchWarningMessages(adapters); - this.removeActiveDataWarningMessages = this.addUserMessages(searchWarningMessages); - - this.activeData = newActiveData; - - this.renderUserMessages(); - - this.loadViewUnderlyingDataArgs(); - }; - - private onRender: ExpressionWrapperProps['onRender$'] = () => { - let datasourceEvents: string[] = []; - let visualizationEvents: string[] = []; - - if (this.savedVis) { - datasourceEvents = Object.values(this.deps.datasourceMap).reduce<string[]>( - (acc, datasource) => [ - ...acc, - ...(datasource.getRenderEventCounters?.( - this.savedVis!.state.datasourceStates[datasource.id] - ) ?? []), - ], - [] - ); - - if (this.savedVis.visualizationType) { - visualizationEvents = - this.deps.visualizationMap[this.savedVis.visualizationType].getRenderEventCounters?.( - this.savedVis!.state.visualization - ) ?? []; - } - } - - const executionContext = this.getExecutionContext(); - - const events = [ - ...datasourceEvents, - ...visualizationEvents, - ...getExecutionContextEvents(executionContext), - ]; - - const adHocDataViews = Object.values(this.savedVis?.state.adHocDataViews || {}); - adHocDataViews.forEach(() => { - events.push('ad_hoc_data_view'); - }); - - trackUiCounterEvents(events, executionContext); - this.trackContentfulRender(); - - this.renderComplete.dispatchComplete(); - this.updateOutput({ - ...this.getOutput(), - rendered: true, - }); - - const inspectorAdapters = this.getInspectorAdapters(); - const timings = getSuccessfulRequestTimings(inspectorAdapters); - if (timings) { - const esRequestMetrics = { - eventName: 'lens_chart_es_request_totals', - duration: timings.requestTimeTotal, - key1: 'es_took_total', - value1: timings.esTookTotal, - }; - reportPerformanceMetricEvent(this.deps.coreStart.analytics, esRequestMetrics); - } - }; - - getExecutionContext() { - if (this.savedVis) { - const parentContext = this.parent?.getInput().executionContext || this.input.executionContext; - const child: KibanaExecutionContext = { - type: 'lens', - name: this.savedVis.visualizationType ?? '', - id: this.id, - description: this.savedVis.title || this.input.title || '', - url: this.output.editUrl, - }; - - return parentContext - ? { - ...parentContext, - child, - } - : child; - } - } - - /** - * - * @param {HTMLElement} domNode - * @param {ContainerState} containerState - */ - render(domNode: HTMLElement | Element) { - this.domNode = domNode; - if (!this.savedVis || !this.isInitialized || this.isDestroyed) { - return; - } - super.render(domNode as HTMLElement); - - if (this.input.onLoad) { - this.input.onLoad(true); - } - - this.domNode.setAttribute('data-shared-item', ''); - - const blockingErrors = this.getUserMessages(blockingMessageDisplayLocations, { - severity: 'error', - }); - - this.updateOutput({ - loading: true, - error: blockingErrors.length - ? new Error( - typeof blockingErrors[0].longMessage === 'string' - ? blockingErrors[0].longMessage - : blockingErrors[0].shortMessage - ) - : undefined, - }); - - if (blockingErrors.length) { - this.renderComplete.dispatchError(); - } else { - this.renderComplete.dispatchInProgress(); - } - - const input = this.getInput(); - - const getInternalTables = (states: Record<string, unknown>) => { - const result: Record<string, Datatable> = {}; - if ('textBased' in states) { - const layers = (states.textBased as TextBasedPersistedState).layers; - for (const layer in layers) { - if (layers[layer] && layers[layer].table) { - result[layer] = layers[layer].table!; - } - } - } - return result; - }; - - if (this.expression && !blockingErrors.length) { - render( - <> - <KibanaRenderContextProvider {...this.deps.coreStart}> - <ExpressionWrapper - ExpressionRenderer={this.expressionRenderer} - expression={this.expression || null} - lensInspector={this.lensInspector} - searchContext={this.getMergedSearchContext()} - variables={{ - embeddableTitle: this.getTitle(), - ...(input.palette ? { theme: { palette: input.palette } } : {}), - ...('overrides' in input ? { overrides: input.overrides } : {}), - ...getInternalTables(this.savedVis.state.datasourceStates), - }} - searchSessionId={this.getInput().searchSessionId} - handleEvent={this.handleEvent} - onData$={this.updateActiveData} - onRender$={this.onRender} - interactive={!input.disableTriggers} - renderMode={input.renderMode} - syncColors={input.syncColors} - syncTooltips={input.syncTooltips} - syncCursor={input.syncCursor} - hasCompatibleActions={this.hasCompatibleActions} - getCompatibleCellValueActions={this.getCompatibleCellValueActions} - className={input.className} - style={input.style} - executionContext={this.getExecutionContext()} - abortController={this.input.abortController} - addUserMessages={(messages) => this.addUserMessages(messages)} - onRuntimeError={(error) => { - this.updateOutput({ error }); - this.logError('runtime'); - }} - noPadding={this.visDisplayOptions.noPadding} - /> - </KibanaRenderContextProvider> - <MessagesBadge - onMount={(el) => { - this.badgeDomNode = el; - this.renderBadgeMessages(); - }} - /> - </>, - domNode - ); - } - - this.renderUserMessages(); - } - - private trackContentfulRender() { - if (!this.activeData || !canTrackContentfulRender(this.parent)) { - return; - } - - const hasData = Object.values(this.activeData).some((table) => { - if (table.meta?.statistics?.totalCount != null) { - // if totalCount is set, refer to total count - return table.meta.statistics.totalCount > 0; - } - // if not, fall back to check the rows of the table - return table.rows.length > 0; - }); - - if (hasData) { - this.parent.trackContentfulRender(); - } - } - - private renderUserMessages() { - const errors = this.getUserMessages(['visualization', 'visualizationOnEmbeddable'], { - severity: 'error', - }); - - if (errors.length && this.domNode) { - render( - <> - <KibanaRenderContextProvider {...this.deps.coreStart}> - <VisualizationErrorPanel - errors={errors} - canEdit={this.getIsEditable() && this.input.viewMode === 'edit'} - /> - </KibanaRenderContextProvider> - <MessagesBadge - onMount={(el) => { - this.badgeDomNode = el; - this.renderBadgeMessages(); - }} - /> - </>, - this.domNode - ); - } - - this.renderBadgeMessages(); - } - - badgeDomNode?: HTMLDivElement; - - /** - * This method is called on every render, and also whenever the badges dom node is created - * That happens after either the expression renderer or the visualization error panel is rendered. - * - * You should not call this method on its own. Use renderUserMessages instead. - */ - private renderBadgeMessages = () => { - const messages = this.getUserMessages('embeddableBadge'); - const [warningOrErrorMessages, infoMessages] = partition( - messages, - ({ severity }) => severity !== 'info' - ); - - if (this.badgeDomNode) { - render( - <KibanaRenderContextProvider {...this.deps.coreStart}> - <EmbeddableMessagesPopover messages={warningOrErrorMessages} /> - <EmbeddableFeatureBadge messages={infoMessages} /> - </KibanaRenderContextProvider>, - this.badgeDomNode - ); - } - }; - - private readonly hasCompatibleActions = async ( - event: ExpressionRendererEvent - ): Promise<boolean> => { - if ( - isLensTableRowContextMenuClickEvent(event) || - isLensMultiFilterEvent(event) || - isLensFilterEvent(event) - ) { - const { getTriggerCompatibleActions } = this.deps; - if (!getTriggerCompatibleActions) { - return false; - } - const actions = await getTriggerCompatibleActions(VIS_EVENT_TO_TRIGGER[event.name], { - data: event.data, - embeddable: this, - }); - - return actions.length > 0; - } - - return false; - }; - - private readonly getCompatibleCellValueActions: GetCompatibleCellValueActions = async (data) => { - const { getTriggerCompatibleActions } = this.deps; - if (getTriggerCompatibleActions) { - const embeddable = this; - const actions: Array<Action<CellValueContext>> = (await getTriggerCompatibleActions( - CELL_VALUE_TRIGGER, - { data, embeddable } - )) as Array<Action<CellValueContext>>; - return actions - .sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity)) - .map((action) => ({ - id: action.id, - type: action.type, - iconType: action.getIconType({ embeddable, data, trigger: cellValueTrigger })!, - displayName: action.getDisplayName({ embeddable, data, trigger: cellValueTrigger }), - execute: (cellData) => - action.execute({ embeddable, data: cellData, trigger: cellValueTrigger }), - })); - } - return []; - }; - - /** - * Combines the embeddable context with the saved object context, and replaces - * any references to index patterns - */ - private getMergedSearchContext(): ExecutionContextSearch { - if (!this.savedVis) { - throw new Error('savedVis is required for getMergedSearchContext'); - } - - const input = this.getInput(); - const context: ExecutionContextSearch = { - now: this.deps.data.nowProvider.get().getTime(), - timeRange: - input.timeslice !== undefined - ? { - from: new Date(input.timeslice[0]).toISOString(), - to: new Date(input.timeslice[1]).toISOString(), - mode: 'absolute' as 'absolute', - } - : input.timeRange, - query: [this.savedVis.state.query], - filters: this.deps.injectFilterReferences( - this.savedVis.state.filters, - this.savedVis.references - ), - disableWarningToasts: true, - }; - - if (input.query) { - context.query = [input.query, ...(context.query as Query[])]; - } - - if (input.filters?.length) { - context.filters = [ - ...input.filters.filter((filter) => !filter.meta.disabled), - ...(context.filters as Filter[]), - ]; - } - - return context; - } - - private get onEditAction(): Visualization['onEditAction'] { - const visType = this.savedVis?.visualizationType; - - if (!visType) { - return; - } - - return this.deps.visualizationMap[visType].onEditAction; - } - - handleEvent = async (event: ExpressionRendererEvent) => { - if (!this.deps.getTrigger || this.input.disableTriggers) { - return; - } - - let eventHandler: - | LensBaseEmbeddableInput['onBrushEnd'] - | LensBaseEmbeddableInput['onFilter'] - | LensBaseEmbeddableInput['onTableRowClick']; - let shouldExecuteDefaultTriggers = true; - - if (isLensBrushEvent(event)) { - eventHandler = this.input.onBrushEnd; - } else if (isLensFilterEvent(event) || isLensMultiFilterEvent(event)) { - eventHandler = this.input.onFilter; - } else if (isLensTableRowContextMenuClickEvent(event)) { - eventHandler = this.input.onTableRowClick; - } - // if the embeddable is located in an app where there is the Unified search bar with the ES|QL editor, then use this query - // otherwise use the query from the saved object - let esqlQuery: AggregateQuery | Query | undefined; - if (this.isTextBasedLanguage()) { - const query = this.deps.data.query.queryString.getQuery(); - esqlQuery = isOfAggregateQueryType(query) ? query : this.savedVis?.state.query; - } - - eventHandler?.({ - ...event.data, - preventDefault: () => { - shouldExecuteDefaultTriggers = false; - }, - }); - - if (isLensFilterEvent(event) || isLensMultiFilterEvent(event) || isLensBrushEvent(event)) { - if (shouldExecuteDefaultTriggers) { - this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ - data: { - ...event.data, - timeFieldName: - event.data.timeFieldName || inferTimeField(this.deps.data.datatableUtilities, event), - query: esqlQuery, - }, - embeddable: this, - }); - } - } - - if (isLensTableRowContextMenuClickEvent(event)) { - if (shouldExecuteDefaultTriggers) { - this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec( - { - data: event.data, - embeddable: this, - }, - true - ); - } - } - - // We allow for edit actions in the Embeddable for display purposes only (e.g. changing the datatable sort order). - // No state changes made here with an edit action are persisted. - if (isLensEditEvent(event) && this.onEditAction) { - if (!this.savedVis) return; - - // have to dance since this.savedVis.state is readonly - const newVis = JSON.parse(JSON.stringify(this.savedVis)) as Document; - newVis.state.visualization = this.onEditAction(newVis.state.visualization, event); - this.savedVis = newVis; - - const { ast } = await getExpressionFromDocument( - this.savedVis, - this.deps.documentToExpression - ); - - this.expression = ast; - - this.reload(); - } - }; - - reload() { - if (!this.savedVis || !this.isInitialized || this.isDestroyed) { - return; - } - - if (this.domNode) { - this.render(this.domNode); - } - } - - private async loadViewUnderlyingDataArgs(): Promise<void> { - if ( - !this.savedVis || - !this.activeData || - !this.activeDatasource || - !this.activeDatasourceState || - !this.activeVisualization || - !this.activeVisualizationState - ) { - this.canViewUnderlyingData$.next(false); - return; - } - - const mergedSearchContext = this.getMergedSearchContext(); - - if (!mergedSearchContext.timeRange) { - this.canViewUnderlyingData$.next(false); - return; - } - - const viewUnderlyingDataArgs = getViewUnderlyingDataArgs({ - activeDatasource: this.activeDatasource, - activeDatasourceState: this.activeDatasourceState, - activeVisualization: this.activeVisualization, - activeVisualizationState: this.activeVisualizationState, - activeData: this.activeData, - dataViews: this.internalDataViews, - capabilities: this.deps.capabilities, - query: mergedSearchContext.query, - filters: mergedSearchContext.filters || [], - timeRange: mergedSearchContext.timeRange, - esQueryConfig: getEsQueryConfig(this.deps.uiSettings), - indexPatternsCache: this.indexPatterns, - }); - - const loaded = typeof viewUnderlyingDataArgs !== 'undefined'; - if (loaded) { - this.viewUnderlyingDataArgs = viewUnderlyingDataArgs; - } - - this.canViewUnderlyingData$.next(loaded); - } - - /** - * Returns the necessary arguments to view the underlying data in discover. - * - * Only makes sense to call this after canViewUnderlyingData has been checked - */ - public getViewUnderlyingDataArgs() { - return this.viewUnderlyingDataArgs; - } - - public canViewUnderlyingData$ = new BehaviorSubject<boolean>(false); - - async initializeOutput() { - if (!this.savedVis) { - return; - } - - const { indexPatterns } = await getIndexPatternsObjects( - this.savedVis?.references.map(({ id }) => id) || [], - this.deps.dataViews - ); - ( - await Promise.all( - Object.values(this.savedVis?.state.adHocDataViews || {}).map((spec) => - this.deps.dataViews.create(spec) - ) - ) - ).forEach((dataView) => indexPatterns.push(dataView)); - - this.internalDataViews = uniqBy(indexPatterns, 'id'); - - // passing edit url and index patterns to the output of this embeddable for - // the container to pick them up and use them to configure filter bar and - // config dropdown correctly. - const input = this.getInput(); - - // if at least one indexPattern is time based, then the Lens embeddable requires the timeRange prop - // this is necessary for the dataview embeddable but not the ES|QL one - if ( - !Boolean(this.isTextBasedLanguage()) && - input.timeRange == null && - indexPatterns.some((indexPattern) => indexPattern.isTimeBased()) - ) { - this.addUserMessages([ - { - uniqueId: 'missing-time-range-on-embeddable', - severity: 'error', - fixableInEditor: false, - displayLocations: [{ id: 'visualization' }], - shortMessage: i18n.translate('xpack.lens.embeddable.missingTimeRangeParam.shortMessage', { - defaultMessage: `Missing timeRange property`, - }), - longMessage: i18n.translate('xpack.lens.embeddable.missingTimeRangeParam.longMessage', { - defaultMessage: `The timeRange property is required for the given configuration`, - }), - }, - ]); - } - - const blockingErrors = this.getUserMessages(blockingMessageDisplayLocations, { - severity: 'error', - }); - if (blockingErrors.length) { - this.logError('validation'); - } - - const title = input.hidePanelTitles ? '' : input.title ?? this.savedVis.title; - const description = input.hidePanelTitles ? '' : input.description ?? this.savedVis.description; - const savedObjectId = (input as LensByReferenceInput).savedObjectId; - this.updateOutput({ - defaultTitle: this.savedVis.title, - defaultDescription: this.savedVis.description, - /** lens visualizations allow inline editing action - * navigation to the editor is allowed through the flyout - */ - editable: this.getIsEditable(), - inlineEditable: true, - title, - description, - editPath: getEditPath(savedObjectId), - editUrl: this.deps.basePath.prepend(`/app/lens${getEditPath(savedObjectId)}`), - indexPatterns: this.internalDataViews, - }); - } - - public getIsEditable() { - // for ES|QL, editing is allowed only if the advanced setting is on - if (Boolean(this.isTextBasedLanguage()) && !this.deps.uiSettings.get(ENABLE_ESQL)) { - return false; - } - return ( - this.deps.capabilities.canSaveVisualizations || - (!this.inputIsRefType(this.getInput()) && - this.deps.capabilities.canSaveDashboards && - this.deps.capabilities.canOpenVisualizations) - ); - } - - public inputIsRefType = ( - input: LensByValueInput | LensByReferenceInput - ): input is LensByReferenceInput => { - return this.deps.attributeService.inputIsRefType(input); - }; - - public getInputAsRefType = async (): Promise<LensByReferenceInput> => { - return this.deps.attributeService.getInputAsRefType(this.getExplicitInput(), { - showSaveModal: true, - saveModalTitle: this.getTitle(), - }); - }; - - public getInputAsValueType = async (): Promise<LensByValueInput> => { - return this.deps.attributeService.getInputAsValueType(this.getExplicitInput()); - }; - - /** - * Gets the Lens embeddable's local filters - * @returns Local/panel-level array of filters for Lens embeddable - */ - public getFilters() { - try { - return mapAndFlattenFilters( - this.deps.injectFilterReferences( - this.savedVis?.state.filters ?? [], - this.savedVis?.references ?? [] - ) - ); - } catch (e) { - // if we can't parse the filters, we publish an empty array. - return []; - } - } - - /** - * Gets the Lens embeddable's local query - * @returns Local/panel-level query for Lens embeddable - */ - public getQuery() { - return this.savedVis?.state.query; - } - - public getSavedVis(): Readonly<LensSavedObjectAttributes | undefined> { - if (!this.savedVis) { - return; - } - - // Why are 'type' and 'savedObjectId' keys being removed? - // Prior to removing them, - // this method returned 'Readonly<Document | undefined>' while consumers typed the results as 'LensSavedObjectAttributes'. - // Removing 'type' and 'savedObjectId' keys to align method results with consumer typing. - const savedVis = { ...this.savedVis }; - delete savedVis.type; - delete savedVis.savedObjectId; - return savedVis; - } - - destroy() { - this.isDestroyed = true; - super.destroy(); - if (this.inputReloadSubscriptions.length > 0) { - this.inputReloadSubscriptions.forEach((reloadSub) => { - reloadSub.unsubscribe(); - }); - } - if (this.domNode) { - unmountComponentAtNode(this.domNode); - } - } - - public getSelfStyledOptions() { - return { - hideTitle: this.visDisplayOptions.noPanelTitle, - }; - } - - private get visDisplayOptions(): VisualizationDisplayOptions { - if (!this.savedVis?.visualizationType) { - return {}; - } - - let displayOptions = - this.deps.visualizationMap[this.savedVis.visualizationType]?.getDisplayOptions?.() ?? {}; - - if (this.input.noPadding !== undefined) { - displayOptions = { - ...displayOptions, - noPadding: this.input.noPadding, - }; - } - - return displayOptions; - } -} diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx deleted file mode 100644 index f433f71d453b..000000000000 --- a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx +++ /dev/null @@ -1,188 +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 React, { FC, useEffect } from 'react'; -import type { CoreStart } from '@kbn/core/public'; -import type { Action, UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import type { Start as InspectorStartContract } from '@kbn/inspector-plugin/public'; -import { PanelLoader } from '@kbn/panel-loader'; -import { EuiLoadingChart } from '@elastic/eui'; -import { - EmbeddableFactory, - EmbeddableInput, - EmbeddableOutput, - EmbeddablePanel, - EmbeddableRoot, - EmbeddableStart, - IEmbeddable, - useEmbeddableFactory, -} from '@kbn/embeddable-plugin/public'; -import type { LensByReferenceInput, LensByValueInput } from './embeddable'; -import type { Document } from '../persistence'; -import type { FormBasedPersistedState } from '../datasources/form_based/types'; -import type { TextBasedPersistedState } from '../datasources/text_based/types'; -import type { XYState } from '../visualizations/xy/types'; -import type { - PieVisualizationState, - LegacyMetricState, - AllowedGaugeOverrides, - AllowedPartitionOverrides, - AllowedSettingsOverrides, - AllowedXYOverrides, -} from '../../common/types'; -import type { DatatableVisualizationState } from '../visualizations/datatable/visualization'; -import type { MetricVisualizationState } from '../visualizations/metric/types'; -import type { HeatmapVisualizationState } from '../visualizations/heatmap/types'; -import type { GaugeVisualizationState } from '../visualizations/gauge/constants'; - -type LensAttributes<TVisType, TVisState> = Omit< - Document, - 'savedObjectId' | 'type' | 'state' | 'visualizationType' -> & { - visualizationType: TVisType; - state: Omit<Document['state'], 'datasourceStates' | 'visualization'> & { - datasourceStates: { - formBased?: FormBasedPersistedState; - textBased?: TextBasedPersistedState; - }; - visualization: TVisState; - }; -}; - -/** - * Type-safe variant of by value embeddable input for Lens. - * This can be used to hardcode certain Lens chart configurations within another app. - */ -export type TypedLensByValueInput = Omit<LensByValueInput, 'attributes' | 'overrides'> & { - attributes: - | LensAttributes<'lnsXY', XYState> - | LensAttributes<'lnsPie', PieVisualizationState> - | LensAttributes<'lnsHeatmap', HeatmapVisualizationState> - | LensAttributes<'lnsGauge', GaugeVisualizationState> - | LensAttributes<'lnsDatatable', DatatableVisualizationState> - | LensAttributes<'lnsLegacyMetric', LegacyMetricState> - | LensAttributes<'lnsMetric', MetricVisualizationState> - | LensAttributes<string, unknown>; - - /** - * Overrides can tweak the style of the final embeddable and are executed at the end of the Lens rendering pipeline. - * XY charts offer an override of the Settings ('settings') and Axis ('axisX', 'axisLeft', 'axisRight') components. - * While it is not possible to pass function/callback/handlers to the renderer, it is possible to stop them by passing the - * "ignore" string as override value (i.e. onBrushEnd: "ignore") - */ - overrides?: - | AllowedSettingsOverrides - | AllowedXYOverrides - | AllowedPartitionOverrides - | AllowedGaugeOverrides; -}; - -export type EmbeddableComponentProps = (TypedLensByValueInput | LensByReferenceInput) & { - withDefaultActions?: boolean; - extraActions?: Action[]; - showInspector?: boolean; - abortController?: AbortController; -}; - -export type EmbeddableComponent = React.ComponentType<EmbeddableComponentProps>; - -interface PluginsStartDependencies { - uiActions: UiActionsStart; - embeddable: EmbeddableStart; - inspector: InspectorStartContract; -} - -export function getEmbeddableComponent(core: CoreStart, plugins: PluginsStartDependencies) { - const { embeddable: embeddableStart, uiActions } = plugins; - const factory = embeddableStart.getEmbeddableFactory('lens')!; - return (props: EmbeddableComponentProps) => { - const input = { ...props }; - const hasActions = - Boolean(input.withDefaultActions) || (input.extraActions && input.extraActions?.length > 0); - - if (hasActions) { - return ( - <EmbeddablePanelWrapper - factory={factory} - uiActions={uiActions} - actionPredicate={() => hasActions} - input={input} - extraActions={input.extraActions} - showInspector={input.showInspector} - withDefaultActions={input.withDefaultActions} - /> - ); - } - return <EmbeddableRootWrapper factory={factory} input={input} />; - }; -} - -function EmbeddableRootWrapper({ - factory, - input, -}: { - factory: EmbeddableFactory<EmbeddableInput, EmbeddableOutput>; - input: EmbeddableComponentProps; -}) { - const [embeddable, loading, error] = useEmbeddableFactory({ factory, input }); - if (loading) { - return <EuiLoadingChart />; - } - return <EmbeddableRoot embeddable={embeddable} loading={loading} error={error} input={input} />; -} - -interface EmbeddablePanelWrapperProps { - factory: EmbeddableFactory<EmbeddableInput, EmbeddableOutput>; - uiActions: PluginsStartDependencies['uiActions']; - actionPredicate: (id: string) => boolean; - input: EmbeddableComponentProps; - extraActions?: Action[]; - showInspector?: boolean; - withDefaultActions?: boolean; - abortController?: AbortController; -} - -const EmbeddablePanelWrapper: FC<EmbeddablePanelWrapperProps> = ({ - factory, - uiActions, - actionPredicate, - input, - extraActions, - showInspector = true, - withDefaultActions, - abortController, -}) => { - const [embeddable, loading] = useEmbeddableFactory({ factory, input }); - useEffect(() => { - if (embeddable) { - embeddable.updateInput(input); - } - }, [embeddable, input]); - - if (loading || !embeddable) { - return <PanelLoader />; - } - - return ( - <EmbeddablePanel - hideHeader={false} - embeddable={embeddable as IEmbeddable<EmbeddableInput, EmbeddableOutput>} - getActions={async (triggerId, context) => { - const actions = withDefaultActions - ? await uiActions.getTriggerCompatibleActions(triggerId, context) - : []; - - return [...(extraActions ?? []), ...actions]; - }} - hideInspector={!showInspector} - actionPredicate={actionPredicate} - showNotifications={false} - showShadow={false} - showBadges={false} - /> - ); -}; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts deleted file mode 100644 index d84aca319a42..000000000000 --- a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts +++ /dev/null @@ -1,157 +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 type { - Capabilities, - CoreStart, - HttpSetup, - IUiSettingsClient, - ThemeServiceStart, -} from '@kbn/core/public'; -import { i18n } from '@kbn/i18n'; -import { RecursiveReadonly } from '@kbn/utility-types'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import { DataPublicPluginStart, FilterManager, TimefilterContract } from '@kbn/data-plugin/public'; -import type { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { ReactExpressionRendererType } from '@kbn/expressions-plugin/public'; -import { - EmbeddableFactoryDefinition, - IContainer, - ErrorEmbeddable, -} from '@kbn/embeddable-plugin/public'; -import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import type { Start as InspectorStart } from '@kbn/inspector-plugin/public'; -import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import type { LensByReferenceInput, LensEmbeddableInput } from './embeddable'; -import type { Document } from '../persistence/saved_object_store'; -import type { LensAttributeService } from '../lens_attribute_service'; -import { DOC_TYPE } from '../../common/constants'; -import { extract, inject } from '../../common/embeddable_factory'; -import type { DatasourceMap, VisualizationMap } from '../types'; -import type { DocumentToExpressionReturnType } from '../editor_frame_service/editor_frame'; - -export interface LensEmbeddableStartServices { - data: DataPublicPluginStart; - timefilter: TimefilterContract; - coreHttp: HttpSetup; - coreStart: CoreStart; - inspector: InspectorStart; - attributeService: LensAttributeService; - capabilities: RecursiveReadonly<Capabilities>; - expressionRenderer: ReactExpressionRendererType; - dataViews: DataViewsContract; - uiActions?: UiActionsStart; - usageCollection?: UsageCollectionSetup; - documentToExpression: (doc: Document) => Promise<DocumentToExpressionReturnType>; - injectFilterReferences: FilterManager['inject']; - visualizationMap: VisualizationMap; - datasourceMap: DatasourceMap; - spaces?: SpacesPluginStart; - theme: ThemeServiceStart; - uiSettings: IUiSettingsClient; -} - -export class EmbeddableFactory implements EmbeddableFactoryDefinition { - type = DOC_TYPE; - savedObjectMetaData = { - name: i18n.translate('xpack.lens.lensSavedObjectLabel', { - defaultMessage: 'Lens Visualization', - }), - type: DOC_TYPE, - getIconForSavedObject: () => 'lensApp', - }; - - constructor(private getStartServices: () => Promise<LensEmbeddableStartServices>) {} - - public isEditable = async () => { - const { capabilities } = await this.getStartServices(); - return Boolean(capabilities.visualize.save || capabilities.dashboard?.showWriteControls); - }; - - canCreateNew() { - return false; - } - - getDisplayName() { - return i18n.translate('xpack.lens.embeddableDisplayName', { - defaultMessage: 'Lens', - }); - } - - createFromSavedObject = async ( - savedObjectId: string, - input: LensEmbeddableInput, - parent?: IContainer - ) => { - if (!(input as LensByReferenceInput).savedObjectId) { - (input as LensByReferenceInput).savedObjectId = savedObjectId; - } - return this.create(input, parent); - }; - - async create(input: LensEmbeddableInput, parent?: IContainer) { - try { - const { - data, - timefilter, - expressionRenderer, - documentToExpression, - injectFilterReferences, - visualizationMap, - datasourceMap, - uiActions, - coreHttp, - coreStart, - attributeService, - dataViews, - capabilities, - usageCollection, - inspector, - spaces, - uiSettings, - } = await this.getStartServices(); - - const { Embeddable } = await import('../async_services'); - - return new Embeddable( - { - attributeService, - data, - dataViews, - timefilter, - inspector, - expressionRenderer, - basePath: coreHttp.basePath, - getTrigger: uiActions?.getTrigger, - getTriggerCompatibleActions: uiActions?.getTriggerCompatibleActions, - documentToExpression, - injectFilterReferences, - visualizationMap, - datasourceMap, - capabilities: { - canSaveDashboards: Boolean(capabilities.dashboard?.showWriteControls), - canSaveVisualizations: Boolean(capabilities.visualize.save), - canOpenVisualizations: Boolean(capabilities.visualize.show), - navLinks: capabilities.navLinks, - discover: capabilities.discover, - }, - coreStart, - usageCollection, - spaces, - uiSettings, - }, - input, - parent - ); - } catch (e) { - return new ErrorEmbeddable(e, input, parent); - } - } - - extract = extract; - inject = inject; -} diff --git a/x-pack/plugins/lens/public/embeddable/interfaces/lens_api.ts b/x-pack/plugins/lens/public/embeddable/interfaces/lens_api.ts deleted file mode 100644 index 11b70cd6e776..000000000000 --- a/x-pack/plugins/lens/public/embeddable/interfaces/lens_api.ts +++ /dev/null @@ -1,45 +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 type { - HasParentApi, - HasType, - PublishesUnifiedSearch, - PublishesPanelTitle, - PublishingSubject, -} from '@kbn/presentation-publishing'; -import { - apiIsOfType, - apiPublishesUnifiedSearch, - apiPublishesPanelTitle, -} from '@kbn/presentation-publishing'; -import { LensSavedObjectAttributes, ViewUnderlyingDataArgs } from '../embeddable'; - -export type HasLensConfig = HasType<'lens'> & { - getSavedVis: () => Readonly<LensSavedObjectAttributes | undefined>; - canViewUnderlyingData$: PublishingSubject<boolean>; - getViewUnderlyingDataArgs: () => ViewUnderlyingDataArgs; - getFullAttributes: () => LensSavedObjectAttributes | undefined; -}; - -export type LensApi = HasLensConfig & - PublishesPanelTitle & - PublishesUnifiedSearch & - Partial<HasParentApi<Partial<PublishesUnifiedSearch>>>; - -export const isLensApi = (api: unknown): api is LensApi => { - return Boolean( - api && - apiIsOfType(api, 'lens') && - typeof (api as HasLensConfig).getSavedVis === 'function' && - (api as HasLensConfig).canViewUnderlyingData$ && - typeof (api as HasLensConfig).getViewUnderlyingDataArgs === 'function' && - typeof (api as HasLensConfig).getFullAttributes === 'function' && - apiPublishesPanelTitle(api) && - apiPublishesUnifiedSearch(api) - ); -}; diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 026da7988a30..aea728024b57 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -7,12 +7,21 @@ import { LensPlugin } from './plugin'; -export { isLensApi } from './embeddable/interfaces/lens_api'; +export { isLensApi } from './react_embeddable/type_guards'; +export { type EmbeddableComponent } from './react_embeddable/renderer/lens_custom_renderer_component'; export type { - EmbeddableComponentProps, - EmbeddableComponent, + LensApi, + LensSerializedState, + LensRuntimeState, + LensByValueInput, + LensByReferenceInput, TypedLensByValueInput, -} from './embeddable/embeddable_component'; + LensEmbeddableInput, + LensEmbeddableOutput, + LensSavedObjectAttributes, + LensRendererProps as EmbeddableComponentProps, +} from './react_embeddable/types'; + export type { XYState, XYReferenceLineLayerConfig, @@ -110,14 +119,6 @@ export type { export type { InlineEditLensEmbeddableContext } from './trigger_actions/open_lens_config/in_app_embeddable_edit/types'; -export type { - LensApi, - LensEmbeddableInput, - LensSavedObjectAttributes, - Embeddable, - LensEmbeddableOutput, -} from './embeddable'; - export type { ChartInfo } from './chart_info_api'; export { layerTypes } from '../common/layer_types'; diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts index eb827d87d641..b5eeaae5d0f5 100644 --- a/x-pack/plugins/lens/public/lens_attribute_service.ts +++ b/x-pack/plugins/lens/public/lens_attribute_service.ts @@ -6,27 +6,52 @@ */ import type { CoreStart } from '@kbn/core/public'; -import type { AttributeService } from '@kbn/embeddable-plugin/public'; +import type { SavedObjectReference } from '@kbn/core/types'; import { OnSaveProps } from '@kbn/saved-objects-plugin/public'; import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; +import { noop } from 'lodash'; +import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; import type { LensPluginStartDependencies } from './plugin'; -import type { LensSavedObjectAttributes as LensSavedObjectAttributesWithoutReferences } from '../common/content_management'; import type { - LensSavedObjectAttributes, - LensByValueInput, - LensUnwrapMetaInfo, - LensUnwrapResult, - LensByReferenceInput, -} from './embeddable/embeddable'; + LensSavedObject, + LensSavedObjectAttributes as LensSavedObjectAttributesWithoutReferences, +} from '../common/content_management'; +import { extract, inject } from '../common/embeddable_factory'; import { SavedObjectIndexStore, checkForDuplicateTitle } from './persistence'; import { DOC_TYPE } from '../common/constants'; +import { SharingSavedObjectProps } from './types'; +import { LensRuntimeState, LensSavedObjectAttributes } from './react_embeddable/types'; -export type LensAttributeService = AttributeService< - LensSavedObjectAttributes, - LensByValueInput, - LensByReferenceInput, - LensUnwrapMetaInfo ->; +type Reference = LensSavedObject['references'][number]; + +type CheckDuplicateTitleProps = OnSaveProps & { + id?: string; + displayName: string; + lastSavedTitle: string; + copyOnSave: boolean; +}; + +export interface LensAttributesService { + loadFromLibrary: (savedObjectId: string) => Promise<{ + attributes: LensSavedObjectAttributes; + sharingSavedObjectProps: SharingSavedObjectProps; + managed: boolean; + }>; + saveToLibrary: ( + attributes: LensSavedObjectAttributesWithoutReferences, + references: Reference[], + savedObjectId?: string + ) => Promise<string>; + checkForDuplicateTitle: (props: CheckDuplicateTitleProps) => Promise<{ isDuplicate: boolean }>; + injectReferences: ( + runtimeState: LensRuntimeState, + references: SavedObjectReference[] | undefined + ) => LensRuntimeState; + extractReferences: (runtimeState: LensRuntimeState) => { + rawState: LensRuntimeState; + references: SavedObjectReference[]; + }; +} export const savedObjectToEmbeddableAttributes = ( savedObject: SavedObjectCommon<LensSavedObjectAttributesWithoutReferences> @@ -41,60 +66,86 @@ export const savedObjectToEmbeddableAttributes = ( export function getLensAttributeService( core: CoreStart, startDependencies: LensPluginStartDependencies -): LensAttributeService { +): LensAttributesService { const savedObjectStore = new SavedObjectIndexStore(startDependencies.contentManagement); - return startDependencies.embeddable.getAttributeService< - LensSavedObjectAttributes, - LensByValueInput, - LensByReferenceInput, - LensUnwrapMetaInfo - >(DOC_TYPE, { - saveMethod: async (attributes: LensSavedObjectAttributes, savedObjectId?: string) => { - const savedDoc = await savedObjectStore.save({ + return { + loadFromLibrary: async ( + savedObjectId: string + ): Promise<{ + attributes: LensSavedObjectAttributes; + sharingSavedObjectProps: SharingSavedObjectProps; + managed: boolean; + }> => { + const { meta, item } = await savedObjectStore.load(savedObjectId); + return { + attributes: { + ...item.attributes, + state: item.attributes.state as LensSavedObjectAttributes['state'], + references: item.references, + }, + sharingSavedObjectProps: { + aliasTargetId: meta.aliasTargetId, + outcome: meta.outcome, + aliasPurpose: meta.aliasPurpose, + sourceId: item.id, + }, + managed: Boolean(item.managed), + }; + }, + saveToLibrary: async ( + attributes: LensSavedObjectAttributesWithoutReferences, + references: Reference[], + savedObjectId?: string + ) => { + const result = await savedObjectStore.save({ ...attributes, + state: attributes.state as LensSavedObjectAttributes['state'], + references, savedObjectId, - type: DOC_TYPE, }); - return { id: savedDoc.savedObjectId }; + return result.savedObjectId; }, - unwrapMethod: async (savedObjectId: string): Promise<LensUnwrapResult> => { - const { - item: savedObject, - meta: { outcome, aliasTargetId, aliasPurpose }, - } = await savedObjectStore.load(savedObjectId); - const { id } = savedObject; - - const sharingSavedObjectProps = { - aliasTargetId, - outcome, - aliasPurpose, - sourceId: id, - }; - + checkForDuplicateTitle: async ({ + newTitle, + isTitleDuplicateConfirmed, + onTitleDuplicate = noop, + displayName = DOC_TYPE, + lastSavedTitle = '', + copyOnSave = false, + id, + }: CheckDuplicateTitleProps) => { return { - attributes: savedObjectToEmbeddableAttributes(savedObject), - metaInfo: { - sharingSavedObjectProps, - managed: savedObject.managed, - }, + isDuplicate: await checkForDuplicateTitle( + { + id, + title: newTitle, + isTitleDuplicateConfirmed, + displayName, + lastSavedTitle, + copyOnSave, + }, + onTitleDuplicate, + { + client: savedObjectStore, + ...core, + } + ), }; }, - checkForDuplicateTitle: (props: OnSaveProps) => { - return checkForDuplicateTitle( - { - title: props.newTitle, - displayName: DOC_TYPE, - isTitleDuplicateConfirmed: props.isTitleDuplicateConfirmed, - lastSavedTitle: '', - copyOnSave: false, - }, - props.onTitleDuplicate, - { - client: savedObjectStore, - ...core, - } - ); + // Make sure to inject references from the container down to the runtime state + // this ensure migrations/copy to spaces works correctly + injectReferences: (runtimeState, references) => { + return inject( + runtimeState as unknown as EmbeddableStateWithType, + references ?? runtimeState.attributes.references + ) as unknown as LensRuntimeState; }, - }); + // Make sure to move the internal references into the parent references + // so migrations/move to spaces can work properly + extractReferences: (runtimeState) => { + const { state, references } = extract(runtimeState as unknown as EmbeddableStateWithType); + return { rawState: state as unknown as LensRuntimeState, references }; + }, + }; } diff --git a/x-pack/plugins/lens/public/lens_inspector_service.ts b/x-pack/plugins/lens/public/lens_inspector_service.ts index 4de0a8ec1340..052a741851ba 100644 --- a/x-pack/plugins/lens/public/lens_inspector_service.ts +++ b/x-pack/plugins/lens/public/lens_inspector_service.ts @@ -18,7 +18,7 @@ export const getLensInspectorService = (inspector: InspectorStartContract) => { const adapters: Adapters = createDefaultInspectorAdapters(); let overlayRef: InspectorSession | undefined; return { - adapters, + getInspectorAdapters: () => adapters, inspect: (options?: InspectorOptions) => { overlayRef = inspector.open(adapters, options); overlayRef.onClose.then(() => { @@ -28,7 +28,7 @@ export const getLensInspectorService = (inspector: InspectorStartContract) => { }); return overlayRef; }, - close: () => overlayRef?.close(), + closeInspector: async () => overlayRef?.close(), }; }; diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.test.ts b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.test.ts index 177a7e2e0d33..fa53ec84293c 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.test.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.test.ts @@ -7,7 +7,7 @@ import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { mergeSuggestionWithVisContext } from './helpers'; import { mockAllSuggestions } from '../mocks'; -import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; +import { TypedLensByValueInput } from '../react_embeddable/types'; const context = { dataViewSpec: { diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts index 394d32e8c5bb..5e000d1f14c8 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/helpers.ts @@ -7,7 +7,7 @@ import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import { getDatasourceId } from '@kbn/visualization-utils'; import type { VisualizeEditorContext, Suggestion } from '../types'; -import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; +import { TypedLensByValueInput } from '../react_embeddable/types'; /** * Returns the suggestion updated with external visualization state for ES|QL charts diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/index.ts b/x-pack/plugins/lens/public/lens_suggestions_api/index.ts index c73379d9a42c..6f3f558b60b1 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api/index.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/index.ts @@ -10,7 +10,7 @@ import type { ChartType } from '@kbn/visualization-utils'; import { getSuggestions } from '../editor_frame_service/editor_frame/suggestion_helpers'; import type { DatasourceMap, VisualizationMap, VisualizeEditorContext } from '../types'; import type { DataViewsState } from '../state_management'; -import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; +import type { TypedLensByValueInput } from '../react_embeddable/types'; import { mergeSuggestionWithVisContext } from './helpers'; interface SuggestionsApiProps { diff --git a/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts b/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts index e5e60284e491..784c0ae03e56 100644 --- a/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts +++ b/x-pack/plugins/lens/public/lens_suggestions_api/lens_suggestions_api.test.ts @@ -10,7 +10,7 @@ import { ChartType } from '@kbn/visualization-utils'; import { createMockVisualization, DatasourceMock, createMockDatasource } from '../mocks'; import { DatasourceSuggestion } from '../types'; import { suggestionsApi } from '.'; -import type { TypedLensByValueInput } from '../embeddable/embeddable_component'; +import { TypedLensByValueInput } from '../react_embeddable/types'; const generateSuggestion = (state = {}, layerId: string = 'first'): DatasourceSuggestion => ({ state, diff --git a/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts b/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts index db7ab00de22e..8628cc29c194 100644 --- a/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts +++ b/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts @@ -48,13 +48,13 @@ export function mockDataPlugin( function createMockSearchService() { let sessionIdCounter = initialSessionId ? 1 : 0; let currentSessionId: string | undefined = initialSessionId; - const start = () => { - currentSessionId = `sessionId-${++sessionIdCounter}`; - return currentSessionId; - }; + return { session: { - start: jest.fn(start), + start: jest.fn(() => { + currentSessionId = `sessionId-${++sessionIdCounter}`; + return currentSessionId; + }), clear: jest.fn(), getSessionId: jest.fn(() => currentSessionId), getSession$: jest.fn(() => sessionIdSubject.asObservable()), @@ -146,5 +146,6 @@ export function mockDataPlugin( fieldFormats: { deserialize: jest.fn(), }, + datatableUtilities: { getDateHistogramMeta: jest.fn(() => true) }, } as unknown as DataPublicPluginStart; } diff --git a/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx b/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx index 4e5f83c7db83..cbb2f0c5dddb 100644 --- a/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx +++ b/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx @@ -16,7 +16,7 @@ type Start = jest.Mocked<LensPublicStart>; export const lensPluginMock = { createStartContract: (): Start => { const startContract: Start = { - EmbeddableComponent: jest.fn(() => { + EmbeddableComponent: jest.fn((props) => { return <span>Lens Embeddable Component</span>; }), SaveModalComponent: jest.fn(() => { diff --git a/x-pack/plugins/lens/public/mocks/services_mock.tsx b/x-pack/plugins/lens/public/mocks/services_mock.tsx index 18fa29fd6caf..b5366984c435 100644 --- a/x-pack/plugins/lens/public/mocks/services_mock.tsx +++ b/x-pack/plugins/lens/public/mocks/services_mock.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import React from 'react'; import { Subject } from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; import { navigationPluginMock } from '@kbn/navigation-plugin/public/mocks'; @@ -20,46 +19,35 @@ import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; -import { - mockAttributeService, - createEmbeddableStateTransferMock, -} from '@kbn/embeddable-plugin/public/mocks'; +import { createEmbeddableStateTransferMock } from '@kbn/embeddable-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import type { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public'; import { presentationUtilPluginMock } from '@kbn/presentation-util-plugin/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import type { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'; -import type { LensAttributeService } from '../lens_attribute_service'; -import type { - LensByValueInput, - LensByReferenceInput, - LensSavedObjectAttributes, - LensUnwrapMetaInfo, -} from '../embeddable/embeddable'; -import { DOC_TYPE } from '../../common/constants'; + import { LensAppServices } from '../app_plugin/types'; import { mockDataPlugin } from './data_plugin_mock'; import { getLensInspectorService } from '../lens_inspector_service'; -import { SavedObjectIndexStore } from '../persistence'; +import { LensDocument, SavedObjectIndexStore } from '../persistence'; +import { LensAttributesService } from '../lens_attribute_service'; +import { mockDatasourceStates } from './store_mocks'; const startMock = coreMock.createStart(); -export const defaultDoc = { +export const defaultDoc: LensDocument = { savedObjectId: '1234', title: 'An extremely cool default document!', - expression: 'definitely a valid expression', visualizationType: 'testVis', state: { - query: 'kuery', + query: { query: 'test', language: 'kuery' }, filters: [{ query: { match_phrase: { src: 'test' } }, meta: { index: 'index-pattern-0' } }], - datasourceStates: { - testDatasource: 'datasource', - }, + datasourceStates: mockDatasourceStates(), visualization: {}, }, references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], -} as unknown as Document; +}; export const exactMatchDoc = { attributes: { @@ -70,6 +58,27 @@ export const exactMatchDoc = { }, }; +export function makeAttributeService(doc: LensDocument): jest.Mocked<LensAttributesService> { + const attributeServiceMock: jest.Mocked<LensAttributesService> = { + loadFromLibrary: jest.fn().mockResolvedValue(exactMatchDoc), + saveToLibrary: jest.fn().mockResolvedValue(doc.savedObjectId), + checkForDuplicateTitle: jest.fn(), + injectReferences: jest.fn((_runtimeState, references) => ({ + ..._runtimeState, + attributes: { + ..._runtimeState.attributes, + references: references?.length ? references : _runtimeState.attributes.references, + }, + })), + extractReferences: jest.fn((_runtimeState) => ({ + rawState: _runtimeState, + references: _runtimeState.attributes.references || [], + })), + }; + + return attributeServiceMock; +} + export function makeDefaultServices( sessionIdSubject = new Subject<string>(), sessionId: string | undefined = undefined, @@ -106,44 +115,16 @@ export function makeDefaultServices( const navigationStartMock = navigationPluginMock.createStartContract(); - jest - .spyOn(navigationStartMock.ui.AggregateQueryTopNavMenu.prototype, 'constructor') - .mockImplementation(() => { - return <div className="topNavMenu" />; - }); - - function makeAttributeService(): LensAttributeService { - const attributeServiceMock = mockAttributeService< - LensSavedObjectAttributes, - LensByValueInput, - LensByReferenceInput, - LensUnwrapMetaInfo - >( - DOC_TYPE, - { - saveMethod: jest.fn(), - unwrapMethod: jest.fn(), - checkForDuplicateTitle: jest.fn(), - }, - core - ); - attributeServiceMock.unwrapAttributes = jest.fn().mockResolvedValue(exactMatchDoc); - attributeServiceMock.wrapAttributes = jest.fn().mockResolvedValue({ - savedObjectId: (doc as unknown as LensByReferenceInput).savedObjectId, - }); - - return attributeServiceMock; - } - return { ...startMock, chrome: core.chrome, navigation: navigationStartMock, - attributeService: makeAttributeService(), + attributeService: makeAttributeService(doc), inspector: { - adapters: getLensInspectorService(inspectorPluginMock.createStartContract()).adapters, + getInspectorAdapters: getLensInspectorService(inspectorPluginMock.createStartContract()) + .getInspectorAdapters, inspect: jest.fn(), - close: jest.fn(), + closeInspector: jest.fn(), }, presentationUtil: presentationUtilPluginMock.createStartContract(), savedObjectStore: { @@ -158,6 +139,9 @@ export function makeDefaultServices( capabilities: { ...core.application.capabilities, visualize: { save: true, saveQuery: true, show: true, createShortUrl: true }, + dashboard: { + showWriteControls: true, + }, }, getUrlForApp: jest.fn((appId: string) => `/testbasepath/app/${appId}#/`), }, diff --git a/x-pack/plugins/lens/public/mocks/store_mocks.tsx b/x-pack/plugins/lens/public/mocks/store_mocks.tsx index f465eadc9dfd..87667c21fed2 100644 --- a/x-pack/plugins/lens/public/mocks/store_mocks.tsx +++ b/x-pack/plugins/lens/public/mocks/store_mocks.tsx @@ -8,7 +8,6 @@ import React, { PropsWithChildren, ReactElement } from 'react'; import { ReactWrapper, mount } from 'enzyme'; import { Provider } from 'react-redux'; -import { act } from 'react-dom/test-utils'; import { PreloadedState } from '@reduxjs/toolkit'; import { RenderOptions, render } from '@testing-library/react'; import { I18nProvider } from '@kbn/i18n-react'; @@ -20,17 +19,25 @@ import { mockVisualizationMap } from './visualization_mock'; import { mockDatasourceMap } from './datasource_mock'; import { makeDefaultServices } from './services_mock'; -export const mockStoreDeps = (deps?: { - lensServices?: LensAppServices; - datasourceMap?: DatasourceMap; - visualizationMap?: VisualizationMap; -}) => { - return { - datasourceMap: deps?.datasourceMap || mockDatasourceMap(), - visualizationMap: deps?.visualizationMap || mockVisualizationMap(), - lensServices: deps?.lensServices || makeDefaultServices(), - }; -}; +export const mockStoreDeps = ( + { + lensServices = makeDefaultServices(), + datasourceMap = mockDatasourceMap(), + visualizationMap = mockVisualizationMap(), + }: { + lensServices?: LensAppServices; + datasourceMap?: DatasourceMap; + visualizationMap?: VisualizationMap; + } = { + lensServices: makeDefaultServices(), + datasourceMap: mockDatasourceMap(), + visualizationMap: mockVisualizationMap(), + } +) => ({ + datasourceMap, + visualizationMap, + lensServices, +}); export function mockDatasourceStates() { return { @@ -138,12 +145,7 @@ export const mountWithProvider = async ( } ) => { const { mountArgs, lensStore, deps } = getMountWithProviderParams(component, store, options); - - let instance: ReactWrapper = {} as ReactWrapper; - - await act(async () => { - instance = mount(mountArgs.component, mountArgs.options); - }); + const instance = mount(mountArgs.component, mountArgs.options); return { instance, lensStore, deps }; }; diff --git a/x-pack/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/plugins/lens/public/persistence/saved_object_store.ts index d15386548dac..9edd481f7b62 100644 --- a/x-pack/plugins/lens/public/persistence/saved_object_store.ts +++ b/x-pack/plugins/lens/public/persistence/saved_object_store.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { Filter, Query } from '@kbn/es-query'; -import { SavedObjectReference } from '@kbn/core/public'; +import type { AggregateQuery, Filter, Query } from '@kbn/es-query'; +import type { SavedObjectReference } from '@kbn/core/public'; import type { DataViewSpec } from '@kbn/data-views-plugin/public'; import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import type { SearchQuery } from '@kbn/content-management-plugin/common'; @@ -14,7 +14,7 @@ import type { VisualizationClient } from '@kbn/visualizations-plugin/public'; import type { LensSavedObjectAttributes, LensSearchQuery } from '../../common/content_management'; import { getLensClient } from './lens_client'; -export interface Document { +export interface LensDocument { savedObjectId?: string; type?: string; visualizationType: string | null; @@ -23,7 +23,7 @@ export interface Document { state: { datasourceStates: Record<string, unknown>; visualization: unknown; - query: Query; + query: Query | AggregateQuery; globalPalette?: { activePaletteId: string; state?: unknown; @@ -36,7 +36,7 @@ export interface Document { } export interface DocumentSaver { - save: (vis: Document) => Promise<{ savedObjectId: string }>; + save: (vis: LensDocument) => Promise<{ savedObjectId: string }>; } export interface DocumentLoader { @@ -52,9 +52,8 @@ export class SavedObjectIndexStore implements SavedObjectStore { this.client = getLensClient(cm); } - save = async (vis: Document) => { - const { savedObjectId, type, references, ...rest } = vis; - const attributes = rest; + save = async (vis: LensDocument) => { + const { savedObjectId, type, references, ...attributes } = vis; if (savedObjectId) { const result = await this.client.update({ @@ -65,15 +64,14 @@ export class SavedObjectIndexStore implements SavedObjectStore { }, }); return { ...vis, savedObjectId: result.item.id }; - } else { - const result = await this.client.create({ - data: attributes, - options: { - references, - }, - }); - return { ...vis, savedObjectId: result.item.id }; } + const result = await this.client.create({ + data: attributes, + options: { + references, + }, + }); + return { ...vis, savedObjectId: result.item.id }; }; async load(savedObjectId: string) { diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 3145606abaf6..38f831ce3415 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -14,8 +14,9 @@ import type { } from '@kbn/usage-collection-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; +import type { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plugin/public'; import type { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { @@ -24,6 +25,7 @@ import type { ExpressionsStart, } from '@kbn/expressions-plugin/public'; import { + ACTION_CONVERT_DASHBOARD_PANEL_TO_LENS, DASHBOARD_VISUALIZATION_PANEL_TRIGGER, VisualizationsSetup, VisualizationsStart, @@ -94,7 +96,13 @@ import type { HeatmapVisualization as HeatmapVisualizationType } from './visuali import type { GaugeVisualization as GaugeVisualizationType } from './visualizations/gauge'; import type { TagcloudVisualization as TagcloudVisualizationType } from './visualizations/tagcloud'; -import { APP_ID, getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common/constants'; +import { + APP_ID, + getEditPath, + LENS_EMBEDDABLE_TYPE, + LENS_ICON, + NOT_INTERNATIONALIZED_PRODUCT_NAME, +} from '../common/constants'; import type { FormatFactory } from '../common/types'; import type { Visualization, @@ -103,10 +111,11 @@ import type { LensTopNavMenuEntryGenerator, VisualizeEditorContext, Suggestion, + DatasourceMap, + VisualizationMap, } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { createOpenInDiscoverAction } from './trigger_actions/open_in_discover_action'; -import { ConfigureInLensPanelAction } from './trigger_actions/open_lens_config/edit_action'; import { CreateESQLPanelAction } from './trigger_actions/open_lens_config/create_action'; import { inAppEmbeddableEditTrigger, @@ -115,12 +124,12 @@ import { import { EditLensEmbeddableAction } from './trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; import { visualizeTSVBAction } from './trigger_actions/visualize_tsvb_actions'; -import { visualizeAggBasedVisAction } from './trigger_actions/visualize_agg_based_vis_actions'; -import { visualizeDashboardVisualizePanelction } from './trigger_actions/dashboard_visualize_panel_actions'; -import type { LensByValueInput, LensEmbeddableInput } from './embeddable'; -import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory'; -import { EmbeddableComponent, getEmbeddableComponent } from './embeddable/embeddable_component'; +import type { + LensEmbeddableStartServices, + LensSerializedState, + TypedLensByValueInput, +} from './react_embeddable/types'; import { getSaveModalComponent } from './app_plugin/shared/saved_modal_lazy'; import type { SaveModalContainerProps } from './app_plugin/save_modal_container'; @@ -130,15 +139,16 @@ import { OpenInDiscoverDrilldown } from './trigger_actions/open_in_discover_dril import { ChartInfoApi } from './chart_info_api'; import { type LensAppLocator, LensAppLocatorDefinition } from '../common/locator/locator'; import { downloadCsvShareProvider } from './app_plugin/csv_download_provider/csv_download_provider'; - +import { LensDocument } from './persistence/saved_object_store'; import { CONTENT_ID, LATEST_VERSION, LensSavedObjectAttributes, } from '../common/content_management'; import type { EditLensConfigurationProps } from './app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; -import { savedObjectToEmbeddableAttributes } from './lens_attribute_service'; -import type { TypedLensByValueInput } from './embeddable/embeddable_component'; +import { convertToLensActionFactory } from './trigger_actions/convert_to_lens_action'; +import { LensRenderer } from './react_embeddable/renderer/lens_custom_renderer_component'; +import { deserializeState } from './react_embeddable/helper'; export type { SaveProps } from './app_plugin'; @@ -182,6 +192,7 @@ export interface LensPluginStartDependencies { contentManagement: ContentManagementPublicStart; serverless?: ServerlessPluginStart; licensing?: LicensingPluginStart; + embeddableEnhanced?: EmbeddableEnhancedPluginStart; } export interface LensPublicSetup { @@ -221,7 +232,7 @@ export interface LensPublicStart { * * @experimental */ - EmbeddableComponent: EmbeddableComponent; + EmbeddableComponent: typeof LensRenderer; /** * React component which can be used to embed a Lens Visualization Save Modal Component. * See `x-pack/examples/embedded_lens_example` for exemplary usage. @@ -248,7 +259,7 @@ export interface LensPublicStart { * @experimental */ navigateToPrefilledEditor: ( - input: LensEmbeddableInput | undefined, + input: LensSerializedState | undefined, options?: { openInNewTab?: boolean; originatingApp?: string; @@ -303,9 +314,14 @@ export class LensPlugin { private topNavMenuEntries: LensTopNavMenuEntryGenerator[] = []; private hasDiscoverAccess: boolean = false; private dataViewsService: DataViewsPublicPluginStart | undefined; - private initDependenciesForApi: () => void = () => {}; private locator?: LensAppLocator; + // Note: this method will be overwritten in the setup flow + private initEditorFrameService = async (): Promise<{ + datasourceMap: DatasourceMap; + visualizationMap: VisualizationMap; + }> => ({ datasourceMap: {}, visualizationMap: {} }); + setup( core: CoreSetup<LensPluginStartDependencies, void>, { @@ -326,26 +342,16 @@ export class LensPlugin { const startServices = createStartServicesGetter(core.getStartServices); const getStartServicesForEmbeddable = async (): Promise<LensEmbeddableStartServices> => { - const { getLensAttributeService, setUsageCollectionStart, initMemoizedErrorNotification } = - await import('./async_services'); + const { setUsageCollectionStart, initMemoizedErrorNotification } = await import( + './async_services' + ); const { core: coreStart, plugins } = startServices(); - await this.initParts( - core, - data, - charts, - expressions, - fieldFormats, - plugins.fieldFormats.deserialize - ); - const [visualizationMap, datasourceMap] = await Promise.all([ - this.editorFrameService!.loadVisualizations(), - this.editorFrameService!.loadDatasources(), + const { visualizationMap, datasourceMap } = await this.initEditorFrameService(); + const [{ getLensAttributeService }, eventAnnotationService] = await Promise.all([ + import('./async_services'), + plugins.eventAnnotation.getService(), ]); - const { setVisualizationMap, setDatasourceMap } = await import('./async_services'); - setDatasourceMap(datasourceMap); - setVisualizationMap(visualizationMap); - const eventAnnotationService = await plugins.eventAnnotation.getService(); if (plugins.usageCollection) { setUsageCollectionStart(plugins.usageCollection); @@ -354,14 +360,14 @@ export class LensPlugin { initMemoizedErrorNotification(coreStart); return { + ...plugins, attributeService: getLensAttributeService(coreStart, plugins), capabilities: coreStart.application.capabilities, coreHttp: coreStart.http, coreStart, - data: plugins.data, timefilter: plugins.data.query.timefilter.timefilter, expressionRenderer: plugins.expressions.ReactExpressionRenderer, - documentToExpression: (doc) => + documentToExpression: (doc: LensDocument) => this.editorFrameService!.documentToExpression(doc, { dataViews: plugins.dataViews, storage: new Storage(localStorage), @@ -373,36 +379,45 @@ export class LensPlugin { injectFilterReferences: data.query.filterManager.inject.bind(data.query.filterManager), visualizationMap, datasourceMap, - dataViews: plugins.dataViews, - uiActions: plugins.uiActions, - usageCollection, - inspector: plugins.inspector, - spaces: plugins.spaces, theme: core.theme, uiSettings: core.uiSettings, }; }; if (embeddable) { - embeddable.registerEmbeddableFactory( - 'lens', - new EmbeddableFactory(getStartServicesForEmbeddable) - ); - - embeddable.registerSavedObjectToPanelMethod<LensSavedObjectAttributes, LensByValueInput>( - CONTENT_ID, - (savedObject) => { - if (!savedObject.managed) { - return { savedObjectId: savedObject.id }; - } - - const panel = { - attributes: savedObjectToEmbeddableAttributes(savedObject), - }; - - return panel; - } - ); + // Let Kibana know about the Lens embeddable + embeddable.registerReactEmbeddableFactory(LENS_EMBEDDABLE_TYPE, async () => { + const [deps, { createLensEmbeddableFactory }] = await Promise.all([ + getStartServicesForEmbeddable(), + import('./react_embeddable/lens_embeddable'), + ]); + return createLensEmbeddableFactory(deps); + }); + + // Let Dashboard know about the Lens panel type + embeddable.registerReactEmbeddableSavedObject<LensSavedObjectAttributes>({ + onAdd: async (container, savedObject) => { + const { attributeService } = await getStartServicesForEmbeddable(); + // deserialize the saved object from visualize library + // this make sure to fit into the new embeddable model, where the following build() + // function expects a fully loaded runtime state + const state = await deserializeState( + attributeService, + { savedObjectId: savedObject.id }, + savedObject.references + ); + container.addNewPanel({ + panelType: LENS_EMBEDDABLE_TYPE, + initialState: state, + }); + }, + embeddableType: LENS_EMBEDDABLE_TYPE, + savedObjectType: LENS_EMBEDDABLE_TYPE, + savedObjectName: i18n.translate('xpack.lens.mapSavedObjectLabel', { + defaultMessage: 'Lens', + }), + getIconForSavedObject: () => LENS_ICON, + }); } if (share) { @@ -509,9 +524,10 @@ export class LensPlugin { ); } - urlForwarding.forwardApp('lens', 'lens'); + urlForwarding.forwardApp(APP_ID, APP_ID); - this.initDependenciesForApi = async () => { + // Note: this overwrites a method defined above + this.initEditorFrameService = async () => { const { plugins } = startServices(); await this.initParts( core, @@ -521,6 +537,15 @@ export class LensPlugin { fieldFormats, plugins.fieldFormats.deserialize ); + // This needs to be executed before the import call to avoid race conditions + const [visualizationMap, datasourceMap] = await Promise.all([ + this.editorFrameService!.loadVisualizations(), + this.editorFrameService!.loadDatasources(), + ]); + const { setVisualizationMap, setDatasourceMap } = await import('./async_services'); + setDatasourceMap(datasourceMap); + setVisualizationMap(visualizationMap); + return { datasourceMap, visualizationMap }; }; return { @@ -625,21 +650,33 @@ export class LensPlugin { startDependencies.uiActions.addTriggerAction( DASHBOARD_VISUALIZATION_PANEL_TRIGGER, - visualizeDashboardVisualizePanelction(core.application) + convertToLensActionFactory( + ACTION_CONVERT_DASHBOARD_PANEL_TO_LENS, + i18n.translate('xpack.lens.visualizeLegacyVisualizationChart', { + defaultMessage: 'Visualize legacy visualization chart', + }), + i18n.translate('xpack.lens.dashboardLabel', { + defaultMessage: 'Dashboard', + }) + )(core.application) ); startDependencies.uiActions.addTriggerAction( AGG_BASED_VISUALIZATION_TRIGGER, - visualizeAggBasedVisAction(core.application) + convertToLensActionFactory( + ACTION_CONVERT_DASHBOARD_PANEL_TO_LENS, + i18n.translate('xpack.lens.visualizeAggBasedLegend', { + defaultMessage: 'Visualize agg based chart', + }), + i18n.translate('xpack.lens.AggBasedLabel', { + defaultMessage: 'aggregation based visualization', + }) + )(core.application) ); - const editInLensAction = new ConfigureInLensPanelAction(startDependencies, core); - // dashboard edit panel action - startDependencies.uiActions.addTriggerAction('CONTEXT_MENU_TRIGGER', editInLensAction); - - // Allows the Lens embeddable to easily open the inapp editing flyout + // Allows the Lens embeddable to easily open the inline editing flyout const editLensEmbeddableAction = new EditLensEmbeddableAction(startDependencies, core); - // embeddable edit panel action + // embeddable inline edit panel action startDependencies.uiActions.addTriggerAction( IN_APP_EMBEDDABLE_EDIT_TRIGGER, editLensEmbeddableAction @@ -648,7 +685,7 @@ export class LensPlugin { // Displays the add ESQL panel in the dashboard add Panel menu const createESQLPanelAction = new CreateESQLPanelAction(startDependencies, core, async () => { if (!this.editorFrameService) { - await this.initDependenciesForApi(); + await this.initEditorFrameService(); } return this.editorFrameService!; @@ -668,7 +705,7 @@ export class LensPlugin { } return { - EmbeddableComponent: getEmbeddableComponent(core, startDependencies), + EmbeddableComponent: LensRenderer, SaveModalComponent: getSaveModalComponent(core, startDependencies), navigateToPrefilledEditor: ( input, @@ -705,16 +742,15 @@ export class LensPlugin { const { createFormulaPublicApi, createChartInfoApi, suggestionsApi } = await import( './async_services' ); - if (!this.editorFrameService) { - await this.initDependenciesForApi(); - } - const [visualizationMap, datasourceMap] = await Promise.all([ - this.editorFrameService!.loadVisualizations(), - this.editorFrameService!.loadDatasources(), - ]); + + const { visualizationMap, datasourceMap } = await this.initEditorFrameService(); return { formula: createFormulaPublicApi(), - chartInfo: createChartInfoApi(startDependencies.dataViews, this.editorFrameService), + chartInfo: createChartInfoApi( + startDependencies.dataViews, + visualizationMap, + datasourceMap + ), suggestions: ( context, dataView, @@ -734,15 +770,11 @@ export class LensPlugin { }, }; }, + // TODO: remove this in faviour of the custom action thing + // This is currently used in Discover by the unified histogram plugin EditLensConfigPanelApi: async () => { + const { visualizationMap, datasourceMap } = await this.initEditorFrameService(); const { getEditLensConfiguration } = await import('./async_services'); - if (!this.editorFrameService) { - this.initDependenciesForApi(); - } - const [visualizationMap, datasourceMap] = await Promise.all([ - this.editorFrameService!.loadVisualizations(), - this.editorFrameService!.loadDatasources(), - ]); const Component = await getEditLensConfiguration( core, startDependencies, diff --git a/x-pack/plugins/lens/public/react_embeddable/data_loader.ts b/x-pack/plugins/lens/public/react_embeddable/data_loader.ts new file mode 100644 index 000000000000..0aed3edf70b8 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/data_loader.ts @@ -0,0 +1,329 @@ +/* + * 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 { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; +import { fetch$, type FetchContext } from '@kbn/presentation-publishing'; +import { apiPublishesSearchSession } from '@kbn/presentation-publishing/interfaces/fetch/publishes_search_session'; +import { type KibanaExecutionContext } from '@kbn/core/public'; +import { + BehaviorSubject, + type Subscription, + distinctUntilChanged, + debounceTime, + skip, + pipe, + merge, + tap, + map, +} from 'rxjs'; +import fastIsEqual from 'fast-deep-equal'; +import { getEditPath } from '../../common/constants'; +import type { + GetStateType, + LensApi, + LensInternalApi, + LensPublicCallbacks, + VisualizationContextHelper, +} from './types'; +import { getExpressionRendererParams } from './expressions/expression_params'; +import type { LensEmbeddableStartServices } from './types'; +import { prepareCallbacks } from './expressions/callbacks'; +import { buildUserMessagesHelpers } from './user_messages/api'; +import { getLogError } from './expressions/telemetry'; +import type { SharingSavedObjectProps, UserMessagesDisplayLocationId } from '../types'; +import { apiHasLensComponentCallbacks } from './type_guards'; +import { getRenderMode, getParentContext } from './helper'; +import { addLog } from './logger'; +import { getUsedDataViews } from './expressions/update_data_views'; +import { getMergedSearchContext } from './expressions/merged_search_context'; + +const blockingMessageDisplayLocations: UserMessagesDisplayLocationId[] = [ + 'visualization', + 'visualizationOnEmbeddable', +]; + +type ReloadReason = + | 'attributes' + | 'savedObjectId' + | 'overrides' + | 'disableTriggers' + | 'viewMode' + | 'searchContext'; + +/** + * The function computes the expression used to render the panel and produces the necessary props + * for the ExpressionWrapper component, binding any outer context to them. + * @returns + */ +export function loadEmbeddableData( + uuid: string, + getState: GetStateType, + api: LensApi, + parentApi: unknown, + internalApi: LensInternalApi, + services: LensEmbeddableStartServices, + { getVisualizationContext, updateVisualizationContext }: VisualizationContextHelper, + metaInfo?: SharingSavedObjectProps +) { + const { onLoad, onBeforeBadgesRender, ...callbacks } = apiHasLensComponentCallbacks(parentApi) + ? parentApi + : ({} as LensPublicCallbacks); + + // Some convenience api for the user messaging + const { + getUserMessages, + addUserMessages, + updateBlockingErrors, + updateValidationErrors, + updateWarnings, + resetMessages, + updateMessages, + } = buildUserMessagesHelpers( + api, + internalApi, + getVisualizationContext, + services, + onBeforeBadgesRender, + services.spaces, + metaInfo + ); + + const dispatchBlockingErrorIfAny = () => { + const blockingErrors = getUserMessages(blockingMessageDisplayLocations, { + severity: 'error', + }); + updateValidationErrors(blockingErrors); + updateBlockingErrors(blockingErrors); + if (blockingErrors.length > 0) { + internalApi.dispatchError(); + } + return blockingErrors.length > 0; + }; + + const onRenderComplete = () => { + updateMessages(getUserMessages('embeddableBadge')); + // No issues so far, blocking errors are handled directly by Lens from this point on + if (!dispatchBlockingErrorIfAny()) { + internalApi.dispatchRenderComplete(); + } + }; + + const unifiedSearch$ = new BehaviorSubject< + Pick<FetchContext, 'query' | 'filters' | 'timeRange' | 'timeslice' | 'searchSessionId'> + >({ + query: undefined, + filters: undefined, + timeRange: undefined, + timeslice: undefined, + searchSessionId: undefined, + }); + + async function reload( + // make reload easier to debug + sourceId: ReloadReason + ) { + addLog(`Embeddable reload reason: ${sourceId}`); + resetMessages(); + + // reset the render on reload + internalApi.dispatchRenderStart(); + + // notify about data loading + internalApi.updateDataLoading(true); + + // the component is ready to load + if (apiHasLensComponentCallbacks(parentApi)) { + parentApi.onLoad?.(true); + } + + const currentState = getState(); + + const { searchSessionId, ...unifiedSearch } = unifiedSearch$.getValue(); + + const getExecutionContext = () => { + const parentContext = getParentContext(parentApi); + const lastState = getState(); + if (lastState.attributes) { + const child: KibanaExecutionContext = { + type: 'lens', + name: lastState.attributes.visualizationType ?? '', + id: uuid || 'new', + description: lastState.attributes.title || lastState.title || '', + url: `${services.coreStart.application.getUrlForApp('lens')}${getEditPath( + lastState.savedObjectId + )}`, + }; + + return parentContext + ? { + ...parentContext, + child, + } + : child; + } + }; + + const onDataCallback = (adapters: Partial<DefaultInspectorAdapters> | undefined) => { + updateVisualizationContext({ + activeData: adapters?.tables?.tables, + }); + // data has loaded + internalApi.updateDataLoading(false); + // The third argument here is an observable to let the + // consumer to be notified on data change + onLoad?.(false, adapters, api.dataLoading); + + api.loadViewUnderlyingData(); + + updateWarnings(); + // Render can still go wrong, so perfor a new check + dispatchBlockingErrorIfAny(); + }; + + const { onRender, onData, handleEvent, disableTriggers } = prepareCallbacks( + api, + internalApi, + parentApi, + getState, + services, + getExecutionContext(), + onDataCallback, + onRenderComplete, + callbacks + ); + + const searchContext = getMergedSearchContext( + currentState, + unifiedSearch, + api.timeRange$, + parentApi, + services + ); + + // Go concurrently: build the expression and fetch the dataViews + const [{ params, abortController, ...rest }, dataViews] = await Promise.all([ + getExpressionRendererParams(currentState, { + searchContext, + api, + settings: { + syncColors: currentState.syncColors, + syncCursor: currentState.syncCursor, + syncTooltips: currentState.syncTooltips, + }, + renderMode: getRenderMode(parentApi), + services, + searchSessionId, + abortController: internalApi.expressionAbortController$.getValue(), + getExecutionContext, + logError: getLogError(getExecutionContext), + addUserMessages, + onRender, + onData, + handleEvent, + disableTriggers, + updateBlockingErrors, + renderCount: internalApi.renderCount$.getValue(), + }), + getUsedDataViews( + currentState.attributes.references, + currentState.attributes.state?.adHocDataViews, + services.dataViews + ), + ]); + + // update the visualization context before anything else + // as it will be used to compute blocking errors also in case of issues + updateVisualizationContext({ + doc: currentState.attributes, + mergedSearchContext: params?.searchContext || {}, + ...rest, + }); + + // Publish the used dataViews on the Lens API + internalApi.updateDataViews(dataViews); + + if (params?.expression != null && !dispatchBlockingErrorIfAny()) { + internalApi.updateExpressionParams(params); + } + + internalApi.updateAbortController(abortController); + } + + // Build a custom operator to be resused for various observables + function waitUntilChanged() { + return pipe(distinctUntilChanged(fastIsEqual), skip(1)); + } + + const mergedSubscriptions = merge( + // on data change from the parentApi, reload + fetch$(api).pipe( + tap((data) => { + const searchSessionId = apiPublishesSearchSession(parentApi) ? data.searchSessionId : ''; + unifiedSearch$.next({ + query: data.query, + filters: data.filters, + timeRange: data.timeRange, + timeslice: data.timeslice, + searchSessionId, + }); + }), + map(() => 'searchContext' as ReloadReason) + ), + // On state change, reload + // this is used to refresh the chart on inline editing + // just make sure to avoid to rerender if there's no substantial change + // make sure to debounce one tick to make the refresh work + internalApi.attributes$.pipe( + waitUntilChanged(), + tap(() => { + // the ES|QL query may have changed, so recompute the args for view underlying data + if (api.isTextBasedLanguage()) { + api.loadViewUnderlyingData(); + } + }), + map(() => 'attributes' as ReloadReason) + ), + api.savedObjectId.pipe( + waitUntilChanged(), + map(() => 'savedObjectId' as ReloadReason) + ), + internalApi.overrides$.pipe( + waitUntilChanged(), + map(() => 'overrides' as ReloadReason) + ), + internalApi.disableTriggers$.pipe( + waitUntilChanged(), + map(() => 'disableTriggers' as ReloadReason) + ) + ); + + const subscriptions: Subscription[] = [ + mergedSubscriptions.pipe(debounceTime(0)).subscribe(reload), + // make sure to reload on viewMode change + api.viewMode.subscribe(() => { + // only reload if drilldowns are set + if (getState().enhancements?.dynamicActions) { + reload('viewMode'); + } + }), + ]; + // There are few key moments when errors are checked and displayed: + // * at setup time (here) before the first expression evaluation + // * at runtime => when the expression is running and ES/Kibana server could emit errors) + // * at data time => data has arrived but for something goes wrong + // * at render time => rendering happened but somethign went wrong + // Bubble the error up to the embeddable system if any + dispatchBlockingErrorIfAny(); + + return { + cleanup: () => { + for (const subscription of subscriptions) { + subscription.unsubscribe(); + } + }, + }; +} diff --git a/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/react_embeddable/expression_wrapper.tsx similarity index 88% rename from x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx rename to x-pack/plugins/lens/public/react_embeddable/expression_wrapper.tsx index d16df5bf9d1e..e0d21d9ba835 100644 --- a/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/expression_wrapper.tsx @@ -17,7 +17,7 @@ import { DefaultInspectorAdapters, RenderMode } from '@kbn/expressions-plugin/co import classNames from 'classnames'; import { getOriginalRequestErrorMessages } from '../editor_frame_service/error_helper'; import { LensInspector } from '../lens_inspector_service'; -import { AddUserMessages } from '../types'; +import { UserMessage } from '../types'; export interface ExpressionWrapperProps { ExpressionRenderer: ReactExpressionRendererType; @@ -31,7 +31,7 @@ export interface ExpressionWrapperProps { data: unknown, inspectorAdapters?: Partial<DefaultInspectorAdapters> | undefined ) => void; - onRender$: () => void; + onRender$: (count: number) => void; renderMode?: RenderMode; syncColors?: boolean; syncTooltips?: boolean; @@ -40,7 +40,7 @@ export interface ExpressionWrapperProps { getCompatibleCellValueActions?: ReactExpressionRendererProps['getCompatibleCellValueActions']; style?: React.CSSProperties; className?: string; - addUserMessages: AddUserMessages; + addUserMessages: (messages: UserMessage[]) => void; onRuntimeError: (error: Error) => void; executionContext?: KibanaExecutionContext; lensInspector: LensInspector; @@ -75,7 +75,11 @@ export function ExpressionWrapper({ }: ExpressionWrapperProps) { if (!expression) return null; return ( - <div className={classNames('lnsExpressionRenderer', className)} style={style}> + <div + className={classNames('lnsExpressionRenderer', className)} + style={style} + data-test-subj="lens-embeddable" + > <ExpressionRendererComponent className="lnsExpressionRenderer__component" padding={noPadding ? undefined : 's'} @@ -88,7 +92,7 @@ export function ExpressionWrapper({ // @ts-expect-error upgrade typescript v4.9.5 onData$={onData$} onRender$={onRender$} - inspectorAdapters={lensInspector.adapters} + inspectorAdapters={lensInspector.getInspectorAdapters()} renderMode={renderMode} syncColors={syncColors} syncTooltips={syncTooltips} @@ -98,12 +102,7 @@ export function ExpressionWrapper({ renderError={(errorMessage, error) => { const messages = getOriginalRequestErrorMessages(error || null); addUserMessages(messages); - if (error?.original) { - onRuntimeError(error.original); - } else { - onRuntimeError(new Error(errorMessage ? errorMessage : '')); - } - + onRuntimeError(error?.original || new Error(errorMessage ? errorMessage : '')); return <></>; // the embeddable will take care of displaying the messages }} onEvent={handleEvent} diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/callbacks.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/callbacks.ts new file mode 100644 index 000000000000..78a9aa6ab918 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/callbacks.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 type { KibanaExecutionContext } from '@kbn/core/public'; +import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; +import { apiHasDisableTriggers } from '@kbn/presentation-publishing'; +import { + GetStateType, + LensApi, + LensEmbeddableStartServices, + LensInternalApi, + LensPublicCallbacks, +} from '../types'; +import { prepareOnRender } from './on_render'; +import { prepareEventHandler } from './on_event'; +import { addLog } from '../logger'; + +export function prepareCallbacks( + api: LensApi, + internalApi: LensInternalApi, + parentApi: unknown, + getState: GetStateType, + services: LensEmbeddableStartServices, + executionContext: KibanaExecutionContext | undefined, + onDataUpdate: (adapters: Partial<DefaultInspectorAdapters | undefined>) => void, + dispatchRenderComplete: () => void, + callbacks: LensPublicCallbacks +) { + const disableTriggers = apiHasDisableTriggers(parentApi) ? parentApi.disableTriggers : undefined; + return { + disableTriggers, + onRender: prepareOnRender( + api, + internalApi, + parentApi, + getState, + services, + executionContext, + dispatchRenderComplete + ), + onData: (_data: unknown, adapters: Partial<DefaultInspectorAdapters> | undefined) => { + addLog(`onData$`); + onDataUpdate(adapters); + }, + handleEvent: prepareEventHandler(api, getState, callbacks, services, disableTriggers), + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/expression_params.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/expression_params.ts new file mode 100644 index 000000000000..e10dded4ad8f --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/expression_params.ts @@ -0,0 +1,238 @@ +/* + * 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 { KibanaExecutionContext } from '@kbn/core-execution-context-common'; +import type { Action } from '@kbn/ui-actions-plugin/public'; +import { RenderMode } from '@kbn/expressions-plugin/common'; +import { ExpressionRendererEvent } from '@kbn/expressions-plugin/public'; +import { toExpression } from '@kbn/interpreter'; +import { noop } from 'lodash'; +import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { + CellValueContext, + cellValueTrigger, + CELL_VALUE_TRIGGER, +} from '@kbn/embeddable-plugin/public'; +import { DocumentToExpressionReturnType } from '../../async_services'; +import { LensDocument } from '../../persistence'; +import { + GetCompatibleCellValueActions, + IndexPatternMap, + IndexPatternRef, + UserMessage, + isLensFilterEvent, + isLensMultiFilterEvent, + isLensTableRowContextMenuClickEvent, +} from '../../types'; +import type { + ExpressionWrapperProps, + LensApi, + LensEmbeddableStartServices, + LensRuntimeState, +} from '../types'; +import { getVariables } from './variables'; +// import { +// getSearchContextIncompatibleMessage, +// isSearchContextIncompatibleWithDataViews, +// } from '../user_messages/checks'; +import { getExecutionSearchContext, type MergedSearchContext } from './merged_search_context'; + +interface GetExpressionRendererPropsParams { + searchContext: MergedSearchContext; + disableTriggers?: boolean; + renderMode?: RenderMode; + settings: { + syncColors?: boolean; + syncCursor?: boolean; + syncTooltips?: boolean; + }; + services: LensEmbeddableStartServices; + getExecutionContext: () => KibanaExecutionContext | undefined; + searchSessionId?: string; + abortController?: AbortController; + onRender: (count: number) => void; + handleEvent: (event: ExpressionRendererEvent) => void; + onData: ExpressionWrapperProps['onData$']; + logError: (type: 'runtime' | 'validation') => void; + api: LensApi; + addUserMessages: (messages: UserMessage[]) => void; + updateBlockingErrors: (error: Error) => void; + renderCount: number; +} + +async function getExpressionFromDocument( + document: LensDocument, + documentToExpression: (doc: LensDocument) => Promise<DocumentToExpressionReturnType> +) { + const { ast, indexPatterns, indexPatternRefs, activeVisualizationState, activeDatasourceState } = + await documentToExpression(document); + return { + expression: ast ? toExpression(ast) : null, + indexPatterns, + indexPatternRefs, + activeVisualizationState, + activeDatasourceState, + }; +} + +function buildHasCompatibleActions(api: LensApi, { uiActions }: LensEmbeddableStartServices) { + return async (event: ExpressionRendererEvent): Promise<boolean> => { + if (!uiActions?.getTriggerCompatibleActions) { + return false; + } + if ( + isLensTableRowContextMenuClickEvent(event) || + isLensMultiFilterEvent(event) || + isLensFilterEvent(event) + ) { + const actions = await uiActions.getTriggerCompatibleActions( + VIS_EVENT_TO_TRIGGER[event.name], + { + data: event.data, + embeddable: api, + } + ); + + return actions.length > 0; + } + + return false; + }; +} + +function buildGetCompatibleCellValueActions( + api: LensApi, + { uiActions }: LensEmbeddableStartServices +): GetCompatibleCellValueActions { + return async (data) => { + if (!uiActions?.getTriggerCompatibleActions) { + return []; + } + const actions: Array<Action<CellValueContext>> = (await uiActions.getTriggerCompatibleActions( + CELL_VALUE_TRIGGER, + { data, embeddable: api } + )) as Array<Action<CellValueContext>>; + return actions + .sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity)) + .map((action) => ({ + id: action.id, + type: action.type, + iconType: action.getIconType({ embeddable: api, data, trigger: cellValueTrigger })!, + displayName: action.getDisplayName({ embeddable: api, data, trigger: cellValueTrigger }), + execute: (cellData) => + action.execute({ embeddable: api, data: cellData, trigger: cellValueTrigger }), + })); + }; +} + +export async function getExpressionRendererParams( + state: LensRuntimeState, + { + settings: { syncColors = true, syncCursor = true, syncTooltips = false }, + services, + disableTriggers = false, + getExecutionContext, + searchSessionId, + abortController, + onRender, + handleEvent, + onData = noop, + logError, + api, + addUserMessages, + updateBlockingErrors, + searchContext, + renderCount, + }: GetExpressionRendererPropsParams +): Promise<{ + params: ExpressionWrapperProps | null; + abortController?: AbortController; + indexPatterns: IndexPatternMap; + indexPatternRefs: IndexPatternRef[]; + activeVisualizationState?: unknown; + activeDatasourceState?: unknown; +}> { + const { expressionRenderer, documentToExpression } = services; + + const { + expression, + indexPatterns, + indexPatternRefs, + activeVisualizationState, + activeDatasourceState, + } = await getExpressionFromDocument(state.attributes, documentToExpression); + + // Apparently this change produces had lots of issues with solutions not using + // the Embeddable incorrectly. Will comment for now and later on will restore it when + // https://github.com/elastic/kibana/issues/200236 is resolved + // + // if at least one indexPattern is time based, then the Lens embeddable requires the timeRange prop + // this is necessary for the dataview embeddable but not the ES|QL one + // if ( + // isSearchContextIncompatibleWithDataViews( + // api, + // getExecutionContext(), + // searchContext, + // indexPatternRefs, + // indexPatterns + // ) + // ) { + // addUserMessages([getSearchContextIncompatibleMessage()]); + // } + + if (expression) { + const params: ExpressionWrapperProps = { + expression, + syncColors, + syncCursor, + syncTooltips, + searchSessionId, + onRender$: onRender, + handleEvent, + onData$: onData, + // Remove ES|QL query from it + searchContext: getExecutionSearchContext(searchContext), + interactive: !disableTriggers, + executionContext: getExecutionContext(), + lensInspector: { + getInspectorAdapters: api.getInspectorAdapters, + inspect: api.inspect, + closeInspector: api.closeInspector, + }, + ExpressionRenderer: expressionRenderer, + addUserMessages, + onRuntimeError: (error: Error) => { + updateBlockingErrors(error); + logError('runtime'); + }, + abortController, + hasCompatibleActions: buildHasCompatibleActions(api, services), + getCompatibleCellValueActions: buildGetCompatibleCellValueActions(api, services), + variables: getVariables(api, state), + style: state.style, + className: state.className, + noPadding: state.noPadding, + }; + return { + indexPatterns, + indexPatternRefs, + activeVisualizationState, + activeDatasourceState, + params, + abortController, + }; + } + + return { + params: null, + abortController, + indexPatterns, + indexPatternRefs, + activeVisualizationState, + activeDatasourceState, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/merged_search_context.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/merged_search_context.ts new file mode 100644 index 000000000000..5b467dd706a6 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/merged_search_context.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DataPublicPluginStart, FilterManager } from '@kbn/data-plugin/public'; +import { + type AggregateQuery, + type Filter, + isOfAggregateQueryType, + type Query, + type TimeRange, + ExecutionContextSearch, +} from '@kbn/es-query'; +import { PublishingSubject, apiPublishesTimeslice } from '@kbn/presentation-publishing'; +import type { LensRuntimeState } from '../types'; +import { nonNullable } from '../../utils'; + +export interface MergedSearchContext { + now: number; + timeRange: TimeRange | undefined; + query: Array<Query | AggregateQuery>; + filters: Filter[]; + disableWarningToasts: boolean; +} + +export function getMergedSearchContext( + { attributes }: LensRuntimeState, + { + filters, + query, + timeRange, + }: { + filters?: Filter[]; + query?: Query | AggregateQuery; + timeRange?: TimeRange; + }, + customTimeRange$: PublishingSubject<TimeRange | undefined>, + parentApi: unknown, + { + data, + injectFilterReferences, + }: { data: DataPublicPluginStart; injectFilterReferences: FilterManager['inject'] } +): MergedSearchContext { + const parentTimeSlice = apiPublishesTimeslice(parentApi) + ? parentApi.timeslice$.getValue() + : undefined; + + const timesliceTimeRange = parentTimeSlice + ? { + from: new Date(parentTimeSlice[0]).toISOString(), + to: new Date(parentTimeSlice[1]).toISOString(), + mode: 'absolute' as 'absolute', + } + : undefined; + + const customTimeRange = customTimeRange$.getValue(); + + const timeRangeToRender = customTimeRange ?? timesliceTimeRange ?? timeRange; + const context = { + now: data.nowProvider.get().getTime(), + timeRange: timeRangeToRender, + query: [attributes.state.query].filter(nonNullable), + filters: injectFilterReferences(attributes.state.filters || [], attributes.references), + disableWarningToasts: true, + }; + // Prepend query and filters from dashboard to the visualization ones + if (query) { + if (!isOfAggregateQueryType(query)) { + context.query.unshift(query); + } + } + if (filters) { + context.filters.unshift(...filters.filter(({ meta }) => !meta.disabled)); + } + return context; +} + +export function getExecutionSearchContext( + searchContext: MergedSearchContext +): ExecutionContextSearch { + if (!isOfAggregateQueryType(searchContext.query[0])) { + return searchContext as ExecutionContextSearch; + } + return { + ...searchContext, + query: [], + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/on_event.test.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/on_event.test.ts new file mode 100644 index 000000000000..dfddfe84b57c --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/on_event.test.ts @@ -0,0 +1,181 @@ +/* + * 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 { ExpressionRendererEvent } from '@kbn/expressions-plugin/public'; +import { getLensApiMock, getLensRuntimeStateMock, makeEmbeddableServices } from '../mocks'; +import { LensApi, LensEmbeddableStartServices, LensPublicCallbacks } from '../types'; +import { prepareEventHandler } from './on_event'; +import faker from 'faker'; +import { + LENS_EDIT_PAGESIZE_ACTION, + LENS_EDIT_RESIZE_ACTION, + LENS_EDIT_SORT_ACTION, + LENS_TOGGLE_ACTION, +} from '../../visualizations/datatable/components/constants'; + +describe('Embeddable interaction event handlers', () => { + beforeEach(() => { + // LensAPI mock is a static mock, so we need to reset it between tests + jest.resetAllMocks(); + }); + + function getCallbacks(shouldPreventDefault?: boolean) { + if (!shouldPreventDefault) { + return { onFilter: jest.fn(), onBrushEnd: jest.fn(), onTableRowClick: jest.fn() }; + } + return { + onFilter: jest.fn((event) => event.preventDefault()), + onBrushEnd: jest.fn((event) => event.preventDefault()), + onTableRowClick: jest.fn((event) => event.preventDefault()), + }; + } + + function getHandler( + api: LensApi = getLensApiMock(), + callbacks: LensPublicCallbacks = getCallbacks(), + services: LensEmbeddableStartServices = makeEmbeddableServices(), + disableTriggers: boolean = false + ) { + return prepareEventHandler( + api, + jest.fn(() => getLensRuntimeStateMock()), + callbacks, + services, + disableTriggers + ); + } + + function getTable() { + return { columns: { test: { meta: { field: '@timestamp', sourceParams: {} } } } }; + } + + async function submitEvent(event: ExpressionRendererEvent, callPreventDefault: boolean = false) { + const onEditAction = jest.fn(); + const callbacks = getCallbacks(callPreventDefault); + const services = makeEmbeddableServices(undefined, undefined, { + visOverrides: { id: 'lnsXY', onEditAction }, + }); + const lensApi = getLensApiMock(); + const handler = getHandler(lensApi, callbacks, services); + + await handler(event); + + return { + reSubmit: (newEvent: ExpressionRendererEvent) => handler(newEvent), + callbacks, + getTrigger: services.uiActions.getTrigger, + updateAttributes: lensApi.updateAttributes, + onEditAction, + }; + } + + it('should call onTableRowClick event ', async () => { + const event = { + name: 'tableRowContextMenuClick', + data: { rowIndex: 1, table: getTable() }, + }; + const { callbacks } = await submitEvent(event); + expect(callbacks.onTableRowClick).toHaveBeenCalledWith(expect.objectContaining(event.data)); + }); + it('should prevent onTableRowClick trigger when calling preventDefault ', async () => { + const event = { + name: 'tableRowContextMenuClick', + data: { rowIndex: 1, table: getTable() }, + }; + const { getTrigger } = await submitEvent(event, true); + expect(getTrigger).not.toHaveBeenCalled(); + }); + it('should call onBrush event on filter call ', async () => { + const event = { + name: 'brush', + data: { column: 'test', range: [1, 2], table: getTable() }, + }; + const { callbacks } = await submitEvent(event); + expect(callbacks.onBrushEnd).toHaveBeenCalledWith(expect.objectContaining(event.data)); + }); + it('should prevent the onBrush trigger when calling preventDefault', async () => { + const event = { + name: 'brush', + data: { column: 'test', range: [1, 2], table: getTable() }, + }; + const { getTrigger } = await submitEvent(event, true); + expect(getTrigger).not.toHaveBeenCalled(); + }); + it('should call onFilter event on filter call ', async () => { + const event = { + name: 'filter', + data: { + data: [{ value: faker.random.number(), row: 1, column: 'test', table: getTable() }], + }, + }; + const { callbacks } = await submitEvent(event); + expect(callbacks.onFilter).toHaveBeenCalledWith(expect.objectContaining(event.data)); + }); + it('should prevent the onFilter trigger when calling preventDefault', async () => { + const event = { + name: 'filter', + data: { + data: [{ value: faker.random.number(), row: 1, column: 'test', table: getTable() }], + }, + }; + const { getTrigger } = await submitEvent(event, true); + expect(getTrigger).not.toHaveBeenCalled(); + }); + + it('should reload on edit events', async () => { + const { reSubmit, onEditAction, updateAttributes } = await submitEvent({ + name: 'edit', + data: { action: LENS_EDIT_SORT_ACTION }, + }); + + expect(onEditAction).toHaveBeenCalled(); + expect(updateAttributes).toHaveBeenCalled(); + + await reSubmit({ name: 'edit', data: { action: LENS_EDIT_RESIZE_ACTION } }); + + expect(onEditAction).toHaveBeenCalled(); + expect(updateAttributes).toHaveBeenCalled(); + + await reSubmit({ name: 'edit', data: { action: LENS_TOGGLE_ACTION } }); + + expect(onEditAction).toHaveBeenCalled(); + expect(updateAttributes).toHaveBeenCalled(); + + await reSubmit({ name: 'edit', data: { action: LENS_EDIT_PAGESIZE_ACTION } }); + + expect(onEditAction).toHaveBeenCalled(); + expect(updateAttributes).toHaveBeenCalled(); + }); + + it('should not reload on non-edit events', async () => { + const { reSubmit, onEditAction, updateAttributes } = await submitEvent({ + name: 'tableRowContextMenuClick', + data: { rowIndex: 1, table: getTable() }, + }); + + expect(onEditAction).not.toHaveBeenCalled(); + expect(updateAttributes).not.toHaveBeenCalled(); + + await reSubmit({ + name: 'brush', + data: { column: 'test', range: [1, 2], table: getTable() }, + }); + + expect(onEditAction).not.toHaveBeenCalled(); + expect(updateAttributes).not.toHaveBeenCalled(); + + await reSubmit({ + name: 'filter', + data: { + data: [{ value: faker.random.number(), row: 1, column: 'test', table: getTable() }], + }, + }); + + expect(onEditAction).not.toHaveBeenCalled(); + expect(updateAttributes).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/on_event.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/on_event.ts new file mode 100644 index 000000000000..71ce4e15693c --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/on_event.ts @@ -0,0 +1,113 @@ +/* + * 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 { ExpressionRendererEvent } from '@kbn/expressions-plugin/public'; +import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; +import { type AggregateQuery, type Query, isOfAggregateQueryType } from '@kbn/es-query'; +import { + isLensBrushEvent, + isLensEditEvent, + isLensFilterEvent, + isLensMultiFilterEvent, + isLensTableRowContextMenuClickEvent, +} from '../../types'; +import { inferTimeField } from '../../utils'; +import type { + GetStateType, + LensApi, + LensEmbeddableStartServices, + LensPublicCallbacks, +} from '../types'; +import { isTextBasedLanguage } from '../helper'; +import { addLog } from '../logger'; + +export const prepareEventHandler = + ( + api: LensApi, + getState: GetStateType, + callbacks: LensPublicCallbacks, + { data, uiActions, visualizationMap }: LensEmbeddableStartServices, + disableTriggers: boolean | undefined + ) => + async (event: ExpressionRendererEvent) => { + if (!uiActions?.getTrigger || disableTriggers) { + return; + } + addLog(`onEvent$`); + + let eventHandler: + | LensPublicCallbacks['onBrushEnd'] + | LensPublicCallbacks['onFilter'] + | LensPublicCallbacks['onTableRowClick']; + let shouldExecuteDefaultTriggers = true; + + if (isLensBrushEvent(event)) { + eventHandler = callbacks.onBrushEnd; + } else if (isLensFilterEvent(event) || isLensMultiFilterEvent(event)) { + eventHandler = callbacks.onFilter; + } else if (isLensTableRowContextMenuClickEvent(event)) { + eventHandler = callbacks.onTableRowClick; + } + const currentState = getState(); + + eventHandler?.({ + ...event.data, + preventDefault: () => { + shouldExecuteDefaultTriggers = false; + }, + }); + + if (isLensFilterEvent(event) || isLensMultiFilterEvent(event) || isLensBrushEvent(event)) { + if (shouldExecuteDefaultTriggers) { + // if the embeddable is located in an app where there is the Unified search bar with the ES|QL editor, then use this query + // otherwise use the query from the saved object + let esqlQuery: AggregateQuery | Query | undefined; + if (isTextBasedLanguage(currentState)) { + const query = data.query.queryString.getQuery(); + esqlQuery = isOfAggregateQueryType(query) ? query : currentState.attributes.state.query; + } + uiActions.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec({ + data: { + ...event.data, + timeFieldName: + event.data.timeFieldName || inferTimeField(data.datatableUtilities, event), + query: esqlQuery, + }, + embeddable: api, + }); + } + } + + if (isLensTableRowContextMenuClickEvent(event)) { + if (shouldExecuteDefaultTriggers) { + uiActions.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec( + { + data: event.data, + embeddable: api, + }, + true + ); + } + } + + const onEditAction = currentState.attributes.visualizationType + ? visualizationMap[currentState.attributes.visualizationType]?.onEditAction + : undefined; + + // We allow for edit actions in the Embeddable for display purposes only (e.g. changing the datatable sort order). + // No state changes made here with an edit action are persisted. + if (isLensEditEvent(event) && onEditAction) { + // updating the state would trigger a reload + api.updateAttributes({ + ...currentState.attributes, + state: { + ...currentState.attributes.state, + visualization: onEditAction(currentState.attributes.state.visualization, event), + }, + }); + } + }; diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/on_render.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/on_render.ts new file mode 100644 index 000000000000..ba0a47b5944e --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/on_render.ts @@ -0,0 +1,112 @@ +/* + * 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 { KibanaExecutionContext } from '@kbn/core-execution-context-common'; +import { canTrackContentfulRender } from '@kbn/presentation-containers'; +import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; +import { TableInspectorAdapter } from '../../editor_frame_service/types'; + +import { getExecutionContextEvents, trackUiCounterEvents } from '../../lens_ui_telemetry'; +import { GetStateType, LensApi, LensEmbeddableStartServices, LensInternalApi } from '../types'; +import { getSuccessfulRequestTimings } from '../../report_performance_metric_util'; +import { addLog } from '../logger'; + +function trackContentfulRender(activeData: TableInspectorAdapter, parentApi: unknown) { + if (!canTrackContentfulRender(parentApi)) { + return; + } + + const hasData = Object.values(activeData).some((table) => { + if (table.meta?.statistics?.totalCount != null) { + // if totalCount is set, refer to total count + return table.meta.statistics.totalCount > 0; + } + // if not, fall back to check the rows of the table + return table.rows.length > 0; + }); + + if (hasData) { + parentApi.trackContentfulRender(); + } +} + +function trackPerformanceMetrics( + api: LensApi, + coreStart: LensEmbeddableStartServices['coreStart'] +) { + const inspectorAdapters = api.getInspectorAdapters(); + const timings = getSuccessfulRequestTimings(inspectorAdapters); + if (timings) { + const esRequestMetrics = { + eventName: 'lens_chart_es_request_totals', + duration: timings.requestTimeTotal, + key1: 'es_took_total', + value1: timings.esTookTotal, + }; + reportPerformanceMetricEvent(coreStart.analytics, esRequestMetrics); + } +} + +export function prepareOnRender( + api: LensApi, + internalApi: LensInternalApi, + parentApi: unknown, + getState: GetStateType, + { datasourceMap, visualizationMap, coreStart }: LensEmbeddableStartServices, + executionContext: KibanaExecutionContext | undefined, + dispatchRenderComplete: () => void +) { + return function onRender$(count: number) { + addLog(`onRender$ ${count}`); + // for some reason onRender$ is emitting multiple times with the same render count + // so avoid to repeat the same logic on duplicate calls + if (count === internalApi.renderCount$.getValue()) { + return; + } + let datasourceEvents: string[] = []; + let visualizationEvents: string[] = []; + const currentState = getState(); + + if (currentState) { + datasourceEvents = Object.values(datasourceMap).reduce<string[]>( + (acc, datasource) => [ + ...acc, + ...(datasource.getRenderEventCounters?.( + currentState.attributes.state.datasourceStates[datasource.id] + ) ?? []), + ], + [] + ); + + if (currentState.attributes.visualizationType) { + visualizationEvents = + visualizationMap[currentState.attributes.visualizationType].getRenderEventCounters?.( + currentState.attributes.state.visualization + ) ?? []; + } + } + + const events = [ + ...datasourceEvents, + ...visualizationEvents, + ...getExecutionContextEvents(executionContext), + ]; + + const adHocDataViews = Object.values(currentState.attributes.state.adHocDataViews || {}); + adHocDataViews.forEach(() => { + events.push('ad_hoc_data_view'); + }); + + trackUiCounterEvents(events, executionContext); + + trackContentfulRender(api.getInspectorAdapters().tables?.tables, parentApi); + + dispatchRenderComplete(); + + trackPerformanceMetrics(api, coreStart); + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/telemetry.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/telemetry.ts new file mode 100644 index 000000000000..ede2f1b0aaf3 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/telemetry.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { KibanaExecutionContext } from '@kbn/core/public'; +import { trackUiCounterEvents } from '../../lens_ui_telemetry'; + +export function getLogError(getExecutionContext: () => KibanaExecutionContext | undefined) { + return (type: 'runtime' | 'validation') => { + trackUiCounterEvents( + type === 'runtime' ? 'embeddable_runtime_error' : 'embeddable_validation_error', + getExecutionContext() + ); + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/update_data_views.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/update_data_views.ts new file mode 100644 index 000000000000..0e7f130d339d --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/update_data_views.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { uniqBy } from 'lodash'; +import { getIndexPatternsObjects } from '../../utils'; +import { LensEmbeddableStartServices, LensRuntimeState } from '../types'; + +export async function getUsedDataViews( + references: LensRuntimeState['attributes']['references'], + adHocDataViewsSpecs: LensRuntimeState['attributes']['state']['adHocDataViews'], + dataViews: LensEmbeddableStartServices['dataViews'] +) { + const [{ indexPatterns }, ...adHocDataViews] = await Promise.all([ + getIndexPatternsObjects(references.map(({ id }) => id) || [], dataViews), + ...Object.values(adHocDataViewsSpecs || {}).map((spec) => dataViews.create(spec)), + ]); + + return uniqBy(indexPatterns.concat(adHocDataViews), 'id'); +} diff --git a/x-pack/plugins/lens/public/react_embeddable/expressions/variables.ts b/x-pack/plugins/lens/public/react_embeddable/expressions/variables.ts new file mode 100644 index 000000000000..c1fdda750199 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/expressions/variables.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Datatable } from '@kbn/expressions-plugin/common'; +import type { TextBasedPersistedState } from '../../datasources/text_based/types'; +import { LensApi, LensRuntimeState } from '../types'; + +function getInternalTables(states: Record<string, unknown>) { + const result: Record<string, Datatable> = {}; + if ('textBased' in states) { + const layers = (states.textBased as TextBasedPersistedState).layers; + for (const layer in layers) { + if (layers[layer]?.table) { + result[layer] = layers[layer].table!; + } + } + } + return result; +} + +/** + * Collect all the data that need to be forwarded at the end of the + * expression pipeline as overrides, palette, etc... and merged them all here + */ +export function getVariables(api: LensApi, state: LensRuntimeState) { + return { + embeddableTitle: api.defaultPanelTitle?.getValue(), + ...(state.palette ? { theme: { palette: state.palette } } : {}), + ...('overrides' in state ? { overrides: state.overrides } : {}), + ...getInternalTables(state.attributes.state.datasourceStates), + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/helper.test.ts b/x-pack/plugins/lens/public/react_embeddable/helper.test.ts new file mode 100644 index 000000000000..33a8d0d0093d --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/helper.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { defaultDoc, makeAttributeService } from '../mocks/services_mock'; +import { deserializeState } from './helper'; + +describe('Embeddable helpers', () => { + describe('deserializeState', () => { + it('should forward a by value raw state', async () => { + const attributeService = makeAttributeService(defaultDoc); + const rawState = { + attributes: defaultDoc, + }; + const runtimeState = await deserializeState(attributeService, rawState); + expect(runtimeState).toEqual(rawState); + }); + + it('should wrap Lens doc/attributes into component state shape', async () => { + const attributeService = makeAttributeService(defaultDoc); + const runtimeState = await deserializeState(attributeService, defaultDoc); + expect(runtimeState).toEqual( + expect.objectContaining({ + attributes: { ...defaultDoc, references: defaultDoc.references }, + }) + ); + }); + + it('load a by-ref doc from the attribute service', async () => { + const attributeService = makeAttributeService(defaultDoc); + await deserializeState(attributeService, { + savedObjectId: '123', + }); + + expect(attributeService.loadFromLibrary).toHaveBeenCalledWith('123'); + }); + + it('should fallback to an empty Lens doc if the saved object is not found', async () => { + const attributeService = makeAttributeService(defaultDoc); + attributeService.loadFromLibrary.mockRejectedValueOnce(new Error('not found')); + const runtimeState = await deserializeState(attributeService, { + savedObjectId: '123', + }); + // check the visualizationType set to null for empty state + expect(runtimeState.attributes.visualizationType).toBeNull(); + }); + + describe('injected references should overwrite inner ones', () => { + // There are 3 possible scenarios here for reference injections: + // * default space for a by-value + // * default space for a by-ref with a "lens" panel reference type + // * other space for a by-value with new ref ids + + it('should inject correctly serialized references into runtime state for a by value in the default space', async () => { + const attributeService = makeAttributeService(defaultDoc); + const mockedReferences = [ + { id: 'serializedRefs', name: 'index-pattern-0', type: 'mocked-reference' }, + ]; + const runtimeState = await deserializeState( + attributeService, + { + attributes: defaultDoc, + }, + mockedReferences + ); + expect(attributeService.injectReferences).toHaveBeenCalled(); + expect(runtimeState.attributes.references).toEqual(mockedReferences); + }); + + it('should inject correctly serialized references into runtime state for a by ref in the default space', async () => { + const attributeService = makeAttributeService(defaultDoc); + const mockedReferences = [ + { id: 'serializedRefs', name: 'index-pattern-0', type: 'mocked-reference' }, + ]; + const runtimeState = await deserializeState( + attributeService, + { + savedObjectId: '123', + }, + mockedReferences + ); + expect(attributeService.injectReferences).not.toHaveBeenCalled(); + // Note the original references should be kept + expect(runtimeState.attributes.references).toEqual(defaultDoc.references); + }); + + it('should inject correctly serialized references into runtime state for a by value in another space', async () => { + const attributeService = makeAttributeService(defaultDoc); + const mockedReferences = [ + { id: 'serializedRefs', name: 'index-pattern-0', type: 'mocked-reference' }, + ]; + const runtimeState = await deserializeState( + attributeService, + { + attributes: defaultDoc, + }, + mockedReferences + ); + expect(attributeService.injectReferences).toHaveBeenCalled(); + // note: in this case the references are swapped + expect(runtimeState.attributes.references).toEqual(mockedReferences); + }); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/react_embeddable/helper.ts b/x-pack/plugins/lens/public/react_embeddable/helper.ts new file mode 100644 index 000000000000..3ee63d907068 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/helper.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + apiHasParentApi, + apiPublishesViewMode, + getInheritedViewMode, + ViewMode, + type PublishingSubject, + apiHasExecutionContext, +} from '@kbn/presentation-publishing'; +import { isObject } from 'lodash'; +import { BehaviorSubject } from 'rxjs'; +import fastIsEqual from 'fast-deep-equal'; +import { isOfAggregateQueryType } from '@kbn/es-query'; +import { RenderMode } from '@kbn/expressions-plugin/common'; +import { SavedObjectReference } from '@kbn/core/types'; +import { LensRuntimeState, LensSerializedState } from './types'; +import type { LensAttributesService } from '../lens_attribute_service'; + +export function createEmptyLensState( + visualizationType: null | string = null, + title?: LensSerializedState['title'], + description?: LensSerializedState['description'], + query?: LensSerializedState['query'], + filters?: LensSerializedState['filters'] +) { + const isTextBased = query && isOfAggregateQueryType(query); + return { + attributes: { + title: title ?? '', + description: description ?? '', + visualizationType, + references: [], + state: { + query: query || { query: '', language: 'kuery' }, + filters: filters || [], + internalReferences: [], + datasourceStates: { ...(isTextBased ? { text_based: {} } : { form_based: {} }) }, + visualization: {}, + }, + }, + }; +} + +// Shared logic to ensure the attributes are correctly loaded +// Make sure to inject references from the container down to the runtime state +// this ensure migrations/copy to spaces works correctly +export async function deserializeState( + attributeService: LensAttributesService, + rawState: LensSerializedState, + references?: SavedObjectReference[] +) { + if (rawState.savedObjectId) { + try { + const { attributes, managed, sharingSavedObjectProps } = + await attributeService.loadFromLibrary(rawState.savedObjectId); + return { ...rawState, attributes, managed, sharingSavedObjectProps }; + } catch (e) { + // return an empty Lens document if no saved object is found + return { ...rawState, attributes: createEmptyLensState().attributes }; + } + } + // Inject applied only to by-value SOs + return attributeService.injectReferences( + ('attributes' in rawState ? rawState : { attributes: rawState }) as LensRuntimeState, + references?.length ? references : undefined + ); +} + +export function emptySerializer() { + return {}; +} + +export type ComparatorType<T extends unknown> = [ + BehaviorSubject<T>, + (newValue: T) => void, + (a: T, b: T) => boolean +]; + +export function makeComparator<T extends unknown>( + observable: BehaviorSubject<T> +): ComparatorType<T> { + return [observable, (newValue: T) => observable.next(newValue), fastIsEqual]; +} + +/** + * Helper function to either extract an observable from an API or create a new one + * with a default value to start with. + * Note that extracting from the API will make subscription emit if the value changes upstream + * as it keeps the original reference without cloning. + * @returns the observable and a comparator to use for detecting "unsaved changes" on it + */ +export function buildObservableVariable<T extends unknown>( + variable: T | PublishingSubject<T> +): [BehaviorSubject<T>, ComparatorType<T>] { + if (variable instanceof BehaviorSubject) { + return [variable, makeComparator(variable)]; + } + const variable$ = new BehaviorSubject<T>(variable as T); + return [variable$, makeComparator(variable$)]; +} + +export function isTextBasedLanguage(state: LensRuntimeState) { + return isOfAggregateQueryType(state.attributes?.state.query); +} + +export function getViewMode(api: unknown) { + return apiPublishesViewMode(api) ? getInheritedViewMode(api) : undefined; +} + +export function getRenderMode(api: unknown): RenderMode { + const mode = getViewMode(api) ?? 'view'; + return mode === 'print' ? 'view' : mode; +} + +function apiHasExecutionContextFunction( + api: unknown +): api is { getAppContext: () => { currentAppId: string } } { + return isObject(api) && 'getAppContext' in api && typeof api.getAppContext === 'function'; +} + +export function getParentContext(parentApi: unknown) { + if (apiHasExecutionContext(parentApi)) { + return parentApi.executionContext; + } + if (apiHasExecutionContextFunction(parentApi)) { + return { type: parentApi.getAppContext().currentAppId }; + } + return; +} + +export function extractInheritedViewModeObservable( + parentApi?: unknown +): PublishingSubject<ViewMode> { + if (apiPublishesViewMode(parentApi)) { + return parentApi.viewMode; + } + if (apiHasParentApi(parentApi)) { + return extractInheritedViewModeObservable(parentApi.parentApi); + } + return new BehaviorSubject<ViewMode>('view'); +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.test.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.test.ts new file mode 100644 index 000000000000..a4f84c329bd3 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.test.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pick } from 'lodash'; +import faker from 'faker'; +import type { LensRuntimeState, VisualizationContext } from '../types'; +import { initializeActionApi } from './initialize_actions'; +import { + getLensApiMock, + makeEmbeddableServices, + getLensRuntimeStateMock, + getVisualizationContextHelperMock, + createUnifiedSearchApi, +} from '../mocks'; +import { createEmptyLensState } from '../helper'; +const DATAVIEW_ID = 'myDataView'; + +jest.mock('../../app_plugin/show_underlying_data', () => { + return { + ...jest.requireActual('../../app_plugin/show_underlying_data'), + getLayerMetaInfo: jest.fn(() => ({ + meta: { + id: DATAVIEW_ID, + columns: ['a', 'b'], + filters: { disabled: [], enabled: [] }, + }, + error: undefined, + isVisible: true, + })), + }; +}); + +function setupActionsApi( + stateOverrides?: Partial<LensRuntimeState>, + contextOverrides?: Omit<VisualizationContext, 'doc'> +) { + const services = makeEmbeddableServices(undefined, undefined, { + visOverrides: { id: 'lnsXY' }, + dataOverrides: { id: 'form_based' }, + }); + const uuid = faker.random.uuid(); + const runtimeState = getLensRuntimeStateMock(stateOverrides); + const apiMock = getLensApiMock(); + + const { api } = initializeActionApi( + uuid, + runtimeState, + () => runtimeState, + createUnifiedSearchApi(), + pick(apiMock, ['timeRange$']), + pick(apiMock, ['panelTitle']), + getVisualizationContextHelperMock(stateOverrides?.attributes, contextOverrides), + { + ...services, + data: { + ...services.data, + nowProvider: { ...services.data.nowProvider, get: jest.fn(() => new Date()) }, + }, + } + ); + return api; +} + +describe('Dashboard actions', () => { + describe('Drilldowns', () => { + it('should expose drilldowns for DSL based visualization', async () => { + const api = setupActionsApi(); + expect(api.enhancements).toBeDefined(); + }); + + it('should not expose drilldowns for ES|QL chart types', async () => { + const api = setupActionsApi( + createEmptyLensState('lnsXY', faker.lorem.words(), faker.lorem.text(), { + esql: 'FROM index', + }) + ); + expect(api.enhancements).toBeUndefined(); + }); + }); + + describe('Explore in Discover', () => { + // make it pass the basic check on viewUnderlyingData + const visualizationContextMockOverrides = { + mergedSearchContext: {}, + indexPatterns: { + [DATAVIEW_ID]: { + id: DATAVIEW_ID, + title: 'idx1', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields: [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + ], + getFieldByName: jest.fn(), + isPersisted: true, + spec: {}, + }, + }, + indexPatternRefs: [], + activeVisualizationState: {}, + activeDatasourceState: {}, + activeData: {}, + }; + it('should expose the "explore in discover" capability for DSL based visualization when compatible', async () => { + const api = setupActionsApi(undefined, visualizationContextMockOverrides); + api.loadViewUnderlyingData(); + expect(api.canViewUnderlyingData$.getValue()).toBe(true); + }); + + it('should expose the "explore in discover" capability for ES|QL chart types', async () => { + const api = setupActionsApi( + createEmptyLensState('lnsXY', faker.lorem.words(), faker.lorem.text(), { + esql: 'FROM index', + }), + visualizationContextMockOverrides + ); + api.loadViewUnderlyingData(); + expect(api.canViewUnderlyingData$.getValue()).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.ts new file mode 100644 index 000000000000..65fd13c8fca5 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_actions.ts @@ -0,0 +1,276 @@ +/* + * 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 { Capabilities } from '@kbn/core-capabilities-common'; +import { getEsQueryConfig } from '@kbn/data-plugin/public'; +import { + AggregateQuery, + EsQueryConfig, + Filter, + Query, + TimeRange, + isOfQueryType, +} from '@kbn/es-query'; +import { + PublishingSubject, + StateComparators, + apiPublishesUnifiedSearch, + getUnchangingComparator, +} from '@kbn/presentation-publishing'; +import { HasDynamicActions } from '@kbn/embeddable-enhanced-plugin/public'; +import { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public/plugin'; +import { partition } from 'lodash'; +import { Visualization } from '../..'; +import { combineQueryAndFilters, getLayerMetaInfo } from '../../app_plugin/show_underlying_data'; +import { TableInspectorAdapter } from '../../editor_frame_service/types'; + +import { Datasource, IndexPatternMap } from '../../types'; +import { getMergedSearchContext } from '../expressions/merged_search_context'; +import { buildObservableVariable, isTextBasedLanguage } from '../helper'; +import type { + GetStateType, + LensEmbeddableStartServices, + LensRuntimeState, + ViewInDiscoverCallbacks, + ViewUnderlyingDataArgs, + VisualizationContextHelper, +} from '../types'; +import { getActiveDatasourceIdFromDoc, getActiveVisualizationIdFromDoc } from '../../utils'; + +function getViewUnderlyingDataArgs({ + activeDatasource, + activeDatasourceState, + activeVisualization, + activeVisualizationState, + activeData, + dataViews, + capabilities, + query, + filters, + timeRange, + esQueryConfig, +}: { + activeDatasource: Datasource; + activeDatasourceState: unknown; + activeVisualization: Visualization; + activeVisualizationState: unknown; + activeData: TableInspectorAdapter | undefined; + dataViews: IndexPatternMap; + capabilities: { + canSaveVisualizations: boolean; + canOpenVisualizations: boolean; + canSaveDashboards: boolean; + navLinks: Capabilities['navLinks']; + discover: Capabilities['discover']; + }; + query: Array<Query | AggregateQuery>; + filters: Filter[]; + timeRange: TimeRange; + esQueryConfig: EsQueryConfig; +}) { + const { error, meta } = getLayerMetaInfo( + activeDatasource, + activeDatasourceState, + activeVisualization, + activeVisualizationState, + activeData, + dataViews, + timeRange, + capabilities + ); + + if (error || !meta) { + return; + } + const luceneOrKuery: Query[] = []; + const aggregateQueries: AggregateQuery[] = []; + + if (Array.isArray(query)) { + const [kqlOrLuceneQueries, esqlQueries] = partition(query, isOfQueryType); + if (kqlOrLuceneQueries.length) { + luceneOrKuery.push(...kqlOrLuceneQueries); + } + if (esqlQueries.length) { + aggregateQueries.push(...esqlQueries); + } + } + + const { filters: newFilters, query: newQuery } = combineQueryAndFilters( + luceneOrKuery.length > 0 ? luceneOrKuery : aggregateQueries[0], + filters, + meta, + Object.values(dataViews), + esQueryConfig + ); + + const dataViewSpec = dataViews[meta.id]!.spec; + + return { + dataViewSpec, + timeRange, + filters: newFilters, + query: aggregateQueries.length > 0 ? aggregateQueries[0] : newQuery, + columns: meta.columns, + }; +} + +function loadViewUnderlyingDataArgs( + state: LensRuntimeState, + { getVisualizationContext }: VisualizationContextHelper, + searchContextApi: { timeRange$: PublishingSubject<TimeRange | undefined> }, + parentApi: unknown, + { + capabilities, + uiSettings, + injectFilterReferences, + data, + datasourceMap, + visualizationMap, + }: LensEmbeddableStartServices +) { + const { doc, activeData, activeDatasourceState, activeVisualizationState, indexPatterns } = + getVisualizationContext(); + const activeVisualizationId = getActiveVisualizationIdFromDoc(doc); + const activeDatasourceId = getActiveDatasourceIdFromDoc(doc); + const activeVisualization = activeVisualizationId + ? visualizationMap[activeVisualizationId] + : undefined; + const activeDatasource = activeDatasourceId ? datasourceMap[activeDatasourceId] : undefined; + if ( + !doc || + !activeData || + !activeDatasource || + !activeDatasourceState || + !activeVisualization || + !activeVisualizationState + ) { + return; + } + + const { filters$, query$, timeRange$ } = apiPublishesUnifiedSearch(parentApi) + ? parentApi + : { filters$: undefined, query$: undefined, timeRange$: undefined }; + + const mergedSearchContext = getMergedSearchContext( + state, + { + filters: filters$?.getValue(), + query: query$?.getValue(), + timeRange: timeRange$?.getValue(), + }, + searchContextApi.timeRange$, + parentApi, + { + data, + injectFilterReferences, + } + ); + + if (!mergedSearchContext.timeRange) { + return; + } + + const viewUnderlyingDataArgs = getViewUnderlyingDataArgs({ + activeDatasource, + activeDatasourceState, + activeVisualization, + activeVisualizationState, + activeData, + capabilities: { + canSaveDashboards: Boolean(capabilities.dashboard?.showWriteControls), + canSaveVisualizations: Boolean(capabilities.visualize.save), + canOpenVisualizations: Boolean(capabilities.visualize.show), + navLinks: capabilities.navLinks, + discover: capabilities.discover, + }, + query: mergedSearchContext.query, + filters: mergedSearchContext.filters || [], + timeRange: mergedSearchContext.timeRange, + esQueryConfig: getEsQueryConfig(uiSettings), + dataViews: indexPatterns, + }); + + return viewUnderlyingDataArgs; +} + +function createViewUnderlyingDataApis( + getState: GetStateType, + visualizationContextHelper: VisualizationContextHelper, + searchContextApi: { timeRange$: PublishingSubject<TimeRange | undefined> }, + parentApi: unknown, + services: LensEmbeddableStartServices +): ViewInDiscoverCallbacks { + let viewUnderlyingDataArgs: undefined | ViewUnderlyingDataArgs; + + const [canViewUnderlyingData$] = buildObservableVariable<boolean>(false); + + return { + canViewUnderlyingData$, + loadViewUnderlyingData: () => { + viewUnderlyingDataArgs = loadViewUnderlyingDataArgs( + getState(), + visualizationContextHelper, + searchContextApi, + parentApi, + services + ); + canViewUnderlyingData$.next(viewUnderlyingDataArgs != null); + }, + getViewUnderlyingDataArgs: () => { + return viewUnderlyingDataArgs; + }, + }; +} + +/** + * Initialize APIs used for actions on Lens panels + * This includes drilldowns, explore data, and more + */ +export function initializeActionApi( + uuid: string, + initialState: LensRuntimeState, + getLatestState: GetStateType, + parentApi: unknown, + searchContextApi: { timeRange$: PublishingSubject<TimeRange | undefined> }, + titleApi: { panelTitle: PublishingSubject<string | undefined> }, + visualizationContextHelper: VisualizationContextHelper, + services: LensEmbeddableStartServices +): { + api: ViewInDiscoverCallbacks & HasDynamicActions; + comparators: StateComparators<DynamicActionsSerializedState>; + serialize: () => {}; + cleanup: () => void; +} { + const dynamicActionsApi = services.embeddableEnhanced?.initializeReactEmbeddableDynamicActions( + uuid, + () => titleApi.panelTitle.getValue(), + initialState + ); + const maybeStopDynamicActions = dynamicActionsApi?.startDynamicActions(); + + return { + api: { + ...(isTextBasedLanguage(initialState) ? {} : dynamicActionsApi?.dynamicActionsApi ?? {}), + ...createViewUnderlyingDataApis( + getLatestState, + visualizationContextHelper, + searchContextApi, + parentApi, + services + ), + }, + comparators: { + ...(dynamicActionsApi?.dynamicActionsComparator ?? { + enhancements: getUnchangingComparator(), + }), + }, + serialize: () => dynamicActionsApi?.serializeDynamicActions() ?? {}, + cleanup: () => { + maybeStopDynamicActions?.stopDynamicActions(); + }, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_dashboard_service.test.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_dashboard_service.test.ts new file mode 100644 index 000000000000..2a0c469b3bbf --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_dashboard_service.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LensRuntimeState } from '../types'; +import { getLensRuntimeStateMock, getLensInternalApiMock, makeEmbeddableServices } from '../mocks'; +import { initializeStateManagement } from './initialize_state_management'; +import { initializeDashboardServices } from './initialize_dashboard_services'; +import faker from 'faker'; +import { createEmptyLensState } from '../helper'; + +function setupDashboardServicesApi(runtimeOverrides?: Partial<LensRuntimeState>) { + const services = makeEmbeddableServices(); + const internalApiMock = getLensInternalApiMock(); + const runtimeState = getLensRuntimeStateMock(runtimeOverrides); + const stateManagementConfig = initializeStateManagement(runtimeState, internalApiMock); + const { api } = initializeDashboardServices( + runtimeState, + () => runtimeState, + internalApiMock, + stateManagementConfig, + {}, + services + ); + return api; +} + +describe('Transformation API', () => { + it("should not save to library if there's already a saveObjectId", async () => { + const api = setupDashboardServicesApi({ savedObjectId: faker.random.uuid() }); + expect(await api.canLinkToLibrary()).toBe(false); + }); + + it("should save to library if there's no saveObjectId declared", async () => { + const api = setupDashboardServicesApi(); + expect(await api.canLinkToLibrary()).toBe(true); + }); + + it('should not save to library for ES|QL chart types', async () => { + // setup a state with an ES|QL query + const api = setupDashboardServicesApi( + createEmptyLensState('lnsXY', faker.lorem.words(), faker.lorem.text(), { + esql: 'FROM index', + }) + ); + expect(await api.canLinkToLibrary()).toBe(false); + }); +}); diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_dashboard_services.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_dashboard_services.ts new file mode 100644 index 000000000000..d030a92a02b5 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_dashboard_services.ts @@ -0,0 +1,185 @@ +/* + * 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 { noop } from 'lodash'; +import { + HasInPlaceLibraryTransforms, + HasLibraryTransforms, + PublishesWritablePanelTitle, + PublishesWritablePanelDescription, + SerializedTitles, + StateComparators, + getUnchangingComparator, + initializeTitles, +} from '@kbn/presentation-publishing'; +import { apiPublishesSettings } from '@kbn/presentation-containers'; +import { buildObservableVariable, isTextBasedLanguage } from '../helper'; +import type { + LensComponentProps, + LensPanelProps, + LensRuntimeState, + LensEmbeddableStartServices, + LensOverrides, + LensSharedProps, + IntegrationCallbacks, + LensInternalApi, +} from '../types'; +import { apiHasLensComponentProps } from '../type_guards'; +import { StateManagementConfig } from './initialize_state_management'; + +// Convenience type for the serialized props of this initializer +type SerializedProps = SerializedTitles & LensPanelProps & LensOverrides & LensSharedProps; + +export interface DashboardServicesConfig { + api: PublishesWritablePanelTitle & + PublishesWritablePanelDescription & + HasInPlaceLibraryTransforms & + HasLibraryTransforms<LensRuntimeState> & + Pick<IntegrationCallbacks, 'updateOverrides' | 'getTriggerCompatibleActions'>; + serialize: () => SerializedProps; + comparators: StateComparators<SerializedProps & { isNewPanel?: boolean }>; + cleanup: () => void; +} + +/** + * Everything about panel and library services + */ +export function initializeDashboardServices( + initialState: LensRuntimeState, + getLatestState: () => LensRuntimeState, + internalApi: LensInternalApi, + stateConfig: StateManagementConfig, + parentApi: unknown, + { attributeService, uiActions }: LensEmbeddableStartServices +): DashboardServicesConfig { + const { titlesApi, serializeTitles, titleComparators } = initializeTitles(initialState); + // For some legacy reason the title and description default value is picked differently + // ( based on existing FTR tests ). + const [defaultPanelTitle$] = buildObservableVariable<string | undefined>( + initialState.title || internalApi.attributes$.getValue().title + ); + const [defaultPanelDescription$] = buildObservableVariable<string | undefined>( + initialState.savedObjectId + ? internalApi.attributes$.getValue().description || initialState.description + : initialState.description + ); + // The observable references here are the same to the internalApi, + // the buildObservableVariable re-uses the same observable when detected but it builds the right comparator + const [overrides$, overridesComparator] = buildObservableVariable<LensOverrides['overrides']>( + internalApi.overrides$ + ); + const [disableTriggers$, disabledTriggersComparator] = buildObservableVariable< + boolean | undefined + >(internalApi.disableTriggers$); + + return { + api: { + defaultPanelTitle: defaultPanelTitle$, + defaultPanelDescription: defaultPanelDescription$, + ...titlesApi, + libraryId$: stateConfig.api.savedObjectId, + updateOverrides: internalApi.updateOverrides, + getTriggerCompatibleActions: uiActions.getTriggerCompatibleActions, + // The functions below brings the HasInPlaceLibraryTransforms compliance (new interface) + saveToLibrary: async (title: string) => { + const { attributes } = getLatestState(); + const savedObjectId = await attributeService.saveToLibrary( + { + ...attributes, + title, + }, + attributes.references + ); + // keep in sync the state + stateConfig.api.updateSavedObjectId(savedObjectId); + return savedObjectId; + }, + checkForDuplicateTitle: async ( + newTitle: string, + isTitleDuplicateConfirmed: boolean, + onTitleDuplicate: () => void + ) => { + await attributeService.checkForDuplicateTitle({ + newTitle, + isTitleDuplicateConfirmed, + onTitleDuplicate, + newCopyOnSave: false, + newDescription: '', + displayName: '', + lastSavedTitle: '', + copyOnSave: false, + }); + }, + canLinkToLibrary: async () => + !getLatestState().savedObjectId && !isTextBasedLanguage(getLatestState()), + canUnlinkFromLibrary: async () => Boolean(getLatestState().savedObjectId), + unlinkFromLibrary: () => { + // broadcast the change to the main state serializer + stateConfig.api.updateSavedObjectId(undefined); + + if ((titlesApi.panelTitle.getValue() ?? '').length === 0) { + titlesApi.setPanelTitle(defaultPanelTitle$.getValue()); + } + if ((titlesApi.panelDescription.getValue() ?? '').length === 0) { + titlesApi.setPanelDescription(defaultPanelDescription$.getValue()); + } + defaultPanelTitle$.next(undefined); + defaultPanelDescription$.next(undefined); + }, + getByValueRuntimeSnapshot: (): Omit<LensRuntimeState, 'savedObjectId'> => { + const { savedObjectId, ...rest } = getLatestState(); + return rest; + }, + // The functions below brings the HasLibraryTransforms compliance (old interface) + getByReferenceState: () => getLatestState(), + getByValueState: (): Omit<LensRuntimeState, 'savedObjectId'> => { + const { savedObjectId, ...rest } = getLatestState(); + return rest; + }, + }, + serialize: () => { + const { style, noPadding, className } = apiHasLensComponentProps(parentApi) + ? parentApi + : ({} as LensComponentProps); + const settings = apiPublishesSettings(parentApi) + ? { + syncColors: parentApi.settings.syncColors$.getValue(), + syncCursor: parentApi.settings.syncCursor$.getValue(), + syncTooltips: parentApi.settings.syncTooltips$.getValue(), + } + : {}; + return { + ...serializeTitles(), + style, + noPadding, + className, + ...settings, + palette: initialState.palette, + overrides: overrides$.getValue(), + disableTriggers: disableTriggers$.getValue(), + }; + }, + comparators: { + ...titleComparators, + id: getUnchangingComparator<SerializedTitles & LensPanelProps, 'id'>(), + palette: getUnchangingComparator<SerializedTitles & LensPanelProps, 'palette'>(), + renderMode: getUnchangingComparator<SerializedTitles & LensPanelProps, 'renderMode'>(), + syncColors: getUnchangingComparator<SerializedTitles & LensPanelProps, 'syncColors'>(), + syncCursor: getUnchangingComparator<SerializedTitles & LensPanelProps, 'syncCursor'>(), + syncTooltips: getUnchangingComparator<SerializedTitles & LensPanelProps, 'syncTooltips'>(), + executionContext: getUnchangingComparator<LensSharedProps, 'executionContext'>(), + noPadding: getUnchangingComparator<LensSharedProps, 'noPadding'>(), + viewMode: getUnchangingComparator<LensSharedProps, 'viewMode'>(), + style: getUnchangingComparator<LensSharedProps, 'style'>(), + className: getUnchangingComparator<LensSharedProps, 'className'>(), + overrides: overridesComparator, + disableTriggers: disabledTriggersComparator, + isNewPanel: getUnchangingComparator<{ isNewPanel?: boolean }, 'isNewPanel'>(), + }, + cleanup: noop, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx new file mode 100644 index 000000000000..81372dad339f --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx @@ -0,0 +1,237 @@ +/* + * 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 { + HasEditCapabilities, + HasSupportedTriggers, + PublishesDisabledActionIds, + PublishesViewMode, + ViewMode, + apiHasAppContext, + apiPublishesDisabledActionIds, +} from '@kbn/presentation-publishing'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; +import { noop } from 'lodash'; +import { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public'; +import { tracksOverlays } from '@kbn/presentation-containers'; +import { i18n } from '@kbn/i18n'; +import { APP_ID, getEditPath } from '../../../common/constants'; +import { + GetStateType, + LensEmbeddableStartServices, + LensInspectorAdapters, + LensInternalApi, + LensRuntimeState, +} from '../types'; +import { + buildObservableVariable, + emptySerializer, + extractInheritedViewModeObservable, +} from '../helper'; +import { prepareInlineEditPanel } from '../inline_editing/setup_inline_editing'; +import { setupPanelManagement } from '../inline_editing/panel_management'; +import { mountInlineEditPanel } from '../inline_editing/mount'; +import { StateManagementConfig } from './initialize_state_management'; +import { apiPublishesInlineEditingCapabilities } from '../type_guards'; + +function getSupportedTriggers( + getState: GetStateType, + visualizationMap: LensEmbeddableStartServices['visualizationMap'] +) { + return () => { + const currentState = getState(); + if (currentState.attributes?.visualizationType) { + return visualizationMap[currentState.attributes.visualizationType]?.triggers || []; + } + return []; + }; +} + +/** + * Initialize the edit API for the embeddable + **/ +export function initializeEditApi( + uuid: string, + initialState: LensRuntimeState, + getState: GetStateType, + internalApi: LensInternalApi, + stateApi: StateManagementConfig['api'], + inspectorApi: LensInspectorAdapters, + isTextBasedLanguage: (currentState: LensRuntimeState) => boolean, + startDependencies: LensEmbeddableStartServices, + parentApi?: unknown +): { + api: HasSupportedTriggers & + PublishesDisabledActionIds & + HasEditCapabilities & + PublishesViewMode & { uuid: string }; + comparators: {}; + serialize: () => {}; + cleanup: () => void; +} { + const supportedTriggers = getSupportedTriggers(getState, startDependencies.visualizationMap); + + const isESQLModeEnabled = () => uiSettings.get(ENABLE_ESQL); + + const [viewMode$] = buildObservableVariable<ViewMode>( + extractInheritedViewModeObservable(parentApi) + ); + + const { disabledActionIds, setDisabledActionIds } = apiPublishesDisabledActionIds(parentApi) + ? parentApi + : { disabledActionIds: undefined, setDisabledActionIds: noop }; + const [disabledActionIds$, disabledActionIdsComparator] = buildObservableVariable< + string[] | undefined + >(disabledActionIds); + + if (isTextBasedLanguage(initialState)) { + // do not expose the drilldown action for ES|QL + disabledActionIds$.next(disabledActionIds$.getValue()?.concat(['OPEN_FLYOUT_ADD_DRILLDOWN'])); + } + + /** + * Inline editing section + */ + const navigateToLensEditor = + (stateTransfer: EmbeddableStateTransfer, skipAppLeave?: boolean) => async () => { + if (!parentApi || !apiHasAppContext(parentApi)) { + return; + } + const parentApiContext = parentApi.getAppContext(); + const currentState = getState(); + await stateTransfer.navigateToEditor(APP_ID, { + path: getEditPath(currentState.savedObjectId), + state: { + embeddableId: uuid, + valueInput: currentState, + originatingApp: parentApiContext.currentAppId ?? 'dashboards', + originatingPath: parentApiContext.getCurrentPath?.(), + searchSessionId: currentState.searchSessionId, + }, + skipAppLeave, + }); + }; + + const panelManagementApi = setupPanelManagement(uuid, parentApi, { + isNewlyCreated$: internalApi.isNewlyCreated$, + setAsCreated: internalApi.setAsCreated, + }); + + const updateState = (newState: Pick<LensRuntimeState, 'attributes' | 'savedObjectId'>) => { + stateApi.updateAttributes(newState.attributes); + stateApi.updateSavedObjectId(newState.savedObjectId); + }; + + const openInlineEditor = prepareInlineEditPanel( + initialState, + getState, + updateState, + internalApi, + panelManagementApi, + inspectorApi, + startDependencies, + navigateToLensEditor, + uuid + ); + + /** + * The rest of the edit stuff + */ + const { uiSettings, capabilities, data } = startDependencies; + + const canEdit = () => { + if (viewMode$.getValue() !== 'edit') { + return false; + } + // check if it's in ES|QL mode + if (isTextBasedLanguage(getState()) && !isESQLModeEnabled()) { + return false; + } + return ( + Boolean(capabilities.visualize.save) || + (!getState().savedObjectId && + Boolean(capabilities.dashboard?.showWriteControls) && + Boolean(capabilities.visualize.show)) + ); + }; + + // this will force the embeddable to toggle the inline editing feature + const canEditInline = apiPublishesInlineEditingCapabilities(parentApi) + ? parentApi.canEditInline + : true; + + return { + comparators: { disabledActionIds: disabledActionIdsComparator }, + serialize: emptySerializer, + cleanup: noop, + api: { + uuid, + viewMode: viewMode$, + getTypeDisplayName: () => + i18n.translate('xpack.lens.embeddableDisplayName', { + defaultMessage: 'Lens', + }), + supportedTriggers, + disabledActionIds: disabledActionIds$, + setDisabledActionIds, + + /** + * This is the key method to enable the new Editing capabilities API + * Lens will leverage the netural nature of this function to build the inline editing experience + */ + onEdit: async () => { + if (!parentApi || !apiHasAppContext(parentApi)) { + return; + } + // just navigate directly to the editor + if (!canEditInline) { + const navigateFn = navigateToLensEditor( + new EmbeddableStateTransfer( + startDependencies.coreStart.application.navigateToApp, + startDependencies.coreStart.application.currentAppId$ + ), + true + ); + return navigateFn(); + } + + // save the initial state in case it needs to revert later on + const firstState = getState(); + + const rootEmbeddable = parentApi; + const overlayTracker = tracksOverlays(rootEmbeddable) ? rootEmbeddable : undefined; + const ConfigPanel = await openInlineEditor({ + onApply: (attributes: LensRuntimeState['attributes']) => + updateState({ ...getState(), attributes }), + // restore the first state found when the panel opened + onCancel: () => updateState({ ...firstState }), + }); + if (ConfigPanel) { + mountInlineEditPanel(ConfigPanel, startDependencies.coreStart, overlayTracker, uuid); + } + }, + /** + * Check everything here: user/app permissions and the current inline editing state + */ + isEditingEnabled: () => { + return apiHasAppContext(parentApi) && canEdit() && panelManagementApi.isEditingEnabled(); + }, + getEditHref: async () => { + if (!parentApi || !apiHasAppContext(parentApi)) { + return; + } + const currentState = getState(); + return getEditPath( + currentState.savedObjectId, + currentState.timeRange, + currentState.filters, + data.query.timefilter.timefilter.getRefreshInterval() + ); + }, + }, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_inspector.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_inspector.ts new file mode 100644 index 000000000000..733a1d4eac46 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_inspector.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { noop } from 'lodash'; +import { BehaviorSubject } from 'rxjs'; +import type { Adapters } from '@kbn/inspector-plugin/public'; +import { getLensInspectorService } from '../../lens_inspector_service'; +import { emptySerializer } from '../helper'; +import type { LensEmbeddableStartServices, LensInspectorAdapters } from '../types'; + +export function initializeInspector(services: LensEmbeddableStartServices): { + api: LensInspectorAdapters; + comparators: {}; + serialize: () => {}; + cleanup: () => void; +} { + const inspectorApi = getLensInspectorService(services.inspector); + + return { + api: { + ...inspectorApi, + adapters$: new BehaviorSubject<Adapters>(inspectorApi.getInspectorAdapters()), + }, + comparators: {}, + serialize: emptySerializer, + cleanup: noop, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_integrations.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_integrations.ts new file mode 100644 index 000000000000..c3501bdfcafb --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_integrations.ts @@ -0,0 +1,61 @@ +/* + * 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 { + getAggregateQueryMode, + getLanguageDisplayName, + isOfAggregateQueryType, +} from '@kbn/es-query'; +import { noop } from 'lodash'; +import type { HasSerializableState } from '@kbn/presentation-containers'; +import { emptySerializer, isTextBasedLanguage } from '../helper'; +import type { GetStateType, LensEmbeddableStartServices } from '../types'; +import type { IntegrationCallbacks } from '../types'; + +export function initializeIntegrations( + getLatestState: GetStateType, + { attributeService }: LensEmbeddableStartServices +): { + api: Omit< + IntegrationCallbacks, + | 'updateState' + | 'updateAttributes' + | 'updateDataViews' + | 'updateSavedObjectId' + | 'updateOverrides' + | 'updateDataLoading' + | 'getTriggerCompatibleActions' + > & + HasSerializableState; + cleanup: () => void; + serialize: () => {}; + comparators: {}; +} { + return { + api: { + serializeState: () => { + const currentState = getLatestState(); + return attributeService.extractReferences(currentState); + }, + // TODO: workout why we have this duplicated + getFullAttributes: () => getLatestState().attributes, + getSavedVis: () => getLatestState().attributes, + isTextBasedLanguage: () => isTextBasedLanguage(getLatestState()), + getTextBasedLanguage: () => { + const query = getLatestState().attributes?.state.query; + if (!query || !isOfAggregateQueryType(query)) { + return; + } + const language = getAggregateQueryMode(query); + return getLanguageDisplayName(language).toUpperCase(); + }, + }, + comparators: {}, + serialize: emptySerializer, + cleanup: noop, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_internal_api.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_internal_api.ts new file mode 100644 index 000000000000..2bdc00b3124a --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_internal_api.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { BehaviorSubject } from 'rxjs'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { buildObservableVariable, createEmptyLensState } from '../helper'; +import type { + ExpressionWrapperProps, + LensInternalApi, + LensOverrides, + LensRuntimeState, +} from '../types'; +import { apiHasAbortController } from '../type_guards'; +import type { UserMessage } from '../../types'; + +export function initializeInternalApi( + initialState: LensRuntimeState, + parentApi: unknown +): LensInternalApi { + const [hasRenderCompleted$] = buildObservableVariable<boolean>(false); + const [expressionParams$] = buildObservableVariable<ExpressionWrapperProps | null>(null); + const expressionAbortController$ = new BehaviorSubject<AbortController | undefined>(undefined); + if (apiHasAbortController(parentApi)) { + expressionAbortController$.next(parentApi.abortController); + } + const [renderCount$] = buildObservableVariable<number>(0); + + const attributes$ = new BehaviorSubject<LensRuntimeState['attributes']>( + initialState.attributes || createEmptyLensState().attributes + ); + const overrides$ = new BehaviorSubject(initialState.overrides); + const disableTriggers$ = new BehaviorSubject(initialState.disableTriggers); + const dataLoading$ = new BehaviorSubject<boolean | undefined>(undefined); + + const dataViews$ = new BehaviorSubject<DataView[] | undefined>(undefined); + // This is an internal error state, not to be confused with the runtime error state thrown by the expression pipeline + // In both cases a blocking error can happen, but for Lens validation errors we want to have full control over the UI + // while for runtime errors the error will bubble up to the embeddable presentation layer + const validationMessages$ = new BehaviorSubject<UserMessage[]>([]); + // This other set of messages is for non-blocking messages that can be displayed in the UI + const messages$ = new BehaviorSubject<UserMessage[]>([]); + + // This should settle the thing once and for all + // the isNewPanel won't be serialized so it will be always false after the edit panel closes applying the changes + const isNewlyCreated$ = new BehaviorSubject<boolean>(initialState.isNewPanel || false); + + // No need to expose anything at public API right now, that would happen later on + // where each initializer will pick what it needs and publish it + return { + attributes$, + overrides$, + disableTriggers$, + dataLoading$, + hasRenderCompleted$, + expressionParams$, + expressionAbortController$, + renderCount$, + isNewlyCreated$, + dataViews: dataViews$, + dispatchError: () => { + hasRenderCompleted$.next(true); + renderCount$.next(renderCount$.getValue() + 1); + }, + dispatchRenderStart: () => hasRenderCompleted$.next(false), + dispatchRenderComplete: () => { + renderCount$.next(renderCount$.getValue() + 1); + hasRenderCompleted$.next(true); + }, + updateExpressionParams: (newParams: ExpressionWrapperProps | null) => + expressionParams$.next(newParams), + updateDataLoading: (newDataLoading: boolean | undefined) => dataLoading$.next(newDataLoading), + updateOverrides: (overrides: LensOverrides['overrides']) => overrides$.next(overrides), + updateAttributes: (attributes: LensRuntimeState['attributes']) => attributes$.next(attributes), + updateAbortController: (abortController: AbortController | undefined) => + expressionAbortController$.next(abortController), + updateDataViews: (dataViews: DataView[] | undefined) => dataViews$.next(dataViews), + messages$, + updateMessages: (newMessages: UserMessage[]) => messages$.next(newMessages), + validationMessages$, + updateValidationMessages: (newMessages: UserMessage[]) => validationMessages$.next(newMessages), + resetAllMessages: () => { + messages$.next([]); + validationMessages$.next([]); + }, + setAsCreated: () => isNewlyCreated$.next(false), + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_search_context.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_search_context.ts new file mode 100644 index 000000000000..1a608de11e23 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_search_context.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Filter, Query, AggregateQuery } from '@kbn/es-query'; +import { + PublishesUnifiedSearch, + StateComparators, + getUnchangingComparator, + initializeTimeRange, +} from '@kbn/presentation-publishing'; +import { noop } from 'lodash'; +import { + PublishesSearchSession, + apiPublishesSearchSession, +} from '@kbn/presentation-publishing/interfaces/fetch/publishes_search_session'; +import { buildObservableVariable } from '../helper'; +import { LensInternalApi, LensRuntimeState, LensUnifiedSearchContext } from '../types'; + +export function initializeSearchContext( + initialState: LensRuntimeState, + internalApi: LensInternalApi, + parentApi: unknown +): { + api: PublishesUnifiedSearch & PublishesSearchSession; + comparators: StateComparators<LensUnifiedSearchContext>; + serialize: () => LensUnifiedSearchContext; + cleanup: () => void; +} { + const [searchSessionId$] = buildObservableVariable<string | undefined>( + apiPublishesSearchSession(parentApi) ? parentApi.searchSessionId$ : undefined + ); + + const attributes = internalApi.attributes$.getValue(); + + const [lastReloadRequestTime] = buildObservableVariable<number | undefined>(undefined); + + const [filters$] = buildObservableVariable<Filter[] | undefined>(attributes.state.filters); + + const [query$] = buildObservableVariable<Query | AggregateQuery | undefined>( + attributes.state.query + ); + + const [timeslice$] = buildObservableVariable<[number, number] | undefined>(undefined); + + const timeRange = initializeTimeRange(initialState); + return { + api: { + searchSessionId$, + filters$, + query$, + timeslice$, + isCompatibleWithUnifiedSearch: () => true, + ...timeRange.api, + }, + comparators: { + query: getUnchangingComparator<LensUnifiedSearchContext, 'query'>(), + filters: getUnchangingComparator<LensUnifiedSearchContext, 'filters'>(), + timeslice: getUnchangingComparator<LensUnifiedSearchContext, 'timeslice'>(), + searchSessionId: getUnchangingComparator<LensUnifiedSearchContext, 'searchSessionId'>(), + lastReloadRequestTime: getUnchangingComparator< + LensUnifiedSearchContext, + 'lastReloadRequestTime' + >(), + ...timeRange.comparators, + }, + cleanup: noop, + serialize: () => ({ + searchSessionId: searchSessionId$.getValue(), + filters: filters$.getValue(), + query: query$.getValue(), + timeslice: timeslice$.getValue(), + lastReloadRequestTime: lastReloadRequestTime.getValue(), + ...timeRange.serialize(), + }), + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_state_management.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_state_management.ts new file mode 100644 index 000000000000..af5ecddecd2b --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_state_management.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 { + getUnchangingComparator, + type PublishesBlockingError, + type PublishesDataLoading, + type PublishesDataViews, + type PublishesSavedObjectId, + type StateComparators, +} from '@kbn/presentation-publishing'; +import { noop } from 'lodash'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { BehaviorSubject } from 'rxjs'; +import type { IntegrationCallbacks, LensInternalApi, LensRuntimeState } from '../types'; +import { buildObservableVariable } from '../helper'; +import { SharingSavedObjectProps } from '../../types'; + +export interface StateManagementConfig { + api: Pick<IntegrationCallbacks, 'updateAttributes' | 'updateSavedObjectId'> & + PublishesSavedObjectId & + PublishesDataViews & + PublishesDataLoading & + PublishesBlockingError; + serialize: () => Pick<LensRuntimeState, 'attributes' | 'savedObjectId'>; + comparators: StateComparators< + Pick<LensRuntimeState, 'attributes' | 'savedObjectId' | 'abortController'> & { + managed?: boolean | undefined; + sharingSavedObjectProps?: SharingSavedObjectProps | undefined; + } + >; + cleanup: () => void; +} + +/** + * Due to inline editing we need something advanced to handle the state + * management at the embeddable level, so here's the initializers for it + */ +export function initializeStateManagement( + initialState: LensRuntimeState, + internalApi: LensInternalApi +): StateManagementConfig { + const [attributes$, attributesComparator] = buildObservableVariable< + LensRuntimeState['attributes'] + >(internalApi.attributes$); + + const [savedObjectId$, savedObjectIdComparator] = buildObservableVariable< + LensRuntimeState['savedObjectId'] + >(initialState.savedObjectId); + + const [dataViews$] = buildObservableVariable<DataView[] | undefined>(internalApi.dataViews); + const [dataLoading$] = buildObservableVariable<boolean | undefined>(internalApi.dataLoading$); + const [abortController$, abortControllerComparator] = buildObservableVariable< + AbortController | undefined + >(internalApi.expressionAbortController$); + + // This is the way to communicate to the embeddable panel to render a blocking error with the + // default panel error component - i.e. cannot find a Lens SO type of thing. + // For Lens specific errors, we use a Lens specific error component. + const [blockingError$] = buildObservableVariable<Error | undefined>(undefined); + return { + api: { + updateAttributes: internalApi.updateAttributes, + updateSavedObjectId: (newSavedObjectId: LensRuntimeState['savedObjectId']) => + savedObjectId$.next(newSavedObjectId), + savedObjectId: savedObjectId$, + dataViews: dataViews$, + dataLoading: dataLoading$, + blockingError: blockingError$, + }, + serialize: () => { + return { + attributes: attributes$.getValue(), + savedObjectId: savedObjectId$.getValue(), + abortController: abortController$.getValue(), + }; + }, + comparators: { + // need to force cast this to make it pass the type check + // @TODO: workout why this is needed + attributes: attributesComparator as [ + BehaviorSubject<LensRuntimeState['attributes']>, + (newValue: LensRuntimeState['attributes'] | undefined) => void, + ( + a: LensRuntimeState['attributes'] | undefined, + b: LensRuntimeState['attributes'] | undefined + ) => boolean + ], + savedObjectId: savedObjectIdComparator, + abortController: abortControllerComparator, + sharingSavedObjectProps: getUnchangingComparator(), + managed: getUnchangingComparator(), + }, + cleanup: noop, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_visualization_context.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_visualization_context.ts new file mode 100644 index 000000000000..93d544013e71 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_visualization_context.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LensInternalApi, VisualizationContext, VisualizationContextHelper } from '../types'; + +export function initializeVisualizationContext( + internalApi: LensInternalApi +): VisualizationContextHelper { + // TODO: this will likely be merged together with the state$ observable + let visualizationContext: VisualizationContext = { + doc: internalApi.attributes$.getValue(), + mergedSearchContext: {}, + indexPatterns: {}, + indexPatternRefs: [], + activeVisualizationState: undefined, + activeDatasourceState: undefined, + activeData: undefined, + }; + return { + getVisualizationContext: () => visualizationContext, + updateVisualizationContext: (newVisualizationContext: Partial<VisualizationContext>) => { + visualizationContext = { + ...visualizationContext, + ...newVisualizationContext, + }; + }, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/inline_editing/mount.tsx b/x-pack/plugins/lens/public/react_embeddable/inline_editing/mount.tsx new file mode 100644 index 000000000000..566c5b27b654 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/inline_editing/mount.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart } from '@kbn/core/public'; +import { TracksOverlays } from '@kbn/presentation-containers'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +/** + * Shared logic to mount the inline config panel + * @param ConfigPanel + * @param coreStart + * @param overlayTracker + * @param uuid + * @param container + */ +export function mountInlineEditPanel( + ConfigPanel: JSX.Element, + coreStart: CoreStart, + overlayTracker: TracksOverlays | undefined, + uuid?: string, + container?: HTMLElement | null +) { + if (container) { + ReactDOM.render(ConfigPanel, container); + } else { + const handle = coreStart.overlays.openFlyout( + toMountPoint( + React.cloneElement(ConfigPanel, { + closeFlyout: () => { + overlayTracker?.clearOverlays(); + handle.close(); + }, + }), + coreStart + ), + { + className: 'lnsConfigPanel__overlay', + size: 's', + 'data-test-subj': 'customizeLens', + type: 'push', + paddingSize: 'm', + maxWidth: 800, + hideCloseButton: true, + isResizable: true, + onClose: (overlayRef) => { + overlayTracker?.clearOverlays(); + overlayRef.close(); + }, + outsideClickCloses: true, + } + ); + if (uuid) { + overlayTracker?.openOverlay(handle, { focusedPanelId: uuid }); + } + } +} diff --git a/x-pack/plugins/lens/public/react_embeddable/inline_editing/panel_management.tsx b/x-pack/plugins/lens/public/react_embeddable/inline_editing/panel_management.tsx new file mode 100644 index 000000000000..5753c8112d87 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/inline_editing/panel_management.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { BehaviorSubject } from 'rxjs'; +import { PublishingSubject } from '@kbn/presentation-publishing'; +import { LensRuntimeState } from '../types'; + +export interface PanelManagementApi { + isEditingEnabled: () => boolean; + isNewPanel: () => boolean; + onStopEditing: (isCancel: boolean, state: LensRuntimeState | undefined) => void; +} + +export function setupPanelManagement( + uuid: string, + parentApi: unknown, + { + isNewlyCreated$, + setAsCreated, + }: { + isNewlyCreated$: PublishingSubject<boolean>; + setAsCreated: () => void; + } +): PanelManagementApi { + const isEditing$ = new BehaviorSubject(false); + + return { + isEditingEnabled: () => true, + isNewPanel: () => isNewlyCreated$.getValue(), + onStopEditing: (isCancel: boolean = false, state: LensRuntimeState | undefined) => { + isEditing$.next(false); + if (isNewlyCreated$.getValue() && isCancel && !state) { + if (apiIsPresentationContainer(parentApi)) { + parentApi?.removePanel(uuid); + } + } + setAsCreated(); + }, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/inline_editing/setup_inline_editing.tsx b/x-pack/plugins/lens/public/react_embeddable/inline_editing/setup_inline_editing.tsx new file mode 100644 index 000000000000..e37e67113296 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/inline_editing/setup_inline_editing.tsx @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public'; +import React from 'react'; +import { EditLensConfigurationProps } from '../../app_plugin/shared/edit_on_the_fly/get_edit_lens_configuration'; +import { EditConfigPanelProps } from '../../app_plugin/shared/edit_on_the_fly/types'; +import { getActiveDatasourceIdFromDoc } from '../../utils'; +import { isTextBasedLanguage } from '../helper'; +import { + GetStateType, + LensEmbeddableStartServices, + LensInspectorAdapters, + LensInternalApi, + LensRuntimeState, + TypedLensSerializedState, +} from '../types'; +import { PanelManagementApi } from './panel_management'; +import { getStateManagementForInlineEditing } from './state_management'; + +export function prepareInlineEditPanel( + initialState: LensRuntimeState, + getState: GetStateType, + updateState: (newState: Pick<LensRuntimeState, 'attributes' | 'savedObjectId'>) => void, + { dataLoading$, isNewlyCreated$ }: Pick<LensInternalApi, 'dataLoading$' | 'isNewlyCreated$'>, + panelManagementApi: PanelManagementApi, + inspectorApi: LensInspectorAdapters, + { + coreStart, + ...startDependencies + }: Omit< + LensEmbeddableStartServices, + | 'timefilter' + | 'coreHttp' + | 'capabilities' + | 'expressionRenderer' + | 'documentToExpression' + | 'injectFilterReferences' + | 'visualizationMap' + | 'datasourceMap' + | 'theme' + | 'uiSettings' + | 'attributeService' + >, + navigateToLensEditor?: ( + stateTransfer: EmbeddableStateTransfer, + skipAppLeave?: boolean + ) => () => Promise<void>, + uuid?: string +) { + return async function openConfigPanel({ + onApply, + onCancel, + hideTimeFilterInfo, + }: Partial<Pick<EditConfigPanelProps, 'onApply' | 'onCancel' | 'hideTimeFilterInfo'>> = {}) { + const { getEditLensConfiguration, getVisualizationMap, getDatasourceMap } = await import( + '../../async_services' + ); + const visualizationMap = getVisualizationMap(); + const datasourceMap = getDatasourceMap(); + + const currentState = getState(); + const attributes = currentState.attributes as TypedLensSerializedState['attributes']; + const activeDatasourceId = (getActiveDatasourceIdFromDoc(attributes) || + 'formBased') as EditLensConfigurationProps['datasourceId']; + + const { updatePanelState, updateSuggestion } = getStateManagementForInlineEditing( + activeDatasourceId, + () => getState().attributes as TypedLensSerializedState['attributes'], + (attrs: TypedLensSerializedState['attributes'], resetId: boolean = false) => { + updateState({ + attributes: attrs, + savedObjectId: resetId ? undefined : currentState.savedObjectId, + }); + }, + visualizationMap, + datasourceMap, + startDependencies.data.query.filterManager.extract + ); + + const updateByRefInput = (savedObjectId: LensRuntimeState['savedObjectId']) => { + updateState({ attributes, savedObjectId }); + }; + const Component = await getEditLensConfiguration( + coreStart, + startDependencies, + visualizationMap, + datasourceMap + ); + + if (attributes?.visualizationType == null) { + return null; + } + return ( + <Component + attributes={attributes} + updateByRefInput={updateByRefInput} + updatePanelState={updatePanelState} + updateSuggestion={updateSuggestion} + datasourceId={activeDatasourceId} + lensAdapters={inspectorApi.getInspectorAdapters()} + dataLoading$={dataLoading$} + panelId={uuid} + savedObjectId={currentState.savedObjectId} + navigateToLensEditor={ + !isTextBasedLanguage(currentState) && navigateToLensEditor + ? navigateToLensEditor( + new EmbeddableStateTransfer( + coreStart.application.navigateToApp, + coreStart.application.currentAppId$ + ), + true + ) + : undefined + } + displayFlyoutHeader + canEditTextBasedQuery={isTextBasedLanguage(currentState)} + isNewPanel={panelManagementApi.isNewPanel()} + onCancel={() => { + panelManagementApi.onStopEditing( + true, + // DSL/form based charts are created via the full editor, so there's + // an initial state to preserve. ES|QL charts are created inline, so it needs to pass an empty state + // and the panelManagementApi will decide whether to remove the panel or not + isNewlyCreated$.getValue() ? undefined : initialState + ); + onCancel?.(); + }} + onApply={(newAttributes) => { + panelManagementApi.onStopEditing(false, { ...getState(), attributes: newAttributes }); + if (newAttributes.visualizationType != null) { + onApply?.(newAttributes); + } + }} + hideTimeFilterInfo={hideTimeFilterInfo} + /> + ); + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/inline_editing/state_management.tsx b/x-pack/plugins/lens/public/react_embeddable/inline_editing/state_management.tsx new file mode 100644 index 000000000000..2a4f1f48fd0d --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/inline_editing/state_management.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FilterManager } from '@kbn/data-plugin/public'; +import { mergeToNewDoc } from '../../state_management/shared_logic'; +import type { DatasourceStates } from '../../state_management/types'; +import type { VisualizationMap, DatasourceMap } from '../../types'; +import type { TypedLensSerializedState } from '../types'; + +export function getStateManagementForInlineEditing( + activeDatasourceId: 'formBased' | 'textBased', + getAttributes: () => TypedLensSerializedState['attributes'], + updateAttributes: ( + newAttributes: TypedLensSerializedState['attributes'], + resetId?: boolean + ) => void, + visualizationMap: VisualizationMap, + datasourceMap: DatasourceMap, + extractFilterReferences: FilterManager['extract'] +) { + const updatePanelState = ( + datasourceState: unknown, + visualizationState: unknown, + visualizationType?: string + ) => { + const viz = getAttributes(); + const datasourceStates: DatasourceStates = { + [activeDatasourceId]: { + isLoading: false, + state: datasourceState, + }, + }; + const newViz = mergeToNewDoc( + viz, + { + activeId: visualizationType || viz.visualizationType, + state: visualizationState, + }, + datasourceStates, + viz.state.query, + viz.state.filters, + activeDatasourceId, + viz.state.adHocDataViews || {}, + { visualizationMap, datasourceMap, extractFilterReferences } + ); + const newDoc = { + ...viz, + ...newViz, + }; + + if (newDoc.state) { + updateAttributes(newDoc, true); + } + }; + + const updateSuggestion = updateAttributes; + + return { updateSuggestion, updatePanelState }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.tsx b/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.tsx new file mode 100644 index 000000000000..8c17063f97a2 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/lens_embeddable.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 from 'react'; +import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { DOC_TYPE } from '../../common/constants'; +import { + LensApi, + LensEmbeddableStartServices, + LensRuntimeState, + LensSerializedState, +} from './types'; + +import { loadEmbeddableData } from './data_loader'; +import { isTextBasedLanguage, deserializeState } from './helper'; +import { initializeEditApi } from './initializers/initialize_edit'; +import { initializeInspector } from './initializers/initialize_inspector'; +import { initializeDashboardServices } from './initializers/initialize_dashboard_services'; +import { initializeInternalApi } from './initializers/initialize_internal_api'; +import { initializeSearchContext } from './initializers/initialize_search_context'; +import { initializeVisualizationContext } from './initializers/initialize_visualization_context'; +import { initializeActionApi } from './initializers/initialize_actions'; +import { initializeIntegrations } from './initializers/initialize_integrations'; +import { initializeStateManagement } from './initializers/initialize_state_management'; +import { LensEmbeddableComponent } from './renderer/lens_embeddable_component'; + +export const createLensEmbeddableFactory = ( + services: LensEmbeddableStartServices +): ReactEmbeddableFactory<LensSerializedState, LensRuntimeState, LensApi> => { + return { + type: DOC_TYPE, + /** + * This is called before the build and will make sure that the + * final state will contain the attributes object + */ + deserializeState: async ({ rawState, references }) => + deserializeState(services.attributeService, rawState, references), + /** + * This is called after the deserialize, so some assumptions can be made about its arguments: + * @param state the Lens "runtime" state, which means that 'attributes' is always present. + * The difference for a by-value and a by-ref can be determined by the presence of 'savedObjectId' in the state + * @param buildApi a utility function to build the Lens API together to instrument the embeddable container on how to detect + * significative changes in the state (i.e. worth a save or not) + * @param uuid a unique identifier for the embeddable panel + * @param parentApi a set of props passed down from the embeddable container. Note: no assumptions can be made about its content + * so the usage of type-guards is recommended before extracting data from it. + * Due to the new embeddable being rendered by a <ReactEmbeddableRenderer /> wrapper, this is the only way + * to pass data/props from a container. + * Typical use cases is the forwarding of the unifiedSearch context to the embeddable, or the passing props + * from the Lens component container to the Lens embeddable. + * @returns an object with the Lens API and the React component to render in the Embeddable + */ + buildEmbeddable: async (initialState, buildApi, uuid, parentApi) => { + /** + * Observables and functions declared here are used internally to store mutating state values + * This is an internal API not exposed outside of the embeddable. + */ + const internalApi = initializeInternalApi(initialState, parentApi); + + const visualizationContextHelper = initializeVisualizationContext(internalApi); + + /** + * Initialize various configurations required to build all the required + * parts for the Lens embeddable. + * Each initialize call returns an object with the following properties: + * - api: a set of methods or observables (also non-serializable) who can be picked up within the component + * - serialize: a serializable subset of the Lens runtime state + * - comparators: a set of comparators to help Dashboard determine if the state has changed since its saved state + * - cleanup: a function to clean up any resources when the component is unmounted + * + * Mind: the getState argument is ok to pass as long as it is lazy evaluated (i.e. called within a function). + * If there's something that should be immediately computed use the "initialState" deserialized variable. + */ + const stateConfig = initializeStateManagement(initialState, internalApi); + const dashboardConfig = initializeDashboardServices( + initialState, + getState, + internalApi, + stateConfig, + parentApi, + services + ); + + const inspectorConfig = initializeInspector(services); + + const editConfig = initializeEditApi( + uuid, + initialState, + getState, + internalApi, + stateConfig.api, + inspectorConfig.api, + isTextBasedLanguage, + services, + parentApi + ); + + const searchContextConfig = initializeSearchContext(initialState, internalApi, parentApi); + const integrationsConfig = initializeIntegrations(getState, services); + const actionsConfig = initializeActionApi( + uuid, + initialState, + getState, + parentApi, + searchContextConfig.api, + dashboardConfig.api, + visualizationContextHelper, + services + ); + + /** + * This is useful to have always the latest version of the state + * at hand when calling callbacks or performing actions + */ + function getState(): LensRuntimeState { + return { + ...actionsConfig.serialize(), + ...editConfig.serialize(), + ...inspectorConfig.serialize(), + ...dashboardConfig.serialize(), + ...searchContextConfig.serialize(), + ...integrationsConfig.serialize(), + ...stateConfig.serialize(), + }; + } + + /** + * Lens API is the object that can be passed to the final component/renderer and + * provide access to the services for and by the outside world + */ + const api: LensApi = buildApi( + // Note: the order matters here, so make sure to have the + // dashboardConfig who owns the savedObjectId after the + // stateConfig one who owns the inline editing + { + ...editConfig.api, + ...inspectorConfig.api, + ...searchContextConfig.api, + ...actionsConfig.api, + ...integrationsConfig.api, + ...stateConfig.api, + ...dashboardConfig.api, + }, + { + ...stateConfig.comparators, + ...editConfig.comparators, + ...inspectorConfig.comparators, + ...searchContextConfig.comparators, + ...actionsConfig.comparators, + ...integrationsConfig.comparators, + ...dashboardConfig.comparators, + } + ); + + // Compute the expression using the provided parameters + // Inside a subscription will be updated based on each unifiedSearch change + // and as side effect update few observables as expressionParams$, expressionAbortController$ and renderCount$ with the new values upon updates + const expressionConfig = loadEmbeddableData( + uuid, + getState, + api, + parentApi, + internalApi, + services, + visualizationContextHelper + ); + + const onUnmount = () => { + editConfig.cleanup(); + inspectorConfig.cleanup(); + searchContextConfig.cleanup(); + expressionConfig.cleanup(); + actionsConfig.cleanup(); + integrationsConfig.cleanup(); + dashboardConfig.cleanup(); + }; + + return { + api, + Component: () => ( + <LensEmbeddableComponent api={api} internalApi={internalApi} onUnmount={onUnmount} /> + ), + }; + }, + }; +}; diff --git a/x-pack/plugins/lens/public/react_embeddable/logger.ts b/x-pack/plugins/lens/public/react_embeddable/logger.ts new file mode 100644 index 000000000000..05454843b681 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/logger.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Conditional (window.ELASTIC_LENS_LOGGER needs to be set to true) logger function + * @param message - mandatory message to log + * @param payload - optional object to log + */ + +export const addLog = (message: string, payload?: unknown) => { + // @ts-expect-error + const logger = window?.ELASTIC_LENS_LOGGER; + + if (logger) { + if (logger === 'debug') { + // eslint-disable-next-line no-console + console.log(`[Lens] ${message}`, payload); + } else { + // eslint-disable-next-line no-console + console.log(`[Lens] ${message}`); + } + } +}; diff --git a/x-pack/plugins/lens/public/react_embeddable/mocks/index.tsx b/x-pack/plugins/lens/public/react_embeddable/mocks/index.tsx new file mode 100644 index 000000000000..a3992e504c4d --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/mocks/index.tsx @@ -0,0 +1,352 @@ +/* + * 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 { BehaviorSubject, Subject } from 'rxjs'; +import deepMerge from 'deepmerge'; +import React from 'react'; +import faker from 'faker'; +import { Query, Filter, AggregateQuery, TimeRange } from '@kbn/es-query'; +import { PhaseEvent, ViewMode } from '@kbn/presentation-publishing'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { Adapters } from '@kbn/inspector-plugin/common'; +import { coreMock } from '@kbn/core/public/mocks'; +import { visualizationsPluginMock } from '@kbn/visualizations-plugin/public/mocks'; +import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks'; +import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; +import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { ReactExpressionRendererProps } from '@kbn/expressions-plugin/public'; +import { ReactEmbeddableDynamicActionsApi } from '@kbn/embeddable-enhanced-plugin/public/plugin'; +import { DOC_TYPE } from '../../../common/constants'; +import { createEmptyLensState } from '../helper'; +import { + ExpressionWrapperProps, + LensApi, + LensEmbeddableStartServices, + LensInternalApi, + LensRendererProps, + LensRuntimeState, + LensSerializedState, + VisualizationContext, +} from '../types'; +import { + createMockDatasource, + createMockVisualization, + defaultDoc, + makeDefaultServices, +} from '../../mocks'; +import { + Datasource, + DatasourceMap, + UserMessage, + Visualization, + VisualizationMap, +} from '../../types'; + +const LensApiMock: LensApi = { + // Static props + type: DOC_TYPE, + uuid: faker.random.uuid(), + // Shared Embeddable Observables + panelTitle: new BehaviorSubject<string | undefined>(faker.lorem.words()), + hidePanelTitle: new BehaviorSubject<boolean | undefined>(false), + filters$: new BehaviorSubject<Filter[] | undefined>([]), + query$: new BehaviorSubject<Query | AggregateQuery | undefined>({ + query: 'test', + language: 'kuery', + }), + timeRange$: new BehaviorSubject<TimeRange | undefined>({ from: 'now-15m', to: 'now' }), + dataLoading: new BehaviorSubject<boolean | undefined>(false), + // Methods + getSavedVis: jest.fn(), + getFullAttributes: jest.fn(), + canViewUnderlyingData$: new BehaviorSubject<boolean>(false), + loadViewUnderlyingData: jest.fn(), + getViewUnderlyingDataArgs: jest.fn(() => ({ + dataViewSpec: { id: 'index-pattern-id' }, + timeRange: { from: 'now-7d', to: 'now' }, + filters: [], + query: undefined, + columns: [], + })), + isTextBasedLanguage: jest.fn(() => true), + getTextBasedLanguage: jest.fn(), + getInspectorAdapters: jest.fn(() => ({})), + inspect: jest.fn(), + closeInspector: jest.fn(async () => {}), + supportedTriggers: jest.fn(() => []), + canLinkToLibrary: jest.fn(async () => false), + canUnlinkFromLibrary: jest.fn(async () => false), + unlinkFromLibrary: jest.fn(), + checkForDuplicateTitle: jest.fn(), + /** New embeddable api inherited methods */ + resetUnsavedChanges: jest.fn(), + serializeState: jest.fn(), + snapshotRuntimeState: jest.fn(), + saveToLibrary: jest.fn(async () => 'saved-id'), + getByValueRuntimeSnapshot: jest.fn(), + onEdit: jest.fn(), + isEditingEnabled: jest.fn(() => true), + getTypeDisplayName: jest.fn(() => 'Lens'), + setPanelTitle: jest.fn(), + setHidePanelTitle: jest.fn(), + phase$: new BehaviorSubject<PhaseEvent | undefined>({ + id: faker.random.uuid(), + status: 'rendered', + timeToEvent: 1000, + }), + unsavedChanges: new BehaviorSubject<object | undefined>(undefined), + dataViews: new BehaviorSubject<DataView[] | undefined>(undefined), + libraryId$: new BehaviorSubject<string | undefined>(undefined), + savedObjectId: new BehaviorSubject<string | undefined>(undefined), + adapters$: new BehaviorSubject<Adapters>({}), + updateAttributes: jest.fn(), + updateSavedObjectId: jest.fn(), + updateOverrides: jest.fn(), + getByReferenceState: jest.fn(), + getByValueState: jest.fn(), + getTriggerCompatibleActions: jest.fn(), + blockingError: new BehaviorSubject<Error | undefined>(undefined), + panelDescription: new BehaviorSubject<string | undefined>(undefined), + setPanelDescription: jest.fn(), + viewMode: new BehaviorSubject<ViewMode>('view'), + disabledActionIds: new BehaviorSubject<string[] | undefined>(undefined), + setDisabledActionIds: jest.fn(), +}; + +const LensSerializedStateMock: LensSerializedState = createEmptyLensState( + 'lnsXY', + faker.lorem.words(), + faker.lorem.text(), + { query: 'test', language: 'kuery' } +); + +export function getLensAttributesMock(attributes?: Partial<LensRuntimeState['attributes']>) { + return deepMerge(LensSerializedStateMock.attributes!, attributes ?? {}); +} + +export function getLensApiMock(overrides: Partial<LensApi> = {}) { + return { + ...LensApiMock, + ...overrides, + }; +} + +export function getLensSerializedStateMock(overrides: Partial<LensSerializedState> = {}) { + return { + savedObjectId: faker.random.uuid(), + ...LensSerializedStateMock, + ...overrides, + }; +} + +export function getLensRuntimeStateMock( + overrides: Partial<LensRuntimeState> = {} +): LensRuntimeState { + return { + ...(LensSerializedStateMock as LensRuntimeState), + ...overrides, + }; +} + +export function getLensComponentProps(overrides: Partial<LensRendererProps> = {}) { + return { + ...LensSerializedStateMock, + ...LensApiMock, + ...overrides, + }; +} + +export function makeEmbeddableServices( + sessionIdSubject = new Subject<string>(), + sessionId: string | undefined = undefined, + { + visOverrides, + dataOverrides, + }: { + visOverrides?: { id: string } & Partial<Visualization>; + dataOverrides?: { id: string } & Partial<Datasource>; + } = {} +): jest.Mocked<LensEmbeddableStartServices> { + const services = makeDefaultServices(sessionIdSubject, sessionId); + return { + ...services, + expressions: expressionsPluginMock.createStartContract(), + visualizations: visualizationsPluginMock.createStartContract(), + embeddable: embeddablePluginMock.createStartContract(), + eventAnnotation: {} as LensEmbeddableStartServices['eventAnnotation'], + timefilter: services.data.query.timefilter.timefilter, + coreHttp: services.http, + coreStart: coreMock.createStart(), + capabilities: services.application.capabilities, + expressionRenderer: jest.fn().mockReturnValue(null), + documentToExpression: jest.fn(), + injectFilterReferences: services.data.query.filterManager.inject as jest.Mock, + visualizationMap: mockVisualizationMap(visOverrides?.id, visOverrides), + datasourceMap: mockDatasourceMap(dataOverrides?.id, dataOverrides), + charts: chartPluginMock.createStartContract(), + inspector: { + ...services.inspector, + isAvailable: jest.fn().mockReturnValue(true), + open: jest.fn(), + }, + uiActions: { + ...services.uiActions, + getTrigger: jest.fn().mockImplementation(() => ({ exec: jest.fn() })), + }, + embeddableEnhanced: { + initializeReactEmbeddableDynamicActions: jest.fn( + () => + ({ + dynamicActionsApi: { + enhancements: { dynamicActions: {} }, + setDynamicActions: jest.fn(), + dynamicActionsState$: {}, + }, + dynamicActionsComparator: jest.fn(), + serializeDynamicActions: jest.fn(), + startDynamicActions: jest.fn(), + } as unknown as ReactEmbeddableDynamicActionsApi) + ), + }, + }; +} + +export const mockVisualizationMap = ( + type: string | undefined = undefined, + overrides: Partial<Visualization> = {} +): VisualizationMap => { + if (type == null) { + return {}; + } + return { + [type]: { ...createMockVisualization(type), ...overrides }, + }; +}; + +export const mockDatasourceMap = ( + type: string | undefined = undefined, + overrides: Partial<Datasource> = {} +): DatasourceMap => { + const baseMap = { + // define the existing ones + formBased: createMockDatasource('formBased'), + textBased: createMockDatasource('textBased'), + }; + if (type == null) { + return baseMap; + } + return { + // define the existing ones + ...baseMap, + // override at will + [type]: { + ...createMockDatasource(type), + ...overrides, + }, + }; +}; + +export function createExpressionRendererMock(): jest.Mock< + React.ReactElement, + [ReactExpressionRendererProps] +> { + return jest.fn(({ expression }) => ( + <span data-test-subj="lnsExpressionRenderer"> + {(expression as string) || 'Expression renderer mock'} + </span> + )); +} + +function getValidExpressionParams( + overrides: Partial<ExpressionWrapperProps> = {} +): ExpressionWrapperProps { + return { + ExpressionRenderer: createExpressionRendererMock(), + expression: 'test', + searchContext: {}, + handleEvent: jest.fn(), + onData$: jest.fn(), + onRender$: jest.fn(), + addUserMessages: jest.fn(), + onRuntimeError: jest.fn(), + lensInspector: { + getInspectorAdapters: jest.fn(), + inspect: jest.fn(), + closeInspector: jest.fn(), + }, + ...overrides, + }; +} + +const LensInternalApiMock: LensInternalApi = { + dataViews: new BehaviorSubject<DataView[] | undefined>(undefined), + attributes$: new BehaviorSubject<LensRuntimeState['attributes']>(defaultDoc), + overrides$: new BehaviorSubject<LensRuntimeState['overrides']>(undefined), + disableTriggers$: new BehaviorSubject<LensRuntimeState['disableTriggers']>(undefined), + dataLoading$: new BehaviorSubject<boolean | undefined>(undefined), + hasRenderCompleted$: new BehaviorSubject<boolean>(true), + expressionParams$: new BehaviorSubject<ExpressionWrapperProps | null>(getValidExpressionParams()), + expressionAbortController$: new BehaviorSubject<AbortController | undefined>(undefined), + renderCount$: new BehaviorSubject<number>(0), + messages$: new BehaviorSubject<UserMessage[]>([]), + validationMessages$: new BehaviorSubject<UserMessage[]>([]), + isNewlyCreated$: new BehaviorSubject<boolean>(true), + updateAttributes: jest.fn(), + updateOverrides: jest.fn(), + dispatchRenderStart: jest.fn(), + dispatchRenderComplete: jest.fn(), + updateDataLoading: jest.fn(), + updateExpressionParams: jest.fn(), + updateAbortController: jest.fn(), + updateDataViews: jest.fn(), + updateMessages: jest.fn(), + resetAllMessages: jest.fn(), + dispatchError: jest.fn(), + updateValidationMessages: jest.fn(), + setAsCreated: jest.fn(), +}; + +export function getLensInternalApiMock(overrides: Partial<LensInternalApi> = {}): LensInternalApi { + return { + ...LensInternalApiMock, + ...overrides, + }; +} + +export function getVisualizationContextHelperMock( + attributesOverrides?: Partial<LensRuntimeState['attributes']>, + contextOverrides?: Omit<Partial<VisualizationContext>, 'doc'> +) { + return { + getVisualizationContext: jest.fn(() => ({ + mergedSearchContext: {}, + indexPatterns: {}, + indexPatternRefs: [], + activeVisualizationState: undefined, + activeDatasourceState: undefined, + activeData: undefined, + ...contextOverrides, + doc: getLensAttributesMock(attributesOverrides), + })), + updateVisualizationContext: jest.fn(), + }; +} + +export function createUnifiedSearchApi( + query: Query | AggregateQuery = { + query: '', + language: 'kuery', + }, + filters: Filter[] = [], + timeRange: TimeRange = { from: 'now-7d', to: 'now' } +) { + return { + filters$: new BehaviorSubject<Filter[] | undefined>(filters), + query$: new BehaviorSubject<Query | AggregateQuery | undefined>(query), + timeRange$: new BehaviorSubject<TimeRange | undefined>(timeRange), + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/renderer/hooks.ts b/x-pack/plugins/lens/public/react_embeddable/renderer/hooks.ts new file mode 100644 index 000000000000..c6d97d16ad38 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/renderer/hooks.ts @@ -0,0 +1,42 @@ +/* + * 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 { partition } from 'lodash'; +import { useEffect, useMemo, useRef } from 'react'; +import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; +import { dispatchRenderComplete, dispatchRenderStart } from '@kbn/kibana-utils-plugin/public'; +import { LensApi, LensInternalApi } from '../types'; + +/** + * This hooks known how to extract message based on types for the UI + */ +export function useMessages({ messages$ }: LensInternalApi) { + const latestMessages = useStateFromPublishingSubject(messages$); + return useMemo( + () => partition(latestMessages, ({ severity }) => severity !== 'info'), + [latestMessages] + ); +} + +/** + * This hook is responsible to emit the render start/complete JS event + * The render error is handled by the data_loader itself when updating the blocking errors + */ +export function useDispatcher(hasRendered: boolean, api: LensApi) { + const rootRef = useRef<HTMLDivElement | null>(null); + useEffect(() => { + if (!rootRef.current || api.blockingError?.getValue()) { + return; + } + if (hasRendered) { + dispatchRenderComplete(rootRef.current); + } else { + dispatchRenderStart(rootRef.current); + } + }, [hasRendered, api.blockingError, rootRef]); + return rootRef; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx b/x-pack/plugins/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx new file mode 100644 index 000000000000..5bc55d43c321 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/renderer/lens_custom_renderer_component.tsx @@ -0,0 +1,158 @@ +/* + * 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 { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public'; +import { useSearchApi } from '@kbn/presentation-publishing'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import type { PresentationPanelProps } from '@kbn/presentation-panel-plugin/public'; +import type { LensApi, LensRendererProps, LensRuntimeState, LensSerializedState } from '../types'; +import { LENS_EMBEDDABLE_TYPE } from '../../../common/constants'; +import { createEmptyLensState } from '../helper'; + +// This little utility uses the same pattern of the useSearchApi hook: +// create the Subject once and then update its value on change +function useObservableVariable<T extends unknown>(value: T) { + // eslint-disable-next-line react-hooks/exhaustive-deps + const observable = useMemo(() => new BehaviorSubject<T>(value), []); + + // update the observable on change + useEffect(() => { + observable.next(value); + }, [observable, value]); + + return observable; +} + +type PanelProps = Pick< + PresentationPanelProps<LensApi>, + | 'showShadow' + | 'showBorder' + | 'showBadges' + | 'showNotifications' + | 'hideLoader' + | 'hideHeader' + | 'hideInspector' + | 'getActions' +>; + +/** + * The aim of this component is to provide a wrapper for other plugins who want to + * use a Lens component into their own page. This hides the embeddable parts of it + * by wrapping it into a ReactEmbeddableRenderer component and exposing a custom API + */ +export function LensRenderer({ + title, + withDefaultActions, + extraActions, + showInspector, + syncColors, + syncCursor, + syncTooltips, + viewMode, + id, + query, + filters, + timeRange, + disabledActions, + ...props +}: LensRendererProps) { + // Use the settings interface to store panel settings + const settings = useMemo(() => { + return { + syncColors$: new BehaviorSubject(false), + syncCursor$: new BehaviorSubject(false), + syncTooltips$: new BehaviorSubject(false), + }; + }, []); + const disabledActionIds$ = useObservableVariable(disabledActions); + const viewMode$ = useObservableVariable(viewMode); + + // Lens API will be set once, but when set trigger a reflow to adopt the latest attributes + const [lensApi, setLensApi] = useState<LensApi | undefined>(undefined); + const initialStateRef = useRef<LensSerializedState>( + props.attributes ? { attributes: props.attributes } : createEmptyLensState(null, title) + ); + + const searchApi = useSearchApi({ query, filters, timeRange }); + + const showPanelChrome = Boolean(withDefaultActions) || (extraActions?.length || 0) > 0; + + // Re-render on changes + // internally the embeddable will evaluate whether it is worth to actual render or not + useEffect(() => { + // trigger a re-render if the attributes change + if (lensApi) { + lensApi.updateAttributes({ + ...('attributes' in initialStateRef.current + ? initialStateRef.current.attributes + : initialStateRef.current), + ...props.attributes, + }); + lensApi.updateOverrides(props.overrides); + } + }, [lensApi, props.attributes, props.overrides]); + + useEffect(() => { + if (syncColors != null && settings.syncColors$.getValue() !== syncColors) { + settings.syncColors$.next(syncColors); + } + if (syncCursor != null && settings.syncCursor$.getValue() !== syncCursor) { + settings.syncCursor$.next(syncCursor); + } + if (syncTooltips != null && settings.syncTooltips$.getValue() !== syncTooltips) { + settings.syncTooltips$.next(syncTooltips); + } + }, [settings, syncColors, syncCursor, syncTooltips]); + + const panelProps: PanelProps = useMemo(() => { + return { + hideInspector: !showInspector, + hideHeader: showPanelChrome, + showNotifications: false, + showShadow: false, + showBadges: false, + getActions: async (triggerId, context) => { + const actions = withDefaultActions + ? await lensApi?.getTriggerCompatibleActions(triggerId, context) + : []; + + return (extraActions ?? []).concat(actions || []); + }, + }; + }, [showInspector, showPanelChrome, withDefaultActions, extraActions, lensApi]); + + return ( + <ReactEmbeddableRenderer<LensSerializedState, LensRuntimeState, LensApi> + type={LENS_EMBEDDABLE_TYPE} + maybeId={id} + getParentApi={() => ({ + // forward the Lens components to the embeddable + ...props, + // forward the unified search context + ...searchApi, + disabledActionIds: disabledActionIds$, + setDisabledActionIds: (ids: string[] | undefined) => disabledActionIds$.next(ids), + viewMode: viewMode$, + // pass the sync* settings with the unified settings interface + settings, + // make sure to provide the initial state (useful for the comparison check) + getSerializedStateForChild: () => ({ rawState: initialStateRef.current, references: [] }), + // update the runtime state on changes + getRuntimeStateForChild: () => ({ + ...initialStateRef.current, + attributes: props.attributes, + }), + })} + onApiAvailable={setLensApi} + hidePanelChrome={!showPanelChrome} + panelProps={panelProps} + /> + ); +} + +export type EmbeddableComponent = React.ComponentType<LensRendererProps>; diff --git a/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.test.tsx b/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.test.tsx new file mode 100644 index 000000000000..04c3511ab3d4 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.test.tsx @@ -0,0 +1,42 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import { getLensApiMock, getLensInternalApiMock } from '../mocks'; +import { LensApi, LensInternalApi } from '../types'; +import { BehaviorSubject } from 'rxjs'; +import { PublishingSubject } from '@kbn/presentation-publishing'; +import React from 'react'; +import { LensEmbeddableComponent } from './lens_embeddable_component'; + +type GetValueType<Type> = Type extends PublishingSubject<infer X> ? X : never; + +function getDefaultProps({ + internalApiOverrides = undefined, + apiOverrides = undefined, +}: { internalApiOverrides?: Partial<LensInternalApi>; apiOverrides?: Partial<LensApi> } = {}) { + return { + internalApi: getLensInternalApiMock(internalApiOverrides), + api: getLensApiMock(apiOverrides), + onUnmount: jest.fn(), + }; +} + +describe('Lens Embeddable component', () => { + it('should not render the visualization if any error arises', () => { + const props = getDefaultProps({ + internalApiOverrides: { + expressionParams$: new BehaviorSubject<GetValueType<LensInternalApi['expressionParams$']>>( + null + ), + }, + }); + + render(<LensEmbeddableComponent {...props} />); + expect(screen.queryByTestId('lens-embeddable')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.tsx b/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.tsx new file mode 100644 index 000000000000..6d98b901d905 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/renderer/lens_embeddable_component.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; +import React, { useEffect } from 'react'; +import { LensApi } from '../..'; +import { ExpressionWrapper } from '../expression_wrapper'; +import { LensInternalApi } from '../types'; +import { UserMessages } from '../user_messages/container'; +import { useMessages, useDispatcher } from './hooks'; +import { getViewMode } from '../helper'; +import { addLog } from '../logger'; + +export function LensEmbeddableComponent({ + internalApi, + api, + onUnmount, +}: { + internalApi: LensInternalApi; + api: LensApi; + onUnmount: () => void; +}) { + const [ + // Pick up updated params from the observable + expressionParams, + // used for functional tests + renderCount, + // has the render completed? + hasRendered, + // these are blocking errors that can be shown in a badge + // without replacing the entire panel + blockingErrors, + // has view mode changed? + latestViewMode, + ] = useBatchedPublishingSubjects( + internalApi.expressionParams$, + internalApi.renderCount$, + internalApi.hasRenderCompleted$, + internalApi.validationMessages$, + api.viewMode + ); + const canEdit = Boolean(api.isEditingEnabled?.() && getViewMode(latestViewMode) === 'edit'); + + const [warningOrErrors, infoMessages] = useMessages(internalApi); + + // On unmount call all the cleanups + useEffect(() => { + addLog(`Mounting Lens Embeddable component: ${api.defaultPanelTitle?.getValue()}`); + return onUnmount; + }, [api, onUnmount]); + + // take care of dispatching the event from the DOM node + const rootRef = useDispatcher(hasRendered, api); + + // Publish the data attributes only if avaialble/visible + const title = api.hidePanelTitle?.getValue() + ? undefined + : { 'data-title': api.panelTitle?.getValue() ?? api.defaultPanelTitle?.getValue() }; + const description = api.panelDescription?.getValue() + ? { + 'data-description': + api.panelDescription?.getValue() ?? api.defaultPanelDescription?.getValue(), + } + : undefined; + + return ( + <div + style={{ width: '100%', height: '100%' }} + data-rendering-count={renderCount + 1} + data-render-complete={hasRendered} + {...title} + {...description} + data-shared-item + ref={rootRef} + > + {expressionParams == null || blockingErrors.length ? null : ( + <ExpressionWrapper {...expressionParams} /> + )} + <UserMessages + blockingErrors={blockingErrors} + warningOrErrors={warningOrErrors} + infoMessages={infoMessages} + canEdit={canEdit} + /> + </div> + ); +} diff --git a/x-pack/plugins/lens/public/react_embeddable/type_guards.ts b/x-pack/plugins/lens/public/react_embeddable/type_guards.ts new file mode 100644 index 000000000000..95e8311a7a3c --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/type_guards.ts @@ -0,0 +1,74 @@ +/* + * 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 { + apiIsOfType, + apiPublishesPanelTitle, + apiPublishesUnifiedSearch, +} from '@kbn/presentation-publishing'; +import { isObject } from 'lodash'; +import { + LensApiCallbacks, + LensApi, + LensComponentForwardedProps, + LensPublicCallbacks, +} from './types'; + +function apiHasLensCallbacks(api: unknown): api is LensApiCallbacks { + const fns = [ + 'getSavedVis', + 'getViewUnderlyingDataArgs', + 'isTextBasedLanguage', + 'getTextBasedLanguage', + ] as Array<keyof LensApiCallbacks>; + return fns.every((fn) => typeof (api as LensApiCallbacks)[fn] === 'function'); +} + +export const isLensApi = (api: unknown): api is LensApi => { + return Boolean( + api && + apiIsOfType(api, 'lens') && + 'canViewUnderlyingData$' in api && + apiHasLensCallbacks(api) && + apiPublishesPanelTitle(api) && + apiPublishesUnifiedSearch(api) + ); +}; + +export function apiHasLensComponentCallbacks(api: unknown): api is LensPublicCallbacks { + return ( + isObject(api) && + ['onFilter', 'onBrushEnd', 'onLoad', 'onTableRowClick', 'onBeforeBadgesRender'].some((fn) => + Object.hasOwn(api, fn) + ) + ); +} + +export function apiHasLensComponentProps(api: unknown): api is LensComponentForwardedProps { + return ( + isObject(api) && + ['style', 'className', 'noPadding', 'viewMode', 'abortController'].some((prop) => + Object.hasOwn(api, prop) + ) + ); +} + +export function apiHasAbortController(api: unknown): api is { abortController: AbortController } { + return isObject(api) && Object.hasOwn(api, 'abortController'); +} + +export function apiHasLastReloadRequestTime( + api: unknown +): api is { lastReloadRequestTime: number } { + return isObject(api) && Object.hasOwn(api, 'lastReloadRequestTime'); +} + +export function apiPublishesInlineEditingCapabilities( + api: unknown +): api is { canEditInline: boolean } { + return isObject(api) && Object.hasOwn(api, 'canEditInline'); +} diff --git a/x-pack/plugins/lens/public/react_embeddable/types.ts b/x-pack/plugins/lens/public/react_embeddable/types.ts new file mode 100644 index 000000000000..03a9801507d1 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/types.ts @@ -0,0 +1,494 @@ +/* + * 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 { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; +import type { + AggregateQuery, + ExecutionContextSearch, + Filter, + Query, + TimeRange, +} from '@kbn/es-query'; +import type { Adapters, InspectorOptions } from '@kbn/inspector-plugin/public'; +import type { + HasEditCapabilities, + HasInPlaceLibraryTransforms, + HasLibraryTransforms, + HasSupportedTriggers, + PublishesBlockingError, + PublishesDataLoading, + PublishesDataViews, + PublishesDisabledActionIds, + PublishesSavedObjectId, + PublishesUnifiedSearch, + PublishesViewMode, + PublishesWritablePanelDescription, + PublishesWritablePanelTitle, + PublishingSubject, + SerializedTitles, + ViewMode, +} from '@kbn/presentation-publishing'; +import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public/plugin'; +import type { + BrushTriggerEvent, + ClickTriggerEvent, + MultiClickTriggerEvent, +} from '@kbn/charts-plugin/public'; +import type { PaletteOutput } from '@kbn/coloring'; +import type { DefaultInspectorAdapters, RenderMode } from '@kbn/expressions-plugin/common'; +import type { + Capabilities, + CoreStart, + HttpSetup, + IUiSettingsClient, + KibanaExecutionContext, + OverlayRef, + SavedObjectReference, + ThemeServiceStart, +} from '@kbn/core/public'; +import type { TimefilterContract, FilterManager } from '@kbn/data-plugin/public'; +import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; +import type { + ExpressionRendererEvent, + ReactExpressionRendererProps, + ReactExpressionRendererType, +} from '@kbn/expressions-plugin/public'; +import type { RecursiveReadonly } from '@kbn/utility-types'; +import type { AllowedChartOverrides, AllowedSettingsOverrides } from '@kbn/charts-plugin/common'; +import type { AllowedGaugeOverrides } from '@kbn/expression-gauge-plugin/common'; +import type { AllowedPartitionOverrides } from '@kbn/expression-partition-vis-plugin/common'; +import type { AllowedXYOverrides } from '@kbn/expression-xy-plugin/common'; +import type { Action } from '@kbn/ui-actions-plugin/public'; +import type { LegacyMetricState } from '../../common'; +import type { LensDocument } from '../persistence'; +import type { LensInspector } from '../lens_inspector_service'; +import type { LensAttributesService } from '../lens_attribute_service'; +import type { + DatatableVisualizationState, + DocumentToExpressionReturnType, + HeatmapVisualizationState, + XYState, +} from '../async_services'; +import type { + DatasourceMap, + IndexPatternMap, + IndexPatternRef, + LensTableRowContextMenuEvent, + SharingSavedObjectProps, + Simplify, + UserMessage, + VisualizationMap, +} from '../types'; +import type { LensPluginStartDependencies } from '../plugin'; +import type { TableInspectorAdapter } from '../editor_frame_service/types'; +import type { PieVisualizationState } from '../../common/types'; +import type { FormBasedPersistedState } from '..'; +import type { TextBasedPersistedState } from '../datasources/text_based/types'; +import type { GaugeVisualizationState } from '../visualizations/gauge/constants'; +import type { MetricVisualizationState } from '../visualizations/metric/types'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface LensApiProps {} + +export type LensSavedObjectAttributes = Omit<LensDocument, 'savedObjectId' | 'type'>; + +export interface VisualizationContext { + doc: LensDocument | undefined; + mergedSearchContext: ExecutionContextSearch; + indexPatterns: IndexPatternMap; + indexPatternRefs: IndexPatternRef[]; + activeVisualizationState: unknown; + activeDatasourceState: unknown; + activeData?: TableInspectorAdapter; +} + +export interface VisualizationContextHelper { + getVisualizationContext: () => VisualizationContext; + updateVisualizationContext: (newContext: Partial<VisualizationContext>) => void; +} + +export interface ViewUnderlyingDataArgs { + dataViewSpec: DataViewSpec; + timeRange: TimeRange; + filters: Filter[]; + query: Query | AggregateQuery | undefined; + columns: string[]; +} + +export type LensEmbeddableStartServices = Simplify< + LensPluginStartDependencies & { + timefilter: TimefilterContract; + coreHttp: HttpSetup; + coreStart: CoreStart; + capabilities: RecursiveReadonly<Capabilities>; + expressionRenderer: ReactExpressionRendererType; + documentToExpression: (doc: LensDocument) => Promise<DocumentToExpressionReturnType>; + injectFilterReferences: FilterManager['inject']; + visualizationMap: VisualizationMap; + datasourceMap: DatasourceMap; + theme: ThemeServiceStart; + uiSettings: IUiSettingsClient; + attributeService: LensAttributesService; + } +>; + +export interface PreventableEvent { + preventDefault(): void; +} + +interface LensByValue { + // by-value + attributes?: Simplify<LensSavedObjectAttributes>; +} + +export interface LensOverrides { + /** + * Overrides can tweak the style of the final embeddable and are executed at the end of the Lens rendering pipeline. + * Each visualization type offers various type of overrides, per component (i.e. 'setting', 'axisX', 'partition', etc...) + * + * While it is not possible to pass function/callback/handlers to the renderer, it is possible to overwrite + * the current behaviour by passing the "ignore" string to the override prop (i.e. onBrushEnd: "ignore" to stop brushing) + */ + overrides?: + | AllowedChartOverrides + | AllowedSettingsOverrides + | AllowedXYOverrides + | AllowedPartitionOverrides + | AllowedGaugeOverrides; +} + +/** + * Lens embeddable props broken down by type + */ + +export interface LensByReference { + // by-reference + savedObjectId?: string; +} + +interface ContentManagementProps { + sharingSavedObjectProps?: SharingSavedObjectProps; + managed?: boolean; +} + +export type LensPropsVariants = (LensByValue & LensByReference) & { + references?: SavedObjectReference[]; +}; + +export interface ViewInDiscoverCallbacks extends LensApiProps { + canViewUnderlyingData$: PublishingSubject<boolean>; + loadViewUnderlyingData: () => void; + getViewUnderlyingDataArgs: () => ViewUnderlyingDataArgs | undefined; +} + +export interface IntegrationCallbacks extends LensApiProps { + isTextBasedLanguage: () => boolean | undefined; + getTextBasedLanguage: () => string | undefined; + getSavedVis: () => Readonly<LensSavedObjectAttributes | undefined>; + getFullAttributes: () => LensDocument | undefined; + updateAttributes: (newAttributes: LensRuntimeState['attributes']) => void; + updateSavedObjectId: (newSavedObjectId: LensRuntimeState['savedObjectId']) => void; + updateOverrides: (newOverrides: LensOverrides['overrides']) => void; + getTriggerCompatibleActions: (triggerId: string, context: object) => Promise<Action[]>; +} + +/** + * Public Callbacks are function who are exposed thru the Lens custom renderer component, + * so not directly exposed in the Lens API, rather passed down as parentApi to the Lens Embeddable + */ +export interface LensPublicCallbacks extends LensApiProps { + onBrushEnd?: (data: Simplify<BrushTriggerEvent['data'] & PreventableEvent>) => void; + onLoad?: ( + isLoading: boolean, + adapters?: Partial<DefaultInspectorAdapters>, + dataLoading$?: PublishingSubject<boolean | undefined> + ) => void; + onFilter?: ( + data: Simplify<(ClickTriggerEvent['data'] | MultiClickTriggerEvent['data']) & PreventableEvent> + ) => void; + onTableRowClick?: ( + data: Simplify<LensTableRowContextMenuEvent['data'] & PreventableEvent> + ) => void; + /** + * Let the consumer overwrite embeddable user messages + */ + onBeforeBadgesRender?: (userMessages: UserMessage[]) => UserMessage[]; +} + +/** + * API callbacks are function who are used by direct Embeddable consumers (i.e. Dashboard or our own Lens custom renderer) + */ +export type LensApiCallbacks = Simplify<ViewInDiscoverCallbacks & IntegrationCallbacks>; + +export interface LensUnifiedSearchContext { + filters?: Filter[]; + query?: Query | AggregateQuery; + timeRange?: TimeRange; + timeslice?: [number, number]; + searchSessionId?: string; + lastReloadRequestTime?: number; +} + +export interface LensPanelProps { + id?: string; + renderMode?: ViewMode; + disableTriggers?: boolean; + syncColors?: boolean; + syncTooltips?: boolean; + syncCursor?: boolean; + palette?: PaletteOutput; +} + +/** + * This set of props are exposes by the Lens component too + */ +export interface LensSharedProps { + executionContext?: KibanaExecutionContext; + style?: React.CSSProperties; + className?: string; + noPadding?: boolean; + viewMode?: ViewMode; +} + +interface LensRequestHandlersProps { + /** + * Custom abort controller to be used for the ES client + */ + abortController?: AbortController; +} + +/** + * Compose together all the props and make them inspectable via Simplify + * + * The LensSerializedState is the state stored for a dashboard panel + * that contains: + * * Lens document state + * * Panel settings + * * other props from the embeddable + */ +export type LensSerializedState = Simplify< + LensPropsVariants & + LensOverrides & + LensUnifiedSearchContext & + LensPanelProps & + SerializedTitles & + LensSharedProps & + Partial<DynamicActionsSerializedState> & { isNewPanel?: boolean } +>; + +/** + * Custom props exposed on the Lens exported component + */ +export type LensComponentProps = Simplify< + LensRequestHandlersProps & + LensSharedProps & { + /** + * When enabled the Lens component will render as a dashboard panel + */ + withDefaultActions?: boolean; + /** + * Allow custom actions to be rendered in the panel + */ + extraActions?: Action[]; + /** + * Disable specific actions for the embeddable + */ + disabledActions?: string[]; + /** + * Toggles the inspector + */ + showInspector?: boolean; + /** + * Toggle inline editing feature + */ + canEditInline?: boolean; + } +>; + +/** + * This is the subset of props that from the LensComponent will be forwarded to the Lens embeddable + */ +export type LensComponentForwardedProps = Pick< + LensComponentProps, + 'style' | 'className' | 'noPadding' | 'abortController' | 'executionContext' | 'viewMode' +>; + +/** + * Carefully chosen props to expose on the Lens renderer component used by + * other plugins + */ + +type ComponentProps = LensComponentProps & LensPublicCallbacks; +type ComponentSerializedProps = TypedLensSerializedState; + +type LensRendererPrivateProps = ComponentSerializedProps & ComponentProps; +export type LensRendererProps = Simplify<LensRendererPrivateProps>; + +/** + * The LensRuntimeState is the state stored for a dashboard panel + * that contains: + * * Lens document state + * * Panel settings + * * other props from the embeddable + */ +export type LensRuntimeState = Simplify< + Omit<ComponentSerializedProps, 'attributes' | 'references'> & { + attributes: NonNullable<LensSerializedState['attributes']>; + } & Pick<LensComponentForwardedProps, 'viewMode' | 'abortController' | 'executionContext'> & + ContentManagementProps +>; + +export interface LensInspectorAdapters { + getInspectorAdapters: () => Adapters; + inspect: (options?: InspectorOptions) => OverlayRef; + closeInspector: () => Promise<void>; + // expose a handler for the inspector adapters + // to be able to subscribe to changes + // a typical use case is the inline editing, where the editor + // needs to be updated on data changes + adapters$: PublishingSubject<Adapters>; +} + +export type LensApi = Simplify< + DefaultEmbeddableApi<LensSerializedState, LensRuntimeState> & + // This is used by actions to operate the edit action + HasEditCapabilities & + // for blocking errors leverage the embeddable panel UI + PublishesBlockingError & + // This is used by dashboard/container to show filters/queries on the panel + PublishesUnifiedSearch & + // Let the container know the loading state + PublishesDataLoading & + // Let the container know the used data views + PublishesDataViews & + // Let the container operate on panel title/description + PublishesWritablePanelTitle & + PublishesWritablePanelDescription & + // This embeddable can narrow down specific triggers usage + HasSupportedTriggers & + PublishesDisabledActionIds & + // Offers methods to operate from/on the linked saved object + HasInPlaceLibraryTransforms & + HasLibraryTransforms<LensRuntimeState> & + // Let the container know the view mode + PublishesViewMode & + // Let the container know the saved object id + PublishesSavedObjectId & + // Lens specific API methods: + // Let the container know when the data has been loaded/updated + LensInspectorAdapters & + LensRequestHandlersProps & + LensApiCallbacks +>; + +// This is an API only used internally to the embeddable but not exported elsewhere +// there's some overlapping between this and the LensApi but they are shared references +export type LensInternalApi = Simplify< + Pick<IntegrationCallbacks, 'updateAttributes' | 'updateOverrides'> & + PublishesDataViews & { + attributes$: PublishingSubject<LensRuntimeState['attributes']>; + overrides$: PublishingSubject<LensOverrides['overrides']>; + disableTriggers$: PublishingSubject<LensPanelProps['disableTriggers']>; + dataLoading$: PublishingSubject<boolean | undefined>; + hasRenderCompleted$: PublishingSubject<boolean>; + isNewlyCreated$: PublishingSubject<boolean>; + setAsCreated: () => void; + dispatchRenderStart: () => void; + dispatchRenderComplete: () => void; + dispatchError: () => void; + updateDataLoading: (newDataLoading: boolean | undefined) => void; + expressionParams$: PublishingSubject<ExpressionWrapperProps | null>; + updateExpressionParams: (newParams: ExpressionWrapperProps | null) => void; + expressionAbortController$: PublishingSubject<AbortController | undefined>; + updateAbortController: (newAbortController: AbortController | undefined) => void; + renderCount$: PublishingSubject<number>; + updateDataViews: (dataViews: DataView[] | undefined) => void; + messages$: PublishingSubject<UserMessage[]>; + updateMessages: (newMessages: UserMessage[]) => void; + validationMessages$: PublishingSubject<UserMessage[]>; + updateValidationMessages: (newMessages: UserMessage[]) => void; + resetAllMessages: () => void; + } +>; + +export interface ExpressionWrapperProps { + ExpressionRenderer: ReactExpressionRendererType; + expression: string | null; + variables?: Record<string, unknown>; + interactive?: boolean; + searchContext: ExecutionContextSearch; + searchSessionId?: string; + handleEvent: (event: ExpressionRendererEvent) => void; + onData$: ( + data: unknown, + inspectorAdapters?: Partial<DefaultInspectorAdapters> | undefined + ) => void; + onRender$: (count: number) => void; + renderMode?: RenderMode; + syncColors?: boolean; + syncTooltips?: boolean; + syncCursor?: boolean; + hasCompatibleActions?: ReactExpressionRendererProps['hasCompatibleActions']; + getCompatibleCellValueActions?: ReactExpressionRendererProps['getCompatibleCellValueActions']; + style?: React.CSSProperties; + className?: string; + addUserMessages: (messages: UserMessage[]) => void; + onRuntimeError: (error: Error) => void; + executionContext?: KibanaExecutionContext; + lensInspector: LensInspector; + noPadding?: boolean; + abortController?: AbortController; +} + +export type GetStateType = () => LensRuntimeState; + +/** + * Custom Lens component exported by the plugin + * For better DX of Lens component consumers, expose a typed version of the serialized state + */ + +/** Utility function to build typed version for each chart */ +type TypedLensAttributes<TVisType, TVisState> = Simplify< + Omit<LensDocument, 'savedObjectId' | 'type' | 'state' | 'visualizationType'> & { + visualizationType: TVisType; + state: Simplify< + Omit<LensDocument['state'], 'datasourceStates' | 'visualization'> & { + datasourceStates: { + formBased?: FormBasedPersistedState; + textBased?: TextBasedPersistedState; + }; + visualization: TVisState; + } + >; + } +>; + +/** + * Type-safe variant of by value embeddable input for Lens. + * This can be used to hardcode certain Lens chart configurations within another app. + */ +export type TypedLensSerializedState = Simplify< + Omit<LensSerializedState, 'attributes'> & { + attributes: + | TypedLensAttributes<'lnsXY', XYState> + | TypedLensAttributes<'lnsPie', PieVisualizationState> + | TypedLensAttributes<'lnsHeatmap', HeatmapVisualizationState> + | TypedLensAttributes<'lnsGauge', GaugeVisualizationState> + | TypedLensAttributes<'lnsDatatable', DatatableVisualizationState> + | TypedLensAttributes<'lnsLegacyMetric', LegacyMetricState> + | TypedLensAttributes<'lnsMetric', MetricVisualizationState> + | TypedLensAttributes<string, unknown>; + } +>; + +/** + * Backward compatibility types + */ +export type LensByValueInput = Omit<LensRendererPrivateProps, 'savedObjectId'>; +export type LensByReferenceInput = Omit<LensRendererPrivateProps, 'attributes'>; +export type TypedLensByValueInput = Omit<LensRendererProps, 'savedObjectId'>; +export type LensEmbeddableInput = LensByValueInput | LensByReferenceInput; +export type LensEmbeddableOutput = LensApi; diff --git a/x-pack/plugins/lens/public/react_embeddable/user_messages/api.ts b/x-pack/plugins/lens/public/react_embeddable/user_messages/api.ts new file mode 100644 index 000000000000..90061cfb7c2f --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/api.ts @@ -0,0 +1,288 @@ +/* + * 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 { SpacesApi } from '@kbn/spaces-plugin/public'; +import { Adapters } from '@kbn/inspector-plugin/common'; +import { BehaviorSubject } from 'rxjs'; +import { + filterAndSortUserMessages, + getApplicationUserMessages, + handleMessageOverwriteFromConsumer, +} from '../../app_plugin/get_application_user_messages'; +import { getDatasourceLayers } from '../../state_management/utils'; +import { + UserMessagesGetter, + UserMessage, + FramePublicAPI, + SharingSavedObjectProps, +} from '../../types'; +import { + getActiveDatasourceIdFromDoc, + getActiveVisualizationIdFromDoc, + getInitialDataViewsObject, +} from '../../utils'; +import { + LensPublicCallbacks, + LensEmbeddableStartServices, + VisualizationContext, + VisualizationContextHelper, + LensApi, + LensInternalApi, +} from '../types'; +import { getLegacyURLConflictsMessage, hasLegacyURLConflict } from './checks'; +import { getSearchWarningMessages } from '../../utils'; +import { addLog } from '../logger'; + +function getUpdatedState( + getVisualizationContext: VisualizationContextHelper['getVisualizationContext'], + visualizationMap: LensEmbeddableStartServices['visualizationMap'], + datasourceMap: LensEmbeddableStartServices['datasourceMap'] +) { + const { + doc, + mergedSearchContext, + indexPatterns, + indexPatternRefs, + activeVisualizationState, + activeDatasourceState, + activeData, + } = getVisualizationContext(); + const activeVisualizationId = getActiveVisualizationIdFromDoc(doc); + const activeDatasourceId = getActiveDatasourceIdFromDoc(doc); + const activeDatasource = activeDatasourceId ? datasourceMap[activeDatasourceId] : null; + const activeVisualization = activeVisualizationId + ? visualizationMap[activeVisualizationId] + : undefined; + const dataViewObject = getInitialDataViewsObject(indexPatterns, indexPatternRefs); + return { + doc, + mergedSearchContext, + activeDatasource, + activeVisualization, + activeVisualizationId, + dataViewObject, + activeVisualizationState, + activeDatasourceState, + activeDatasourceId, + activeData, + }; +} + +function getWarningMessages( + { + activeDatasource, + activeDatasourceId, + activeDatasourceState, + }: ReturnType<typeof getUpdatedState>, + adapters: Adapters, + data: LensEmbeddableStartServices['data'] +) { + if (!activeDatasource || !activeDatasourceId || !adapters?.requests) { + return []; + } + + const requestWarnings = getSearchWarningMessages( + adapters.requests, + activeDatasource, + activeDatasourceState, + { + searchService: data.search, + } + ); + + return requestWarnings; +} + +export function buildUserMessagesHelpers( + api: LensApi, + internalApi: LensInternalApi, + getVisualizationContext: () => VisualizationContext, + { coreStart, data, visualizationMap, datasourceMap }: LensEmbeddableStartServices, + onBeforeBadgesRender: LensPublicCallbacks['onBeforeBadgesRender'], + spaces?: SpacesApi, + metaInfo?: SharingSavedObjectProps +): { + getUserMessages: UserMessagesGetter; + addUserMessages: (messages: UserMessage[]) => void; + updateWarnings: () => void; + updateMessages: (messages: UserMessage[]) => void; + resetMessages: () => void; + updateBlockingErrors: (blockingMessages: UserMessage[] | Error) => void; + updateValidationErrors: (messages: UserMessage[]) => void; +} { + let runtimeUserMessages: Record<string, UserMessage> = {}; + const addUserMessages = (messages: UserMessage[]) => { + if (messages.length) { + addLog(`addUserMessages: "${messages.map(({ uniqueId }) => uniqueId).join('", "')}"`); + } + for (const message of messages) { + runtimeUserMessages[message.uniqueId] = message; + } + }; + + const resetMessages = () => { + runtimeUserMessages = {}; + internalApi.resetAllMessages(); + }; + + const getUserMessages: UserMessagesGetter = (locationId, filters) => { + const { + doc, + activeVisualizationState, + activeVisualization, + activeVisualizationId, + activeDatasource, + activeDatasourceState, + activeDatasourceId, + dataViewObject, + mergedSearchContext, + activeData, + } = getUpdatedState(getVisualizationContext, visualizationMap, datasourceMap); + const userMessages: UserMessage[] = []; + + userMessages.push( + ...getApplicationUserMessages({ + visualizationType: doc?.visualizationType, + visualizationState: { + state: activeVisualizationState, + activeId: activeVisualizationId, + }, + visualization: activeVisualization, + activeDatasource, + activeDatasourceState: { + isLoading: !activeDatasourceState, + state: activeDatasourceState, + }, + dataViews: dataViewObject, + core: coreStart, + }) + ); + + if (!doc || !activeDatasourceState || !activeVisualizationState) { + return userMessages; + } + + const framePublicAPI: FramePublicAPI = { + dataViews: dataViewObject, + datasourceLayers: getDatasourceLayers( + { + [activeDatasourceId!]: { + isLoading: !activeDatasourceState, + state: activeDatasourceState, + }, + }, + datasourceMap, + dataViewObject.indexPatterns + ), + query: doc.state.query, + filters: mergedSearchContext.filters ?? [], + dateRange: { + fromDate: mergedSearchContext.timeRange?.from ?? '', + toDate: mergedSearchContext.timeRange?.to ?? '', + }, + absDateRange: { + fromDate: mergedSearchContext.timeRange?.from ?? '', + toDate: mergedSearchContext.timeRange?.to ?? '', + }, + activeData, + }; + + if (hasLegacyURLConflict(metaInfo, spaces)) { + userMessages.push(getLegacyURLConflictsMessage(metaInfo!, spaces!)); + } + + userMessages.push( + ...(activeDatasource?.getUserMessages(activeDatasourceState, { + setState: () => {}, + frame: framePublicAPI, + visualizationInfo: activeVisualization?.getVisualizationInfo?.( + activeVisualizationState, + framePublicAPI + ), + }) ?? []), + ...(activeVisualization?.getUserMessages?.(activeVisualizationState, { + frame: framePublicAPI, + }) ?? []) + ); + + return handleMessageOverwriteFromConsumer( + filterAndSortUserMessages( + userMessages.concat(Object.values(runtimeUserMessages)), + locationId, + filters ?? {} + ), + onBeforeBadgesRender + ); + }; + + return { + addUserMessages, + resetMessages, + getUserMessages, + /** + * Here pass all the messages that comes directly from the Lens validation/info system + * who includes: + * * configuration errors (i.e. missing fields) + * * warning messages (badge related) + * * info messages (badge related) + */ + updateMessages: (messages: UserMessage[]) => { + // update the messages only if something changed + const existingMessages = new Set( + internalApi.messages$.getValue().map(({ uniqueId }) => uniqueId) + ); + if ( + existingMessages.size !== messages.length || + messages.some(({ uniqueId }) => !existingMessages.has(uniqueId)) + ) { + internalApi.updateMessages(messages); + } + }, + updateValidationErrors: (messages: UserMessage[]) => { + addLog( + `Validation error: ${ + messages.length ? messages.map(({ uniqueId }) => uniqueId).join(', ') : 'No errors' + }` + ); + internalApi.updateValidationMessages(messages); + }, + /** + * This type of errors are those who need to be rendered in the embeddable native error panel + * like runtime errors. + */ + updateBlockingErrors: (blockingMessages: UserMessage[] | Error) => { + const error = + blockingMessages instanceof Error + ? blockingMessages + : blockingMessages.length + ? new Error( + typeof blockingMessages[0].longMessage === 'string' && blockingMessages[0].longMessage + ? blockingMessages[0].longMessage + : blockingMessages[0].shortMessage + ) + : undefined; + + if (error) { + addLog(`Blocking error: ${error?.message}`); + } + + if (error?.message !== api.blockingError.getValue()?.message) { + const finalError = error?.message === '' ? undefined : error; + (api.blockingError as BehaviorSubject<Error | undefined>).next(finalError); + } + }, + updateWarnings: () => { + addUserMessages( + getWarningMessages( + getUpdatedState(getVisualizationContext, visualizationMap, datasourceMap), + api.adapters$.getValue(), + data + ) + ); + }, + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/user_messages/checks.tsx b/x-pack/plugins/lens/public/react_embeddable/user_messages/checks.tsx new file mode 100644 index 000000000000..50250b31fdc7 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/checks.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { SpacesApi } from '@kbn/spaces-plugin/public'; +import React from 'react'; +import { DOC_TYPE } from '../../../common/constants'; +import type { + IndexPatternMap, + IndexPatternRef, + SharingSavedObjectProps, + UserMessage, +} from '../../types'; +import type { LensApi } from '../types'; +import type { MergedSearchContext } from '../expressions/merged_search_context'; +import { MISSING_TIME_RANGE_ON_EMBEDDABLE, URL_CONFLICT } from '../../user_messages_ids'; + +export function hasLegacyURLConflict(metaInfo?: SharingSavedObjectProps, spaces?: SpacesApi) { + return metaInfo?.outcome === 'conflict' && spaces?.ui?.components?.getEmbeddableLegacyUrlConflict; +} + +export function getLegacyURLConflictsMessage( + metaInfo: SharingSavedObjectProps, + spaces: SpacesApi +): UserMessage { + const LegacyURLConfig = spaces.ui.components.getEmbeddableLegacyUrlConflict; + return { + uniqueId: URL_CONFLICT, + severity: 'error', + displayLocations: [{ id: 'visualization' }], + shortMessage: i18n.translate('xpack.lens.legacyURLConflict.shortMessage', { + defaultMessage: `You've encountered a URL conflict`, + }), + longMessage: <LegacyURLConfig targetType={DOC_TYPE} sourceId={metaInfo.sourceId!} />, + fixableInEditor: false, + }; +} + +export function isSearchContextIncompatibleWithDataViews( + api: LensApi, + context: { type?: string; id?: string } | undefined, + searchContext: MergedSearchContext, + indexPatternRefs: IndexPatternRef[], + indexPatterns: IndexPatternMap +) { + return ( + !api.isTextBasedLanguage() && + searchContext.timeRange == null && + indexPatternRefs.some(({ id }) => { + const indexPattern = indexPatterns[id]; + return indexPattern?.timeFieldName && indexPattern.getFieldByName(indexPattern.timeFieldName); + }) + ); +} + +export function getSearchContextIncompatibleMessage(): UserMessage { + return { + uniqueId: MISSING_TIME_RANGE_ON_EMBEDDABLE, + severity: 'error', + fixableInEditor: false, + displayLocations: [{ id: 'visualization' }], + shortMessage: i18n.translate('xpack.lens.missingTimeRangeParam.shortMessage', { + defaultMessage: `Missing timeRange property`, + }), + longMessage: i18n.translate('xpack.lens.missingTimeRangeParam.longMessage', { + defaultMessage: `The timeRange property is required for the given configuration`, + }), + }; +} diff --git a/x-pack/plugins/lens/public/react_embeddable/user_messages/container.tsx b/x-pack/plugins/lens/public/react_embeddable/user_messages/container.tsx new file mode 100644 index 000000000000..451de837e96e --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/container.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { css } from '@emotion/react'; +import React from 'react'; +import type { UserMessage } from '../../types'; +import { VisualizationErrorPanel } from './error_panel'; +import { EmbeddableFeatureBadge } from './info_badges'; +import { MessagesPopover } from './message_popover'; + +export function UserMessages({ + blockingErrors, + warningOrErrors, + infoMessages, + canEdit, +}: { + canEdit: boolean; + blockingErrors: UserMessage[]; + warningOrErrors: UserMessage[]; + infoMessages: UserMessage[]; +}) { + if (!blockingErrors.length && !warningOrErrors.length && !infoMessages.length) { + return null; + } + return ( + <> + <VisualizationErrorPanel errors={blockingErrors} canEdit={canEdit} /> + <div + css={css({ + position: 'absolute', + zIndex: 2, + left: 0, + bottom: 0, + })} + > + <MessagesPopover messages={warningOrErrors} /> + <EmbeddableFeatureBadge messages={infoMessages} /> + </div> + </> + ); +} diff --git a/x-pack/plugins/lens/public/react_embeddable/user_messages/error_panel.tsx b/x-pack/plugins/lens/public/react_embeddable/user_messages/error_panel.tsx new file mode 100644 index 000000000000..ee050382914c --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/error_panel.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiEmptyPrompt } from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { UserMessage } from '../../types'; +import { getLongMessage } from '../../user_messages_utils'; + +export function VisualizationErrorPanel({ + errors, + canEdit, +}: { + errors: UserMessage[]; + canEdit: boolean; +}) { + if (!errors.length) { + return null; + } + const showMore = errors.length > 1; + const canFixInLens = canEdit && errors.some(({ fixableInEditor }) => fixableInEditor); + return ( + <div className="lnsEmbeddedError"> + <EuiEmptyPrompt + iconType="warning" + iconColor="danger" + data-test-subj="embeddable-lens-failure" + body={ + <> + {errors.length ? ( + <> + <p>{getLongMessage(errors[0]) || errors[0].shortMessage}</p> + {showMore && !canFixInLens ? ( + <p> + <FormattedMessage + id="xpack.lens.moreErrors" + defaultMessage="Edit in Lens editor to see more errors" + /> + </p> + ) : null} + {canFixInLens ? ( + <p> + <FormattedMessage + id="xpack.lens.fixErrors" + defaultMessage="Edit in Lens editor to fix the error" + /> + </p> + ) : null} + </> + ) : ( + <p> + <FormattedMessage + id="xpack.lens.failure" + defaultMessage="Visualization couldn't be displayed" + /> + </p> + )} + </> + } + /> + </div> + ); +} diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.scss b/x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.scss similarity index 62% rename from x-pack/plugins/lens/public/embeddable/embeddable_info_badges.scss rename to x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.scss index 55407855b49f..7435808095a1 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.scss +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.scss @@ -1,4 +1,4 @@ -.lnsEmbeddablePanelFeatureList { +.lnsPanelFeatureList { max-height: $euiSize * 20; @include euiYScroll; } diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.test.tsx b/x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.test.tsx similarity index 97% rename from x-pack/plugins/lens/public/embeddable/embeddable_info_badges.test.tsx rename to x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.test.tsx index b70b102a7848..ef3ee40e17d1 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.test.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.test.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; -import { EmbeddableFeatureBadge } from './embeddable_info_badges'; -import { UserMessage } from '../types'; +import { EmbeddableFeatureBadge } from './info_badges'; +import { UserMessage } from '../../types'; describe('EmbeddableFeatureBadge', () => { async function renderPopup(messages: UserMessage[], count: number = messages.length) { diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.tsx b/x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.tsx similarity index 89% rename from x-pack/plugins/lens/public/embeddable/embeddable_info_badges.tsx rename to x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.tsx index 18cff3f2ac90..5b120625b662 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_info_badges.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/info_badges.tsx @@ -18,9 +18,9 @@ import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; import { useState } from 'react'; -import type { UserMessage } from '../types'; -import './embeddable_info_badges.scss'; -import { getLongMessage } from '../user_messages_utils'; +import type { UserMessage } from '../../types'; +import './info_badges.scss'; +import { getLongMessage } from '../../user_messages_utils'; export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] }) => { const { euiTheme } = useEuiTheme(); @@ -31,7 +31,7 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] } if (!messages.length) { return null; } - const iconTitle = i18n.translate('xpack.lens.embeddable.featureBadge.iconDescription', { + const iconTitle = i18n.translate('xpack.lens.featureBadge.iconDescription', { defaultMessage: `{count} visualization {count, plural, one {modifier} other {modifiers}}`, values: { count: messages.length, @@ -51,7 +51,7 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] } <EuiToolTip content={iconTitle}> <EuiButtonEmpty data-test-subj="lns-feature-badges-trigger" - className="lnsEmbeddablePanelFeatureList_button" + className="lnsPanelFeatureList_button" color={'text'} onClick={onButtonClick} title={iconTitle} @@ -64,6 +64,9 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] } .euiButtonEmpty__content { gap: ${euiTheme.size.xs}; } + &:hover { + color: ${euiTheme.colors.text}; + } `} iconType="wrench" > @@ -98,7 +101,7 @@ export const EmbeddableFeatureBadge = ({ messages }: { messages: UserMessage[] } <EuiTitle size="xxs" css={css`color=${euiTheme.colors.title}`}> <h3>{shortMessage}</h3> </EuiTitle> - <ul className="lnsEmbeddablePanelFeatureList"> + <ul className="lnsPanelFeatureList"> {messageGroup.map((message, i) => ( <Fragment key={`${uniqueId}-${i}`}>{getLongMessage(message)}</Fragment> ))} diff --git a/x-pack/plugins/lens/public/react_embeddable/user_messages/message_popover.tsx b/x-pack/plugins/lens/public/react_embeddable/user_messages/message_popover.tsx new file mode 100644 index 000000000000..a6359bd683d1 --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/message_popover.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEuiTheme, useEuiFontSize } from '@elastic/eui'; +import { css } from '@emotion/react'; + +import React from 'react'; +import { MessageList } from '../../editor_frame_service/editor_frame/workspace_panel/message_list'; +import { UserMessage } from '../../types'; + +export const MessagesPopover = ({ messages }: { messages: UserMessage[] }) => { + const { euiTheme } = useEuiTheme(); + const xsFontSize = useEuiFontSize('xs').fontSize; + + if (!messages.length) { + return null; + } + + return ( + <MessageList + messages={messages} + customButtonStyles={css` + block-size: ${euiTheme.size.l}; + font-size: ${xsFontSize}; + padding: 0 ${euiTheme.size.xs}; + & > * { + gap: ${euiTheme.size.xs}; + } + `} + /> + ); +}; diff --git a/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap b/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap index a1ae0da67680..8af3d61bf668 100644 --- a/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap +++ b/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap @@ -28,7 +28,6 @@ Object { "persistedDoc": Object { "exactMatchDoc": Object { "attributes": Object { - "expression": "definitely a valid expression", "references": Array [ Object { "id": "1", @@ -39,7 +38,10 @@ Object { "savedObjectId": "1234", "state": Object { "datasourceStates": Object { - "testDatasource": "datasource", + "testDatasource": Object { + "isLoading": false, + "state": Object {}, + }, }, "filters": Array [ Object { @@ -53,7 +55,10 @@ Object { }, }, ], - "query": "kuery", + "query": Object { + "language": "kuery", + "query": "test", + }, "visualization": Object {}, }, "title": "An extremely cool default document!", diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/index.ts b/x-pack/plugins/lens/public/state_management/init_middleware/index.ts index 0858d9d8af78..b0011c3c822e 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/index.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/index.ts @@ -12,6 +12,7 @@ import { loadInitial as loadInitialAction } from '..'; import { loadInitial } from './load_initial'; import { readFromStorage } from '../../settings_storage'; import { AUTO_APPLY_DISABLED_STORAGE_KEY } from '../../editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper'; +import { type InitialAppState } from '../lens_slice'; const autoApplyDisabled = () => { return readFromStorage(new Storage(localStorage), AUTO_APPLY_DISABLED_STORAGE_KEY) === 'true'; @@ -20,7 +21,7 @@ const autoApplyDisabled = () => { export const initMiddleware = (storeDeps: LensStoreDeps) => (store: MiddlewareAPI) => { return (next: Dispatch) => (action: PayloadAction) => { if (loadInitialAction.match(action)) { - return loadInitial(store, storeDeps, action.payload, autoApplyDisabled()); + return loadInitial(store, storeDeps, action.payload as InitialAppState, autoApplyDisabled()); } next(action); }; diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index 606ede8cd268..458285096f7e 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -9,51 +9,55 @@ import { cloneDeep } from 'lodash'; import { MiddlewareAPI } from '@reduxjs/toolkit'; import { i18n } from '@kbn/i18n'; import { History } from 'history'; -import { setState, initExisting, initEmpty, LensStoreDeps } from '..'; -import { disableAutoApply, getPreloadedState } from '../lens_slice'; +import { setState, initExisting, initEmpty, LensStoreDeps, LensAppState } from '..'; +import { type InitialAppState, disableAutoApply, getPreloadedState } from '../lens_slice'; import { SharingSavedObjectProps } from '../../types'; -import { LensEmbeddableInput, LensByReferenceInput } from '../../embeddable/embeddable'; import { getInitialDatasourceId, getInitialDataViewsObject } from '../../utils'; import { initializeSources } from '../../editor_frame_service/editor_frame'; import { LensAppServices } from '../../app_plugin/types'; import { getEditPath, getFullPath, LENS_EMBEDDABLE_TYPE } from '../../../common/constants'; -import { Document } from '../../persistence'; +import { LensDocument } from '../../persistence'; +import { LensSerializedState } from '../../react_embeddable/types'; -export const getPersisted = async ({ +interface PersistedDoc { + doc: LensDocument; + sharingSavedObjectProps: Omit<SharingSavedObjectProps, 'sourceId'>; + managed: boolean; +} + +/** + * This function returns a Saved object from a either a by reference or by value input + */ +export const getFromPreloaded = async ({ initialInput, lensServices, history, }: { - initialInput: LensEmbeddableInput; + initialInput: LensSerializedState; lensServices: Pick<LensAppServices, 'attributeService' | 'notifications' | 'spaces' | 'http'>; history?: History<unknown>; -}): Promise< - | { - doc: Document; - sharingSavedObjectProps: Omit<SharingSavedObjectProps, 'sourceId'>; - managed: boolean; - } - | undefined -> => { +}): Promise<PersistedDoc | undefined> => { const { notifications, spaces, attributeService } = lensServices; - let doc: Document; + let doc: LensDocument; try { - const result = await attributeService.unwrapAttributes(initialInput); - if (!result) { + const docFromSavedObject = await (initialInput.savedObjectId + ? attributeService.loadFromLibrary(initialInput.savedObjectId) + : undefined); + if (!docFromSavedObject) { return { + // @TODO: it would be nice to address this type checks once for all doc: { - ...initialInput, + ...initialInput.attributes, type: LENS_EMBEDDABLE_TYPE, - } as unknown as Document, + } as LensDocument, sharingSavedObjectProps: { outcome: 'exactMatch', }, managed: false, }; } - const { metaInfo, attributes } = result; - const sharingSavedObjectProps = metaInfo?.sharingSavedObjectProps; + const { sharingSavedObjectProps, attributes, managed } = docFromSavedObject; if (spaces && sharingSavedObjectProps?.outcome === 'aliasMatch' && history) { // We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash const newObjectId = sharingSavedObjectProps.aliasTargetId!; // This is always defined if outcome === 'aliasMatch' @@ -80,7 +84,7 @@ export const getPersisted = async ({ aliasTargetId: sharingSavedObjectProps?.aliasTargetId, outcome: sharingSavedObjectProps?.outcome, }, - managed: Boolean(metaInfo?.managed), + managed: Boolean(managed), }; } catch (e) { notifications.toasts.addDanger( @@ -91,30 +95,242 @@ export const getPersisted = async ({ } }; -export function loadInitial( +interface LoaderSharedArgs { + visualizationMap: LensStoreDeps['visualizationMap']; + datasourceMap: LensStoreDeps['datasourceMap']; + initialContext: LensStoreDeps['initialContext']; + dataViews: LensStoreDeps['lensServices']['dataViews']; + storage: LensStoreDeps['lensServices']['storage']; + eventAnnotationService: LensStoreDeps['lensServices']['eventAnnotationService']; + defaultIndexPatternId: string; +} + +type PreloadedState = Omit< + LensAppState, + 'resolvedDateRange' | 'searchSessionId' | 'isLinkedToOriginatingApp' +>; + +async function loadFromLocatorState( + store: MiddlewareAPI, + initialState: NonNullable<LensStoreDeps['initialStateFromLocator']>, + loaderSharedArgs: LoaderSharedArgs, + { notifications, data }: LensStoreDeps['lensServices'], + emptyState: PreloadedState, + autoApplyDisabled: boolean +) { + const { lens } = store.getState(); + const locatorReferences = 'references' in initialState ? initialState.references : undefined; + + const { + datasourceStates, + visualizationState, + indexPatterns, + indexPatternRefs, + annotationGroups, + } = await initializeSources( + { + visualizationState: emptyState.visualization, + datasourceStates: emptyState.datasourceStates, + adHocDataViews: lens.persistedDoc?.state.adHocDataViews || initialState.dataViewSpecs, + references: locatorReferences, + ...loaderSharedArgs, + }, + { + isFullEditor: true, + } + ); + const currentSessionId = initialState?.searchSessionId || data.search.session.getSessionId(); + store.dispatch( + initExisting({ + isSaveable: true, + filters: initialState.filters || data.query.filterManager.getFilters(), + query: initialState.query || emptyState.query, + searchSessionId: currentSessionId, + activeDatasourceId: emptyState.activeDatasourceId, + visualization: { + activeId: emptyState.visualization.activeId, + state: visualizationState, + }, + dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), + datasourceStates: Object.entries(datasourceStates).reduce( + (state, [datasourceId, datasourceState]) => ({ + ...state, + [datasourceId]: { + ...datasourceState, + isLoading: false, + }, + }), + {} + ), + isLoading: false, + annotationGroups, + }) + ); + + if (autoApplyDisabled) { + store.dispatch(disableAutoApply()); + } +} + +async function loadFromEmptyState( + store: MiddlewareAPI, + emptyState: PreloadedState, + loaderSharedArgs: LoaderSharedArgs, + { data }: LensStoreDeps['lensServices'], + activeDatasourceId: string | undefined, + autoApplyDisabled: boolean +) { + const { lens } = store.getState(); + const { datasourceStates, indexPatterns, indexPatternRefs } = await initializeSources( + { + visualizationState: lens.visualization, + datasourceStates: lens.datasourceStates, + adHocDataViews: lens.persistedDoc?.state.adHocDataViews, + ...loaderSharedArgs, + }, + { + isFullEditor: true, + } + ); + + store.dispatch( + initEmpty({ + newState: { + ...emptyState, + dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), + searchSessionId: data.search.session.getSessionId() || data.search.session.start(), + ...(activeDatasourceId && { activeDatasourceId }), + datasourceStates: Object.entries(datasourceStates).reduce( + (state, [datasourceId, datasourceState]) => ({ + ...state, + [datasourceId]: { + ...datasourceState, + isLoading: false, + }, + }), + {} + ), + isLoading: false, + }, + initialContext: loaderSharedArgs.initialContext, + }) + ); + if (autoApplyDisabled) { + store.dispatch(disableAutoApply()); + } +} + +async function loadFromSavedObject( + store: MiddlewareAPI, + savedObjectId: string | undefined, + persisted: PersistedDoc, + loaderSharedArgs: LoaderSharedArgs, + { data, chrome }: LensStoreDeps['lensServices'], + autoApplyDisabled: boolean, + inlineEditing?: boolean +) { + const { doc, sharingSavedObjectProps, managed } = persisted; + if (savedObjectId) { + chrome.recentlyAccessed.add(getFullPath(savedObjectId), doc.title, savedObjectId); + } + + const docDatasourceStates = Object.entries(doc.state.datasourceStates).reduce( + (stateMap, [datasourceId, datasourceState]) => ({ + ...stateMap, + [datasourceId]: { + isLoading: true, + state: datasourceState, + }, + }), + {} + ); + + // when the embeddable is initialized from the dashboard we don't want to inject the filters + // as this will replace the parent application filters (such as a dashboard) + if (!inlineEditing) { + const filters = data.query.filterManager.inject(doc.state.filters, doc.references); + // Don't overwrite any pinned filters + data.query.filterManager.setAppFilters(filters); + } + + const docVisualizationState = { + activeId: doc.visualizationType, + state: doc.state.visualization, + }; + const { + datasourceStates, + visualizationState, + indexPatterns, + indexPatternRefs, + annotationGroups, + } = await initializeSources( + { + visualizationState: docVisualizationState, + datasourceStates: docDatasourceStates, + references: [...doc.references, ...(doc.state.internalReferences || [])], + adHocDataViews: doc.state.adHocDataViews, + ...loaderSharedArgs, + }, + { isFullEditor: true } + ); + const currentSessionId = data.search.session.getSessionId(); + store.dispatch( + initExisting({ + isSaveable: true, + sharingSavedObjectProps, + filters: data.query.filterManager.getFilters(), + query: doc.state.query, + searchSessionId: + !savedObjectId && currentSessionId + ? currentSessionId + : !inlineEditing + ? data.search.session.start() + : undefined, + persistedDoc: doc, + activeDatasourceId: getInitialDatasourceId(loaderSharedArgs.datasourceMap, doc), + visualization: { + activeId: doc.visualizationType, + state: visualizationState, + }, + dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), + datasourceStates: Object.entries(datasourceStates).reduce( + (state, [datasourceId, datasourceState]) => ({ + ...state, + [datasourceId]: { + ...datasourceState, + isLoading: false, + }, + }), + {} + ), + isLoading: false, + annotationGroups, + managed, + }) + ); + + if (autoApplyDisabled) { + store.dispatch(disableAutoApply()); + } +} + +export async function loadInitial( store: MiddlewareAPI, storeDeps: LensStoreDeps, - { - redirectCallback, - initialInput, - history, - inlineEditing, - }: { - redirectCallback?: (savedObjectId?: string) => void; - initialInput?: LensEmbeddableInput; - history?: History<unknown>; - inlineEditing?: boolean; - }, + { redirectCallback, initialInput, history, inlineEditing }: InitialAppState, autoApplyDisabled: boolean ) { const { lensServices, datasourceMap, initialContext, initialStateFromLocator, visualizationMap } = storeDeps; const { resolvedDateRange, searchSessionId, isLinkedToOriginatingApp, ...emptyState } = getPreloadedState(storeDeps); - const { attributeService, notifications, data } = lensServices; + const { notifications, data } = lensServices; const { lens } = store.getState(); - const loaderSharedArgs = { + const loaderSharedArgs: LoaderSharedArgs = { + visualizationMap, + initialContext, + datasourceMap, dataViews: lensServices.dataViews, storage: lensServices.storage, eventAnnotationService: lensServices.eventAnnotationService, @@ -144,79 +360,27 @@ export function loadInitial( // URL Reporting is using the locator params but also passing the savedObjectId // so be sure to not go here as there's no full snapshot URL if (!initialInput) { - const locatorReferences = - 'references' in initialStateFromLocator ? initialStateFromLocator.references : undefined; - - return initializeSources( - { - datasourceMap, - visualizationMap, - visualizationState: emptyState.visualization, - datasourceStates: emptyState.datasourceStates, - initialContext, - adHocDataViews: - lens.persistedDoc?.state.adHocDataViews || initialStateFromLocator.dataViewSpecs, - references: locatorReferences, - ...loaderSharedArgs, - }, - { - isFullEditor: true, - } - ) - .then( - ({ - datasourceStates, - visualizationState, - indexPatterns, - indexPatternRefs, - annotationGroups, - }) => { - const currentSessionId = - initialStateFromLocator?.searchSessionId || data.search.session.getSessionId(); - store.dispatch( - initExisting({ - isSaveable: true, - filters: initialStateFromLocator.filters || data.query.filterManager.getFilters(), - query: initialStateFromLocator.query || emptyState.query, - searchSessionId: currentSessionId, - activeDatasourceId: emptyState.activeDatasourceId, - visualization: { - activeId: emptyState.visualization.activeId, - state: visualizationState, - }, - dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), - datasourceStates: Object.entries(datasourceStates).reduce( - (state, [datasourceId, datasourceState]) => ({ - ...state, - [datasourceId]: { - ...datasourceState, - isLoading: false, - }, - }), - {} - ), - isLoading: false, - annotationGroups, - }) - ); - - if (autoApplyDisabled) { - store.dispatch(disableAutoApply()); - } - } - ) - .catch((e: { message: string }) => { - notifications.toasts.addDanger({ - title: e.message, - }); + try { + return loadFromLocatorState( + store, + initialStateFromLocator, + loaderSharedArgs, + lensServices, + emptyState, + autoApplyDisabled + ); + } catch ({ message }) { + notifications.toasts.addDanger({ + title: message, }); + return; + } } } if ( !initialInput || - (attributeService.inputIsRefType(initialInput) && - initialInput.savedObjectId === lens.persistedDoc?.savedObjectId) + (initialInput.savedObjectId && initialInput.savedObjectId === lens.persistedDoc?.savedObjectId) ) { const newFilters = initialContext && 'searchFilters' in initialContext && initialContext.searchFilters @@ -226,179 +390,57 @@ export function loadInitial( if (newFilters) { data.query.filterManager.setAppFilters(newFilters); } - - return initializeSources( - { - datasourceMap, - visualizationMap, - visualizationState: lens.visualization, - datasourceStates: lens.datasourceStates, - initialContext, - adHocDataViews: lens.persistedDoc?.state.adHocDataViews, - ...loaderSharedArgs, - }, - { - isFullEditor: true, - } - ) - .then(({ datasourceStates, indexPatterns, indexPatternRefs }) => { - store.dispatch( - initEmpty({ - newState: { - ...emptyState, - dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), - searchSessionId: data.search.session.getSessionId() || data.search.session.start(), - ...(activeDatasourceId && { activeDatasourceId }), - datasourceStates: Object.entries(datasourceStates).reduce( - (state, [datasourceId, datasourceState]) => ({ - ...state, - [datasourceId]: { - ...datasourceState, - isLoading: false, - }, - }), - {} - ), - isLoading: false, - }, - initialContext, - }) - ); - if (autoApplyDisabled) { - store.dispatch(disableAutoApply()); - } - }) - .catch((e: { message: string }) => { - notifications.toasts.addDanger({ - title: e.message, - }); - redirectCallback?.(); + try { + return loadFromEmptyState( + store, + emptyState, + loaderSharedArgs, + lensServices, + activeDatasourceId, + autoApplyDisabled + ); + } catch ({ message }) { + notifications.toasts.addDanger({ + title: message, }); + return redirectCallback?.(); + } } - return getPersisted({ initialInput, lensServices, history }) - .then( - (persisted) => { - if (persisted) { - const { doc, sharingSavedObjectProps, managed } = persisted; - if (attributeService.inputIsRefType(initialInput)) { - lensServices.chrome.recentlyAccessed.add( - getFullPath(initialInput.savedObjectId), - doc.title, - initialInput.savedObjectId - ); - } - - const docDatasourceStates = Object.entries(doc.state.datasourceStates).reduce( - (stateMap, [datasourceId, datasourceState]) => ({ - ...stateMap, - [datasourceId]: { - isLoading: true, - state: datasourceState, - }, - }), - {} - ); - - // when the embeddable is initialized from the dashboard we don't want to inject the filters - // as this will replace the parent application filters (such as a dashboard) - if (!Boolean(inlineEditing)) { - const filters = data.query.filterManager.inject(doc.state.filters, doc.references); - // Don't overwrite any pinned filters - data.query.filterManager.setAppFilters(filters); - } - - const docVisualizationState = { - activeId: doc.visualizationType, - state: doc.state.visualization, - }; - return initializeSources( - { - datasourceMap, - visualizationMap, - visualizationState: docVisualizationState, - datasourceStates: docDatasourceStates, - references: [...doc.references, ...(doc.state.internalReferences || [])], - initialContext, - dataViews: lensServices.dataViews, - eventAnnotationService: lensServices.eventAnnotationService, - storage: lensServices.storage, - adHocDataViews: doc.state.adHocDataViews, - defaultIndexPatternId: lensServices.uiSettings.get('defaultIndex'), - }, - { isFullEditor: true } - ) - .then( - ({ - datasourceStates, - visualizationState, - indexPatterns, - indexPatternRefs, - annotationGroups, - }) => { - const currentSessionId = data.search.session.getSessionId(); - store.dispatch( - initExisting({ - isSaveable: true, - sharingSavedObjectProps, - filters: data.query.filterManager.getFilters(), - query: doc.state.query, - searchSessionId: - !(initialInput as LensByReferenceInput)?.savedObjectId && currentSessionId - ? currentSessionId - : !inlineEditing - ? data.search.session.start() - : undefined, - persistedDoc: doc, - activeDatasourceId: getInitialDatasourceId(datasourceMap, doc), - visualization: { - activeId: doc.visualizationType, - state: visualizationState, - }, - dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), - datasourceStates: Object.entries(datasourceStates).reduce( - (state, [datasourceId, datasourceState]) => ({ - ...state, - [datasourceId]: { - ...datasourceState, - isLoading: false, - }, - }), - {} - ), - isLoading: false, - annotationGroups, - managed, - }) - ); - - if (autoApplyDisabled) { - store.dispatch(disableAutoApply()); - } - } - ) - .catch((e: { message: string }) => - notifications.toasts.addDanger({ - title: e.message, - }) - ); - } else { - redirectCallback?.(); - } - }, - () => { - store.dispatch( - setState({ - isLoading: false, - }) + try { + const persisted = await getFromPreloaded({ initialInput, lensServices, history }); + if (persisted) { + try { + return loadFromSavedObject( + store, + initialInput.savedObjectId, + persisted, + loaderSharedArgs, + lensServices, + autoApplyDisabled, + inlineEditing ); - redirectCallback?.(); + } catch ({ message }) { + notifications.toasts.addDanger({ + title: message, + }); } - ) - .catch((e: { message: string }) => { + } else { + return redirectCallback?.(); + } + } catch (e) { + try { + store.dispatch( + setState({ + isLoading: false, + }) + ); + redirectCallback?.(); + } catch ({ message }) { notifications.toasts.addDanger({ - title: e.message, + title: message, }); redirectCallback?.(); - }); + } + } } diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 20c727734aa9..b2a9beb0fb0a 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -14,7 +14,6 @@ import { LayerTypes } from '@kbn/expression-xy-plugin/public'; import { EventAnnotationGroupConfig } from '@kbn/event-annotation-common'; import { DragDropIdentifier, DropType } from '@kbn/dom-drag-drop'; import { SeriesType } from '@kbn/visualizations-plugin/common'; -import { LensEmbeddableInput } from '..'; import { TableInspectorAdapter } from '../editor_frame_service/types'; import type { VisualizeEditorContext, @@ -34,6 +33,7 @@ import type { FramePublicAPI, LensEditContextMapping, LensEditEvent } from '../t import { selectDataViews, selectFramePublicAPI } from './selectors'; import { onDropForVisualization } from '../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils'; import type { LensAppServices } from '../app_plugin/types'; +import type { LensSerializedState } from '../react_embeddable/types'; const getQueryFromContext = ( context: VisualizeFieldContext | VisualizeEditorContext, @@ -149,6 +149,13 @@ export interface SetExecutionContextPayload { resolvedDateRange?: DateRange; } +export interface InitialAppState { + initialInput?: LensSerializedState; + redirectCallback?: (savedObjectId?: string) => void; + history?: History<unknown>; + inlineEditing?: boolean; +} + export const setState = createAction<Partial<LensAppState>>('lens/setState'); export const setExecutionContext = createAction<SetExecutionContextPayload>( 'lens/setExecutionContext' @@ -201,12 +208,7 @@ export const switchAndCleanDatasource = createAction<{ currentIndexPatternId?: string; }>('lens/switchAndCleanDatasource'); export const navigateAway = createAction<void>('lens/navigateAway'); -export const loadInitial = createAction<{ - initialInput?: LensEmbeddableInput; - redirectCallback?: (savedObjectId?: string) => void; - history?: History<unknown>; - inlineEditing?: boolean; -}>('lens/loadInitial'); +export const loadInitial = createAction<InitialAppState>('lens/loadInitial'); export const initEmpty = createAction( 'initEmpty', function prepare({ diff --git a/x-pack/plugins/lens/public/state_management/load_initial.test.tsx b/x-pack/plugins/lens/public/state_management/load_initial.test.tsx index a70b713787ce..1514a508b878 100644 --- a/x-pack/plugins/lens/public/state_management/load_initial.test.tsx +++ b/x-pack/plugins/lens/public/state_management/load_initial.test.tsx @@ -15,10 +15,10 @@ import { } from '../mocks'; import { Location, History } from 'history'; import { act } from 'react-dom/test-utils'; -import { LensEmbeddableInput } from '../embeddable'; -import { loadInitial } from './lens_slice'; +import { InitialAppState, loadInitial } from './lens_slice'; import { Filter } from '@kbn/es-query'; import faker from 'faker'; +import { DOC_TYPE } from '../../common/constants'; const history = { location: { @@ -35,26 +35,37 @@ const preloadedState = { }, }; -const defaultProps = { +const defaultProps: InitialAppState = { redirectCallback: jest.fn(), - initialInput: { savedObjectId: defaultSavedObjectId } as unknown as LensEmbeddableInput, + initialInput: { savedObjectId: defaultSavedObjectId }, history, }; +/** + * This is just a convenience wrapper around act & dispatch + * The loadInitial action is hijacked by a custom middleware which returns a Promise + * therefore we need to await before proceeding with all the checks + * The intent of this wrapper is to avoid confusion with this specific action + */ +async function loadInitialAppState( + store: ReturnType<typeof makeLensStore>['store'], + initialState: InitialAppState +) { + await act(async () => { + await store.dispatch(loadInitial(initialState)); + }); +} + describe('Initializing the store', () => { it('should initialize initial datasource', async () => { - const { store, deps } = await makeLensStore({ preloadedState }); - await act(async () => { - await store.dispatch(loadInitial(defaultProps)); - }); + const { store, deps } = makeLensStore({ preloadedState }); + await loadInitialAppState(store, defaultProps); expect(deps.datasourceMap.testDatasource.initialize).toHaveBeenCalled(); }); it('should have initialized the initial datasource and visualization', async () => { - const { store, deps } = await makeLensStore({ preloadedState }); - await act(async () => { - await store.dispatch(loadInitial({ ...defaultProps, initialInput: undefined })); - }); + const { store, deps } = makeLensStore({ preloadedState }); + await loadInitialAppState(store, { ...defaultProps, initialInput: undefined }); expect(deps.datasourceMap.testDatasource.initialize).toHaveBeenCalled(); expect(deps.datasourceMap.testDatasource2.initialize).not.toHaveBeenCalled(); expect(deps.visualizationMap.testVis.initialize).toHaveBeenCalled(); @@ -65,7 +76,7 @@ describe('Initializing the store', () => { const datasource1State = { datasource1: '' }; const datasource2State = { datasource2: '' }; const services = makeDefaultServices(); - services.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({ + services.attributeService.loadFromLibrary = jest.fn().mockResolvedValue({ attributes: { exactMatchDoc, visualizationType: 'testVis', @@ -107,16 +118,13 @@ describe('Initializing the store', () => { }, }); - const { store, deps } = await makeLensStore({ + const { store, deps } = makeLensStore({ storeDeps, preloadedState, }); - await act(async () => { - await store.dispatch(loadInitial(defaultProps)); - }); + await loadInitialAppState(store, defaultProps); const { datasourceMap } = deps; - expect(datasourceMap.testDatasource.initialize).toHaveBeenCalled(); expect(datasourceMap.testDatasource.initialize).toHaveBeenCalledWith( datasource1State, @@ -139,22 +147,17 @@ describe('Initializing the store', () => { describe('loadInitial', () => { it('does not load a document if there is no initial input', async () => { const { deps, store } = makeLensStore({ preloadedState }); - await act(async () => { - await store.dispatch( - loadInitial({ - ...defaultProps, - initialInput: undefined, - }) - ); + await loadInitialAppState(store, { + ...defaultProps, + initialInput: undefined, }); - expect(deps.lensServices.attributeService.unwrapAttributes).not.toHaveBeenCalled(); + + expect(deps.lensServices.attributeService.loadFromLibrary).not.toHaveBeenCalled(); }); it('starts new searchSessionId', async () => { - const { store } = await makeLensStore({ preloadedState }); - await act(async () => { - await store.dispatch(loadInitial({ ...defaultProps, initialInput: undefined })); - }); + const { store } = makeLensStore({ preloadedState }); + await loadInitialAppState(store, { ...defaultProps, initialInput: undefined }); expect(store.getState()).toEqual({ lens: expect.objectContaining({ searchSessionId: 'sessionId-1', @@ -163,7 +166,7 @@ describe('Initializing the store', () => { }); it('cleans datasource and visualization state properly when reloading', async () => { - const { store, deps } = await makeLensStore({ + const { store, deps } = makeLensStore({ preloadedState: { ...preloadedState, visualization: { @@ -187,13 +190,9 @@ describe('Initializing the store', () => { }), }); - await act(async () => { - await store.dispatch( - loadInitial({ - ...defaultProps, - initialInput: undefined, - }) - ); + await loadInitialAppState(store, { + ...defaultProps, + initialInput: undefined, }); expect(deps.visualizationMap.testVis.initialize).toHaveBeenCalled(); @@ -217,19 +216,17 @@ describe('Initializing the store', () => { it('loads a document and uses query and filters if initial input is provided', async () => { const { store, deps } = makeLensStore({ preloadedState }); - const mockFilters = 'some filters from the filter manager' as unknown as Filter[]; + const mockFilters = faker.lorem.words(3).split(' ') as unknown as Filter[]; jest .spyOn(deps.lensServices.data.query.filterManager, 'getFilters') .mockReturnValue(mockFilters); - await act(async () => { - store.dispatch(loadInitial(defaultProps)); - }); + await loadInitialAppState(store, defaultProps); - expect(deps.lensServices.attributeService.unwrapAttributes).toHaveBeenCalledWith({ - savedObjectId: defaultSavedObjectId, - }); + expect(deps.lensServices.attributeService.loadFromLibrary).toHaveBeenCalledWith( + defaultSavedObjectId + ); expect(deps.lensServices.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([ { query: { match_phrase: { src: 'test' } }, meta: { index: 'injected!' } }, @@ -237,8 +234,8 @@ describe('Initializing the store', () => { expect(store.getState()).toEqual({ lens: expect.objectContaining({ - persistedDoc: { ...defaultDoc, type: 'lens' }, - query: 'kuery', + persistedDoc: { ...defaultDoc, type: DOC_TYPE }, + query: defaultDoc.state.query, isLoading: false, activeDatasourceId: 'testDatasource', filters: mockFilters, @@ -249,68 +246,54 @@ describe('Initializing the store', () => { it('does not load documents on sequential renders unless the id changes', async () => { const { store, deps } = makeLensStore({ preloadedState }); - await act(async () => { - await store.dispatch(loadInitial(defaultProps)); - }); + await loadInitialAppState(store, defaultProps); - await act(async () => { - await store.dispatch(loadInitial(defaultProps)); - }); + await loadInitialAppState(store, defaultProps); - expect(deps.lensServices.attributeService.unwrapAttributes).toHaveBeenCalledTimes(1); + expect(deps.lensServices.attributeService.loadFromLibrary).toHaveBeenCalledTimes(1); - await act(async () => { - await store.dispatch( - loadInitial({ - ...defaultProps, - initialInput: { savedObjectId: '5678' } as unknown as LensEmbeddableInput, - }) - ); + await loadInitialAppState(store, { + ...defaultProps, + initialInput: { savedObjectId: '5678' }, }); - expect(deps.lensServices.attributeService.unwrapAttributes).toHaveBeenCalledTimes(2); + expect(deps.lensServices.attributeService.loadFromLibrary).toHaveBeenCalledTimes(2); }); it('handles document load errors', async () => { const { store, deps } = makeLensStore({ preloadedState }); - deps.lensServices.attributeService.unwrapAttributes = jest + deps.lensServices.attributeService.loadFromLibrary = jest .fn() .mockRejectedValue('failed to load'); const redirectCallback = jest.fn(); - await act(async () => { - await store.dispatch(loadInitial({ ...defaultProps, redirectCallback })); - }); + await loadInitialAppState(store, { ...defaultProps, redirectCallback }); - expect(deps.lensServices.attributeService.unwrapAttributes).toHaveBeenCalledWith({ - savedObjectId: defaultSavedObjectId, - }); + expect(deps.lensServices.attributeService.loadFromLibrary).toHaveBeenCalledWith( + defaultSavedObjectId + ); expect(deps.lensServices.notifications.toasts.addDanger).toHaveBeenCalled(); expect(redirectCallback).toHaveBeenCalled(); }); it('redirects if saved object is an aliasMatch', async () => { const { store, deps } = makeLensStore({ preloadedState }); - deps.lensServices.attributeService.unwrapAttributes = jest.fn().mockResolvedValue({ + deps.lensServices.attributeService.loadFromLibrary = jest.fn().mockResolvedValue({ attributes: { ...defaultDoc, }, - metaInfo: { - sharingSavedObjectProps: { - outcome: 'aliasMatch', - aliasTargetId: 'id2', - aliasPurpose: 'savedObjectConversion', - }, + sharingSavedObjectProps: { + outcome: 'aliasMatch', + aliasTargetId: 'id2', + aliasPurpose: 'savedObjectConversion', }, }); - await act(async () => { - await store.dispatch(loadInitial(defaultProps)); - }); + await loadInitialAppState(store, defaultProps); - expect(deps.lensServices.attributeService.unwrapAttributes).toHaveBeenCalledWith({ - savedObjectId: defaultSavedObjectId, - }); + expect(deps.lensServices.attributeService.loadFromLibrary).toHaveBeenCalledWith( + defaultSavedObjectId + ); expect(deps.lensServices.spaces?.ui.redirectLegacyUrl).toHaveBeenCalledWith({ path: '#/edit/id2?search', aliasPurpose: 'savedObjectConversion', @@ -320,9 +303,7 @@ describe('Initializing the store', () => { it('adds to the recently accessed list on load', async () => { const { store, deps } = makeLensStore({ preloadedState }); - await act(async () => { - await store.dispatch(loadInitial(defaultProps)); - }); + await loadInitialAppState(store, defaultProps); expect(deps.lensServices.chrome.recentlyAccessed.add).toHaveBeenCalledWith( '/app/lens#/edit/1234', diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts index 2187302ae02e..594b1b9632d6 100644 --- a/x-pack/plugins/lens/public/state_management/selectors.ts +++ b/x-pack/plugins/lens/public/state_management/selectors.ts @@ -7,11 +7,11 @@ import { createSelector } from '@reduxjs/toolkit'; import { FilterManager } from '@kbn/data-plugin/public'; -import { SavedObjectReference } from '@kbn/core/public'; -import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common'; +import { isOfAggregateQueryType } from '@kbn/es-query'; import { LensState } from './types'; -import { Datasource, DatasourceMap, VisualizationMap } from '../types'; +import { DatasourceMap, VisualizationMap } from '../types'; import { getDatasourceLayers } from './utils'; +import { mergeToNewDoc } from './shared_logic'; export const selectPersistedDoc = (state: LensState) => state.lens.persistedDoc; export const selectQuery = (state: LensState) => state.lens.query; @@ -60,7 +60,7 @@ export const selectExecutionContext = createSelector( export const selectExecutionContextSearch = createSelector(selectExecutionContext, (res) => ({ now: res.now, - query: res.query, + query: isOfAggregateQueryType(res.query) ? undefined : res.query, timeRange: { from: res.dateRange.fromDate, to: res.dateRange.toDate, @@ -89,107 +89,7 @@ export const selectSavedObjectFormat = createSelector( extractFilterReferences: FilterManager['extract']; }>, ], - ( - persistedDoc, - visualization, - datasourceStates, - query, - filters, - activeDatasourceId, - adHocDataViews, - { datasourceMap, visualizationMap, extractFilterReferences } - ) => { - const activeVisualization = - visualization.state && visualization.activeId - ? visualizationMap[visualization.activeId] - : null; - const activeDatasource = - datasourceStates && activeDatasourceId && !datasourceStates[activeDatasourceId].isLoading - ? datasourceMap[activeDatasourceId] - : undefined; - - if (!activeDatasource || !activeVisualization) { - return; - } - - const activeDatasources: Record<string, Datasource> = Object.keys(datasourceStates).reduce( - (acc, datasourceId) => ({ - ...acc, - [datasourceId]: datasourceMap[datasourceId], - }), - {} - ); - - const persistibleDatasourceStates: Record<string, unknown> = {}; - const references: SavedObjectReference[] = []; - const internalReferences: SavedObjectReference[] = []; - Object.entries(activeDatasources).forEach(([id, datasource]) => { - const { state: persistableState, savedObjectReferences } = datasource.getPersistableState( - datasourceStates[id].state - ); - persistibleDatasourceStates[id] = persistableState; - savedObjectReferences.forEach((r) => { - if (r.type === 'index-pattern' && adHocDataViews[r.id]) { - internalReferences.push(r); - } else { - references.push(r); - } - }); - }); - - let persistibleVisualizationState = visualization.state; - if (activeVisualization.getPersistableState) { - const { state: persistableState, savedObjectReferences } = - activeVisualization.getPersistableState(visualization.state); - persistibleVisualizationState = persistableState; - savedObjectReferences.forEach((r) => { - if (r.type === 'index-pattern' && adHocDataViews[r.id]) { - internalReferences.push(r); - } else { - references.push(r); - } - }); - } - - const persistableAdHocDataViews = Object.fromEntries( - Object.entries(adHocDataViews).map(([id, dataView]) => { - const { references: dataViewReferences, state } = - DataViewPersistableStateService.extract(dataView); - references.push(...dataViewReferences); - return [id, state]; - }) - ); - - const adHocFilters = filters - .filter((f) => !references.some((r) => r.type === 'index-pattern' && r.id === f.meta.index)) - .map((f) => ({ ...f, meta: { ...f.meta, value: undefined } })); - - const referencedFilters = filters.filter((f) => - references.some((r) => r.type === 'index-pattern' && r.id === f.meta.index) - ); - - const { state: persistableFilters, references: filterReferences } = - extractFilterReferences(referencedFilters); - - references.push(...filterReferences); - - return { - savedObjectId: persistedDoc?.savedObjectId, - title: persistedDoc?.title || '', - description: persistedDoc?.description, - visualizationType: visualization.activeId, - type: 'lens', - references, - state: { - visualization: persistibleVisualizationState, - query, - filters: [...persistableFilters, ...adHocFilters], - datasourceStates: persistibleDatasourceStates, - internalReferences, - adHocDataViews: persistableAdHocDataViews, - }, - }; - } + mergeToNewDoc ); export const selectCurrentVisualization = createSelector( diff --git a/x-pack/plugins/lens/public/state_management/shared_logic.ts b/x-pack/plugins/lens/public/state_management/shared_logic.ts new file mode 100644 index 000000000000..4e24d9f3fdaa --- /dev/null +++ b/x-pack/plugins/lens/public/state_management/shared_logic.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectReference } from '@kbn/core-saved-objects-api-server'; +import { DataViewSpec, DataViewPersistableStateService } from '@kbn/data-views-plugin/common'; +import { AggregateQuery, Query, Filter } from '@kbn/es-query'; +import { FilterManager } from '@kbn/data-plugin/public'; +import { DOC_TYPE, INDEX_PATTERN_TYPE } from '../../common/constants'; +import { VisualizationState, DatasourceStates } from '.'; +import { LensDocument } from '../persistence'; +import { DatasourceMap, VisualizationMap, Datasource } from '../types'; + +// This piece of logic is shared between the main editor code base and the inline editor one within the embeddable +export function mergeToNewDoc( + persistedDoc: LensDocument | undefined, + visualization: VisualizationState, + datasourceStates: DatasourceStates, + query: AggregateQuery | Query, + filters: Filter[], + activeDatasourceId: string | null, + adHocDataViews: Record<string, DataViewSpec>, + { + datasourceMap, + visualizationMap, + extractFilterReferences, + }: { + datasourceMap: DatasourceMap; + visualizationMap: VisualizationMap; + extractFilterReferences: FilterManager['extract']; + } +) { + const activeVisualization = + visualization.state && visualization.activeId ? visualizationMap[visualization.activeId] : null; + const activeDatasource = + datasourceStates && activeDatasourceId && !datasourceStates[activeDatasourceId].isLoading + ? datasourceMap[activeDatasourceId] + : undefined; + + if (!activeDatasource || !activeVisualization) { + return; + } + + const activeDatasources: Record<string, Datasource> = Object.keys(datasourceStates).reduce( + (acc, datasourceId) => ({ + ...acc, + [datasourceId]: datasourceMap[datasourceId], + }), + {} + ); + + const persistibleDatasourceStates: Record<string, unknown> = {}; + const references: SavedObjectReference[] = []; + const internalReferences: SavedObjectReference[] = []; + Object.entries(activeDatasources).forEach(([id, datasource]) => { + const { state: persistableState, savedObjectReferences } = datasource.getPersistableState( + datasourceStates[id].state + ); + persistibleDatasourceStates[id] = persistableState; + savedObjectReferences.forEach((r) => { + if (r.type === INDEX_PATTERN_TYPE && adHocDataViews[r.id]) { + internalReferences.push(r); + } else { + references.push(r); + } + }); + }); + + let persistibleVisualizationState = visualization.state; + if (activeVisualization.getPersistableState) { + const { state: persistableState, savedObjectReferences } = + activeVisualization.getPersistableState(visualization.state); + persistibleVisualizationState = persistableState; + savedObjectReferences.forEach((r) => { + if (r.type === INDEX_PATTERN_TYPE && adHocDataViews[r.id]) { + internalReferences.push(r); + } else { + references.push(r); + } + }); + } + + const persistableAdHocDataViews = Object.fromEntries( + Object.entries(adHocDataViews).map(([id, dataView]) => { + const { references: dataViewReferences, state } = + DataViewPersistableStateService.extract(dataView); + references.push(...dataViewReferences); + return [id, state]; + }) + ); + + const adHocFilters = filters + .filter((f) => !references.some((r) => r.type === INDEX_PATTERN_TYPE && r.id === f.meta.index)) + .map((f) => ({ ...f, meta: { ...f.meta, value: undefined } })); + + const referencedFilters = filters.filter((f) => + references.some((r) => r.type === INDEX_PATTERN_TYPE && r.id === f.meta.index) + ); + + const { state: persistableFilters, references: filterReferences } = + extractFilterReferences(referencedFilters); + + references.push(...filterReferences); + + return { + savedObjectId: persistedDoc?.savedObjectId, + title: persistedDoc?.title || '', + description: persistedDoc?.description, + visualizationType: visualization.activeId!, + type: DOC_TYPE, + references, + state: { + visualization: persistibleVisualizationState, + query, + filters: [...persistableFilters, ...adHocFilters], + datasourceStates: persistibleDatasourceStates, + internalReferences, + adHocDataViews: persistableAdHocDataViews, + }, + }; +} diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index 1d683b655b58..cc8f76118cf2 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -7,10 +7,10 @@ import type { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; import type { EmbeddableEditorState } from '@kbn/embeddable-plugin/public'; -import type { Filter, Query } from '@kbn/es-query'; +import type { AggregateQuery, Filter, Query } from '@kbn/es-query'; import type { SavedQuery } from '@kbn/data-plugin/public'; import type { MainHistoryLocationState } from '../../common/locator/locator'; -import type { Document } from '../persistence'; +import type { LensDocument } from '../persistence'; import type { TableInspectorAdapter } from '../editor_frame_service/types'; import type { DateRange } from '../../common/types'; @@ -54,14 +54,14 @@ export interface EditorFrameState extends PreviewState { isFullscreenDatasource?: boolean; } export interface LensAppState extends EditorFrameState { - persistedDoc?: Document; + persistedDoc?: LensDocument; // Determines whether the lens editor shows the 'save and return' button, and the originating app breadcrumb. isLinkedToOriginatingApp?: boolean; isSaveable: boolean; isLoading: boolean; - query: Query; + query: Query | AggregateQuery; filters: Filter[]; savedQuery?: SavedQuery; searchSessionId: string; diff --git a/x-pack/plugins/lens/public/trigger_actions/convert_to_lens_action.ts b/x-pack/plugins/lens/public/trigger_actions/convert_to_lens_action.ts new file mode 100644 index 000000000000..017cb64f9dd4 --- /dev/null +++ b/x-pack/plugins/lens/public/trigger_actions/convert_to_lens_action.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createAction } from '@kbn/ui-actions-plugin/public'; +import { ACTION_CONVERT_TO_LENS } from '@kbn/visualizations-plugin/public'; +import type { ApplicationStart } from '@kbn/core/public'; +import { APP_ID } from '../../common/constants'; +import type { VisualizeEditorContext } from '../types'; + +export const convertToLensActionFactory = + (id: string, displayName: string, originatingApp: string) => (application: ApplicationStart) => + createAction<{ [key: string]: VisualizeEditorContext }>({ + type: ACTION_CONVERT_TO_LENS, + id, + getDisplayName: () => displayName, + isCompatible: async () => !!application.capabilities.visualize.show, + execute: async (context: { [key: string]: VisualizeEditorContext }) => { + const table = Object.values(context.layers); + const payload = { + ...context, + layers: table, + isVisualizeAction: true, + }; + application.navigateToApp(APP_ID, { + state: { + type: ACTION_CONVERT_TO_LENS, + payload, + originatingApp, + }, + }); + }, + }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts index fd1ef4f746c4..c74486abfe8d 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.test.ts @@ -8,30 +8,12 @@ import { DataViewsService } from '@kbn/data-views-plugin/public'; import { type EmbeddableApiContext } from '@kbn/presentation-publishing'; import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; -import { BehaviorSubject } from 'rxjs'; -import { DOC_TYPE } from '../../common/constants'; import { createOpenInDiscoverAction } from './open_in_discover_action'; import type { DiscoverAppLocator } from './open_in_discover_helpers'; +import { getLensApiMock } from '../react_embeddable/mocks'; describe('open in discover action', () => { - const compatibleEmbeddableApi = { - type: DOC_TYPE, - panelTitle: 'some title', - hidePanelTitle: false, - filters$: new BehaviorSubject([]), - query$: new BehaviorSubject({ query: 'test', language: 'kuery' }), - timeRange$: new BehaviorSubject({ from: 'now-15m', to: 'now' }), - getSavedVis: jest.fn(() => undefined), - canViewUnderlyingData$: new BehaviorSubject(true), - getFullAttributes: jest.fn(() => undefined), - getViewUnderlyingDataArgs: jest.fn(() => ({ - dataViewSpec: { id: 'index-pattern-id' }, - timeRange: { from: 'now-7d', to: 'now' }, - filters: [], - query: undefined, - columns: [], - })), - }; + const compatibleEmbeddableApi = getLensApiMock(); describe('compatibility check', () => { it('is incompatible with non-lens embeddables', async () => { @@ -49,6 +31,10 @@ describe('open in discover action', () => { }); it('is incompatible if user cant access Discover app', async () => { // setup + const lensApi = { + ...compatibleEmbeddableApi, + canViewUnderlyingData$: { getValue: jest.fn(() => true) }, + }; let hasDiscoverAccess = true; // make sure it would work if we had access to Discover @@ -58,7 +44,7 @@ describe('open in discover action', () => { {} as DataViewsService, hasDiscoverAccess ).isCompatible({ - embeddable: compatibleEmbeddableApi, + embeddable: lensApi, } as ActionExecutionContext<EmbeddableApiContext>) ).toBeTruthy(); @@ -70,7 +56,7 @@ describe('open in discover action', () => { {} as DataViewsService, hasDiscoverAccess ).isCompatible({ - embeddable: compatibleEmbeddableApi, + embeddable: lensApi, } as ActionExecutionContext<EmbeddableApiContext>) ).toBeFalsy(); }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts index d9dccab616d5..fa67aa74f9de 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_action.ts @@ -10,7 +10,7 @@ import { Action, createAction, IncompatibleActionError } from '@kbn/ui-actions-p import { EmbeddableApiContext } from '@kbn/presentation-publishing'; import type { DataViewsService } from '@kbn/data-views-plugin/public'; import type { DiscoverAppLocator } from './open_in_discover_helpers'; -import { LensApi } from '../embeddable'; +import { LensApi } from '../react_embeddable/types'; const ACTION_OPEN_IN_DISCOVER = 'ACTION_OPEN_IN_DISCOVER'; @@ -41,7 +41,7 @@ export const createOpenInDiscoverAction = ( }, isCompatible: async (context: EmbeddableApiContext) => { const { isCompatible } = await getDiscoverHelpersAsync(); - return isCompatible({ + return await isCompatible({ hasDiscoverAccess, locator, dataViews, diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx index d9b8b93e28d0..199700af157d 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.test.tsx @@ -17,10 +17,10 @@ import { OpenInDiscoverDrilldown, } from './open_in_discover_drilldown'; import { DataViewsService } from '@kbn/data-views-plugin/public'; -import { LensApi } from '../embeddable'; +import { getLensApiMock } from '../react_embeddable/mocks'; jest.mock('./open_in_discover_helpers', () => ({ - isCompatible: jest.fn(() => true), + isCompatible: jest.fn().mockReturnValue(true), getHref: jest.fn(), })); @@ -63,19 +63,13 @@ describe('open in discover drilldown', () => { it('calls through to isCompatible helper', async () => { const filters: Filter[] = [{ meta: { disabled: false } }]; - await drilldown.isCompatible( - { openInNewTab: true }, - { embeddable: { type: 'lens' } as LensApi, filters } - ); + await drilldown.isCompatible({ openInNewTab: true }, { embeddable: getLensApiMock(), filters }); expect(isCompatible).toHaveBeenCalledWith(expect.objectContaining({ filters })); }); it('calls through to getHref helper', async () => { const filters: Filter[] = [{ meta: { disabled: false } }]; - await drilldown.execute( - { openInNewTab: true }, - { embeddable: { type: 'lens' } as LensApi, filters } - ); + await drilldown.execute({ openInNewTab: true }, { embeddable: getLensApiMock(), filters }); expect(getHref).toHaveBeenCalledWith(expect.objectContaining({ filters })); }); }); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx index 6602dc4acb69..0a8f7cb3bf5e 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_drilldown.tsx @@ -21,7 +21,7 @@ import type { DataViewsService } from '@kbn/data-views-plugin/public'; import { apiIsOfType } from '@kbn/presentation-publishing'; import { DOC_TYPE } from '../../common/constants'; import type { DiscoverAppLocator } from './open_in_discover_helpers'; -import { LensApi } from '../embeddable'; +import { LensApi } from '../react_embeddable/types'; export const getDiscoverHelpersAsync = async () => await import('../async_services'); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts index 0a52ea6b4711..3ad62f212e49 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_in_discover_helpers.ts @@ -10,7 +10,7 @@ import type { DataViewsService } from '@kbn/data-views-plugin/public'; import type { LocatorPublic } from '@kbn/share-plugin/public'; import type { SerializableRecord } from '@kbn/utility-types'; import { EmbeddableApiContext } from '@kbn/presentation-publishing'; -import { isLensApi } from '../embeddable'; +import { isLensApi } from '../react_embeddable/type_guards'; interface DiscoverAppLocatorParams extends SerializableRecord { timeRange?: TimeRange; diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts index 96cd0ab6877e..6f875e49f160 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts @@ -20,9 +20,8 @@ import type { Datasource, Visualization } from '../../types'; import type { LensPluginStartDependencies } from '../../plugin'; import { suggestionsApi } from '../../lens_suggestions_api'; import { generateId } from '../../id_generator'; -import { executeEditAction } from './edit_action_helpers'; -import { Embeddable } from '../../embeddable'; import type { EditorFrameService } from '../../editor_frame_service'; +import { LensApi } from '../..'; // datasourceMap and visualizationMap setters/getters export const [getVisualizationMap, setVisualizationMap] = createGetterSetter< @@ -117,29 +116,21 @@ export async function executeCreateAction({ const attrs = getLensAttributesFromSuggestion({ filters: [], query: defaultEsqlQuery, - suggestion: firstSuggestion, + suggestion: { + ...firstSuggestion, + title: '', // when creating a new panel, we don't want to use the title from the suggestion + }, dataView, }); - const embeddable = await api.addNewPanel<object, Embeddable>({ + const embeddable = await api.addNewPanel<object, LensApi>({ panelType: 'lens', initialState: { attributes: attrs, id: generateId(), + isNewPanel: true, }, }); // open the flyout if embeddable has been created successfully - if (embeddable) { - const deletePanel = () => { - api.removePanel(embeddable.id); - }; - - executeEditAction({ - embeddable, - startDependencies: deps, - isNewPanel: true, - deletePanel, - ...core, - }); - } + embeddable?.onEdit?.(); } diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action.test.tsx deleted file mode 100644 index e9daa06b9ac0..000000000000 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action.test.tsx +++ /dev/null @@ -1,119 +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 React from 'react'; -import { coreMock } from '@kbn/core/public/mocks'; -import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; -import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; -import type { LensPluginStartDependencies } from '../../plugin'; -import { createMockStartDependencies } from '../../editor_frame_service/mocks'; -import { DOC_TYPE } from '../../../common/constants'; -import { ConfigureInLensPanelAction } from './edit_action'; - -describe('open config panel action', () => { - const coreStart = coreMock.createStart(); - const mockStartDependencies = - createMockStartDependencies() as unknown as LensPluginStartDependencies; - describe('compatibility check', () => { - it('is incompatible with non-lens embeddables', async () => { - const embeddable = { - type: 'NOT_LENS', - isTextBasedLanguage: () => true, - getInput: () => { - return { - viewMode: 'edit', - }; - }, - } as unknown as IEmbeddable; - const configurablePanelAction = new ConfigureInLensPanelAction( - mockStartDependencies, - coreStart - ); - - const isCompatible = await configurablePanelAction.isCompatible({ - embeddable, - } as ActionExecutionContext<{ embeddable: IEmbeddable }>); - - expect(isCompatible).toBeFalsy(); - }); - - it('is incompatible with input view mode', async () => { - const embeddable = { - type: 'NOT_LENS', - getInput: () => { - return { - viewMode: 'view', - }; - }, - } as unknown as IEmbeddable; - const configurablePanelAction = new ConfigureInLensPanelAction( - mockStartDependencies, - coreStart - ); - - const isCompatible = await configurablePanelAction.isCompatible({ - embeddable, - } as ActionExecutionContext<{ embeddable: IEmbeddable }>); - - expect(isCompatible).toBeFalsy(); - }); - - it('is compatible with text based language embeddable', async () => { - const embeddable = { - type: DOC_TYPE, - isTextBasedLanguage: () => true, - getInput: () => { - return { - viewMode: 'edit', - }; - }, - getIsEditable: () => true, - } as unknown as IEmbeddable; - const configurablePanelAction = new ConfigureInLensPanelAction( - mockStartDependencies, - coreStart - ); - - const isCompatible = await configurablePanelAction.isCompatible({ - embeddable, - } as ActionExecutionContext<{ embeddable: IEmbeddable }>); - - expect(isCompatible).toBeTruthy(); - }); - }); - describe('execution', () => { - it('opens flyout when executed', async () => { - const embeddable = { - type: DOC_TYPE, - isTextBasedLanguage: () => true, - getInput: () => { - return { - viewMode: 'edit', - }; - }, - getIsEditable: () => true, - openConfigPanel: jest.fn().mockResolvedValue(<span>Lens Config Panel Component</span>), - getRoot: () => { - return { - openOverlay: jest.fn(), - clearOverlays: jest.fn(), - }; - }, - } as unknown as IEmbeddable; - const configurablePanelAction = new ConfigureInLensPanelAction( - mockStartDependencies, - coreStart - ); - const spy = jest.spyOn(coreStart.overlays, 'openFlyout'); - - await configurablePanelAction.execute({ - embeddable, - } as ActionExecutionContext<{ embeddable: IEmbeddable }>); - - expect(spy).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action.tsx deleted file mode 100644 index 4ad23bc953d2..000000000000 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action.tsx +++ /dev/null @@ -1,57 +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 { i18n } from '@kbn/i18n'; -import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; -import { Action } from '@kbn/ui-actions-plugin/public'; -import type { LensPluginStartDependencies } from '../../plugin'; -import { isLensEmbeddable } from '../utils'; -import type { StartServices } from '../../types'; - -const ACTION_CONFIGURE_IN_LENS = 'ACTION_CONFIGURE_IN_LENS'; - -interface Context { - embeddable: IEmbeddable; -} - -export const getConfigureLensHelpersAsync = async () => await import('../../async_services'); - -export class ConfigureInLensPanelAction implements Action<Context> { - public type = ACTION_CONFIGURE_IN_LENS; - public id = ACTION_CONFIGURE_IN_LENS; - public order = 50; - - constructor( - protected readonly startDependencies: LensPluginStartDependencies, - protected readonly startServices: StartServices - ) {} - - public getDisplayName({ embeddable }: Context): string { - const language = isLensEmbeddable(embeddable) ? embeddable.getTextBasedLanguage() : undefined; - return i18n.translate('xpack.lens.app.editVisualizationLabel', { - defaultMessage: 'Edit {lang} visualization', - values: { lang: language }, - }); - } - - public getIconType() { - return 'pencil'; - } - - public async isCompatible({ embeddable }: Context) { - const { isEditActionCompatible } = await getConfigureLensHelpersAsync(); - return isEditActionCompatible(embeddable); - } - - public async execute({ embeddable }: Context) { - const { executeEditAction } = await getConfigureLensHelpersAsync(); - return executeEditAction({ - embeddable, - startDependencies: this.startDependencies, - ...this.startServices, - }); - } -} diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action_helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action_helpers.ts deleted file mode 100644 index 7ec70e687efe..000000000000 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/edit_action_helpers.ts +++ /dev/null @@ -1,100 +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 React from 'react'; -import './helpers.scss'; -import { toMountPoint } from '@kbn/react-kibana-mount'; -import { tracksOverlays } from '@kbn/presentation-containers'; -import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; -import { ViewMode } from '@kbn/embeddable-plugin/common'; -import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; -import { isLensEmbeddable } from '../utils'; -import type { LensPluginStartDependencies } from '../../plugin'; -import { StartServices } from '../../types'; - -interface Context extends StartServices { - embeddable: IEmbeddable; - startDependencies: LensPluginStartDependencies; - isNewPanel?: boolean; - deletePanel?: () => void; -} - -export async function isEditActionCompatible(embeddable: IEmbeddable) { - if (!embeddable?.getInput) return false; - // display the action only if dashboard is on editable mode - const inDashboardEditMode = embeddable.getInput().viewMode === ViewMode.EDIT; - return Boolean(isLensEmbeddable(embeddable) && embeddable.getIsEditable() && inDashboardEditMode); -} - -type PanelConfigElement<T = {}> = React.ReactElement<T & { closeFlyout: () => void }>; - -const openInlineLensConfigEditor = ( - startServices: StartServices, - embeddable: IEmbeddable, - EmbeddableInlineConfigEditor: PanelConfigElement -) => { - const rootEmbeddable = embeddable.getRoot(); - const overlayTracker = tracksOverlays(rootEmbeddable) ? rootEmbeddable : undefined; - - const handle = startServices.overlays.openFlyout( - toMountPoint( - React.createElement(function InlineLensConfigEditor() { - React.useEffect(() => { - document.body.style.overflowY = 'hidden'; - - return () => { - document.body.style.overflowY = 'initial'; - }; - }, []); - - return React.cloneElement(EmbeddableInlineConfigEditor, { - closeFlyout: () => { - overlayTracker?.clearOverlays(); - handle.close(); - }, - }); - }), - startServices - ), - { - size: 's', - type: 'push', - paddingSize: 'm', - 'data-test-subj': 'customizeLens', - className: 'lnsConfigPanel__overlay', - hideCloseButton: true, - isResizable: true, - onClose: (overlayRef) => { - overlayTracker?.clearOverlays(); - overlayRef.close(); - }, - outsideClickCloses: true, - } - ); - - overlayTracker?.openOverlay(handle, { - focusedPanelId: embeddable.id, - }); -}; - -export async function executeEditAction({ - embeddable, - startDependencies, - isNewPanel, - deletePanel, - ...startServices -}: Context) { - const isCompatibleAction = await isEditActionCompatible(embeddable); - if (!isCompatibleAction || !isLensEmbeddable(embeddable)) { - throw new IncompatibleActionError(); - } - - const ConfigPanel = await embeddable.openConfigPanel(startDependencies, isNewPanel, deletePanel); - - if (ConfigPanel) { - openInlineLensConfigEditor(startServices, embeddable, ConfigPanel); - } -} diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx index 7525f491e697..1e1ab4cacff2 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.test.tsx @@ -8,13 +8,17 @@ import type { CoreStart } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; import type { LensPluginStartDependencies } from '../../../plugin'; import { createMockStartDependencies } from '../../../editor_frame_service/mocks'; -import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; import { EditLensEmbeddableAction } from './in_app_embeddable_edit_action'; +import { TypedLensSerializedState } from '../../../react_embeddable/types'; +import { BehaviorSubject } from 'rxjs'; describe('inapp editing of Lens embeddable', () => { const core = coreMock.createStart(); const mockStartDependencies = createMockStartDependencies() as unknown as LensPluginStartDependencies; + + const renderComplete$ = new BehaviorSubject(false); + describe('compatibility check', () => { const attributes = { title: 'An extremely cool default document!', @@ -29,7 +33,7 @@ describe('inapp editing of Lens embeddable', () => { visualization: {}, }, references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], - } as unknown as TypedLensByValueInput['attributes']; + } as TypedLensSerializedState['attributes']; it('is incompatible for ESQL charts and if ui setting for ES|QL is off', async () => { const inAppEditAction = new EditLensEmbeddableAction(mockStartDependencies, core); const context = { @@ -37,6 +41,7 @@ describe('inapp editing of Lens embeddable', () => { lensEvent: { adapters: {}, embeddableOutput$: undefined, + renderComplete$, }, onUpdate: jest.fn(), }; @@ -61,6 +66,7 @@ describe('inapp editing of Lens embeddable', () => { lensEvent: { adapters: {}, embeddableOutput$: undefined, + renderComplete$, }, onUpdate: jest.fn(), }; @@ -86,6 +92,7 @@ describe('inapp editing of Lens embeddable', () => { lensEvent: { adapters: {}, embeddableOutput$: undefined, + renderComplete$, }, onUpdate: jest.fn(), }; diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.tsx index c132b5e88c6c..74ffac32605b 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action.tsx @@ -12,8 +12,6 @@ import type { InlineEditLensEmbeddableContext } from './types'; const ACTION_EDIT_LENS_EMBEDDABLE = 'ACTION_EDIT_LENS_EMBEDDABLE'; -export const getAsyncHelpers = async () => await import('../../../async_services'); - export class EditLensEmbeddableAction implements Action<InlineEditLensEmbeddableContext> { public type = ACTION_EDIT_LENS_EMBEDDABLE; public id = ACTION_EDIT_LENS_EMBEDDABLE; @@ -35,7 +33,7 @@ export class EditLensEmbeddableAction implements Action<InlineEditLensEmbeddable } public async isCompatible({ attributes }: InlineEditLensEmbeddableContext) { - const { isEmbeddableEditActionCompatible } = await getAsyncHelpers(); + const { isEmbeddableEditActionCompatible } = await import('../../../async_services'); return isEmbeddableEditActionCompatible(this.core, attributes); } @@ -47,7 +45,7 @@ export class EditLensEmbeddableAction implements Action<InlineEditLensEmbeddable onApply, onCancel, }: InlineEditLensEmbeddableContext) { - const { executeEditEmbeddableAction } = await getAsyncHelpers(); + const { executeEditEmbeddableAction } = await import('../../../async_services'); if (attributes) { executeEditEmbeddableAction({ deps: this.startDependencies, diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx index 0a3dc8feebe0..5eeaf01d2034 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/in_app_embeddable_edit_action_helpers.tsx @@ -4,18 +4,23 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; -import ReactDOM from 'react-dom'; -import type { CoreStart } from '@kbn/core/public'; +import type { CoreStart, OverlayRef } from '@kbn/core/public'; import { isOfAggregateQueryType } from '@kbn/es-query'; import { ENABLE_ESQL } from '@kbn/esql-utils'; -import { toMountPoint } from '@kbn/react-kibana-mount'; import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { BehaviorSubject } from 'rxjs'; +import '../helpers.scss'; +import { PublishingSubject } from '@kbn/presentation-publishing'; +import { generateId } from '../../../id_generator'; +import { setupPanelManagement } from '../../../react_embeddable/inline_editing/panel_management'; +import { prepareInlineEditPanel } from '../../../react_embeddable/inline_editing/setup_inline_editing'; +import { mountInlineEditPanel } from '../../../react_embeddable/inline_editing/mount'; +import type { TypedLensByValueInput, LensRuntimeState } from '../../../react_embeddable/types'; import type { LensPluginStartDependencies } from '../../../plugin'; -import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; -import { extractReferencesFromState } from '../../../utils'; import type { LensChartLoadEvent } from './types'; +const asyncNoop = async () => {}; + export function isEmbeddableEditActionCompatible( core: CoreStart, attributes: TypedLensByValueInput['attributes'] @@ -49,106 +54,41 @@ export async function executeEditEmbeddableAction({ throw new IncompatibleActionError(); } - const { getEditLensConfiguration, getVisualizationMap, getDatasourceMap } = await import( - '../../../async_services' - ); - const visualizationMap = getVisualizationMap(); - const datasourceMap = getDatasourceMap(); - const query = attributes.state.query; - const activeDatasourceId = isOfAggregateQueryType(query) ? 'textBased' : 'formBased'; - - const onUpdatePanelState = ( - datasourceState: unknown, - visualizationState: unknown, - visualizationType?: string - ) => { - if (attributes.state) { - const datasourceStates = { - ...attributes.state.datasourceStates, - [activeDatasourceId]: datasourceState, - }; - - const references = extractReferencesFromState({ - activeDatasources: Object.keys(datasourceStates).reduce( - (acc, datasourceId) => ({ - ...acc, - [datasourceId]: datasourceMap[datasourceId], - }), - {} - ), - datasourceStates: Object.fromEntries( - Object.entries(datasourceStates).map(([id, state]) => [id, { isLoading: false, state }]) - ), - visualizationState, - activeVisualization: visualizationType ? visualizationMap[visualizationType] : undefined, - }); - - const attrs = { - ...attributes, - state: { - ...attributes.state, - visualization: visualizationState, - datasourceStates, - }, - references, - visualizationType: visualizationType ?? attributes.visualizationType, - } as TypedLensByValueInput['attributes']; - - onUpdate(attrs); - } - }; - - const onUpdateSuggestion = (attrs: TypedLensByValueInput['attributes']) => { - const newAttributes = { - ...attributes, - ...attrs, - }; - onUpdate(newAttributes); - }; - - const Component = await getEditLensConfiguration(core, deps, visualizationMap, datasourceMap); - const ConfigPanel = ( - <Component - attributes={attributes} - updatePanelState={onUpdatePanelState} - lensAdapters={lensEvent?.adapters} - output$={lensEvent?.embeddableOutput$} - displayFlyoutHeader - datasourceId={activeDatasourceId} - onApplyCb={onApply} - onCancelCb={onCancel} - canEditTextBasedQuery={activeDatasourceId === 'textBased'} - updateSuggestion={onUpdateSuggestion} - hideTimeFilterInfo={true} - /> + const uuid = generateId(); + const isNewlyCreated$ = new BehaviorSubject<boolean>(false); + const panelManagementApi = setupPanelManagement(uuid, container, { + isNewlyCreated$, + setAsCreated: () => isNewlyCreated$.next(false), + }); + const openInlineEditor = prepareInlineEditPanel( + { attributes }, + () => ({ attributes }), + (newState: LensRuntimeState) => + onUpdate(newState.attributes as TypedLensByValueInput['attributes']), + { + dataLoading$: + lensEvent?.dataLoading$ ?? + (new BehaviorSubject(undefined) as PublishingSubject<boolean | undefined>), + isNewlyCreated$, + }, + panelManagementApi, + { + getInspectorAdapters: () => lensEvent?.adapters, + inspect(): OverlayRef { + return { close: asyncNoop, onClose: Promise.resolve() }; + }, + closeInspector: asyncNoop, + adapters$: new BehaviorSubject(lensEvent?.adapters), + }, + { coreStart: core, ...deps } ); - // in case an element is given render the component in the container, - // otherwise a flyout will open - if (container) { - ReactDOM.render(ConfigPanel, container); - } else { - const handle = core.overlays.openFlyout( - toMountPoint( - React.cloneElement(ConfigPanel, { - closeFlyout: () => { - handle.close(); - }, - }), - core - ), - { - className: 'lnsConfigPanel__overlay', - size: 's', - 'data-test-subj': 'customizeLens', - type: 'push', - paddingSize: 'm', - hideCloseButton: true, - onClose: (overlayRef) => { - overlayRef.close(); - }, - outsideClickCloses: true, - } - ); + const ConfigPanel = await openInlineEditor({ + onApply, + onCancel, + }); + if (ConfigPanel) { + // no need to pass the uuid in this use case + mountInlineEditPanel(ConfigPanel, core, undefined, undefined, container); } } diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts index d86f05d4156e..0176ef4ee9e8 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/in_app_embeddable_edit/types.ts @@ -5,9 +5,8 @@ * 2.0. */ import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; -import type { EmbeddableOutput } from '@kbn/embeddable-plugin/public'; -import type { Observable } from 'rxjs'; -import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; +import { PublishingSubject } from '@kbn/presentation-publishing'; +import type { TypedLensByValueInput } from '../../../react_embeddable/types'; export interface LensChartLoadEvent { /** @@ -15,9 +14,9 @@ export interface LensChartLoadEvent { */ adapters: Partial<DefaultInspectorAdapters>; /** - * Observable of the lens embeddable output + * Observable to track embeddable loading state */ - embeddableOutput$?: Observable<EmbeddableOutput>; + dataLoading$?: PublishingSubject<boolean | undefined>; } export interface InlineEditLensEmbeddableContext { diff --git a/x-pack/plugins/lens/public/trigger_actions/utils.ts b/x-pack/plugins/lens/public/trigger_actions/utils.ts deleted file mode 100644 index 527f1adcf762..000000000000 --- a/x-pack/plugins/lens/public/trigger_actions/utils.ts +++ /dev/null @@ -1,13 +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 type { IEmbeddable } from '@kbn/embeddable-plugin/public'; -import type { Embeddable } from '../embeddable'; -import { DOC_TYPE } from '../../common/constants'; - -export function isLensEmbeddable(embeddable: IEmbeddable): embeddable is Embeddable { - return embeddable.type === DOC_TYPE; -} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 5b5e33564cc7..d6dbccc492a6 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -10,7 +10,7 @@ import type { CoreStart, SavedObjectReference, ResolvedSimpleSavedObject } from import type { ColorMapping, PaletteOutput } from '@kbn/coloring'; import type { TopNavMenuData } from '@kbn/navigation-plugin/public'; import type { MutableRefObject, ReactElement } from 'react'; -import type { Filter, TimeRange } from '@kbn/es-query'; +import type { Query, AggregateQuery, Filter, TimeRange } from '@kbn/es-query'; import type { ExpressionAstExpression, IInterpreterRenderHandlers, @@ -22,7 +22,6 @@ import type { NavigateToLensContext, SeriesType, } from '@kbn/visualizations-plugin/common'; -import type { Query } from '@kbn/es-query'; import type { UiActionsStart, RowClickContext, @@ -63,7 +62,7 @@ import { import type { LensInspector } from './lens_inspector_service'; import type { DataViewsState } from './state_management/types'; import type { IndexPatternServiceAPI } from './data_views_service/service'; -import type { Document } from './persistence/saved_object_store'; +import type { LensDocument } from './persistence/saved_object_store'; import { TableInspectorAdapter } from './editor_frame_service/types'; export type StartServices = Pick< @@ -140,8 +139,8 @@ export interface EditorFrameInstance { export interface EditorFrameSetup { // generic type on the API functions to pull the "unknown vs. specific type" error into the implementation - registerDatasource: <T, P>( - datasource: Datasource<T, P> | (() => Promise<Datasource<T, P>>) + registerDatasource: <T, P, Q>( + datasource: Datasource<T, P, Q> | (() => Promise<Datasource<T, P, Q>>) ) => void; registerVisualization: <T, P, ExtraAppendLayerArg>( visualization: @@ -322,8 +321,11 @@ export type AddUserMessages = (messages: UserMessage[]) => () => void; /** * Interface for the datasource registry + * T type: runtime Lens state + * P type: persisted Lens state + * Q type: Query type (useful to filter form vs text based queries) */ -export interface Datasource<T = unknown, P = unknown> { +export interface Datasource<T = unknown, P = unknown, Q = Query | AggregateQuery> { id: string; alias?: string[]; @@ -382,7 +384,7 @@ export interface Datasource<T = unknown, P = unknown> { LayerSettingsComponent?: ( props: DatasourceLayerSettingsProps<T> ) => React.ReactElement<DatasourceLayerSettingsProps<T>> | null; - DataPanelComponent: (props: DatasourceDataPanelProps<T>) => JSX.Element | null; + DataPanelComponent: (props: DatasourceDataPanelProps<T, Q>) => JSX.Element | null; DimensionTriggerComponent: (props: DatasourceDimensionTriggerProps<T>) => JSX.Element | null; DimensionEditorComponent: ( props: DatasourceDimensionEditorProps<T> @@ -579,7 +581,7 @@ export interface DatasourceLayerSettingsProps<T = unknown> { setState: StateSetter<T>; } -export interface DatasourceDataPanelProps<T = unknown> { +export interface DatasourceDataPanelProps<T = unknown, Q = Query | AggregateQuery> { state: T; setState: StateSetter<T, { applyImmediately?: boolean }>; showNoDataPopover: () => void; @@ -587,7 +589,7 @@ export interface DatasourceDataPanelProps<T = unknown> { CoreStart, 'http' | 'notifications' | 'uiSettings' | 'overlays' | 'theme' | 'application' | 'docLinks' >; - query: Query; + query: Q; dateRange: DateRange; filters: Filter[]; dropOntoWorkspace: (field: DragDropIdentifier) => void; @@ -946,7 +948,7 @@ export interface VisualizationSuggestion<T = unknown> { export type DatasourceLayers = Partial<Record<string, DatasourcePublicAPI>>; export interface FramePublicAPI { - query: Query; + query: Query | AggregateQuery; filters: Filter[]; datasourceLayers: DatasourceLayers; dateRange: DateRange; @@ -1456,10 +1458,10 @@ export type LensTopNavMenuEntryGenerator = (props: { visualizationId: string; datasourceStates: Record<string, { state: unknown }>; visualizationState: unknown; - query: Query; + query: Query | AggregateQuery; filters: Filter[]; initialContext?: VisualizeFieldContext | VisualizeEditorContext; - currentDoc: Document | undefined; + currentDoc: LensDocument | undefined; }) => undefined | TopNavMenuData; export interface LensCellValueAction { @@ -1473,3 +1475,5 @@ export interface LensCellValueAction { export type GetCompatibleCellValueActions = ( data: CellValueContext['data'] ) => Promise<LensCellValueAction[]>; + +export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {}; diff --git a/x-pack/plugins/lens/public/user_messages_ids.ts b/x-pack/plugins/lens/public/user_messages_ids.ts index a57e5f871cbf..1bd15a642ba3 100644 --- a/x-pack/plugins/lens/public/user_messages_ids.ts +++ b/x-pack/plugins/lens/public/user_messages_ids.ts @@ -95,3 +95,6 @@ export const GAUGE_METRIC_GT_MAX = 'gauge_metric_gt_max'; export const GAUGE_GOAL_GT_MAX = 'gauge_goal_gt_max'; export const TEXT_BASED_LANGUAGE_ERROR = 'text_based_lang_error'; + +export const URL_CONFLICT = 'url-conflict'; +export const MISSING_TIME_RANGE_ON_EMBEDDABLE = 'missing-time-range-on-embeddable'; diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index 43129161adde..0b0c7037d076 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -20,7 +20,7 @@ import { ISearchStart } from '@kbn/data-plugin/public'; import type { DraggingIdentifier, DropType } from '@kbn/dom-drag-drop'; import { getAbsoluteTimeRange } from '@kbn/data-plugin/common'; import { DateRange } from '../common/types'; -import type { Document } from './persistence/saved_object_store'; +import type { LensDocument } from './persistence/saved_object_store'; import { Datasource, DatasourceMap, @@ -100,7 +100,7 @@ export function getTimeZone(uiSettings: IUiSettingsClient) { return configuredTimeZone; } -export function getActiveDatasourceIdFromDoc(doc?: Document) { +export function getActiveDatasourceIdFromDoc(doc?: LensDocument) { if (!doc) { return null; } @@ -109,14 +109,14 @@ export function getActiveDatasourceIdFromDoc(doc?: Document) { return firstDatasourceFromDoc || null; } -export function getActiveVisualizationIdFromDoc(doc?: Document) { +export function getActiveVisualizationIdFromDoc(doc?: LensDocument) { if (!doc) { return null; } return doc.visualizationType || null; } -export const getInitialDatasourceId = (datasourceMap: DatasourceMap, doc?: Document) => { +export const getInitialDatasourceId = (datasourceMap: DatasourceMap, doc?: LensDocument) => { return (doc && getActiveDatasourceIdFromDoc(doc)) || Object.keys(datasourceMap)[0] || null; }; diff --git a/x-pack/plugins/lens/public/vis_type_alias.ts b/x-pack/plugins/lens/public/vis_type_alias.ts index 5f08ce1b9ced..74e5a2a0d7ac 100644 --- a/x-pack/plugins/lens/public/vis_type_alias.ts +++ b/x-pack/plugins/lens/public/vis_type_alias.ts @@ -7,15 +7,22 @@ import { i18n } from '@kbn/i18n'; import type { VisTypeAlias } from '@kbn/visualizations-plugin/public'; -import { getBasePath, getEditPath } from '../common/constants'; +import { + APP_ID, + getBasePath, + getEditPath, + LENS_EMBEDDABLE_TYPE, + LENS_ICON, + STAGE_ID, +} from '../common/constants'; import { getLensClient } from './persistence/lens_client'; export const getLensAliasConfig = (): VisTypeAlias => ({ alias: { path: getBasePath(), - app: 'lens', + app: APP_ID, }, - name: 'lens', + name: APP_ID, promotion: true, title: i18n.translate('xpack.lens.visTypeAlias.title', { defaultMessage: 'Lens', @@ -25,11 +32,11 @@ export const getLensAliasConfig = (): VisTypeAlias => ({ 'Create visualizations using an intuitive drag-and-drop interface. Smart suggestions help you follow best practices and find the chart types that best match your data.', }), order: 60, - icon: 'lensApp', - stage: 'production', + icon: LENS_ICON, + stage: STAGE_ID, appExtensions: { visualizations: { - docTypes: ['lens'], + docTypes: [LENS_EMBEDDABLE_TYPE], searchFields: ['title^3'], clientOptions: { update: { overwrite: true } }, client: getLensClient, @@ -43,10 +50,10 @@ export const getLensAliasConfig = (): VisTypeAlias => ({ updatedAt, managed, editor: { editUrl: getEditPath(id), editApp: 'lens' }, - icon: 'lensApp', - stage: 'production', + icon: LENS_ICON, + stage: STAGE_ID, savedObjectType: type, - type: 'lens', + type: LENS_EMBEDDABLE_TYPE, typeTitle: i18n.translate('xpack.lens.visTypeAlias.type', { defaultMessage: 'Lens' }), }; }, diff --git a/x-pack/plugins/lens/public/visualization_container.scss b/x-pack/plugins/lens/public/visualization_container.scss index 3eb4061f8b93..488b138cb069 100644 --- a/x-pack/plugins/lens/public/visualization_container.scss +++ b/x-pack/plugins/lens/public/visualization_container.scss @@ -27,7 +27,7 @@ } // Make the visualization modifiers icon appear only on panel hover -.embPanel__content:hover .lnsEmbeddablePanelFeatureList_button { +.embPanel__content:hover .lnsPanelFeatureList_button { color: $euiTextColor; background: $euiColorEmptyShade; transition: color $euiAnimSpeedSlow, background $euiAnimSpeedSlow; diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.scss b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.scss index 0f0ed53a1002..117c6797c362 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.scss +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.scss @@ -1,5 +1,10 @@ .lnsDataTableContainer { height: 100%; + + // EUI issue to make background transparent https://github.com/elastic/eui/issues/8136 + .euiDataGrid__content { + background: transparent; + } } .lnsTableCell--multiline { diff --git a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx index 0633c13b8097..f30686a44a6a 100644 --- a/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/visualizations/datatable/components/table_basic.tsx @@ -70,7 +70,8 @@ export const DataContext = React.createContext<DataContextType>({}); const gridStyle: EuiDataGridStyle = { border: 'horizontal', - header: 'underline', + header: 'shade', + footer: 'shade', }; export const DEFAULT_PAGE_SIZE = 10; diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index db249f19f361..39ec69385644 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -88,7 +88,6 @@ "@kbn/core-plugins-server", "@kbn/esql", "@kbn/field-utils", - "@kbn/panel-loader", "@kbn/shared-ux-button-toolbar", "@kbn/cell-actions", "@kbn/presentation-containers", @@ -111,9 +110,14 @@ "@kbn/licensing-plugin", "@kbn/react-kibana-context-render", "@kbn/react-kibana-mount", + "@kbn/embeddable-enhanced-plugin", "@kbn/es-types", "@kbn/esql-datagrid", "@kbn/transpose-utils", + "@kbn/core-application-browser", + "@kbn/core-chrome-browser", + "@kbn/core-capabilities-common", + "@kbn/presentation-panel-plugin", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/lists/server/get_space_id.test.ts b/x-pack/plugins/lists/server/get_space_id.test.ts index 3faaf5f1c142..980a4125d254 100644 --- a/x-pack/plugins/lists/server/get_space_id.test.ts +++ b/x-pack/plugins/lists/server/get_space_id.test.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { httpServerMock } from '@kbn/core/server/mocks'; -import { CoreKibanaRequest } from '@kbn/core/server'; import { spacesServiceMock } from '@kbn/spaces-plugin/server/spaces_service/spaces_service.mock'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; import { getSpaceId } from './get_space_id'; describe('get_space_id', () => { - let request = CoreKibanaRequest.from(httpServerMock.createRawRequest({})); + let request = kibanaRequestFactory(httpServerMock.createRawRequest({})); beforeEach(() => { - request = CoreKibanaRequest.from(httpServerMock.createRawRequest({})); + request = kibanaRequestFactory(httpServerMock.createRawRequest({})); jest.clearAllMocks(); }); diff --git a/x-pack/plugins/lists/tsconfig.json b/x-pack/plugins/lists/tsconfig.json index a7d1c5b7b315..61229d39bdbd 100644 --- a/x-pack/plugins/lists/tsconfig.json +++ b/x-pack/plugins/lists/tsconfig.json @@ -42,7 +42,9 @@ "@kbn/core-elasticsearch-client-server-mocks", "@kbn/core-saved-objects-server", "@kbn/zod-helpers", - "@kbn/core-security-server" + "@kbn/core-security-server", + "@kbn/core-http-server-mocks", + "@kbn/core-http-server-utils" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/ml/common/types/storage.test.tsx b/x-pack/plugins/ml/common/types/storage.test.tsx index a1473b23c5dc..5f3cc1222547 100644 --- a/x-pack/plugins/ml/common/types/storage.test.tsx +++ b/x-pack/plugins/ml/common/types/storage.test.tsx @@ -7,7 +7,8 @@ import type { FC } from 'react'; import React from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; import { StorageContextProvider, useStorage } from '@kbn/ml-local-storage'; @@ -61,12 +62,9 @@ describe('useStorage', () => { }); test('updates the storage value', async () => { - const { result, waitForNextUpdate } = renderHook( - () => useStorage('ml.gettingStarted.isDismissed'), - { - wrapper: Provider, - } - ); + const { result } = renderHook(() => useStorage('ml.gettingStarted.isDismissed'), { + wrapper: Provider, + }); const [value, setValue] = result.current; @@ -74,7 +72,6 @@ describe('useStorage', () => { await act(async () => { setValue(false); - await waitForNextUpdate(); }); expect(result.current[0]).toBe(false); @@ -82,12 +79,9 @@ describe('useStorage', () => { }); test('removes the storage value', async () => { - const { result, waitForNextUpdate } = renderHook( - () => useStorage('ml.gettingStarted.isDismissed'), - { - wrapper: Provider, - } - ); + const { result } = renderHook(() => useStorage('ml.gettingStarted.isDismissed'), { + wrapper: Provider, + }); const [value, setValue] = result.current; @@ -95,7 +89,6 @@ describe('useStorage', () => { await act(async () => { setValue(undefined); - await waitForNextUpdate(); }); expect(result.current[0]).toBe(undefined); @@ -103,12 +96,9 @@ describe('useStorage', () => { }); test('updates the value on storage event', async () => { - const { result, waitForNextUpdate } = renderHook( - () => useStorage('ml.gettingStarted.isDismissed'), - { - wrapper: Provider, - } - ); + const { result } = renderHook(() => useStorage('ml.gettingStarted.isDismissed'), { + wrapper: Provider, + }); expect(result.current[0]).toBe(true); @@ -130,7 +120,6 @@ describe('useStorage', () => { newValue: null, }) ); - await waitForNextUpdate(); }); expect(result.current[0]).toBe(undefined); @@ -142,7 +131,6 @@ describe('useStorage', () => { newValue: 'false', }) ); - await waitForNextUpdate(); }); expect(result.current[0]).toBe(false); diff --git a/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.test.tsx b/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.test.tsx index a2623266712d..b78164804f9c 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.test.tsx +++ b/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { of, throwError } from 'rxjs'; import { useMlNotifications, MlNotificationsContextProvider } from './ml_notifications_context'; import { useStorage } from '@kbn/ml-local-storage'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/explorer_chart_label.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/explorer_chart_label.js index cb5a705a4870..a83b1a5a472a 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/explorer_chart_label.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/explorer_chart_label.js @@ -94,7 +94,7 @@ export function ExplorerChartLabel({ } ExplorerChartLabel.propTypes = { detectorLabel: PropTypes.object.isRequired, - isEmbeddable: PropTypes.boolean, + isEmbeddable: PropTypes.bool, entityFields: PropTypes.arrayOf(ExplorerChartLabelBadge.propTypes.entity), infoTooltip: PropTypes.object.isRequired, wrapLabel: PropTypes.bool, diff --git a/x-pack/plugins/ml/public/application/hooks/use_as_observable.test.ts b/x-pack/plugins/ml/public/application/hooks/use_as_observable.test.ts index d8680f8393e6..078300e3733e 100644 --- a/x-pack/plugins/ml/public/application/hooks/use_as_observable.test.ts +++ b/x-pack/plugins/ml/public/application/hooks/use_as_observable.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, renderHook } from '@testing-library/react'; import { useAsObservable } from './use_as_observable'; describe('useAsObservable', () => { @@ -16,6 +16,8 @@ describe('useAsObservable', () => { test('provides and observable preserving a reference', () => { const { result, rerender } = renderHook(useAsObservable, { initialProps: 1 }); + const initial = result.current; + let observableValue; const subscriptionMock = jest.fn((v) => (observableValue = v)); @@ -27,7 +29,7 @@ describe('useAsObservable', () => { act(() => rerender(1)); - expect(result.all[0]).toStrictEqual(result.all[1]); + expect(initial).toStrictEqual(result.current); expect(subscriptionMock).toHaveBeenCalledWith(1); expect(subscriptionMock).toHaveBeenCalledTimes(1); @@ -35,7 +37,7 @@ describe('useAsObservable', () => { }); test('updates the subject with a new value', async () => { - const { result, rerender, waitForNextUpdate } = renderHook(useAsObservable, { + const { result, rerender } = renderHook(useAsObservable, { initialProps: 'test', }); @@ -50,7 +52,6 @@ describe('useAsObservable', () => { await act(async () => { rerender('test update'); - await waitForNextUpdate(); }); expect(subscriptionMock).toHaveBeenCalledTimes(2); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/quick_create_job.ts b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/quick_create_job.ts index 99e605fd5086..cda0e842f2c9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/quick_create_job.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/job_from_lens/quick_create_job.ts @@ -14,7 +14,7 @@ import type { import type { IUiSettingsClient } from '@kbn/core/public'; import type { TimefilterContract } from '@kbn/data-plugin/public'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; -import type { Filter, Query } from '@kbn/es-query'; +import { isOfAggregateQueryType, type Filter, type Query } from '@kbn/es-query'; import type { DashboardStart } from '@kbn/dashboard-plugin/public'; import type { LensApi } from '@kbn/lens-plugin/public'; import type { JobCreatorType } from '../common/job_creator'; @@ -198,6 +198,10 @@ export class QuickLensJobCreator extends QuickJobCreatorBase { bucketSpan: string, layerIndex?: number ) { + // @TODO: ask ML team to check if ES|QL query here is ok + if (isOfAggregateQueryType(chartInfo.query)) { + throw new Error('Cannot create job, query is of aggregate type'); + } const compatibleLayers = chartInfo.layers.filter(isCompatibleLayer); const selectedLayer = diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index a717995d4ee1..d66ab1ab3db1 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -697,7 +697,7 @@ export const ModelsList: FC<Props> = ({ <> {downloadState ? ( - (downloadState.downloaded_parts / downloadState.total_parts) * + (downloadState.downloaded_parts / (downloadState.total_parts || -1)) * 100 ).toFixed(0) + '%' : '100%'} diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts index b3b59c3f12a4..164cbe1b2d95 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useMlKibana, useMlLicenseInfo } from '../contexts/kibana'; import { usePermissionCheck } from '../capabilities/check_capabilities'; import { useRouteResolver } from './use_resolver'; @@ -63,8 +63,8 @@ describe('useResolver', () => { it.skip('redirects to the access denied page if some required capabilities are missing', async () => { (usePermissionCheck as jest.Mock<boolean[]>).mockReturnValueOnce([false]); - const { waitForNextUpdate } = renderHook(() => useRouteResolver('full', ['canGetCalendars'])); - await waitForNextUpdate(); + renderHook(() => useRouteResolver('full', ['canGetCalendars'])); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(useMlKibana().services.application.navigateToUrl).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index 9c4ea6b1d330..fa779258e5f2 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -354,6 +354,10 @@ export function jobsProvider( const result: { datafeed?: Datafeed; job?: Job } = { job: undefined, datafeed: undefined }; if (datafeedResult && datafeedResult.job_id === jobId) { result.datafeed = datafeedResult; + if (result.datafeed.indices_options?.ignore_throttled !== undefined) { + // ignore_throttled is a deprecated setting, remove it from the response + delete result.datafeed.indices_options.ignore_throttled; + } } if (jobResults?.jobs?.length > 0) { diff --git a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts index 27e1b6afe336..2c61dca8859b 100644 --- a/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts @@ -27,7 +27,10 @@ export const indicesOptionsSchema = schema.object({ ), ignore_unavailable: schema.maybe(schema.boolean()), allow_no_indices: schema.maybe(schema.boolean()), + // retaining the deprecated ignore_throttled in case an older jobs are used + // we don't want to fail the schema validation if this is present ignore_throttled: schema.maybe(schema.boolean()), + failure_store: schema.maybe(schema.string()), }); export const datafeedConfigSchema = schema.object({ diff --git a/x-pack/plugins/ml/server/shared_services/shared_services.ts b/x-pack/plugins/ml/server/shared_services/shared_services.ts index f7b0d4240922..d4af7166435d 100644 --- a/x-pack/plugins/ml/server/shared_services/shared_services.ts +++ b/x-pack/plugins/ml/server/shared_services/shared_services.ts @@ -5,20 +5,17 @@ * 2.0. */ -import type { - IClusterClient, - IScopedClusterClient, - SavedObjectsClientContract, - UiSettingsServiceStart, - KibanaRequest, - CoreAuditService, -} from '@kbn/core/server'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/server'; -import { CoreKibanaRequest } from '@kbn/core/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; +import { isCoreKibanaRequest } from '@kbn/core-http-server-utils'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { IClusterClient, IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { UiSettingsServiceStart } from '@kbn/core-ui-settings-server'; +import type { CoreAuditService } from '@kbn/core-security-server'; import type { CompatibleModule } from '../../common/constants/app'; import type { MlLicense } from '../../common/license'; @@ -269,7 +266,7 @@ function getRequestItemsProvider( }; let mlSavedObjectService; - if (request instanceof CoreKibanaRequest) { + if (isCoreKibanaRequest(request)) { scopedClient = clusterClient.asScoped(request); mlSavedObjectService = getSobSavedObjectService(scopedClient); const auditLogger = new MlAuditLogger(auditService, request); diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index 73ab75d5f251..e11358f258a3 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -133,5 +133,9 @@ "@kbn/unified-search-plugin", "@kbn/usage-collection-plugin", "@kbn/utility-types", + "@kbn/core-http-server-utils", + "@kbn/core-saved-objects-api-server", + "@kbn/core-ui-settings-server", + "@kbn/core-security-server", ] } diff --git a/x-pack/plugins/observability_solution/apm/dev_docs/testing.md b/x-pack/plugins/observability_solution/apm/dev_docs/testing.md index 02ff43d37bf7..aeb9435a2e11 100644 --- a/x-pack/plugins/observability_solution/apm/dev_docs/testing.md +++ b/x-pack/plugins/observability_solution/apm/dev_docs/testing.md @@ -3,7 +3,9 @@ We've got three ways of testing our code: - Unit testing with Jest -- API testing +- Integration Tests + - Deployment specific (stateful or serverless) API testing + - Deployment-agnostic (both stateful and serverless) testing - End-to-end testing (with Cypress) API tests are usually preferred. They're stable and reasonably quick, and give a good approximation of real-world usage. @@ -73,7 +75,59 @@ node x-pack/plugins/observability_solution/apm/scripts/test/api --runner --basic #### API Test tips -- For data generation in API tests have a look at the [kbn-apm-synthtrace](../../../../packages/kbn-apm-synthtrace/README.md) package +- For data generation in API tests have a look at the [kbn-apm-synthtrace](../../../../../packages/kbn-apm-synthtrace/README.md) package +- For debugging access Elasticsearch on http://localhost:9220 and Kibana on http://localhost:5620 (`elastic` / `changeme`) + +--- + +## Deployment-agnostic Tests (dat) + +| Option | Description | +| ------------ | ----------------------------------------------- | +| --serverless | Loads serverless configuration | +| --stateful | Loads stateful configuration | +| --server | Only start ES and Kibana | +| --runner | Only run tests | +| --grep | Specify the specs to run | +| --grep-files | Specify the files to run | +| --inspect | Add --inspect-brk flag to the ftr for debugging | +| --times | Repeat the test n number of times | + +Deployment-agnostic tests are located in [`x-pack/test/deployment_agnostic/apis/observability/apm/index.ts`](../../../../test/api_integration/deployment_agnostic/apis/observability/apm/index.ts). + +#### Start server and run test (single process) + +``` +node x-pack/plugins/observability_solution/apm/scripts/test/dat [--serverless/--stateful] [--help] +``` + +The above command will start an ES instance on http://localhost:9220, a Kibana instance on http://localhost:5620 and run the api tests. +Once the tests finish, the instances will be terminated. + +#### Start server and run test (separate processes) + +```sh + +# start server +node x-pack/plugins/observability_solution/apm/scripts/test/dat --server --stateful + +# run tests +node x-pack/plugins/observability_solution/apm/scripts/test/dat --runner --stateful --grep-files=error_group_list +``` + +### Update snapshots (from Kibana root) + +To update snapshots append `--updateSnapshots` to the `--runner` command: + +``` +node x-pack/plugins/observability_solution/apm/scripts/test/dat --runner --stateful --updateSnapshots +``` + +(The test server needs to be running) + +#### API Test tips + +- For data generation in Deployment-agnostic tests have a look at the [kbn-apm-synthtrace](../../../../../packages/kbn-apm-synthtrace/README.md) package - For debugging access Elasticsearch on http://localhost:9220 and Kibana on http://localhost:5620 (`elastic` / `changeme`) --- diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx index 5432c70e57ab..4b93123b589b 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React, { ReactNode } from 'react'; +import React, { PropsWithChildren } from 'react'; import { merge } from 'lodash'; import { createMemoryHistory } from 'history'; -import { renderHook, act } from '@testing-library/react-hooks'; + +import { act, waitFor, renderHook } from '@testing-library/react'; import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { @@ -22,7 +23,7 @@ import { fromQuery } from '../../shared/links/url_helpers'; import { useFailedTransactionsCorrelations } from './use_failed_transactions_correlations'; import type { APIEndpoint } from '../../../../server'; -function wrapper({ children, error = false }: { children?: ReactNode; error: boolean }) { +function wrapper({ children, error = false }: PropsWithChildren<{ error?: boolean }>) { const getHttpMethodMock = (method: 'GET' | 'POST') => jest.fn().mockImplementation(async (pathname) => { await delay(100); @@ -107,17 +108,18 @@ describe('useFailedTransactionsCorrelations', () => { wrapper, }); - try { + await waitFor(() => expect(result.current.progress).toEqual({ isRunning: true, loaded: 0, - }); - expect(result.current.response).toEqual({ ccsWarning: false }); - expect(typeof result.current.startFetch).toEqual('function'); - expect(typeof result.current.cancelFetch).toEqual('function'); - } finally { - unmount(); - } + }) + ); + + expect(result.current.response).toEqual({ ccsWarning: false }); + expect(result.current.startFetch).toEqual(expect.any(Function)); + expect(result.current.cancelFetch).toEqual(expect.any(Function)); + + unmount(); }); it('should not have received any results after 50ms', async () => { @@ -125,21 +127,21 @@ describe('useFailedTransactionsCorrelations', () => { wrapper, }); - try { - jest.advanceTimersByTime(50); + jest.advanceTimersByTime(50); + await waitFor(() => expect(result.current.progress).toEqual({ isRunning: true, loaded: 0, - }); - expect(result.current.response).toEqual({ ccsWarning: false }); - } finally { - unmount(); - } + }) + ); + + expect(result.current.response).toEqual({ ccsWarning: false }); + unmount(); }); it('should receive partial updates and finish running', async () => { - const { result, unmount, waitFor } = renderHook(() => useFailedTransactionsCorrelations(), { + const { result, unmount } = renderHook(() => useFailedTransactionsCorrelations(), { wrapper, }); @@ -253,29 +255,37 @@ describe('useFailedTransactionsCorrelations', () => { }); describe('when throwing an error', () => { it('should automatically start fetching results', async () => { - const { result, unmount } = renderHook(() => useFailedTransactionsCorrelations(), { - wrapper, - initialProps: { - error: true, - }, + const { result, unmount } = renderHook(useFailedTransactionsCorrelations, { + wrapper: ({ children }) => + React.createElement( + wrapper, + { + error: true, + }, + children + ), }); - try { + await waitFor(() => expect(result.current.progress).toEqual({ isRunning: true, loaded: 0, - }); - } finally { - unmount(); - } + }) + ); + + unmount(); }); it('should still be running after 50ms', async () => { - const { result, unmount } = renderHook(() => useFailedTransactionsCorrelations(), { - wrapper, - initialProps: { - error: true, - }, + const { result, unmount } = renderHook(useFailedTransactionsCorrelations, { + wrapper: ({ children }) => + React.createElement( + wrapper, + { + error: true, + }, + children + ), }); try { @@ -292,11 +302,15 @@ describe('useFailedTransactionsCorrelations', () => { }); it('should stop and return an error after more than 100ms', async () => { - const { result, unmount, waitFor } = renderHook(() => useFailedTransactionsCorrelations(), { - wrapper, - initialProps: { - error: true, - }, + const { result, unmount } = renderHook(useFailedTransactionsCorrelations, { + wrapper: ({ children }) => + React.createElement( + wrapper, + { + error: true, + }, + children + ), }); try { @@ -316,7 +330,7 @@ describe('useFailedTransactionsCorrelations', () => { describe('when canceled', () => { it('should stop running', async () => { - const { result, unmount, waitFor } = renderHook(() => useFailedTransactionsCorrelations(), { + const { result, unmount } = renderHook(() => useFailedTransactionsCorrelations(), { wrapper, }); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/use_latency_correlations.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/use_latency_correlations.test.tsx index 70446d1d2b1f..e76420e3c1a9 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/correlations/use_latency_correlations.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/correlations/use_latency_correlations.test.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import React, { ReactNode } from 'react'; +import React, { PropsWithChildren } from 'react'; import { merge } from 'lodash'; import { createMemoryHistory } from 'history'; -import { renderHook, act } from '@testing-library/react-hooks'; + +import { act, waitFor, renderHook } from '@testing-library/react'; import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { @@ -22,7 +23,7 @@ import { fromQuery } from '../../shared/links/url_helpers'; import { useLatencyCorrelations } from './use_latency_correlations'; import type { APIEndpoint } from '../../../../server'; -function wrapper({ children, error = false }: { children?: ReactNode; error: boolean }) { +function wrapper({ children, error = false }: PropsWithChildren<{ error?: boolean }>) { const getHttpMethodMock = (method: 'GET' | 'POST') => jest.fn().mockImplementation(async (pathname) => { await delay(100); @@ -86,16 +87,17 @@ function wrapper({ children, error = false }: { children?: ReactNode; error: boo } describe('useLatencyCorrelations', () => { - beforeEach(async () => { - jest.useFakeTimers({ legacyFakeTimers: true }); - }); - afterEach(() => { - jest.useRealTimers(); - }); - describe('when successfully loading results', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('should automatically start fetching results', async () => { - const { result, unmount } = renderHook(() => useLatencyCorrelations(), { + const { result, unmount } = renderHook(useLatencyCorrelations, { wrapper, }); @@ -113,7 +115,7 @@ describe('useLatencyCorrelations', () => { }); it('should not have received any results after 50ms', async () => { - const { result, unmount } = renderHook(() => useLatencyCorrelations(), { + const { result, unmount } = renderHook(useLatencyCorrelations, { wrapper, }); @@ -131,7 +133,7 @@ describe('useLatencyCorrelations', () => { }); it('should receive partial updates and finish running', async () => { - const { result, unmount, waitFor } = renderHook(() => useLatencyCorrelations(), { + const { result, unmount } = renderHook(useLatencyCorrelations, { wrapper, }); @@ -213,12 +215,17 @@ describe('useLatencyCorrelations', () => { }); describe('when throwing an error', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('should automatically start fetching results', async () => { - const { result, unmount } = renderHook(() => useLatencyCorrelations(), { - wrapper, - initialProps: { - error: true, - }, + const { result, unmount } = renderHook(useLatencyCorrelations, { + wrapper: ({ children }) => wrapper({ children, error: true }), }); try { @@ -232,11 +239,8 @@ describe('useLatencyCorrelations', () => { }); it('should still be running after 50ms', async () => { - const { result, unmount } = renderHook(() => useLatencyCorrelations(), { - wrapper, - initialProps: { - error: true, - }, + const { result, unmount } = renderHook(useLatencyCorrelations, { + wrapper: ({ children }) => wrapper({ children, error: true }), }); try { @@ -253,22 +257,21 @@ describe('useLatencyCorrelations', () => { }); it('should stop and return an error after more than 100ms', async () => { - const { result, unmount, waitFor } = renderHook(() => useLatencyCorrelations(), { - wrapper, - initialProps: { - error: true, - }, + const { result, unmount } = renderHook(useLatencyCorrelations, { + wrapper: ({ children }) => wrapper({ children, error: true }), }); try { - jest.advanceTimersByTime(150); - await waitFor(() => expect(result.current.progress.error).toBeDefined()); - - expect(result.current.progress).toEqual({ - error: 'Something went wrong', - isRunning: false, - loaded: 0, + act(() => { + jest.advanceTimersByTime(150); }); + await waitFor(() => + expect(result.current.progress).toEqual({ + error: 'Something went wrong', + isRunning: false, + loaded: 0, + }) + ); } finally { unmount(); } @@ -276,8 +279,16 @@ describe('useLatencyCorrelations', () => { }); describe('when canceled', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('should stop running', async () => { - const { result, unmount, waitFor } = renderHook(() => useLatencyCorrelations(), { + const { result, unmount } = renderHook(useLatencyCorrelations, { wrapper, }); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/infra_overview/infra_tabs/use_tabs.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/infra_overview/infra_tabs/use_tabs.test.tsx index 245d4e90631a..b1a1fea3924e 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/infra_overview/infra_tabs/use_tabs.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/infra_overview/infra_tabs/use_tabs.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React, { ReactNode } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useTabs } from './use_tabs'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import { CoreStart } from '@kbn/core/public'; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/filter_warning.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/filter_warning.tsx new file mode 100644 index 000000000000..71b487ff626c --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/filter_warning.tsx @@ -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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +interface Props { + hostNames?: string[]; + containerIds?: string[]; +} + +export function FilterWarning({ containerIds = [], hostNames = [] }: Props) { + const hasContainerIds = containerIds.length > 0; + + return hasContainerIds ? ( + <FilterWarningToolTip + values={containerIds} + label={i18n.translate('xpack.apm.profiling.topFunctions.filteredLabel.containerId', { + defaultMessage: "Displaying profiling insights from the service's container id(s)", + })} + /> + ) : ( + <FilterWarningToolTip + values={hostNames} + label={i18n.translate('xpack.apm.profiling.topFunctions.filteredLabel.hostName', { + defaultMessage: "Displaying profiling insights from the service's host(s)", + })} + /> + ); +} + +interface FilterWarningToolTipProps { + values: string[]; + label: string; +} +function FilterWarningToolTip({ values = [], label }: FilterWarningToolTipProps) { + function renderTooltipOptions() { + return ( + <ul> + {values.map((value) => ( + <li key={value}>{`- ${value}`}</li> + ))} + </ul> + ); + } + + return ( + <EuiFlexGroup gutterSize="none"> + <EuiFlexItem grow={false}> + <EuiText size="xs" color="subdued"> + {label} + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiToolTip content={renderTooltipOptions()}> + <EuiIcon type="questionInCircle" /> + </EuiToolTip> + </EuiFlexItem> + </EuiFlexGroup> + ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/host_names_filter_warning.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/host_names_filter_warning.tsx deleted file mode 100644 index 0f3fb6cdb69f..000000000000 --- a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/host_names_filter_warning.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiToolTip } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -interface Props { - hostNames?: string[]; -} -export function HostnamesFilterWarning({ hostNames = [] }: Props) { - function renderTooltipOptions() { - return ( - <ul> - {hostNames.map((hostName) => ( - <li key={hostName}>{`- ${hostName}`}</li> - ))} - </ul> - ); - } - - return ( - <EuiFlexGroup gutterSize="none"> - <EuiFlexItem grow={false}> - <EuiText size="xs" color="subdued"> - {i18n.translate('xpack.apm.profiling.flamegraph.filteredLabel', { - defaultMessage: "Displaying profiling insights from the service's host(s)", - })} - </EuiText> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiToolTip content={renderTooltipOptions()}> - <EuiIcon type="questionInCircle" /> - </EuiToolTip> - </EuiFlexItem> - </EuiFlexGroup> - ); -} diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_flamegraph.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_flamegraph.tsx index 92442d223390..0d6681d942d5 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_flamegraph.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_flamegraph.tsx @@ -9,12 +9,12 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { ApmDataSourceWithSummary } from '../../../../common/data_source'; import { ApmDocumentType } from '../../../../common/document_type'; -import { HOST_NAME } from '../../../../common/es_fields/apm'; +import { CONTAINER_ID, HOST_NAME } from '../../../../common/es_fields/apm'; import { mergeKueries, toKueryFilterFormat } from '../../../../common/utils/kuery_utils'; import { useFetcher } from '../../../hooks/use_fetcher'; import { FlamegraphChart } from '../../shared/charts/flamegraph'; import { ProfilingFlamegraphLink } from '../../shared/profiling/flamegraph/flamegraph_link'; -import { HostnamesFilterWarning } from './host_names_filter_warning'; +import { FilterWarning } from './filter_warning'; interface Props { serviceName: string; @@ -60,17 +60,20 @@ export function ProfilingHostsFlamegraph({ [dataSource, serviceName, start, end, environment, kuery] ); - const hostNamesKueryFormat = toKueryFilterFormat(HOST_NAME, data?.hostNames || []); + const profilingKueryFilter = + data?.containerIds && data.containerIds.length > 0 + ? toKueryFilterFormat(CONTAINER_ID, data?.containerIds || []) + : toKueryFilterFormat(HOST_NAME, data?.hostNames || []); return ( <> <EuiFlexGroup> <EuiFlexItem grow={false}> - <HostnamesFilterWarning hostNames={data?.hostNames} /> + <FilterWarning containerIds={data?.containerIds} hostNames={data?.hostNames} /> </EuiFlexItem> <EuiFlexItem> <ProfilingFlamegraphLink - kuery={mergeKueries([`(${hostNamesKueryFormat})`, kuery])} + kuery={mergeKueries([`(${profilingKueryFilter})`, kuery])} rangeFrom={rangeFrom} rangeTo={rangeTo} justifyContent="flexEnd" diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_top_functions.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_top_functions.tsx index 88726f880b66..2ad1106ab9ad 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_top_functions.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/profiling_overview/profiling_hosts_top_functions.tsx @@ -10,11 +10,11 @@ import { EmbeddableFunctions } from '@kbn/observability-shared-plugin/public'; import React from 'react'; import { ApmDataSourceWithSummary } from '../../../../common/data_source'; import { ApmDocumentType } from '../../../../common/document_type'; -import { HOST_NAME } from '../../../../common/es_fields/apm'; +import { CONTAINER_ID, HOST_NAME } from '../../../../common/es_fields/apm'; import { mergeKueries, toKueryFilterFormat } from '../../../../common/utils/kuery_utils'; import { isPending, useFetcher } from '../../../hooks/use_fetcher'; import { ProfilingTopNFunctionsLink } from '../../shared/profiling/top_functions/top_functions_link'; -import { HostnamesFilterWarning } from './host_names_filter_warning'; +import { FilterWarning } from './filter_warning'; interface Props { serviceName: string; @@ -66,17 +66,20 @@ export function ProfilingHostsTopNFunctions({ [dataSource, serviceName, start, end, environment, startIndex, endIndex, kuery] ); - const hostNamesKueryFormat = toKueryFilterFormat(HOST_NAME, data?.hostNames || []); + const profilingKueryFilter = + data?.containerIds && data.containerIds.length > 0 + ? toKueryFilterFormat(CONTAINER_ID, data?.containerIds || []) + : toKueryFilterFormat(HOST_NAME, data?.hostNames || []); return ( <> <EuiFlexGroup> <EuiFlexItem grow={false}> - <HostnamesFilterWarning hostNames={data?.hostNames} /> + <FilterWarning containerIds={data?.containerIds} hostNames={data?.hostNames} /> </EuiFlexItem> <EuiFlexItem> <ProfilingTopNFunctionsLink - kuery={mergeKueries([`(${hostNamesKueryFormat})`, kuery])} + kuery={mergeKueries([`(${profilingKueryFilter})`, kuery])} rangeFrom={rangeFrom} rangeTo={rangeTo} justifyContent="flexEnd" diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_map/empty_banner.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_map/empty_banner.test.tsx index 20149d0f4079..168180e89c97 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_map/empty_banner.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_map/empty_banner.test.tsx @@ -58,11 +58,12 @@ describe('EmptyBanner', () => { it('does not render null', async () => { const component = renderWithTheme(<EmptyBanner />, { wrapper }); - await act(async () => { + act(() => { cy.add({ data: { id: 'test id' } }); - await waitFor(() => { - expect(component.container.children.length).toBeGreaterThan(0); - }); + }); + + await waitFor(() => { + expect(component.container.children.length).toBeGreaterThan(0); }); }); }); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_map/use_cytoscape_event_handlers.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_map/use_cytoscape_event_handlers.test.tsx index 0f7fd67ae7c0..31604d893401 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_map/use_cytoscape_event_handlers.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_map/use_cytoscape_event_handlers.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import cytoscape from 'cytoscape'; import dagre from 'cytoscape-dagre'; import { EuiTheme } from '@kbn/kibana-react-plugin/common'; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/trace_link/trace_link.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/trace_link/trace_link.test.tsx index 3250702b0cb8..06f7101520ba 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/trace_link/trace_link.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/trace_link/trace_link.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, render, waitFor } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { shallow } from 'enzyme'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; @@ -67,12 +67,9 @@ describe('TraceLink', () => { }, }); - let result; - act(() => { - const component = render(<TraceLink />, renderOptions); + const component = render(<TraceLink />, renderOptions); - result = component.getByText('Fetching trace...'); - }); + const result = component.getByText('Fetching trace...'); await waitFor(() => {}); expect(result).toBeDefined(); }); diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_service_template/use_tabs.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_service_template/use_tabs.test.tsx index 05c8464929d5..fee28395960c 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_service_template/use_tabs.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_service_template/use_tabs.test.tsx @@ -6,7 +6,7 @@ */ import { CoreStart } from '@kbn/core/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import React, { ReactNode } from 'react'; import { ServerlessType } from '../../../../../common/serverless'; diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/charts/timeline/marker/error_marker.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/charts/timeline/marker/error_marker.test.tsx index d71562e425e9..748b78366174 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/charts/timeline/marker/error_marker.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/charts/timeline/marker/error_marker.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { fireEvent } from '@testing-library/react'; -import { act } from '@testing-library/react-hooks'; +import { fireEvent, act } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/links/apm/service_transactions_overview_link.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/links/apm/service_transactions_overview_link.test.tsx index 54999ead161b..ce2a56a3ecc8 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/links/apm/service_transactions_overview_link.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/links/apm/service_transactions_overview_link.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, renderHook } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import React from 'react'; import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; @@ -19,7 +18,7 @@ import { const history = createMemoryHistory(); function wrapper({ queryParams }: { queryParams?: Record<string, unknown> }) { - return ({ children }: { children: React.ReactElement }) => ( + return ({ children }: React.PropsWithChildren) => ( <MockApmPluginContextWrapper history={history}> <MockUrlParamsContextProvider params={queryParams}>{children}</MockUrlParamsContextProvider> </MockApmPluginContextWrapper> diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/links/apm/transaction_overview_link.test.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/links/apm/transaction_overview_link.test.tsx index bf095a4925fc..a1836a2a336e 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/links/apm/transaction_overview_link.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/links/apm/transaction_overview_link.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, renderHook } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import React from 'react'; import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; @@ -15,7 +14,7 @@ import { TransactionOverviewLink, useTransactionsOverviewHref } from './transact const history = createMemoryHistory(); -function Wrapper({ children }: { children: React.ReactElement }) { +function Wrapper({ children }: React.PropsWithChildren) { return ( <MockApmPluginContextWrapper history={history}> <MockUrlParamsContextProvider>{children}</MockUrlParamsContextProvider> diff --git a/x-pack/plugins/observability_solution/apm/public/hooks/use_breakpoints.test.tsx b/x-pack/plugins/observability_solution/apm/public/hooks/use_breakpoints.test.tsx index d311f48fa0e5..7a9a7aa33b6b 100644 --- a/x-pack/plugins/observability_solution/apm/public/hooks/use_breakpoints.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/hooks/use_breakpoints.test.tsx @@ -6,7 +6,7 @@ */ import React, { FC, PropsWithChildren } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { EuiProvider } from '@elastic/eui'; import { useBreakpoints } from './use_breakpoints'; diff --git a/x-pack/plugins/observability_solution/apm/public/hooks/use_debounce.test.tsx b/x-pack/plugins/observability_solution/apm/public/hooks/use_debounce.test.tsx index 6701024eea9e..98543601cab2 100644 --- a/x-pack/plugins/observability_solution/apm/public/hooks/use_debounce.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/hooks/use_debounce.test.tsx @@ -4,11 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; import { useStateDebounced } from './use_debounce'; // Replace 'your-module' with the actual module path describe('useStateDebounced', () => { - jest.useFakeTimers(); beforeAll(() => { // Mocks console.error so it won't polute tests output when testing the api throwing error jest.spyOn(console, 'error').mockImplementation(() => null); @@ -18,6 +18,14 @@ describe('useStateDebounced', () => { jest.restoreAllMocks(); }); + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('returns the initial value and a debounced setter function', () => { const { result } = renderHook(() => useStateDebounced('initialValue', 300)); @@ -34,7 +42,9 @@ describe('useStateDebounced', () => { result.current[1]('updatedValue'); }); expect(result.current[0]).toBe('initialValue'); - jest.advanceTimersByTime(300); + act(() => { + jest.advanceTimersByTime(300); + }); expect(result.current[0]).toBe('updatedValue'); }); @@ -44,12 +54,15 @@ describe('useStateDebounced', () => { act(() => { result.current[1]('updatedValue'); }); - jest.advanceTimersByTime(150); + act(() => { + jest.advanceTimersByTime(150); + }); expect(result.current[0]).toBe('initialValue'); act(() => { result.current[1]('newUpdatedValue'); + jest.advanceTimersByTime(400); }); - jest.advanceTimersByTime(400); + expect(result.current[0]).toBe('newUpdatedValue'); }); }); diff --git a/x-pack/plugins/observability_solution/apm/public/hooks/use_fetcher.test.tsx b/x-pack/plugins/observability_solution/apm/public/hooks/use_fetcher.test.tsx index 14c58ab3977e..be61a03e2bd8 100644 --- a/x-pack/plugins/observability_solution/apm/public/hooks/use_fetcher.test.tsx +++ b/x-pack/plugins/observability_solution/apm/public/hooks/use_fetcher.test.tsx @@ -5,46 +5,42 @@ * 2.0. */ -import { renderHook, RenderHookResult } from '@testing-library/react-hooks'; -import React, { ReactNode } from 'react'; +import React from 'react'; +import { waitFor, act, renderHook, type RenderHookResult } from '@testing-library/react'; import { CoreStart } from '@kbn/core/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import { delay } from '../utils/test_helpers'; -import { FetcherResult, useFetcher, isPending, FETCH_STATUS } from './use_fetcher'; +import { useFetcher, isPending, FETCH_STATUS } from './use_fetcher'; // Wrap the hook with a provider so it can useKibana const KibanaReactContext = createKibanaReactContext({ notifications: { toasts: { add: () => {}, danger: () => {} } }, } as unknown as Partial<CoreStart>); -interface WrapperProps { - children?: ReactNode; - callback: () => Promise<string>; - args: string[]; -} -function wrapper({ children }: WrapperProps) { +function wrapper({ children }: React.PropsWithChildren) { return <KibanaReactContext.Provider>{children}</KibanaReactContext.Provider>; } describe('useFetcher', () => { describe('when resolving after 500ms', () => { - let hook: RenderHookResult< - WrapperProps, - FetcherResult<string> & { - refetch: () => void; - } - >; + let hook: RenderHookResult<ReturnType<typeof useFetcher>, Parameters<typeof useFetcher>>; beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); + jest.useFakeTimers(); + async function fn() { await delay(500); return 'response from hook'; } - hook = renderHook(() => useFetcher(() => fn(), []), { wrapper }); + + hook = renderHook(() => useFetcher(fn, []), { wrapper }); }); - it('should have loading spinner initally', async () => { + afterEach(() => { + jest.useRealTimers(); + }); + + it('should have loading spinner initially', () => { expect(hook.result.current).toEqual({ data: undefined, error: undefined, @@ -53,8 +49,10 @@ describe('useFetcher', () => { }); }); - it('should still show loading spinner after 100ms', async () => { - jest.advanceTimersByTime(100); + it('should still show loading spinner after 100ms', () => { + act(() => { + jest.advanceTimersByTime(100); + }); expect(hook.result.current).toEqual({ data: undefined, @@ -65,8 +63,11 @@ describe('useFetcher', () => { }); it('should show success after 1 second', async () => { - jest.advanceTimersByTime(1000); - await hook.waitForNextUpdate(); + act(() => { + jest.advanceTimersByTime(1000); + }); + + await waitFor(() => expect(hook.result.current.status).toBe('success')); expect(hook.result.current).toEqual({ data: 'response from hook', @@ -78,23 +79,23 @@ describe('useFetcher', () => { }); describe('when throwing after 500ms', () => { - let hook: RenderHookResult< - WrapperProps, - FetcherResult<string> & { - refetch: () => void; - } - >; + let hook: RenderHookResult<ReturnType<typeof useFetcher>, Parameters<typeof useFetcher>>; beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); + jest.useFakeTimers(); + async function fn(): Promise<string> { await delay(500); throw new Error('Something went wrong'); } - hook = renderHook(() => useFetcher(() => fn(), []), { wrapper }); + hook = renderHook(() => useFetcher(fn, []), { wrapper }); + }); + + afterEach(() => { + jest.useRealTimers(); }); - it('should have loading spinner initally', async () => { + it('should have loading spinner initially', () => { expect(hook.result.current).toEqual({ data: undefined, error: undefined, @@ -103,8 +104,10 @@ describe('useFetcher', () => { }); }); - it('should still show loading spinner after 100ms', async () => { - jest.advanceTimersByTime(100); + it('should still show loading spinner after 100ms', () => { + act(() => { + jest.advanceTimersByTime(100); + }); expect(hook.result.current).toEqual({ data: undefined, @@ -115,8 +118,11 @@ describe('useFetcher', () => { }); it('should show error after 1 second', async () => { - jest.advanceTimersByTime(1000); - await hook.waitForNextUpdate(); + act(() => { + jest.advanceTimersByTime(1000); + }); + + await waitFor(() => expect(hook.result.current.status).toBe('failure')); expect(hook.result.current).toEqual({ data: undefined, @@ -128,20 +134,27 @@ describe('useFetcher', () => { }); describe('when a hook already has data', () => { - it('should show "first response" while loading "second response"', async () => { - jest.useFakeTimers({ legacyFakeTimers: true }); + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('should show "first response" while loading "second response"', async () => { const hook = renderHook( /* eslint-disable-next-line react-hooks/exhaustive-deps */ ({ callback, args }) => useFetcher(callback, args), { initialProps: { - callback: async () => 'first response', + callback: () => Promise.resolve('first response'), args: ['a'], }, wrapper, } ); + expect(hook.result.current).toEqual({ data: undefined, error: undefined, @@ -149,15 +162,15 @@ describe('useFetcher', () => { status: 'loading', }); - await hook.waitForNextUpdate(); - // assert: first response has loaded and should be rendered - expect(hook.result.current).toEqual({ - data: 'first response', - error: undefined, - refetch: expect.any(Function), - status: 'success', - }); + await waitFor(() => + expect(hook.result.current).toEqual({ + data: 'first response', + error: undefined, + refetch: expect.any(Function), + status: 'success', + }) + ); // act: re-render hook with async callback hook.rerender({ @@ -168,55 +181,92 @@ describe('useFetcher', () => { args: ['b'], }); - jest.advanceTimersByTime(100); - - // assert: while loading new data the previous data should still be rendered - expect(hook.result.current).toEqual({ - data: 'first response', - error: undefined, - refetch: expect.any(Function), - status: 'loading', + act(() => { + jest.advanceTimersByTime(100); }); - jest.advanceTimersByTime(500); - await hook.waitForNextUpdate(); + // assert: while loading new data the previous data should still be rendered + await waitFor(() => + expect(hook.result.current).toEqual({ + data: 'first response', + error: undefined, + refetch: expect.any(Function), + status: 'loading', + }) + ); - // assert: "second response" has loaded and should be rendered - expect(hook.result.current).toEqual({ - data: 'second response', - error: undefined, - refetch: expect.any(Function), - status: 'success', + act(() => { + jest.advanceTimersByTime(500); }); + + await waitFor(() => + expect(hook.result.current).toEqual({ + data: 'second response', + error: undefined, + refetch: expect.any(Function), + status: 'success', + }) + ); }); it('should return the same object reference when data is unchanged between rerenders', async () => { + const initialProps = { + callback: () => Promise.resolve('data response'), + args: ['a'], + }; + const hook = renderHook( /* eslint-disable-next-line react-hooks/exhaustive-deps */ ({ callback, args }) => useFetcher(callback, args), { - initialProps: { - callback: async () => 'data response', - args: ['a'], - }, + initialProps, wrapper, } ); - await hook.waitForNextUpdate(); + + act(() => { + jest.runAllTimers(); + }); + + // assert: initial data has loaded; + await waitFor(() => + expect(hook.result.current).toEqual( + expect.objectContaining({ + data: 'data response', + status: 'success', + }) + ) + ); + const firstResult = hook.result.current; - hook.rerender(); + hook.rerender(initialProps); + + expect(hook.result.current).toEqual( + expect.objectContaining({ + data: 'data response', + status: 'success', + }) + ); + const secondResult = hook.result.current; // assert: subsequent rerender returns the same object reference expect(secondResult === firstResult).toEqual(true); hook.rerender({ - callback: async () => { - return 'second response'; - }, + callback: () => Promise.resolve('second response'), args: ['b'], }); - await hook.waitForNextUpdate(); + + await waitFor(() => + expect(hook.result.current).toEqual( + expect.objectContaining({ + data: 'second response', + status: 'success', + }) + ) + ); + const thirdResult = hook.result.current; // assert: rerender with different data returns a new object diff --git a/x-pack/plugins/observability_solution/apm/public/hooks/use_time_range.test.ts b/x-pack/plugins/observability_solution/apm/public/hooks/use_time_range.test.ts index 2bbc5c60ca91..2f6c08aad8cc 100644 --- a/x-pack/plugins/observability_solution/apm/public/hooks/use_time_range.test.ts +++ b/x-pack/plugins/observability_solution/apm/public/hooks/use_time_range.test.ts @@ -4,11 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook, RenderHookResult } from '@testing-library/react-hooks'; + +import { renderHook, RenderHookResult } from '@testing-library/react'; import { useTimeRange } from './use_time_range'; describe('useTimeRange', () => { - let hook: RenderHookResult<Parameters<typeof useTimeRange>[0], ReturnType<typeof useTimeRange>>; + let hook: RenderHookResult<ReturnType<typeof useTimeRange>, Parameters<typeof useTimeRange>[0]>; beforeEach(() => { Date.now = jest.fn(() => new Date(Date.UTC(2021, 0, 1, 12)).valueOf()); diff --git a/x-pack/plugins/observability_solution/apm/scripts/test/dat.js b/x-pack/plugins/observability_solution/apm/scripts/test/dat.js new file mode 100644 index 000000000000..b457cd4da5e7 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/scripts/test/dat.js @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable no-console */ +const { times } = require('lodash'); +const yargs = require('yargs'); +const path = require('path'); +const childProcess = require('child_process'); +const { REPO_ROOT } = require('@kbn/repo-info'); + +const { argv } = yargs(process.argv.slice(2)) + .option('serverless', { + default: false, + type: 'boolean', + description: 'Loads serverless configuration', + }) + .option('stateful', { + default: false, + type: 'boolean', + description: 'Loads stateful configuration', + }) + .option('server', { + default: false, + type: 'boolean', + description: 'Only start ES and Kibana', + }) + .option('runner', { + default: false, + type: 'boolean', + description: 'Only run tests', + }) + .option('grep', { + alias: 'spec', + type: 'string', + description: 'Specify the specs to run', + }) + .option('grep-files', { + alias: 'files', + type: 'array', + string: true, + description: 'Specify the files to run', + }) + .option('inspect', { + default: false, + type: 'boolean', + description: 'Add --inspect-brk flag to the ftr for debugging', + }) + .option('times', { + type: 'number', + description: 'Repeat the test n number of times', + }) + .option('updateSnapshots', { + default: false, + type: 'boolean', + description: 'Update snapshots', + }) + .option('bail', { + default: false, + type: 'boolean', + description: 'Stop the test run at the first failure', + }) + .check((argv) => { + const { inspect, runner } = argv; + if (inspect && !runner) { + throw new Error('--inspect can only be used with --runner'); + } else { + return true; + } + }) + .help(); + +const { serverless, stateful, bail, server, runner, grep, grepFiles, inspect, updateSnapshots } = + argv; + +if ((serverless === false && stateful === false) || (serverless && stateful)) { + throw new Error('Please specify either --stateful or --serverless'); +} + +let ftrScript = 'functional_tests'; +if (server) { + ftrScript = 'functional_tests_server'; +} else if (runner) { + ftrScript = 'functional_test_runner'; +} + +const environment = serverless ? 'serverless' : 'stateful'; + +const cmd = [ + 'node', + ...(inspect ? ['--inspect-brk'] : []), + `${REPO_ROOT}/scripts/${ftrScript}`, + ...(grep ? [`--grep "${grep}"`] : []), + ...(updateSnapshots ? [`--updateSnapshots`] : []), + ...(bail ? [`--bail`] : []), + `--config ${REPO_ROOT}/x-pack/test/api_integration/deployment_agnostic/configs/${environment}/oblt.apm.${environment}.config.ts`, +].join(' '); + +console.log(`Running: "${cmd}"`); + +function runTests() { + childProcess.execSync(cmd, { + cwd: path.join(__dirname), + stdio: 'inherit', + env: { ...process.env, APM_TEST_GREP_FILES: JSON.stringify(grepFiles) }, + }); +} + +if (argv.times) { + const runCounter = { succeeded: 0, failed: 0, remaining: argv.times }; + let exitStatus = 0; + times(argv.times, () => { + try { + runTests(); + runCounter.succeeded++; + } catch (e) { + if (bail) { + throw e; + } + + exitStatus = 1; + runCounter.failed++; + } + runCounter.remaining--; + if (argv.times > 1) { + console.log(runCounter); + } + }); + process.exit(exitStatus); +} else { + runTests(); +} diff --git a/x-pack/plugins/observability_solution/apm/server/assistant_functions/index.ts b/x-pack/plugins/observability_solution/apm/server/assistant_functions/index.ts index 6a65e6126ff2..1dff57cef660 100644 --- a/x-pack/plugins/observability_solution/apm/server/assistant_functions/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/assistant_functions/index.ts @@ -72,10 +72,7 @@ export function registerAssistantFunctions({ ruleDataClient, plugins, getApmIndices: async () => { - const coreContext = await resources.context.core; - const apmIndices = await plugins.apmDataAccess.setup.getApmIndices( - coreContext.savedObjects.client - ); + const apmIndices = await plugins.apmDataAccess.setup.getApmIndices(); return apmIndices; }, }; diff --git a/x-pack/plugins/observability_solution/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/observability_solution/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index 861481351bf9..fd456d6b203d 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -11,7 +11,6 @@ import { snakeCase } from 'lodash'; import moment from 'moment'; import { v4 as uuidv4 } from 'uuid'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { waitForIndexStatus } from '@kbn/core-saved-objects-migration-server-internal'; import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; import { ElasticsearchCapabilities } from '@kbn/core-elasticsearch-server'; import { ML_ERRORS } from '../../../common/anomaly_detection'; @@ -51,10 +50,20 @@ export async function createAnomalyDetectionJobs({ return withApmSpan('create_anomaly_detection_jobs', async () => { logger.info(`Creating ML anomaly detection jobs for environments: [${uniqueMlJobEnvs}].`); - const apmMetricIndex = indices.metric; const responses = []; const failedJobs = []; + + if (!esCapabilities.serverless) { + // Waiting for the index is not enabled in serverless, this could potentially cause + // problems when creating jobs in parallel + try { + await waitForIndexStatus(esClient, apmMetricIndex, 'yellow'); + } catch (err) { + logger.warn(`Error waiting for ${apmMetricIndex} to turn yellow before creating ML jobs`); + } + } + // Avoid the creation of multiple ml jobs in parallel // https://github.com/elastic/elasticsearch/issues/36271 for (const environment of uniqueMlJobEnvs) { @@ -62,10 +71,8 @@ export async function createAnomalyDetectionJobs({ responses.push( await createAnomalyDetectionJob({ mlClient, - esClient, environment, apmMetricIndex, - esCapabilities, }) ); } catch (e) { @@ -88,19 +95,13 @@ export async function createAnomalyDetectionJobs({ async function createAnomalyDetectionJob({ mlClient, - esClient, environment, apmMetricIndex, - esCapabilities, }: { mlClient: Required<MlClient>; - esClient: ElasticsearchClient; environment: string; apmMetricIndex: string; - esCapabilities: ElasticsearchCapabilities; }) { - const { serverless } = esCapabilities; - return withApmSpan('create_anomaly_detection_job', async () => { const randomToken = uuidv4().substr(-4); @@ -134,17 +135,6 @@ async function createAnomalyDetectionJob({ ], }); - // Waiting for the index is not enabled in serverless, this could potentially cause - // problems when creating jobs in parallels - if (!serverless) { - await waitForIndexStatus({ - client: esClient, - index: '.ml-*', - timeout: DEFAULT_TIMEOUT, - status: 'yellow', - })(); - } - return anomalyDetectionJob; }); } @@ -166,3 +156,22 @@ async function getUniqueMlJobEnvs(mlClient: MlClient, environments: Environment[ return environments.filter((env) => !existingMlJobEnvs.includes(env)); } + +async function waitForIndexStatus( + esClient: ElasticsearchClient, + index: string, + waitForStatus: 'yellow' | 'green', + timeout = DEFAULT_TIMEOUT +) { + return await esClient.cluster.health( + { + index, + wait_for_status: waitForStatus, + timeout, + }, + { + retryOnTimeout: true, + maxRetries: 3, + } + ); +} diff --git a/x-pack/plugins/observability_solution/apm/server/plugin.ts b/x-pack/plugins/observability_solution/apm/server/plugin.ts index 1142a5c69a51..de49ebcebf8b 100644 --- a/x-pack/plugins/observability_solution/apm/server/plugin.ts +++ b/x-pack/plugins/observability_solution/apm/server/plugin.ts @@ -16,7 +16,6 @@ import { registerAssistantFunctions } from './assistant_functions'; import { registerDeprecations } from './deprecations'; import { APM_FEATURE, registerFeaturesUsage } from './feature'; import { createApmTelemetry } from './lib/apm_telemetry'; -import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client'; import { APM_RULE_TYPE_ALERT_CONTEXT, apmRuleTypeAlertFieldMap, @@ -115,13 +114,6 @@ export class APMPlugin }; }) as APMRouteHandlerResources['plugins']; - const apmIndicesPromise = (async () => { - const coreStart = await getCoreStart(); - const soClient = await getInternalSavedObjectsClient(coreStart); - const { getApmIndices } = plugins.apmDataAccess; - return getApmIndices(soClient); - })(); - // This if else block will go away in favour of removing Home Tutorial Integration // Ideally we will directly register a custom integration and pass the configs // for cloud, onPrem and Serverless so that the actual component can take @@ -129,7 +121,8 @@ export class APMPlugin if (currentConfig.serverlessOnboarding && plugins.customIntegrations) { plugins.customIntegrations?.registerCustomIntegration(apmTutorialCustomIntegration); } else { - apmIndicesPromise + plugins.apmDataAccess + .getApmIndices() .then((apmIndices) => { plugins.home?.tutorials.registerTutorial( tutorialProvider({ diff --git a/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/register_apm_server_routes.ts b/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/register_apm_server_routes.ts index 5a2af3e7dc06..59c9cdac2df8 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/register_apm_server_routes.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/apm_routes/register_apm_server_routes.ts @@ -11,6 +11,7 @@ import { Logger, KibanaRequest, KibanaResponseFactory, RouteRegistrar } from '@k import { errors } from '@elastic/elasticsearch'; import agent from 'elastic-apm-node'; import { + DefaultRouteCreateOptions, IoTsParamsObject, ServerRouteRepository, stripNullishRequestParameters, @@ -30,6 +31,7 @@ import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; import { ApmFeatureFlags } from '../../../common/apm_feature_flags'; import type { APMCore, + APMRouteCreateOptions, MinimalApmPluginRequestHandlerContext, TelemetryUsageCounter, } from '../typings'; @@ -78,7 +80,11 @@ export function registerRoutes({ const router = core.setup.http.createRouter(); routes.forEach((route) => { - const { params, endpoint, options, handler } = route; + const { endpoint, handler, security } = route; + + const options = ('options' in route ? route.options : {}) as DefaultRouteCreateOptions & + APMRouteCreateOptions; + const params = 'params' in route ? route.params : undefined; const { method, pathname, version } = parseEndpoint(endpoint); @@ -109,10 +115,7 @@ export function registerRoutes({ ); const getApmIndices = async () => { - const coreContext = await context.core; - const apmIndices = await plugins.apmDataAccess.setup.getApmIndices( - coreContext.savedObjects.client - ); + const apmIndices = await plugins.apmDataAccess.setup.getApmIndices(); return apmIndices; }; @@ -218,6 +221,7 @@ export function registerRoutes({ path: pathname, options, validate: passThroughValidationObject, + security, }, wrappedHandler ); @@ -231,6 +235,7 @@ export function registerRoutes({ path: pathname, access: pathname.includes('/internal/apm') ? 'internal' : 'public', options, + security, }).addVersion( { version, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts index 84e51675233c..f28e3f9df857 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_observability_alert_details_context/index.ts @@ -38,8 +38,7 @@ export const getAlertDetailsContextHandler = ( return async (requestContext, query) => { const resources = { getApmIndices: async () => { - const coreContext = await requestContext.core; - return resourcePlugins.apmDataAccess.setup.getApmIndices(coreContext.savedObjects.client); + return resourcePlugins.apmDataAccess.setup.getApmIndices(); }, request: requestContext.request, params: { query: { _inspect: false } }, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/fleet/register_fleet_policy_callbacks.ts b/x-pack/plugins/observability_solution/apm/server/routes/fleet/register_fleet_policy_callbacks.ts index 2237548f2d32..9d00c50b4ab4 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/fleet/register_fleet_policy_callbacks.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/fleet/register_fleet_policy_callbacks.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Logger, CoreStart, SavedObjectsClientContract } from '@kbn/core/server'; +import { Logger, CoreStart } from '@kbn/core/server'; import { FleetStartContract, PostPackagePolicyCreateCallback, @@ -22,7 +22,6 @@ import { SOURCE_MAP_API_KEY_PATH, } from './get_package_policy_decorators'; import { createInternalESClient } from '../../lib/helpers/create_es_client/create_internal_es_client'; -import { getInternalSavedObjectsClient } from '../../lib/helpers/get_internal_saved_objects_client'; import { APMRouteHandlerResources } from '../apm_routes/register_apm_server_routes'; export async function registerFleetPolicyCallbacks({ @@ -149,7 +148,7 @@ function onPackagePolicyCreateOrUpdate({ coreStart, }: { fleetPluginStart: FleetStartContract; - getApmIndices: (soClient: SavedObjectsClientContract) => Promise<APMIndices>; + getApmIndices: () => Promise<APMIndices>; coreStart: CoreStart; }): PutPackagePolicyUpdateCallback & PostPackagePolicyCreateCallback { return async (packagePolicy) => { @@ -158,8 +157,7 @@ function onPackagePolicyCreateOrUpdate({ } const { asInternalUser } = coreStart.elasticsearch.client; - const savedObjectsClient = await getInternalSavedObjectsClient(coreStart); - const apmIndices = await getApmIndices(savedObjectsClient); + const apmIndices = await getApmIndices(); const internalESClient = await createInternalESClient({ debug: false, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/profiling/get_service_host_names.ts b/x-pack/plugins/observability_solution/apm/server/routes/profiling/get_service_correlation_fields.ts similarity index 51% rename from x-pack/plugins/observability_solution/apm/server/routes/profiling/get_service_host_names.ts rename to x-pack/plugins/observability_solution/apm/server/routes/profiling/get_service_correlation_fields.ts index 11fe248da563..2171b8e27de4 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/profiling/get_service_host_names.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/profiling/get_service_correlation_fields.ts @@ -5,13 +5,26 @@ * 2.0. */ import { rangeQuery } from '@kbn/observability-plugin/server'; -import { ApmServiceTransactionDocumentType } from '../../../common/document_type'; -import { HOST_HOSTNAME, SERVICE_NAME } from '../../../common/es_fields/apm'; -import { RollupInterval } from '../../../common/rollup'; +import type { ApmServiceTransactionDocumentType } from '../../../common/document_type'; +import { + CONTAINER_ID, + HOST_HOSTNAME, + HOST_NAME, + SERVICE_NAME, +} from '../../../common/es_fields/apm'; +import type { RollupInterval } from '../../../common/rollup'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import type { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; -export async function getServiceHostNames({ +const getBucketKeysAsString = ( + buckets?: Array<{ + doc_count: number; + key: string | number; + key_as_string?: string | undefined; + }> +) => buckets?.map((bucket) => bucket.key as string) || []; + +export async function getServiceCorrelationFields({ apmEventClient, serviceName, start, @@ -45,15 +58,36 @@ export async function getServiceHostNames({ }, }, aggs: { - hostNames: { + hostHostNames: { terms: { field: HOST_HOSTNAME, size: 500, }, }, + hostNames: { + terms: { + field: HOST_NAME, + size: 500, + }, + }, + containerIds: { + terms: { + field: CONTAINER_ID, + size: 500, + }, + }, }, }, }); - return response.aggregations?.hostNames.buckets.map((bucket) => bucket.key as string) || []; + const allHostNames = [ + ...getBucketKeysAsString(response.aggregations?.hostHostNames.buckets), + ...getBucketKeysAsString(response.aggregations?.hostNames.buckets), + ]; + const hostNames = new Set<string>(allHostNames); + + return { + hostNames: Array.from(hostNames), + containerIds: getBucketKeysAsString(response.aggregations?.containerIds.buckets), + }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/profiling/hosts/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/profiling/hosts/route.ts index 5a748ddf5e11..3ddd2782f108 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/profiling/hosts/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/profiling/hosts/route.ts @@ -8,7 +8,7 @@ import { toNumberRt } from '@kbn/io-ts-utils'; import type { BaseFlameGraph, TopNFunctions } from '@kbn/profiling-utils'; import * as t from 'io-ts'; -import { HOST_NAME } from '../../../../common/es_fields/apm'; +import { CONTAINER_ID, HOST_NAME } from '../../../../common/es_fields/apm'; import { mergeKueries, toKueryFilterFormat } from '../../../../common/utils/kuery_utils'; import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; @@ -20,7 +20,7 @@ import { } from '../../default_api_types'; import { fetchFlamegraph } from '../fetch_flamegraph'; import { fetchFunctions } from '../fetch_functions'; -import { getServiceHostNames } from '../get_service_host_names'; +import { getServiceCorrelationFields } from '../get_service_correlation_fields'; const profilingHostsFlamegraphRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/profiling/hosts/flamegraph', @@ -31,7 +31,9 @@ const profilingHostsFlamegraphRoute = createApmServerRoute({ options: { tags: ['access:apm'] }, handler: async ( resources - ): Promise<{ flamegraph: BaseFlameGraph; hostNames: string[] } | undefined> => { + ): Promise< + { flamegraph: BaseFlameGraph; hostNames: string[]; containerIds: string[] } | undefined + > => { const { context, plugins, params } = resources; const core = await context.core; const [esClient, apmEventClient, profilingDataAccessStart] = await Promise.all([ @@ -43,7 +45,7 @@ const profilingHostsFlamegraphRoute = createApmServerRoute({ const { start, end, environment, documentType, rollupInterval, kuery } = params.query; const { serviceName } = params.path; - const serviceHostNames = await getServiceHostNames({ + const { hostNames, containerIds } = await getServiceCorrelationFields({ apmEventClient, start, end, @@ -53,7 +55,7 @@ const profilingHostsFlamegraphRoute = createApmServerRoute({ rollupInterval, }); - if (!serviceHostNames.length) { + if (!hostNames.length && !containerIds.length) { return undefined; } const startSecs = start / 1000; @@ -65,10 +67,13 @@ const profilingHostsFlamegraphRoute = createApmServerRoute({ esClient: esClient.asCurrentUser, start: startSecs, end: endSecs, - kuery: mergeKueries([`(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, kuery]), + kuery: + containerIds.length > 0 + ? mergeKueries([`(${toKueryFilterFormat(CONTAINER_ID, containerIds)})`, kuery]) + : mergeKueries([`(${toKueryFilterFormat(HOST_NAME, hostNames)})`, kuery]), }); - return { flamegraph, hostNames: serviceHostNames }; + return { flamegraph, hostNames, containerIds }; } return undefined; @@ -90,7 +95,9 @@ const profilingHostsFunctionsRoute = createApmServerRoute({ options: { tags: ['access:apm'] }, handler: async ( resources - ): Promise<{ functions: TopNFunctions; hostNames: string[] } | undefined> => { + ): Promise< + { functions: TopNFunctions; hostNames: string[]; containerIds: string[] } | undefined + > => { const { context, plugins, params } = resources; const core = await context.core; const [esClient, apmEventClient, profilingDataAccessStart] = await Promise.all([ @@ -103,7 +110,7 @@ const profilingHostsFunctionsRoute = createApmServerRoute({ params.query; const { serviceName } = params.path; - const serviceHostNames = await getServiceHostNames({ + const { hostNames, containerIds } = await getServiceCorrelationFields({ apmEventClient, start, end, @@ -113,7 +120,7 @@ const profilingHostsFunctionsRoute = createApmServerRoute({ rollupInterval, }); - if (!serviceHostNames.length) { + if (!hostNames.length && !containerIds.length) { return undefined; } @@ -128,10 +135,13 @@ const profilingHostsFunctionsRoute = createApmServerRoute({ endIndex, start: startSecs, end: endSecs, - kuery: mergeKueries([`(${toKueryFilterFormat(HOST_NAME, serviceHostNames)})`, kuery]), + kuery: + containerIds.length > 0 + ? mergeKueries([`(${toKueryFilterFormat(CONTAINER_ID, containerIds)})`, kuery]) + : mergeKueries([`(${toKueryFilterFormat(HOST_NAME, hostNames)})`, kuery]), }); - return { functions, hostNames: serviceHostNames }; + return { functions, hostNames, containerIds }; } return undefined; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/typings.ts b/x-pack/plugins/observability_solution/apm/server/routes/typings.ts index f9ea085a11e6..126ae4893764 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/typings.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/typings.ts @@ -9,7 +9,6 @@ import type { CoreSetup, CustomRequestHandlerContext, CoreStart, - RouteConfigOptions, IScopedClusterClient, IUiSettingsClient, SavedObjectsClientContract, @@ -48,21 +47,18 @@ export type MinimalApmPluginRequestHandlerContext = Omit< }; export interface APMRouteCreateOptions { - options: { - tags: Array< - | 'access:apm' - | 'access:apm_write' - | 'access:apm_settings_write' - | 'access:ml:canGetJobs' - | 'access:ml:canCreateJob' - | 'access:ml:canCloseJob' - | 'access:ai_assistant' - | 'oas-tag:APM agent keys' - | 'oas-tag:APM annotations' - >; - body?: { accepts: Array<'application/json' | 'multipart/form-data'> }; - disableTelemetry?: boolean; - } & RouteConfigOptions<any>; + tags: Array< + | 'access:apm' + | 'access:apm_write' + | 'access:apm_settings_write' + | 'access:ml:canGetJobs' + | 'access:ml:canCreateJob' + | 'access:ml:canCloseJob' + | 'access:ai_assistant' + | 'oas-tag:APM agent keys' + | 'oas-tag:APM annotations' + >; + disableTelemetry?: boolean; } export type TelemetryUsageCounter = ReturnType<UsageCollectionSetup['createUsageCounter']>; diff --git a/x-pack/plugins/observability_solution/apm/tsconfig.json b/x-pack/plugins/observability_solution/apm/tsconfig.json index 0f08bf3143cd..d3de1633dcad 100644 --- a/x-pack/plugins/observability_solution/apm/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm/tsconfig.json @@ -70,7 +70,6 @@ "@kbn/core-saved-objects-api-server-mocks", "@kbn/field-types", "@kbn/babel-register", - "@kbn/core-saved-objects-migration-server-internal", "@kbn/core-elasticsearch-server", "@kbn/safer-lodash-set", "@kbn/shared-ux-router", diff --git a/x-pack/plugins/observability_solution/apm_data_access/kibana.jsonc b/x-pack/plugins/observability_solution/apm_data_access/kibana.jsonc index 51968be90cb7..9d80dcd71ce9 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/kibana.jsonc +++ b/x-pack/plugins/observability_solution/apm_data_access/kibana.jsonc @@ -18,9 +18,7 @@ "requiredPlugins": [ "data" ], - "optionalPlugins": [ - "security" - ], + "optionalPlugins": [], "requiredBundles": [] } } \ No newline at end of file diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/index.ts b/x-pack/plugins/observability_solution/apm_data_access/server/index.ts index 6b6385ded4ce..7afaa656591c 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/server/index.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/index.ts @@ -91,7 +91,6 @@ export type { APMEventESSearchRequest, APMLogEventESSearchRequest, DocumentSourcesRequest, - ApmDataAccessPrivilegesCheck, HostNamesRequest, GetDocumentTypeParams, } from './types'; diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/lib/check_privileges.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/check_privileges.ts deleted file mode 100644 index 6b8e734a10b4..000000000000 --- a/x-pack/plugins/observability_solution/apm_data_access/server/lib/check_privileges.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { KibanaRequest } from '@kbn/core-http-server'; -import { SecurityPluginStart } from '@kbn/security-plugin-types-server'; -import { mapValues } from 'lodash'; -import { APMIndices } from '..'; - -export interface ApmDataAccessPrivilegesCheck { - request: KibanaRequest; - security?: SecurityPluginStart; - getApmIndices: () => Promise<APMIndices>; -} - -export async function checkPrivileges({ - request, - getApmIndices, - security, -}: ApmDataAccessPrivilegesCheck) { - const authorization = security?.authz; - if (!authorization) { - return true; - } - - const [apmIndices, checkPrivilegesFn] = await Promise.all([ - getApmIndices(), - authorization.checkPrivilegesDynamicallyWithRequest(request), - ]); - - const { hasAllRequested } = await checkPrivilegesFn({ - elasticsearch: { - cluster: [], - index: mapValues(apmIndices, () => ['read']), - }, - }); - - return hasAllRequested; -} diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/plugin.ts b/x-pack/plugins/observability_solution/apm_data_access/server/plugin.ts index 680079d080c8..6bf684985583 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/server/plugin.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/plugin.ts @@ -5,32 +5,19 @@ * 2.0. */ -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, - SavedObjectsClientContract, - Logger, -} from '@kbn/core/server'; +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; import { APMDataAccessConfig } from '.'; -import { - ApmDataAccessPluginSetup, - ApmDataAccessPluginStart, - ApmDataAccessServerDependencies, -} from './types'; +import { ApmDataAccessPluginSetup, ApmDataAccessPluginStart } from './types'; import { migrateLegacyAPMIndicesToSpaceAware } from './saved_objects/migrations/migrate_legacy_apm_indices_to_space_aware'; import { apmIndicesSavedObjectDefinition, getApmIndicesSavedObject, } from './saved_objects/apm_indices'; import { getServices } from './services/get_services'; -import { ApmDataAccessPrivilegesCheck, checkPrivileges } from './lib/check_privileges'; export class ApmDataAccessPlugin implements Plugin<ApmDataAccessPluginSetup, ApmDataAccessPluginStart> { - public server?: ApmDataAccessServerDependencies; public config: APMDataAccessConfig; public logger: Logger; @@ -39,45 +26,34 @@ export class ApmDataAccessPlugin this.logger = initContext.logger.get(); } - getApmIndices = async (savedObjectsClient: SavedObjectsClientContract) => { - const apmIndicesFromSavedObject = await getApmIndicesSavedObject(savedObjectsClient); - return { ...this.config.indices, ...apmIndicesFromSavedObject }; - }; - public setup(core: CoreSetup): ApmDataAccessPluginSetup { // register saved object core.savedObjects.registerType(apmIndicesSavedObjectDefinition); + const getApmIndices = async () => { + const [coreStart] = await core.getStartServices(); + const soClient = await coreStart.savedObjects.createInternalRepository(); + + const apmIndicesFromSavedObject = await getApmIndicesSavedObject(soClient); + return { ...this.config.indices, ...apmIndicesFromSavedObject }; + }; + // expose return { apmIndicesFromConfigFile: this.config.indices, - getApmIndices: this.getApmIndices, + getApmIndices, getServices, }; } - public start(core: CoreStart, plugins: ApmDataAccessServerDependencies) { + public start(core: CoreStart) { // TODO: remove in 9.0 migrateLegacyAPMIndicesToSpaceAware({ coreStart: core, logger: this.logger }).catch((e) => { this.logger.error('Failed to run migration making APM indices space aware'); this.logger.error(e); }); - const getApmIndicesWithInternalUserFn = async () => { - const soClient = core.savedObjects.createInternalRepository(); - return this.getApmIndices(soClient); - }; - - const startServices = { - hasPrivileges: ({ request }: Pick<ApmDataAccessPrivilegesCheck, 'request'>) => - checkPrivileges({ - request, - getApmIndices: getApmIndicesWithInternalUserFn, - security: plugins.security, - }), - }; - - return { ...startServices }; + return {}; } public stop() {} diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/types.ts b/x-pack/plugins/observability_solution/apm_data_access/server/types.ts index f10c23c1fd99..968590e780ee 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/server/types.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/types.ts @@ -5,28 +5,17 @@ * 2.0. */ -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { SecurityPluginStart } from '@kbn/security-plugin-types-server'; import type { APMIndices } from '.'; import { getServices } from './services/get_services'; -import type { ApmDataAccessPrivilegesCheck } from './lib/check_privileges'; export interface ApmDataAccessPluginSetup { apmIndicesFromConfigFile: APMIndices; - getApmIndices: (soClient: SavedObjectsClientContract) => Promise<APMIndices>; + getApmIndices: () => Promise<APMIndices>; getServices: typeof getServices; } -export interface ApmDataAccessServerDependencies { - security?: SecurityPluginStart; -} - -export interface ApmDataAccessPluginStart { - hasPrivileges: (params: Pick<ApmDataAccessPrivilegesCheck, 'request'>) => Promise<boolean>; -} -export interface ApmDataAccessServerDependencies { - security?: SecurityPluginStart; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ApmDataAccessPluginStart {} export type ApmDataAccessServices = ReturnType<typeof getServices>; export type { ApmDataAccessServicesParams } from './services/get_services'; @@ -38,4 +27,3 @@ export type { APMEventESSearchRequest, APMLogEventESSearchRequest, } from './lib/helpers'; -export type { ApmDataAccessPrivilegesCheck }; diff --git a/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json b/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json index d4c38fddf967..f7ac83af0922 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json @@ -9,7 +9,6 @@ "@kbn/config-schema", "@kbn/core", "@kbn/i18n", - "@kbn/core-saved-objects-api-server", "@kbn/data-plugin", "@kbn/inspector-plugin", "@kbn/observability-plugin", @@ -18,8 +17,6 @@ "@kbn/apm-types", "@kbn/core-http-server-mocks", "@kbn/apm-utils", - "@kbn/core-http-server", - "@kbn/security-plugin-types-server", "@kbn/utility-types", "@kbn/elastic-agent-utils", "@kbn/observability-utils-common" diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/register_routes.ts index 4e1970d7fc88..db5620e0778f 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/register_routes.ts @@ -39,14 +39,18 @@ export function registerRoutes({ const router = core.http.createRouter(); routes.forEach((route) => { - const { endpoint, options, handler, params } = route; + const { endpoint, handler } = route; const { pathname, method } = parseEndpoint(endpoint); + const params = 'params' in route ? route.params : undefined; + const options = 'options' in route ? route.options : {}; + (router[method] as RouteRegistrar<typeof method, DatasetQualityRequestHandlerContext>)( { path: pathname, validate: passThroughValidationObject, options, + security: route.security, }, async (context, request, response) => { try { diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/types.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/types.ts index 86dd9e098625..298d0c45efc4 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/types.ts @@ -29,7 +29,5 @@ export interface DatasetQualityRouteHandlerResources { } export interface DatasetQualityRouteCreateOptions { - options: { - tags: string[]; - }; + tags: string[]; } diff --git a/x-pack/plugins/observability_solution/entity_manager_app/README.md b/x-pack/plugins/observability_solution/entity_manager_app/README.md new file mode 100644 index 000000000000..1fd230a046c5 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/README.md @@ -0,0 +1,3 @@ +# Entity Manager App Plugin + +This plugin provides a user interface to interact with the Entity Manager. \ No newline at end of file diff --git a/x-pack/plugins/observability_solution/entity_manager_app/jest.config.js b/x-pack/plugins/observability_solution/entity_manager_app/jest.config.js new file mode 100644 index 000000000000..d8217a43063a --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/jest.config.js @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const path = require('path'); + +module.exports = { + preset: '@kbn/test', + rootDir: path.resolve(__dirname, '../../../..'), + roots: ['<rootDir>/x-pack/plugins/observability_solution/entity_manager_app'], + coverageDirectory: + '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/observability_solution/entity_manager_app', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '<rootDir>/x-pack/plugins/observability_solution/entity_manager_app/{common,public,server}/**/*.{js,ts,tsx}', + ], +}; diff --git a/x-pack/plugins/observability_solution/entity_manager_app/kibana.jsonc b/x-pack/plugins/observability_solution/entity_manager_app/kibana.jsonc new file mode 100644 index 000000000000..93e6687f9b4c --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/kibana.jsonc @@ -0,0 +1,29 @@ +{ + "type": "plugin", + "id": "@kbn/entityManager-app-plugin", + "owner": "@elastic/obs-entities", + "group": "observability", + "visibility": "private", + "description": "Entity manager plugin for entity assets (inventory, topology, etc)", + "plugin": { + "id": "entityManagerApp", + "configPath": ["xpack", "entityManagerApp"], + "browser": true, + "server": false, + "requiredPlugins": [ + "entityManager", + "observabilityShared", + "presentationUtil", + "usageCollection", + "licensing" + ], + "optionalPlugins": [ + "cloud", + "serverless" + ], + "requiredBundles": [ + "kibanaReact", + "kibanaUtils" + ] + } +} diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/application.tsx b/x-pack/plugins/observability_solution/entity_manager_app/public/application.tsx new file mode 100644 index 000000000000..8f2e9e2213ba --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/application.tsx @@ -0,0 +1,103 @@ +/* + * 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 { AppMountParameters, APP_WRAPPER_CLASS, CoreStart } from '@kbn/core/public'; +import { PerformanceContextProvider } from '@kbn/ebt-tools'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { EntityClient } from '@kbn/entityManager-plugin/public'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { Router } from '@kbn/shared-ux-router'; +import { PluginContext } from './context/plugin_context'; +import { EntityManagerPluginStart } from './types'; +import { EntityManagerOverviewPage } from './pages/overview'; + +export function renderApp({ + core, + plugins, + appMountParameters, + ObservabilityPageTemplate, + usageCollection, + isDev, + kibanaVersion, + isServerless, + entityClient, +}: { + core: CoreStart; + plugins: EntityManagerPluginStart; + appMountParameters: AppMountParameters; + ObservabilityPageTemplate: React.ComponentType<LazyObservabilityPageTemplateProps>; + usageCollection: UsageCollectionSetup; + isDev?: boolean; + kibanaVersion: string; + isServerless?: boolean; + entityClient: EntityClient; +}) { + const { element, history, theme$ } = appMountParameters; + const isDarkMode = core.theme.getTheme().darkMode; + + // ensure all divs are .kbnAppWrappers + element.classList.add(APP_WRAPPER_CLASS); + + const ApplicationUsageTrackingProvider = + usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; + + const CloudProvider = plugins.cloud?.CloudContextProvider ?? React.Fragment; + + ReactDOM.render( + <KibanaRenderContextProvider {...core}> + <ApplicationUsageTrackingProvider> + <KibanaThemeProvider {...{ theme: { theme$ } }}> + <CloudProvider> + <KibanaContextProvider + services={{ + ...core, + ...plugins, + storage: new Storage(localStorage), + entityClient: new EntityClient(core), + isDev, + kibanaVersion, + isServerless, + }} + > + <PluginContext.Provider + value={{ + isDev, + isServerless, + appMountParameters, + ObservabilityPageTemplate, + entityClient, + }} + > + <Router history={history}> + <EuiThemeProvider darkMode={isDarkMode}> + <RedirectAppLinks coreStart={core} data-test-subj="observabilityMainContainer"> + <PerformanceContextProvider> + <EntityManagerOverviewPage /> + </PerformanceContextProvider> + </RedirectAppLinks> + </EuiThemeProvider> + </Router> + </PluginContext.Provider> + </KibanaContextProvider> + </CloudProvider> + </KibanaThemeProvider> + </ApplicationUsageTrackingProvider> + </KibanaRenderContextProvider>, + element + ); + + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +} diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/context/plugin_context.ts b/x-pack/plugins/observability_solution/entity_manager_app/public/context/plugin_context.ts new file mode 100644 index 000000000000..7da2833be439 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/context/plugin_context.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createContext } from 'react'; +import type { AppMountParameters } from '@kbn/core/public'; +import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; +import { EntityClient } from '@kbn/entityManager-plugin/public'; + +export interface PluginContextValue { + isDev?: boolean; + isServerless?: boolean; + appMountParameters?: AppMountParameters; + ObservabilityPageTemplate: React.ComponentType<LazyObservabilityPageTemplateProps>; + entityClient: EntityClient; +} + +export const PluginContext = createContext<PluginContextValue | null>(null); diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/hooks/use_kibana.ts b/x-pack/plugins/observability_solution/entity_manager_app/public/hooks/use_kibana.ts new file mode 100644 index 000000000000..a515b9b80b01 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/hooks/use_kibana.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart } from '@kbn/core/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { EntityClient } from '@kbn/entityManager-plugin/public'; + +export type StartServices<AdditionalServices extends object = {}> = CoreStart & + AdditionalServices & { + storage: Storage; + kibanaVersion: string; + entityClient: EntityClient; + }; +const useTypedKibana = <AdditionalServices extends object = {}>() => + useKibana<StartServices<AdditionalServices>>(); + +export { useTypedKibana as useKibana }; diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/hooks/use_plugin_context.ts b/x-pack/plugins/observability_solution/entity_manager_app/public/hooks/use_plugin_context.ts new file mode 100644 index 000000000000..d0640deb575b --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/hooks/use_plugin_context.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useContext } from 'react'; +import { PluginContext } from '../context/plugin_context'; +import type { PluginContextValue } from '../context/plugin_context'; + +export function usePluginContext(): PluginContextValue { + const context = useContext(PluginContext); + if (!context) { + throw new Error('Plugin context value is missing!'); + } + + return context; +} diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/index.ts b/x-pack/plugins/observability_solution/entity_manager_app/public/index.ts new file mode 100644 index 000000000000..5b83ea1d297d --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; +import { Plugin } from './plugin'; + +export const plugin: PluginInitializer<{}, {}> = (context: PluginInitializerContext) => { + return new Plugin(context); +}; diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/pages/overview/index.tsx b/x-pack/plugins/observability_solution/entity_manager_app/public/pages/overview/index.tsx new file mode 100644 index 000000000000..8c978db6f675 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/pages/overview/index.tsx @@ -0,0 +1,347 @@ +/* + * 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, { useState } from 'react'; +import { v4 as uuid } from 'uuid'; +import { + EuiBasicTable, + EuiButton, + EuiButtonIcon, + EuiCallOut, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiHorizontalRule, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { EntityV2 } from '@kbn/entities-schema'; +import { usePluginContext } from '../../hooks/use_plugin_context'; + +function EntitySourceForm({ + source, + index, + onFieldChange, +}: { + source: any; + index: number; + onFieldChange: Function; +}) { + const onArrayFieldChange = + (field: Exclude<keyof EntitySource, 'id'>) => (e: React.ChangeEvent<HTMLInputElement>) => { + const value = e.target.value.trim(); + if (!value) { + onFieldChange(index, field, []); + } else { + onFieldChange(index, field, e.target.value.trim().split(',')); + } + }; + + return ( + <EuiFlexGroup> + <EuiFlexItem> + <EuiFormRow label="Index patterns (comma-separated)"> + <EuiFieldText + data-test-subj="entityManagerFormIndexPatterns" + name="index_patterns" + defaultValue={source.index_patterns.join(',')} + isInvalid={source.index_patterns.length === 0} + onChange={onArrayFieldChange('index_patterns')} + /> + </EuiFormRow> + </EuiFlexItem> + + <EuiFlexItem> + <EuiFormRow label="Identify fields (comma-separated field names)"> + <EuiFieldText + data-test-subj="entityManagerFormIdentityFields" + name="identity_fields" + defaultValue={source.identity_fields.join(',')} + isInvalid={source.identity_fields.length === 0} + onChange={onArrayFieldChange('identity_fields')} + /> + </EuiFormRow> + </EuiFlexItem> + + <EuiFlexItem> + <EuiFormRow label="Filters (comma-separated ESQL filters)"> + <EuiFieldText + data-test-subj="entityManagerFormFilters" + name="filters" + defaultValue={source.filters.join(',')} + onChange={onArrayFieldChange('filters')} + /> + </EuiFormRow> + </EuiFlexItem> + + <EuiFlexItem> + <EuiFormRow label="Metadata (comma-separated field names)"> + <EuiFieldText + data-test-subj="entityManagerFormMetadata" + name="metadata" + defaultValue={source.metadata_fields.join(',')} + onChange={onArrayFieldChange('metadata_fields')} + /> + </EuiFormRow> + </EuiFlexItem> + + <EuiFlexItem> + <EuiFormRow label="Timestamp field"> + <EuiFieldText + data-test-subj="entityManagerFormTimestamp" + name="timestamp_field" + defaultValue={source.timestamp_field} + onChange={(e) => onFieldChange(index, 'timestamp_field', e.target.value)} + /> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + ); +} + +interface EntitySource { + id: string; + index_patterns?: string[]; + identity_fields?: string[]; + metadata_fields?: string[]; + filters?: string[]; + timestamp_field?: string; +} + +const newEntitySource = ({ + indexPatterns = [], + identityFields = [], + metadataFields = [], + filters = [], + timestampField = '@timestamp', +}: { + indexPatterns?: string[]; + identityFields?: string[]; + metadataFields?: string[]; + filters?: string[]; + timestampField?: string; +}) => ({ + id: uuid(), + index_patterns: indexPatterns, + identity_fields: identityFields, + metadata_fields: metadataFields, + timestamp_field: timestampField, + filters, +}); + +export function EntityManagerOverviewPage() { + const { ObservabilityPageTemplate, entityClient } = usePluginContext(); + const [previewEntities, setPreviewEntities] = useState<EntityV2[]>([]); + const [isSearchingEntities, setIsSearchingEntities] = useState(false); + const [previewError, setPreviewError] = useState(null); + const [formErrors, setFormErrors] = useState<string[]>([]); + const [entityType, setEntityType] = useState('service'); + const [entitySources, setEntitySources] = useState([ + newEntitySource({ + indexPatterns: ['remote_cluster:logs-*'], + identityFields: ['service.name'], + }), + ]); + + const searchEntities = async () => { + if ( + !entitySources.some( + (source) => source.identity_fields.length > 0 && source.index_patterns.length > 0 + ) + ) { + setFormErrors(['No valid source found']); + return; + } + + setIsSearchingEntities(true); + setFormErrors([]); + setPreviewError(null); + + try { + const { entities } = await entityClient.repositoryClient( + 'POST /internal/entities/v2/_search/preview', + { + params: { + body: { + sources: entitySources + .filter( + (source) => source.index_patterns.length > 0 && source.identity_fields.length > 0 + ) + .map((source) => ({ ...source, type: entityType })), + }, + }, + } + ); + + setPreviewEntities(entities); + } catch (err) { + setPreviewError(err.body?.message); + } finally { + setIsSearchingEntities(false); + } + }; + + return ( + <ObservabilityPageTemplate + data-test-subj="entitiesPage" + pageHeader={{ + bottomBorder: true, + pageTitle: 'Entity Manager', + }} + > + <EuiForm component="form" isInvalid={formErrors.length > 0} error={formErrors}> + <EuiFlexGroup direction="column" gutterSize="s"> + <EuiFlexItem> + <EuiTitle size="s"> + <h2>Entity type</h2> + </EuiTitle> + </EuiFlexItem> + + <EuiFlexItem> + <EuiFormRow> + <EuiFieldText + data-test-subj="entityManagerFormType" + name="type" + defaultValue={entityType} + placeholder="host, service, user..." + onChange={(e) => { + setEntityType(e.target.value.trim()); + }} + /> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + + <EuiSpacer size="m" /> + + <EuiFlexGroup alignItems="center"> + <EuiFlexItem grow={false}> + <EuiTitle size="s"> + <h2>Entity sources</h2> + </EuiTitle> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiButtonIcon + data-test-subj="entityManagerFormAddSource" + iconType="plusInCircle" + onClick={() => setEntitySources([...entitySources, newEntitySource({})])} + /> + </EuiFlexItem> + </EuiFlexGroup> + + <EuiSpacer size="s" /> + + {entitySources.map((source, i) => ( + <div key={source.id}> + <EuiFlexGroup alignItems="center"> + <EuiFlexItem grow={false}> + <EuiTitle size="xxs"> + <h4>Source {i + 1}</h4> + </EuiTitle> + </EuiFlexItem> + {entitySources.length > 1 ? ( + <EuiFlexItem grow={false}> + <EuiButtonIcon + data-test-subj="entityManagerFormRemoveSource" + color={'danger'} + iconType={'minusInCircle'} + onClick={() => { + entitySources.splice(i, 1); + setEntitySources(entitySources.map((_source) => ({ ..._source }))); + }} + /> + </EuiFlexItem> + ) : null} + </EuiFlexGroup> + + <EuiSpacer size="s" /> + + <EntitySourceForm + source={source} + index={i} + onFieldChange={( + index: number, + field: Exclude<keyof EntitySource, 'id'>, + value: any + ) => { + entitySources[index][field] = value; + setEntitySources([...entitySources]); + }} + /> + {i === entitySources.length - 1 ? ( + <EuiSpacer size="m" /> + ) : ( + <EuiHorizontalRule margin="m" /> + )} + </div> + ))} + + <EuiFormRow> + <EuiFlexGroup> + <EuiFlexItem> + <EuiButton + data-test-subj="entityManagerFormPreview" + isDisabled={isSearchingEntities} + onClick={searchEntities} + > + Preview + </EuiButton> + </EuiFlexItem> + <EuiFlexItem> + <EuiButton data-test-subj="entityManagerFormCreate" isDisabled={true} color="primary"> + Create + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFormRow> + </EuiForm> + + <EuiSpacer size="s" /> + + {previewError ? ( + <EuiCallOut title="Error previewing entity definition" color="danger" iconType="error"> + <p>{previewError}</p> + </EuiCallOut> + ) : null} + + <EuiBasicTable + loading={isSearchingEntities} + tableCaption={'Preview entities'} + items={previewEntities} + columns={[ + { + field: 'entity.id', + name: 'entity.id', + }, + { + field: 'entity.type', + name: 'entity.type', + }, + { + field: 'entity.last_seen_timestamp', + name: 'entity.last_seen_timestamp', + }, + ...Array.from(new Set(entitySources.flatMap((source) => source.identity_fields))).map( + (field) => ({ + field, + name: field, + }) + ), + ...Array.from(new Set(entitySources.flatMap((source) => source.metadata_fields))).map( + (field) => ({ + field: `metadata.${field}`, + name: `metadata.${field}`, + }) + ), + ]} + /> + </ObservabilityPageTemplate> + ); +} diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/plugin.ts b/x-pack/plugins/observability_solution/entity_manager_app/public/plugin.ts new file mode 100644 index 000000000000..0db381522b02 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/plugin.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { BehaviorSubject } from 'rxjs'; +import { + App, + AppMountParameters, + AppStatus, + AppUpdater, + CoreSetup, + DEFAULT_APP_CATEGORIES, + PluginInitializerContext, +} from '@kbn/core/public'; +import { Logger } from '@kbn/logging'; +import { EntityClient } from '@kbn/entityManager-plugin/public'; + +import { + EntityManagerAppPluginClass, + EntityManagerPluginStart, + EntityManagerPluginSetup, +} from './types'; + +export class Plugin implements EntityManagerAppPluginClass { + public logger: Logger; + private readonly appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({})); + + constructor(private readonly context: PluginInitializerContext<{}>) { + this.logger = context.logger.get(); + } + + setup(core: CoreSetup<EntityManagerPluginStart, {}>, pluginSetup: EntityManagerPluginSetup) { + const kibanaVersion = this.context.env.packageInfo.version; + + const mount = async (params: AppMountParameters<unknown>) => { + const { renderApp } = await import('./application'); + const [coreStart, pluginsStart] = await core.getStartServices(); + + return renderApp({ + appMountParameters: params, + core: coreStart, + isDev: this.context.env.mode.dev, + kibanaVersion, + usageCollection: pluginSetup.usageCollection, + ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate, + plugins: pluginsStart, + isServerless: !!pluginsStart.serverless, + entityClient: new EntityClient(core), + }); + }; + + const appUpdater$ = this.appUpdater$; + const app: App = { + id: 'entity_manager', + title: 'Entity Manager', + order: 8002, + updater$: appUpdater$, + euiIconType: 'logoObservability', + appRoute: '/app/entity_manager', + category: DEFAULT_APP_CATEGORIES.observability, + mount, + visibleIn: [], + keywords: ['observability', 'monitor', 'entities'], + status: AppStatus.inaccessible, + }; + + core.application.register(app); + + return {}; + } + + start() { + return {}; + } + + stop() {} +} diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/routes.tsx b/x-pack/plugins/observability_solution/entity_manager_app/public/routes.tsx new file mode 100644 index 000000000000..80baa45422bf --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/routes.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EntityManagerOverviewPage } from './pages/overview'; + +interface RouteDef { + [key: string]: { + handler: () => React.ReactElement; + params: Record<string, string>; + exact: boolean; + }; +} + +export function getRoutes(): RouteDef { + return { + '/app/entity_manager': { + handler: () => <EntityManagerOverviewPage />, + params: {}, + exact: true, + }, + }; +} diff --git a/x-pack/plugins/observability_solution/entity_manager_app/public/types.ts b/x-pack/plugins/observability_solution/entity_manager_app/public/types.ts new file mode 100644 index 000000000000..b735771d79f8 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/public/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { Plugin as PluginClass } from '@kbn/core/public'; +import { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; +import { CloudStart } from '@kbn/cloud-plugin/public'; +import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import { + ObservabilitySharedPluginSetup, + ObservabilitySharedPluginStart, +} from '@kbn/observability-shared-plugin/public'; +import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { EntityManagerPublicPluginSetup } from '@kbn/entityManager-plugin/public/types'; + +export interface EntityManagerPluginSetup { + observabilityShared: ObservabilitySharedPluginSetup; + serverless?: ServerlessPluginSetup; + usageCollection: UsageCollectionSetup; + entityManager: EntityManagerPublicPluginSetup; +} + +export interface EntityManagerPluginStart { + presentationUtil: PresentationUtilPluginStart; + cloud?: CloudStart; + serverless?: ServerlessPluginStart; + observabilityShared: ObservabilitySharedPluginStart; +} + +export type EntityManagerAppPluginClass = PluginClass<{}, {}>; diff --git a/x-pack/plugins/observability_solution/entity_manager_app/tsconfig.json b/x-pack/plugins/observability_solution/entity_manager_app/tsconfig.json new file mode 100644 index 000000000000..64c0a293a4e2 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager_app/tsconfig.json @@ -0,0 +1,33 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "../../../../typings/**/*", + "common/**/*", + "public/**/*", + "types/**/*" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/logging", + "@kbn/ebt-tools", + "@kbn/kibana-react-plugin", + "@kbn/kibana-utils-plugin", + "@kbn/observability-shared-plugin", + "@kbn/react-kibana-context-render", + "@kbn/react-kibana-context-theme", + "@kbn/shared-ux-link-redirect-app", + "@kbn/usage-collection-plugin", + "@kbn/shared-ux-router", + "@kbn/presentation-util-plugin", + "@kbn/cloud-plugin", + "@kbn/serverless", + "@kbn/entityManager-plugin", + "@kbn/entities-schema", + ] +} diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx index ee95d2a7f61a..efb6623f80e3 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/components/action_menu/action_menu.tsx @@ -8,7 +8,7 @@ import React, { useState } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { LensEmbeddableInput, TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import { EmbedAction } from '../../header/embed_action'; import { AddToCaseAction } from '../../header/add_to_case_action'; import { useKibana } from '../../hooks/use_kibana'; @@ -94,7 +94,7 @@ export function ExpViewActionMenuContent({ {isSaveOpen && lensAttributes && ( <LensSaveModalComponent - initialInput={lensAttributes as unknown as LensEmbeddableInput} + initialInput={{ attributes: lensAttributes }} onClose={() => setIsSaveOpen(false)} // if we want to do anything after the viz is saved // right now there is no action, so an empty function diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts index 75a5c42c7644..846044a7a7b0 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts @@ -22,6 +22,7 @@ import { obsvReportConfigMap } from '../obsv_exploratory_view'; import { sampleAttributeWithReferenceLines } from './test_data/sample_attribute_with_reference_lines'; import { lensPluginMock } from '@kbn/lens-plugin/public/mocks'; import { FormulaPublicApi, XYState } from '@kbn/lens-plugin/public'; +import { Query } from '@kbn/es-query'; describe('Lens Attribute', () => { mockAppDataView(); @@ -448,7 +449,9 @@ describe('Lens Attribute', () => { reportViewConfig.reportType, formulaHelper ).getJSON(); - expect(multiSeriesLensAttr.state.query.query).toEqual('transaction.duration.us < 60000000'); + expect((multiSeriesLensAttr.state.query as Query).query).toEqual( + 'transaction.duration.us < 60000000' + ); }); describe('Layer breakdowns', function () { diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 69080c22a13d..282ae5b76826 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { capitalize } from 'lodash'; -import { ExistsFilter, isExistsFilter } from '@kbn/es-query'; +import { type ExistsFilter, type Query, type Filter, isExistsFilter } from '@kbn/es-query'; import { AvgIndexPatternColumn, CardinalityIndexPatternColumn, @@ -41,6 +41,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { PersistableFilter } from '@kbn/lens-plugin/common'; import { DataViewSpec } from '@kbn/data-views-plugin/common'; import { LegendSize } from '@kbn/visualizations-plugin/common/constants'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { urlFiltersToKueryString } from '../utils/stringify_kueries'; import { FILTER_RECORDS, @@ -169,17 +170,20 @@ export class LensAttributes { globalFilter?: { query: string; language: string }; reportType: string; lensFormulaHelper?: FormulaPublicApi; + dslFilters?: QueryDslQueryContainer[]; constructor( layerConfigs: LayerConfig[], reportType: string, - lensFormulaHelper?: FormulaPublicApi + lensFormulaHelper?: FormulaPublicApi, + dslFilters?: QueryDslQueryContainer[] ) { this.layers = {}; this.seriesReferenceLines = {}; this.reportType = reportType; this.lensFormulaHelper = lensFormulaHelper; this.isMultiSeries = layerConfigs.length > 1; + this.dslFilters = dslFilters; layerConfigs.forEach(({ seriesConfig, operationType }) => { if (operationType && reportType !== ReportTypes.SINGLE_METRIC) { @@ -1267,11 +1271,36 @@ export class LensAttributes { return { internalReferences, adHocDataViews }; } + getFilters(): Filter[] { + const { internalReferences } = this.getReferences(); + + const dslFilters = this.dslFilters; + if (!dslFilters) { + return []; + } + return dslFilters.map((filter) => { + return { + meta: { + index: internalReferences?.[0].id, + type: 'query_string', + disabled: false, + negate: false, + alias: null, + key: 'query', + }, + $state: { + store: 'appState', + }, + query: filter, + } as Filter; + }); + } + getJSON( visualizationType: 'lnsXY' | 'lnsLegacyMetric' | 'lnsHeatmap' = 'lnsXY', lastRefresh?: number ): TypedLensByValueInput['attributes'] { - const query = this.globalFilter || this.layerConfigs[0].seriesConfig.query; + const query: Query | undefined = this.globalFilter || this.layerConfigs[0].seriesConfig.query; const { internalReferences, adHocDataViews } = this.getReferences(); @@ -1290,7 +1319,7 @@ export class LensAttributes { }, visualization: this.visualization, query: query || { query: '', language: 'kuery' }, - filters: [], + filters: this.getFilters(), }, }; } diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts index 1aab4261a5d1..fe206c64dd61 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts @@ -10,6 +10,7 @@ import { FormulaPublicApi, MetricState, OperationType } from '@kbn/lens-plugin/p import type { DataView } from '@kbn/data-views-plugin/common'; import { Query } from '@kbn/es-query'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { getColorPalette } from '../synthetics/single_metric_config'; import { FORMULA_COLUMN, RECORDS_FIELD } from '../constants'; import { ColumnFilter, MetricOption } from '../../types'; @@ -28,9 +29,10 @@ export class SingleMetricLensAttributes extends LensAttributes { constructor( layerConfigs: LayerConfig[], reportType: string, - lensFormulaHelper: FormulaPublicApi + lensFormulaHelper: FormulaPublicApi, + dslFilters?: QueryDslQueryContainer[] ) { - super(layerConfigs, reportType, lensFormulaHelper); + super(layerConfigs, reportType, lensFormulaHelper, dslFilters); this.layers = {}; this.reportType = reportType; @@ -145,7 +147,7 @@ export class SingleMetricLensAttributes extends LensAttributes { ? { id: 'percent', params: { - decimals: 1, + decimals: 3, }, } : undefined, diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts index 115bb41f6630..c935d45f9e12 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts @@ -106,7 +106,7 @@ export function getSyntheticsKPIConfig({ dataView }: ConfigProps): SeriesConfig label: 'Monitor Errors', id: 'monitor_errors', columnType: OPERATION_COLUMN, - field: 'monitor.check_group', + field: 'state.id', columnFilters: [ { language: 'kuery', diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts index 5f74974a81a0..13d509a0919d 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts @@ -16,8 +16,7 @@ import { ConfigProps, SeriesConfig } from '../../types'; import { FieldLabels, FORMULA_COLUMN, RECORDS_FIELD } from '../constants'; import { buildExistsFilter } from '../utils'; -export const FINAL_SUMMARY_KQL = - 'summary: * and (summary.final_attempt: true or not summary.final_attempt: *)'; +export const FINAL_SUMMARY_KQL = 'summary.final_attempt: true'; export function getSyntheticsSingleMetricConfig({ dataView }: ConfigProps): SeriesConfig { return { defaultSeriesType: 'line', diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts index 914ac7174f5e..a269a7d4c605 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts @@ -50,7 +50,7 @@ export const sampleMetricFormulaAttribute = { format: { id: 'percent', params: { - decimals: 1, + decimals: 3, }, }, formula: "1- (count(kql='summary.down > 0') / count())", diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx index a0079568803b..1b3028a0283e 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/embeddable.tsx @@ -19,6 +19,7 @@ import { ViewMode } from '@kbn/embeddable-plugin/common'; import { observabilityFeatureId } from '@kbn/observability-shared-plugin/public'; import styled from 'styled-components'; import { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { useEBTTelemetry } from '../hooks/use_ebt_telemetry'; import { AllSeries } from '../../../..'; import { AppDataType, ReportViewType } from '../types'; @@ -57,6 +58,7 @@ export interface ExploratoryEmbeddableProps { lineHeight?: number; dataTestSubj?: string; searchSessionId?: string; + dslFilters?: QueryDslQueryContainer[]; } export interface ExploratoryEmbeddableComponentProps extends ExploratoryEmbeddableProps { diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/use_embeddable_attributes.ts b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/use_embeddable_attributes.ts index 4b58ce536651..f1124d2a32a3 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/use_embeddable_attributes.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/embeddable/use_embeddable_attributes.ts @@ -21,6 +21,7 @@ export const useEmbeddableAttributes = ({ reportType, reportConfigMap = {}, lensFormulaHelper, + dslFilters, }: ExploratoryEmbeddableComponentProps) => { const spaceId = useKibanaSpace(); const theme = useTheme(); @@ -40,7 +41,8 @@ export const useEmbeddableAttributes = ({ const lensAttributes = new SingleMetricLensAttributes( layerConfigs, reportType, - lensFormulaHelper! + lensFormulaHelper!, + dslFilters ); return lensAttributes?.getJSON('lnsLegacyMetric'); } else if (reportType === ReportTypes.HEATMAP) { @@ -51,7 +53,12 @@ export const useEmbeddableAttributes = ({ ); return lensAttributes?.getJSON('lnsHeatmap'); } else { - const lensAttributes = new LensAttributes(layerConfigs, reportType, lensFormulaHelper); + const lensAttributes = new LensAttributes( + layerConfigs, + reportType, + lensFormulaHelper, + dslFilters + ); return lensAttributes?.getJSON(); } } catch (error) { @@ -60,6 +67,7 @@ export const useEmbeddableAttributes = ({ }, [ attributes, dataViewState, + dslFilters, lensFormulaHelper, reportConfigMap, reportType, diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_container_metrics_charts.test.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_container_metrics_charts.test.ts index 392582d4f9ee..496029a8b436 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_container_metrics_charts.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_container_metrics_charts.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { ContainerMetricTypes } from '../charts/types'; import { useK8sContainerPageViewMetricsCharts, @@ -48,10 +48,10 @@ describe('useDockerContainerCharts', () => { async (metric) => { const expectedOrder = getContainerChartsExpectedOrder(metric); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useDockerContainerPageViewMetricsCharts({ metricsDataViewId, metric }) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { charts } = result.current; @@ -68,13 +68,14 @@ describe('useDockerContainerCharts', () => { describe('useDockerKPIMetricsCharts', () => { it('should return an array of charts with correct order', async () => { const expectedOrder = ['cpuUsage', 'memoryUsage']; - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useDockerContainerKpiCharts({ dataViewId: metricsDataViewId }) ); - await waitForNextUpdate(); - expect(result.current).toHaveLength(expectedOrder.length); - result.current.forEach((chart, index) => { - expect(chart).toHaveProperty('id', expectedOrder[index]); + await waitFor(() => { + expect(result.current).toHaveLength(expectedOrder.length); + result.current.forEach((chart, index) => { + expect(chart).toHaveProperty('id', expectedOrder[index]); + }); }); }); }); @@ -86,10 +87,10 @@ describe('useK8sContainerCharts', () => { async (metric) => { const expectedOrder = getK8sContainerChartsExpectedOrder(metric); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useK8sContainerPageViewMetricsCharts({ metricsDataViewId, metric }) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { charts } = result.current; @@ -106,13 +107,14 @@ describe('useK8sContainerCharts', () => { describe('useK8sContainerKPIMetricsCharts', () => { it('should return an array of charts with correct order', async () => { const expectedOrder = ['k8sCpuUsage', 'k8sMemoryUsage']; - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useK8sContainerKpiCharts({ dataViewId: metricsDataViewId }) ); - await waitForNextUpdate(); - expect(result.current).toHaveLength(expectedOrder.length); - result.current.forEach((chart, index) => { - expect(chart).toHaveProperty('id', expectedOrder[index]); + await waitFor(() => { + expect(result.current).toHaveLength(expectedOrder.length); + result.current.forEach((chart, index) => { + expect(chart).toHaveProperty('id', expectedOrder[index]); + }); }); }); }); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_entity_summary.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_entity_summary.ts index e62defaac6d4..500dd02c63e1 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_entity_summary.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_entity_summary.ts @@ -6,10 +6,16 @@ */ import * as z from '@kbn/zod'; -import { EntityDataStreamType, ENTITY_TYPES } from '@kbn/observability-shared-plugin/common'; +import { + EntityDataStreamType, + BUILT_IN_ENTITY_TYPES, +} from '@kbn/observability-shared-plugin/common'; import { useFetcher } from '../../../hooks/use_fetcher'; -const EntityTypeSchema = z.union([z.literal(ENTITY_TYPES.HOST), z.literal(ENTITY_TYPES.CONTAINER)]); +const EntityTypeSchema = z.union([ + z.literal(BUILT_IN_ENTITY_TYPES.HOST), + z.literal(BUILT_IN_ENTITY_TYPES.CONTAINER), +]); const EntityDataStreamSchema = z.union([ z.literal(EntityDataStreamType.METRICS), z.literal(EntityDataStreamType.LOGS), diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_host_metrics_charts.test.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_host_metrics_charts.test.ts index 006fae9bec75..f95ab156222e 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_host_metrics_charts.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_host_metrics_charts.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { HostMetricTypes } from '../charts/types'; import { useHostKpiCharts, useHostCharts, useKubernetesCharts } from './use_host_metrics_charts'; @@ -40,10 +40,8 @@ describe('useHostCharts', () => { async (metric) => { const expectedOrder = getHostChartsExpectedOrder(metric, false); - const { result, waitForNextUpdate } = renderHook(() => - useHostCharts({ dataViewId, metric }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useHostCharts({ dataViewId, metric })); + await waitFor(() => new Promise((resolve) => resolve(null))); const { charts } = result.current; @@ -60,10 +58,10 @@ describe('useHostCharts', () => { async (metric) => { const expectedOrder = getHostChartsExpectedOrder(metric, true); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useHostCharts({ dataViewId, metric, overview: true }) ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { charts } = result.current; @@ -80,10 +78,8 @@ describe('useHostCharts', () => { describe('useKubernetesCharts', () => { it('should return an array of charts with correct order - overview', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useKubernetesCharts({ dataViewId, overview: true }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useKubernetesCharts({ dataViewId, overview: true })); + await waitFor(() => new Promise((resolve) => resolve(null))); const expectedOrder = ['nodeCpuCapacity', 'nodeMemoryCapacity']; @@ -97,8 +93,8 @@ describe('useKubernetesCharts', () => { }); it('should return an array of charts with correct order', async () => { - const { result, waitForNextUpdate } = renderHook(() => useKubernetesCharts({ dataViewId })); - await waitForNextUpdate(); + const { result } = renderHook(() => useKubernetesCharts({ dataViewId })); + await waitFor(() => new Promise((resolve) => resolve(null))); const expectedOrder = [ 'nodeCpuCapacity', @@ -119,8 +115,8 @@ describe('useKubernetesCharts', () => { describe('useHostKpiCharts', () => { it('should return an array of charts with correct order', async () => { - const { result, waitForNextUpdate } = renderHook(() => useHostKpiCharts({ dataViewId })); - await waitForNextUpdate(); + const { result } = renderHook(() => useHostKpiCharts({ dataViewId })); + await waitFor(() => new Promise((resolve) => resolve(null))); const expectedOrder = ['cpuUsage', 'normalizedLoad1m', 'memoryUsage', 'diskUsage']; @@ -140,10 +136,8 @@ describe('useHostKpiCharts', () => { getSubtitle: () => 'Custom Subtitle', }; - const { result, waitForNextUpdate } = renderHook(() => - useHostKpiCharts({ dataViewId, ...options }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useHostKpiCharts({ dataViewId, ...options })); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current).toHaveLength(4); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_loading_state.test.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_loading_state.test.ts index becd0e81c0a9..2475c97cec0e 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_loading_state.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_loading_state.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useLoadingState } from './use_loading_state'; import { useDatePickerContext, type UseDateRangeProviderProps } from './use_date_picker'; import { BehaviorSubject, EMPTY, of, Subject, Subscription, skip } from 'rxjs'; @@ -102,7 +102,7 @@ describe('useLoadingState', () => { }); it('should set isAutoRefreshRequestPending to true when there are requests pending', async () => { - const { result, unmount, waitFor } = renderHook(() => useLoadingState()); + const { result, unmount } = renderHook(() => useLoadingState()); let receivedValue = false; subscription.add( @@ -128,7 +128,7 @@ describe('useLoadingState', () => { }); it('should set isAutoRefreshRequestPending to false when all requests complete', async () => { - const { result, unmount, waitFor } = renderHook(() => useLoadingState()); + const { result, unmount } = renderHook(() => useLoadingState()); let receivedValue = true; subscription.add( @@ -153,7 +153,7 @@ describe('useLoadingState', () => { }); it('should not call updateSearchSessionId if waitUntilNextSessionCompletesMock$ returns empty', async () => { - const { unmount, waitFor } = renderHook(() => useLoadingState()); + const { unmount } = renderHook(() => useLoadingState()); // waitUntilNextSessionCompletes$ returns EMPTY when the status is loading or none sessionState$.next(SearchSessionState.Loading); @@ -171,7 +171,7 @@ describe('useLoadingState', () => { }); it('should call updateSearchSessionId when waitUntilNextSessionCompletesMock$ returns', async () => { - const { unmount, waitFor } = renderHook(() => useLoadingState()); + const { unmount } = renderHook(() => useLoadingState()); // waitUntilNextSessionCompletes$ returns something when the status is Completed or BackgroundCompleted sessionState$.next(SearchSessionState.Loading); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_profiling_kuery.test.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_profiling_kuery.test.tsx index 10325ce51f81..0d4db96a23fa 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_profiling_kuery.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_profiling_kuery.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { MemoryRouter, useHistory } from 'react-router-dom'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useProfilingKuery } from './use_profiling_kuery'; import { useAssetDetailsRenderPropsContext } from './use_asset_details_render_props'; diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_request_observable.test.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_request_observable.test.ts index 9ca4bd17ca34..9be24cbfcf3f 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_request_observable.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_request_observable.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useRequestObservable } from './use_request_observable'; import { type RequestState, useLoadingStateContext } from './use_loading_state'; import { useDatePickerContext, type UseDateRangeProviderProps } from './use_date_picker'; @@ -69,7 +69,7 @@ describe('useRequestObservable', () => { }); it('should process a valid request function', async () => { - const { result, waitFor, unmount } = renderHook(() => useRequestObservable()); + const { result, unmount } = renderHook(() => useRequestObservable()); act(() => { result.current.request$.next(() => Promise.resolve()); @@ -85,7 +85,7 @@ describe('useRequestObservable', () => { }); it('should be able to make new requests if isAutoRefreshRequestPending is false', async () => { - const { result, waitFor, unmount } = renderHook(() => useRequestObservable()); + const { result, unmount } = renderHook(() => useRequestObservable()); act(() => { isAutoRefreshRequestPendingMock$.next(false); @@ -102,7 +102,7 @@ describe('useRequestObservable', () => { }); it('should block new requests when isAutoRefreshRequestPending is true', async () => { - const { result, waitFor, unmount } = renderHook(() => useRequestObservable()); + const { result, unmount } = renderHook(() => useRequestObservable()); act(() => { isAutoRefreshRequestPendingMock$.next(false); @@ -123,7 +123,7 @@ describe('useRequestObservable', () => { }); it('should not block new requests when auto-refresh is paused', async () => { - const { result, waitFor, unmount } = renderHook(() => useRequestObservable()); + const { result, unmount } = renderHook(() => useRequestObservable()); act(() => { autoRefreshConfig$.next({ isPaused: true, interval: 5000 }); @@ -144,7 +144,7 @@ describe('useRequestObservable', () => { }); it('should complete the request when an error is thrown', async () => { - const { result, waitFor, unmount } = renderHook(() => useRequestObservable()); + const { result, unmount } = renderHook(() => useRequestObservable()); act(() => { autoRefreshConfig$.next({ isPaused: true, interval: 5000 }); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/processes/processes.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/processes/processes.tsx index 2177cd050908..57a07d4dc296 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/processes/processes.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/processes/processes.tsx @@ -23,7 +23,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { getFieldByType } from '@kbn/metrics-data-access-plugin/common'; import { decodeOrThrow } from '@kbn/io-ts-utils'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { ENTITY_TYPES } from '@kbn/observability-shared-plugin/common'; +import { BUILT_IN_ENTITY_TYPES } from '@kbn/observability-shared-plugin/common'; import { useSourceContext } from '../../../../containers/metrics_source'; import { isPending, useFetcher } from '../../../../hooks/use_fetcher'; import { parseSearchString } from './parse_search_string'; @@ -58,7 +58,7 @@ export const Processes = () => { const { request$ } = useRequestObservable(); const { isActiveTab } = useTabSwitcherContext(); const { dataStreams, status: dataStreamsStatus } = useEntitySummary({ - entityType: ENTITY_TYPES.HOST, + entityType: BUILT_IN_ENTITY_TYPES.HOST, entityId: asset.name, }); const addMetricsCalloutId: AddMetricsCalloutKey = 'hostProcesses'; diff --git a/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.test.ts b/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.test.ts index 80d538ef1e50..b754537107e4 100644 --- a/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useModuleStatus } from './infra_ml_module_status'; describe('useModuleStatus', () => { diff --git a/x-pack/plugins/observability_solution/infra/public/containers/plugin_config_context.test.tsx b/x-pack/plugins/observability_solution/infra/public/containers/plugin_config_context.test.tsx index 62c582a81824..a33c3432f06f 100644 --- a/x-pack/plugins/observability_solution/infra/public/containers/plugin_config_context.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/containers/plugin_config_context.test.tsx @@ -6,15 +6,15 @@ */ import type { InfraConfig } from '../../common/plugin_config_types'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import React from 'react'; import { PluginConfigProvider, usePluginConfig } from './plugin_config_context'; describe('usePluginConfig()', () => { it('throws an error if the context value was not set before using the hook', () => { - const { result } = renderHook(() => usePluginConfig()); - - expect(result.error).not.toEqual(undefined); + expect(() => renderHook(() => usePluginConfig())).toThrow( + /PluginConfigContext value was not initialized./ + ); }); it('returns the plugin config what was set through the provider', () => { @@ -40,7 +40,6 @@ describe('usePluginConfig()', () => { }, }); - expect(result.error).toEqual(undefined); expect(result.current).toEqual(config); }); }); diff --git a/x-pack/plugins/observability_solution/infra/public/hooks/use_alerts_count.test.ts b/x-pack/plugins/observability_solution/infra/public/hooks/use_alerts_count.test.ts index 2b2dc755c94c..87db1db65398 100644 --- a/x-pack/plugins/observability_solution/infra/public/hooks/use_alerts_count.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/hooks/use_alerts_count.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { ALERT_STATUS, ValidFeatureId } from '@kbn/rule-data-utils'; import { useAlertsCount } from './use_alerts_count'; @@ -66,12 +66,12 @@ describe('useAlertsCount', () => { it('should return the mocked data from API', async () => { mockedPostAPI.mockResolvedValue(mockedAlertsCountResponse); - const { result, waitForNextUpdate } = renderHook(() => useAlertsCount({ featureIds })); + const { result } = renderHook(() => useAlertsCount({ featureIds })); expect(result.current.loading).toBe(true); expect(result.current.alertsCount).toEqual(undefined); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { alertsCount, loading, error } = result.current; expect(alertsCount).toEqual(expectedResult); @@ -88,15 +88,13 @@ describe('useAlertsCount', () => { }; mockedPostAPI.mockResolvedValue(mockedAlertsCountResponse); - const { waitForNextUpdate } = renderHook(() => + renderHook(() => useAlertsCount({ featureIds, query, }) ); - await waitForNextUpdate(); - const body = JSON.stringify({ aggs: { count: { @@ -108,9 +106,11 @@ describe('useAlertsCount', () => { size: 0, }); - expect(mockedPostAPI).toHaveBeenCalledWith( - '/internal/rac/alerts/find', - expect.objectContaining({ body }) + await waitFor(() => + expect(mockedPostAPI).toHaveBeenCalledWith( + '/internal/rac/alerts/find', + expect.objectContaining({ body }) + ) ); }); @@ -118,10 +118,8 @@ describe('useAlertsCount', () => { const error = new Error('Fetch Alerts Count Failed'); mockedPostAPI.mockRejectedValueOnce(error); - const { result, waitForNextUpdate } = renderHook(() => useAlertsCount({ featureIds })); - - await waitForNextUpdate(); + const { result } = renderHook(() => useAlertsCount({ featureIds })); - expect(result.current.error?.message).toMatch(error.message); + await waitFor(() => expect(result.current.error?.message).toMatch(error.message)); }); }); diff --git a/x-pack/plugins/observability_solution/infra/public/hooks/use_lens_attributes.test.ts b/x-pack/plugins/observability_solution/infra/public/hooks/use_lens_attributes.test.ts index 0a619069c04a..5e5f00fd6613 100644 --- a/x-pack/plugins/observability_solution/infra/public/hooks/use_lens_attributes.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/hooks/use_lens_attributes.test.ts @@ -6,7 +6,7 @@ */ import 'jest-canvas-mock'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useLensAttributes } from './use_lens_attributes'; import { coreMock } from '@kbn/core/public/mocks'; import { type KibanaReactContextValue, useKibana } from '@kbn/kibana-react-plugin/public'; @@ -72,15 +72,15 @@ describe('useLensAttributes hook', () => { }); it('should return the basic lens attributes', async () => { - const { waitForNextUpdate } = renderHook(() => useLensAttributes(params)); - await waitForNextUpdate(); - - expect(LensConfigBuilderMock.mock.instances[0].build).toHaveBeenCalledWith(params); + renderHook(() => useLensAttributes(params)); + await waitFor(() => + expect(LensConfigBuilderMock.mock.instances[0].build).toHaveBeenCalledWith(params) + ); }); it('should return extra actions', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLensAttributes(params)); - await waitForNextUpdate(); + const { result } = renderHook(() => useLensAttributes(params)); + await waitFor(() => new Promise((resolve) => resolve(null))); const extraActions = result.current.getExtraActions({ timeRange: { diff --git a/x-pack/plugins/observability_solution/infra/public/hooks/use_lens_attributes.ts b/x-pack/plugins/observability_solution/infra/public/hooks/use_lens_attributes.ts index 9e0f9071eb07..b1248a1f05e1 100644 --- a/x-pack/plugins/observability_solution/infra/public/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability_solution/infra/public/hooks/use_lens_attributes.ts @@ -6,7 +6,7 @@ */ import { useCallback } from 'react'; -import { Filter, Query, TimeRange } from '@kbn/es-query'; +import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { i18n } from '@kbn/i18n'; import useAsync from 'react-use/lib/useAsync'; @@ -37,7 +37,13 @@ export const useLensAttributes = (params: UseLensAttributesParams) => { }, [params, dataViews, lens]); const injectFilters = useCallback( - ({ filters, query }: { filters: Filter[]; query: Query }): LensAttributes | null => { + ({ + filters, + query, + }: { + filters: Filter[]; + query: Query | AggregateQuery; + }): LensAttributes | null => { if (!attributes) { return null; } @@ -63,7 +69,7 @@ export const useLensAttributes = (params: UseLensAttributesParams) => { }: { timeRange: TimeRange; filters: Filter[]; - query: Query; + query: Query | AggregateQuery; searchSessionId?: string; }) => () => { @@ -94,7 +100,7 @@ export const useLensAttributes = (params: UseLensAttributesParams) => { }: { timeRange: TimeRange; filters?: Filter[]; - query?: Query; + query?: Query | AggregateQuery; searchSessionId?: string; }) => { const openInLens = getOpenInLensAction( diff --git a/x-pack/plugins/observability_solution/infra/public/pages/link_to/use_host_ip_to_name.test.ts b/x-pack/plugins/observability_solution/infra/public/pages/link_to/use_host_ip_to_name.test.ts index 150416a204b0..2a9d56a6f4df 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/link_to/use_host_ip_to_name.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/link_to/use_host_ip_to_name.test.ts @@ -6,7 +6,7 @@ */ import { useHostIpToName } from './use_host_ip_to_name'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; const renderUseHostIpToNameHook = () => renderHook((props) => useHostIpToName(props.ipAddress, props.indexPattern), { @@ -32,10 +32,10 @@ jest.mock('@kbn/kibana-react-plugin/public', () => { describe('useHostIpToName Hook', () => { it('should basically work', async () => { mockedFetch.mockResolvedValue({ host: 'example-01' } as any); - const { result, waitForNextUpdate } = renderUseHostIpToNameHook(); + const { result } = renderUseHostIpToNameHook(); expect(result.current.name).toBe(null); expect(result.current.loading).toBe(true); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.name).toBe('example-01'); expect(result.current.loading).toBe(false); expect(result.current.error).toBe(null); @@ -44,10 +44,10 @@ describe('useHostIpToName Hook', () => { it('should handle errors', async () => { const error = new Error('Host not found'); mockedFetch.mockRejectedValue(error); - const { result, waitForNextUpdate } = renderUseHostIpToNameHook(); + const { result } = renderUseHostIpToNameHook(); expect(result.current.name).toBe(null); expect(result.current.loading).toBe(true); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.name).toBe(null); expect(result.current.loading).toBe(false); expect(result.current.error).toBe(error); @@ -56,16 +56,16 @@ describe('useHostIpToName Hook', () => { it('should reset errors', async () => { const error = new Error('Host not found'); mockedFetch.mockRejectedValue(error); - const { result, waitForNextUpdate, rerender } = renderUseHostIpToNameHook(); + const { result, rerender } = renderUseHostIpToNameHook(); expect(result.current.name).toBe(null); expect(result.current.loading).toBe(true); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.name).toBe(null); expect(result.current.loading).toBe(false); expect(result.current.error).toBe(error); mockedFetch.mockResolvedValue({ host: 'example-01' } as any); rerender({ ipAddress: '192.168.1.2', indexPattern: 'metricbeat-*' }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current.name).toBe('example-01'); expect(result.current.loading).toBe(false); expect(result.current.error).toBe(null); diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts index a0ebb2018491..abae2d5cadbf 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts @@ -6,7 +6,7 @@ */ import { type HostNodeRow, useHostsTable } from './use_hosts_table'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { InfraAssetMetricsItem } from '../../../../../common/http_api'; import * as useUnifiedSearchHooks from './use_unified_search'; import * as useHostsViewHooks from './use_hosts_view'; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_metrics_charts.test.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_metrics_charts.test.ts index b76d5938f0cb..9d68b3ff76e7 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_metrics_charts.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_metrics_charts.test.ts @@ -6,16 +6,14 @@ */ import type { LensSeriesLayer } from '@kbn/lens-embeddable-utils/config_builder'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { PAGE_SIZE_OPTIONS } from '../constants'; import { useMetricsCharts } from './use_metrics_charts'; describe('useMetricsCharts', () => { it('should return an array of charts with breakdown config', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useMetricsCharts({ dataViewId: 'dataViewId' }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useMetricsCharts({ dataViewId: 'dataViewId' })); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(result.current).toHaveLength(11); @@ -29,10 +27,8 @@ describe('useMetricsCharts', () => { }); it('should return an array of charts with correct order', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useMetricsCharts({ dataViewId: 'dataViewId' }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useMetricsCharts({ dataViewId: 'dataViewId' })); + await waitFor(() => new Promise((resolve) => resolve(null))); const expectedOrder = [ 'cpuUsage', diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.test.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.test.ts index 7fe7b8b3fe18..533857130b11 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { DataView } from '@kbn/data-views-plugin/common'; import { useWaffleFilters, WaffleFiltersState } from './use_waffle_filters'; import { TIMESTAMP_FIELD } from '../../../../../common/constants'; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.test.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.test.ts index 50f4e95b58a3..757a0e955b4d 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useWaffleOptions, WaffleOptionsState } from './use_waffle_options'; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.test.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.test.tsx index bcea796c3b00..0e70071f199f 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useMetricsExplorerState } from './use_metric_explorer_state'; import { MetricsExplorerOptionsContainer } from './use_metrics_explorer_options'; import React from 'react'; @@ -75,6 +75,10 @@ describe('useMetricsExplorerState', () => { delete STORE.MetricsExplorerTimeRange; }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should just work', async () => { mockedUseMetricsExplorerData.mockReturnValue({ isLoading: false, @@ -92,12 +96,14 @@ describe('useMetricsExplorerState', () => { describe('handleRefresh', () => { it('should trigger an addition request when handleRefresh is called', async () => { const { result } = renderUseMetricsExplorerStateHook(); - expect(result.all.length).toBe(2); - const numberOfHookCalls = result.all.length; + + const numberOfHookCalls = mockedUseMetricsExplorerData.mock.calls.length; + + expect(numberOfHookCalls).toEqual(2); act(() => { result.current.refresh(); }); - expect(result.all.length).toBe(numberOfHookCalls + 1); + expect(mockedUseMetricsExplorerData).toHaveBeenCalledTimes(numberOfHookCalls + 1); }); }); diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx index 202ae51990ad..8cc6bff922d9 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx @@ -9,7 +9,7 @@ import React, { FC, PropsWithChildren } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useMetricsExplorerData } from './use_metrics_explorer_data'; import { DataView } from '@kbn/data-views-plugin/common'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, act, renderHook } from '@testing-library/react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { @@ -108,15 +108,14 @@ describe('useMetricsExplorerData Hook', () => { it('should just work', async () => { mockedFetch.mockResolvedValue(resp); - const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook(); + const { result } = renderUseMetricsExplorerDataHook(); expect(result.current.data).toBeUndefined(); expect(result.current.isLoading).toBe(true); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.data!.pages[0]).toEqual(resp); - expect(result.current.isLoading).toBe(false); const { series } = result.current.data!.pages[0]; expect(series).toBeDefined(); expect(series.length).toBe(3); @@ -124,12 +123,11 @@ describe('useMetricsExplorerData Hook', () => { it('should paginate', async () => { mockedFetch.mockResolvedValue(resp); - const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook(); + const { result } = renderUseMetricsExplorerDataHook(); expect(result.current.data).toBeUndefined(); expect(result.current.isLoading).toBe(true); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.data!.pages[0]).toEqual(resp); - expect(result.current.isLoading).toBe(false); const { series } = result.current.data!.pages[0]; expect(series).toBeDefined(); expect(series.length).toBe(3); @@ -137,41 +135,45 @@ describe('useMetricsExplorerData Hook', () => { pageInfo: { total: 10, afterKey: 'host-06' }, series: [createSeries('host-04'), createSeries('host-05'), createSeries('host-06')], } as any); - result.current.fetchNextPage(); - await waitForNextUpdate(); - expect(result.current.isLoading).toBe(false); - const { series: nextSeries } = result.current.data!.pages[1]; - expect(nextSeries).toBeDefined(); - expect(nextSeries.length).toBe(3); + await act(async () => { + await result.current.fetchNextPage(); + }); + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + const { series: nextSeries } = result.current.data!.pages[1]; + expect(nextSeries).toBeDefined(); + expect(nextSeries.length).toBe(3); + }); }); it('should reset error upon recovery', async () => { const error = new Error('Network Error'); mockedFetch.mockRejectedValue(error); - const { result, waitForNextUpdate } = renderUseMetricsExplorerDataHook(); + const { result } = renderUseMetricsExplorerDataHook(); expect(result.current.data).toBeUndefined(); expect(result.current.error).toEqual(null); expect(result.current.isLoading).toBe(true); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.data).toBeUndefined(); expect(result.current.error).toEqual(error); - expect(result.current.isLoading).toBe(false); mockedFetch.mockResolvedValue(resp as any); - result.current.refetch(); - await waitForNextUpdate(); - expect(result.current.data!.pages[0]).toEqual(resp); - expect(result.current.isLoading).toBe(false); - expect(result.current.error).toBe(null); + await act(async () => { + await result.current.refetch(); + }); + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + expect(result.current.data!.pages[0]).toEqual(resp); + expect(result.current.error).toBe(null); + }); }); it('should not paginate on option change', async () => { mockedFetch.mockResolvedValue(resp as any); - const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerDataHook(); + const { result, rerender } = renderUseMetricsExplorerDataHook(); expect(result.current.data).toBeUndefined(); expect(result.current.isLoading).toBe(true); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.data!.pages[0]).toEqual(resp); - expect(result.current.isLoading).toBe(false); const { series } = result.current.data!.pages[0]; expect(series).toBeDefined(); expect(series.length).toBe(3); @@ -187,19 +189,19 @@ describe('useMetricsExplorerData Hook', () => { timestamps, }); expect(result.current.isLoading).toBe(true); - await waitForNextUpdate(); - expect(result.current.data!.pages[0]).toEqual(resp); - expect(result.current.isLoading).toBe(false); + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + expect(result.current.data!.pages[0]).toEqual(resp); + }); }); it('should not paginate on time change', async () => { mockedFetch.mockResolvedValue(resp as any); - const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerDataHook(); + const { result, rerender } = renderUseMetricsExplorerDataHook(); expect(result.current.data).toBeUndefined(); expect(result.current.isLoading).toBe(true); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.data!.pages[0]).toEqual(resp); - expect(result.current.isLoading).toBe(false); const { series } = result.current.data!.pages[0]; expect(series).toBeDefined(); expect(series.length).toBe(3); @@ -211,8 +213,9 @@ describe('useMetricsExplorerData Hook', () => { timestamps: { fromTimestamp: 1678378092225, toTimestamp: 1678381693477, interval: '>=10s' }, }); expect(result.current.isLoading).toBe(true); - await waitForNextUpdate(); - expect(result.current.data!.pages[0]).toEqual(resp); - expect(result.current.isLoading).toBe(false); + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + expect(result.current.data!.pages[0]).toEqual(resp); + }); }); }); diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.test.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.test.tsx index 795728fa8d8e..f5c257e1f86a 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useMetricsExplorerOptions, MetricsExplorerOptions, diff --git a/x-pack/plugins/observability_solution/infra/public/utils/data_search/use_data_search_request.test.tsx b/x-pack/plugins/observability_solution/infra/public/utils/data_search/use_data_search_request.test.tsx index 33369c1125e1..08907b162708 100644 --- a/x-pack/plugins/observability_solution/infra/public/utils/data_search/use_data_search_request.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/utils/data_search/use_data_search_request.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import React from 'react'; import { firstValueFrom, Observable, of, Subject } from 'rxjs'; import type { ISearchGeneric, IKibanaSearchResponse } from '@kbn/search-types'; diff --git a/x-pack/plugins/observability_solution/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx b/x-pack/plugins/observability_solution/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx index a4b01be03d80..24433f23bc67 100644 --- a/x-pack/plugins/observability_solution/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; import { IKibanaSearchRequest } from '@kbn/search-types'; import { ParsedDataSearchRequestDescriptor, ParsedKibanaSearchResponse } from './types'; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/helpers/get_apm_data_access_client.ts b/x-pack/plugins/observability_solution/infra/server/lib/helpers/get_apm_data_access_client.ts index e99d57eb4d6c..dcf63e01b7e9 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/helpers/get_apm_data_access_client.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/helpers/get_apm_data_access_client.ts @@ -27,23 +27,17 @@ export const getApmDataAccessClient = ({ context: InfraPluginRequestHandlerContext; request: KibanaRequest; }) => { - const hasPrivileges = async () => { - const apmDataAccessStart = await libs.plugins.apmDataAccess.start(); - return apmDataAccessStart.hasPrivileges({ request }); - }; - const getServices = async () => { const apmDataAccess = libs.plugins.apmDataAccess.setup; const coreContext = await context.core; - const { savedObjects, uiSettings, elasticsearch } = coreContext; - const savedObjectsClient = savedObjects.client; + const { uiSettings, elasticsearch } = coreContext; const esClient = elasticsearch.client.asCurrentUser; const uiSettingsClient = uiSettings.client; const [apmIndices, includeFrozen] = await Promise.all([ - apmDataAccess.getApmIndices(savedObjectsClient), + apmDataAccess.getApmIndices(), uiSettingsClient.get<boolean>(UI_SETTINGS.SEARCH_INCLUDE_FROZEN), ]); @@ -86,5 +80,5 @@ export const getApmDataAccessClient = ({ }; }; - return { hasPrivileges, getServices }; + return { getServices }; }; diff --git a/x-pack/plugins/observability_solution/infra/server/routes/entities/get_data_stream_types.test.ts b/x-pack/plugins/observability_solution/infra/server/routes/entities/get_data_stream_types.test.ts index e6bf32332a51..75048ac22a6a 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/entities/get_data_stream_types.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/entities/get_data_stream_types.test.ts @@ -10,6 +10,7 @@ import { type InfraMetricsClient } from '../../lib/helpers/get_infra_metrics_cli import { getDataStreamTypes } from './get_data_stream_types'; import { getHasMetricsData } from './get_has_metrics_data'; import { getLatestEntity } from './get_latest_entity'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; jest.mock('./get_has_metrics_data', () => ({ getHasMetricsData: jest.fn(), @@ -25,6 +26,7 @@ describe('getDataStreamTypes', () => { let infraMetricsClient: jest.Mocked<InfraMetricsClient>; let obsEsClient: jest.Mocked<ObservabilityElasticsearchClient>; let entityManagerClient: jest.Mocked<EntityClient>; + const logger = loggingSystemMock.createLogger(); beforeEach(() => { infraMetricsClient = {} as jest.Mocked<InfraMetricsClient>; @@ -43,6 +45,7 @@ describe('getDataStreamTypes', () => { infraMetricsClient, obsEsClient, entityManagerClient, + logger, }; const result = await getDataStreamTypes(params); @@ -65,6 +68,7 @@ describe('getDataStreamTypes', () => { infraMetricsClient, obsEsClient, entityManagerClient, + logger, }; const result = await getDataStreamTypes(params); @@ -84,6 +88,7 @@ describe('getDataStreamTypes', () => { infraMetricsClient, obsEsClient, entityManagerClient, + logger, }; const result = await getDataStreamTypes(params); @@ -95,6 +100,7 @@ describe('getDataStreamTypes', () => { entityId: 'entity123', entityType: 'host', entityManagerClient, + logger, }); }); @@ -109,6 +115,7 @@ describe('getDataStreamTypes', () => { infraMetricsClient, obsEsClient, entityManagerClient, + logger, }; const result = await getDataStreamTypes(params); @@ -128,6 +135,7 @@ describe('getDataStreamTypes', () => { infraMetricsClient, obsEsClient, entityManagerClient, + logger, }; const result = await getDataStreamTypes(params); diff --git a/x-pack/plugins/observability_solution/infra/server/routes/entities/get_data_stream_types.ts b/x-pack/plugins/observability_solution/infra/server/routes/entities/get_data_stream_types.ts index 2d587a6e7d9a..4a949de4d0ed 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/entities/get_data_stream_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/entities/get_data_stream_types.ts @@ -10,6 +10,7 @@ import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import { EntityDataStreamType } from '@kbn/observability-shared-plugin/common'; import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client'; import { castArray } from 'lodash'; +import { Logger } from '@kbn/logging'; import { type InfraMetricsClient } from '../../lib/helpers/get_infra_metrics_client'; import { getHasMetricsData } from './get_has_metrics_data'; import { getLatestEntity } from './get_latest_entity'; @@ -21,6 +22,7 @@ interface Params { infraMetricsClient: InfraMetricsClient; obsEsClient: ObservabilityElasticsearchClient; entityManagerClient: EntityClient; + logger: Logger; } export async function getDataStreamTypes({ @@ -30,6 +32,7 @@ export async function getDataStreamTypes({ entityType, infraMetricsClient, obsEsClient, + logger, }: Params) { const hasMetricsData = await getHasMetricsData({ infraMetricsClient, @@ -48,6 +51,7 @@ export async function getDataStreamTypes({ entityId, entityType, entityManagerClient, + logger, }); if (latestEntity) { diff --git a/x-pack/plugins/observability_solution/infra/server/routes/entities/get_latest_entity.ts b/x-pack/plugins/observability_solution/infra/server/routes/entities/get_latest_entity.ts index 0756bc3d52c8..c7669e6f9acd 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/entities/get_latest_entity.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/entities/get_latest_entity.ts @@ -9,6 +9,7 @@ import { ENTITY_LATEST, entitiesAliasPattern } from '@kbn/entities-schema'; import { type EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client'; import { ENTITY_TYPE, SOURCE_DATA_STREAM_TYPE } from '@kbn/observability-shared-plugin/common'; import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client'; +import type { Logger } from '@kbn/logging'; const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({ type: '*', @@ -24,32 +25,45 @@ export async function getLatestEntity({ entityId, entityType, entityManagerClient, + logger, }: { inventoryEsClient: ObservabilityElasticsearchClient; entityType: 'host' | 'container'; entityId: string; entityManagerClient: EntityClient; + logger: Logger; }): Promise<EntitySourceResponse | undefined> { - const { definitions } = await entityManagerClient.getEntityDefinitions({ - builtIn: true, - type: entityType, - }); + try { + const { definitions } = await entityManagerClient.getEntityDefinitions({ + builtIn: true, + type: entityType, + }); - const hostOrContainerIdentityField = definitions[0]?.identityFields?.[0]?.field; - if (hostOrContainerIdentityField === undefined) { - return undefined; - } + const hostOrContainerIdentityField = definitions[0]?.identityFields?.[0]?.field; + if (hostOrContainerIdentityField === undefined) { + return undefined; + } - const response = await inventoryEsClient.esql<{ - source_data_stream?: { type?: string | string[] }; - }>('get_latest_entities', { - query: `FROM ${ENTITIES_LATEST_ALIAS} + const response = await inventoryEsClient.esql< + { + 'source_data_stream.type'?: string | string; + }, + { transform: 'plain' } + >( + 'get_latest_entities', + { + query: `FROM ${ENTITIES_LATEST_ALIAS} | WHERE ${ENTITY_TYPE} == ? | WHERE ${hostOrContainerIdentityField} == ? | KEEP ${SOURCE_DATA_STREAM_TYPE} `, - params: [entityType, entityId], - }); + params: [entityType, entityId], + }, + { transform: 'plain' } + ); - return { sourceDataStreamType: response[0].source_data_stream?.type }; + return { sourceDataStreamType: response.hits[0]['source_data_stream.type'] }; + } catch (e) { + logger.error(e); + } } diff --git a/x-pack/plugins/observability_solution/infra/server/routes/entities/index.ts b/x-pack/plugins/observability_solution/infra/server/routes/entities/index.ts index 30be4fc9da49..4056faba9427 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/entities/index.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/entities/index.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { METRICS_APP_ID } from '@kbn/deeplinks-observability/constants'; import { entityCentricExperience } from '@kbn/observability-plugin/common'; import { createObservabilityEsClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client'; -import { ENTITY_TYPES } from '@kbn/observability-shared-plugin/common'; +import { BUILT_IN_ENTITY_TYPES } from '@kbn/observability-shared-plugin/common'; import { getInfraMetricsClient } from '../../lib/helpers/get_infra_metrics_client'; import { InfraBackendLibs } from '../../lib/infra_types'; import { getDataStreamTypes } from './get_data_stream_types'; @@ -24,8 +24,8 @@ export const initEntitiesConfigurationRoutes = (libs: InfraBackendLibs) => { validate: { params: schema.object({ entityType: schema.oneOf([ - schema.literal(ENTITY_TYPES.HOST), - schema.literal(ENTITY_TYPES.CONTAINER), + schema.literal(BUILT_IN_ENTITY_TYPES.HOST), + schema.literal(BUILT_IN_ENTITY_TYPES.CONTAINER), ]), entityId: schema.string(), }), @@ -36,8 +36,11 @@ export const initEntitiesConfigurationRoutes = (libs: InfraBackendLibs) => { }, async (requestContext, request, response) => { const { entityId, entityType } = request.params; - const coreContext = await requestContext.core; - const infraContext = await requestContext.infra; + const [coreContext, infraContext] = await Promise.all([ + requestContext.core, + requestContext.infra, + ]); + const entityManagerClient = await infraContext.entityManager.getScopedClient({ request }); const infraMetricsClient = await getInfraMetricsClient({ request, @@ -63,6 +66,7 @@ export const initEntitiesConfigurationRoutes = (libs: InfraBackendLibs) => { entityType, infraMetricsClient, obsEsClient, + logger, }); return response.ok({ diff --git a/x-pack/plugins/observability_solution/infra/server/routes/infra/index.ts b/x-pack/plugins/observability_solution/infra/server/routes/infra/index.ts index 1b720eeb3186..3f91a034c810 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/infra/index.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/infra/index.ts @@ -41,12 +41,11 @@ export const initInfraAssetRoutes = (libs: InfraBackendLibs) => { try { const apmDataAccessClient = getApmDataAccessClient({ request, libs, context }); - const hasApmPrivileges = await apmDataAccessClient.hasPrivileges(); const [infraMetricsClient, alertsClient, apmDataAccessServices] = await Promise.all([ getInfraMetricsClient({ request, libs, context }), getInfraAlertsClient({ libs, request }), - hasApmPrivileges ? apmDataAccessClient.getServices() : undefined, + apmDataAccessClient.getServices(), ]); const hosts = await getHosts({ @@ -97,11 +96,10 @@ export const initInfraAssetRoutes = (libs: InfraBackendLibs) => { try { const apmDataAccessClient = getApmDataAccessClient({ request, libs, context }); - const hasApmPrivileges = await apmDataAccessClient.hasPrivileges(); const [infraMetricsClient, apmDataAccessServices] = await Promise.all([ getInfraMetricsClient({ request, libs, context }), - hasApmPrivileges ? apmDataAccessClient.getServices() : undefined, + apmDataAccessClient.getServices(), ]); const count = await getHostsCount({ diff --git a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/helpers/query.ts b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/helpers/query.ts index 570c1499f3b7..52da69cd7c00 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/helpers/query.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/helpers/query.ts @@ -9,6 +9,7 @@ import { findInventoryModel } from '@kbn/metrics-data-access-plugin/common'; import { termQuery } from '@kbn/observability-plugin/server'; import { ApmDocumentType, type TimeRangeMetadata } from '@kbn/apm-data-access-plugin/common'; import { estypes } from '@elastic/elasticsearch'; +import { castArray } from 'lodash'; import type { ApmDataAccessServicesWrapper } from '../../../../lib/helpers/get_apm_data_access_client'; import { EVENT_MODULE, @@ -17,12 +18,16 @@ import { } from '../../../../../common/constants'; import type { InfraAssetMetricType } from '../../../../../common/http_api/infra'; -export const getFilterByIntegration = (integration: typeof SYSTEM_INTEGRATION) => { +export const getFilterByIntegration = ( + integration: typeof SYSTEM_INTEGRATION, + extraFilter: estypes.QueryDslQueryContainer[] = [] +) => { return { bool: { should: [ ...termQuery(EVENT_MODULE, integration), ...termQuery(METRICSET_MODULE, integration), + ...extraFilter, ], minimum_should_match: 1, }, @@ -63,7 +68,6 @@ export const getDocumentsFilter = async ({ from: number; to: number; }) => { - const filters: estypes.QueryDslQueryContainer[] = [getFilterByIntegration('system')]; const apmDocumentsFilter = apmDataAccessServices && apmDocumentSources ? await getApmDocumentsFilter({ @@ -74,9 +78,9 @@ export const getDocumentsFilter = async ({ }) : undefined; - if (apmDocumentsFilter) { - filters.push(apmDocumentsFilter); - } + const filters: estypes.QueryDslQueryContainer[] = [ + getFilterByIntegration('system', apmDocumentsFilter && castArray(apmDocumentsFilter)), + ]; return filters; }; diff --git a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts.ts b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts.ts index bb5bd51cfe1f..63fef5d438b0 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts.ts @@ -49,6 +49,7 @@ export const getHosts = async ({ const [hostMetricsResponse, alertsCountResponse] = await Promise.all([ getAllHosts({ infraMetricsClient, + apmDataAccessServices, apmDocumentSources, from, to, diff --git a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts_count.ts b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts_count.ts index 154fd8796520..e36811ea5b87 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts_count.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts_count.ts @@ -25,8 +25,14 @@ export async function getHostsCount({ }) { assertQueryStructure(query); + const apmDocumentSources = await apmDataAccessServices?.getDocumentSources({ + start: from, + end: to, + }); + const documentsFilter = await getDocumentsFilter({ apmDataAccessServices, + apmDocumentSources, from, to, }); @@ -39,7 +45,7 @@ export async function getHostsCount({ query: { bool: { filter: [query, ...rangeQuery(from, to)], - should: [...documentsFilter], + must: [...documentsFilter], }, }, aggs: { diff --git a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/types.ts b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/types.ts index 87679f24271d..8f50d9eb89f1 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/types.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/types.ts @@ -13,5 +13,5 @@ import { InfraMetricsClient } from '../../../lib/helpers/get_infra_metrics_clien export interface GetHostParameters extends GetInfraMetricsRequestBodyPayload { infraMetricsClient: InfraMetricsClient; alertsClient: InfraAlertsClient; - apmDataAccessServices?: ApmDataAccessServicesWrapper; + apmDataAccessServices: ApmDataAccessServicesWrapper; } diff --git a/x-pack/plugins/observability_solution/infra/server/routes/services/index.ts b/x-pack/plugins/observability_solution/infra/server/routes/services/index.ts index 9673b3178848..bc6ce91e830a 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/services/index.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/services/index.ts @@ -36,16 +36,6 @@ export const initServicesRoute = (libs: InfraBackendLibs) => { const { from, to, size = 10, validatedFilters } = request.query; const apmDataAccessClient = getApmDataAccessClient({ request, libs, context }); - const hasApmPrivileges = await apmDataAccessClient.hasPrivileges(); - - if (!hasApmPrivileges) { - return response.customError({ - statusCode: 403, - body: { - message: 'APM data access service is not available', - }, - }); - } const apmDataAccessServices = await apmDataAccessClient.getServices(); diff --git a/x-pack/plugins/observability_solution/inventory/common/utils/entity_type_guards.ts b/x-pack/plugins/observability_solution/inventory/common/utils/entity_type_guards.ts index dccc888abd8d..f9ace49b20d3 100644 --- a/x-pack/plugins/observability_solution/inventory/common/utils/entity_type_guards.ts +++ b/x-pack/plugins/observability_solution/inventory/common/utils/entity_type_guards.ts @@ -13,7 +13,7 @@ interface BuiltinEntityMap { container: InventoryEntity & { cloud?: { provider?: string[] } }; service: InventoryEntity & { agent?: { name: AgentName[] }; - service?: { environment?: string }; + service?: { environment?: string | string[] | null }; }; } diff --git a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/generate_data.ts b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/generate_data.ts index 3ddea0d925de..a75165004a5a 100644 --- a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/generate_data.ts +++ b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/generate_data.ts @@ -9,10 +9,12 @@ import { apm, entities, log, timerange } from '@kbn/apm-synthtrace-client'; import { generateLongIdWithSeed } from '@kbn/apm-synthtrace-client/src/lib/utils/generate_id'; const SYNTH_NODE_TRACES_LOGS_ENTITY_ID = generateLongIdWithSeed('service'); +const SERVICE_LOGS_ONLY_ENTITY_ID = generateLongIdWithSeed('service-logs-only'); const HOST_SERVER_1_LOGS_ENTITY_ID = generateLongIdWithSeed('host'); const CONTAINER_ID_METRICS_ENTITY_ID = generateLongIdWithSeed('container'); const SYNTH_NODE_TRACE_LOGS = 'synth-node-trace-logs'; +const SERVICE_LOGS_ONLY = 'service-logs-only'; const HOST_NAME = 'server1'; const CONTAINER_ID = 'foo'; @@ -27,6 +29,13 @@ export function generateEntities({ from, to }: { from: number; to: number }) { entityId: SYNTH_NODE_TRACES_LOGS_ENTITY_ID, }); + const serviceLogsOnly = entities.serviceEntity({ + serviceName: SERVICE_LOGS_ONLY, + agentName: ['host'], + dataStreamType: ['logs'], + entityId: SERVICE_LOGS_ONLY_ENTITY_ID, + }); + const hostServer1Logs = entities.hostEntity({ hostName: HOST_NAME, agentName: ['nodejs'], @@ -49,6 +58,7 @@ export function generateEntities({ from, to }: { from: number; to: number }) { .generator((timestamp) => { return [ serviceSynthNodeTracesLogs.timestamp(timestamp), + serviceLogsOnly.timestamp(timestamp), hostServer1Logs.timestamp(timestamp), containerMetrics.timestamp(timestamp), ]; @@ -90,23 +100,43 @@ export function generateLogs({ from, to }: { from: number; to: number }) { .interval('1m') .rate(1) .generator((timestamp) => { - return Array(3) - .fill(0) - .map(() => { - const index = Math.floor(Math.random() * 3); - const logMessage = MESSAGE_LOG_LEVELS[index]; + return [ + ...Array(3) + .fill(0) + .map(() => { + const index = Math.floor(Math.random() * 3); + const logMessage = MESSAGE_LOG_LEVELS[index]; + + return log + .create({ isLogsDb: false }) + .service(SYNTH_NODE_TRACE_LOGS) + .message(logMessage.message) + .logLevel(logMessage.level) + .setGeoLocation([1]) + .setHostIp('223.72.43.22') + .defaults({ + 'agent.name': 'nodejs', + }) + .timestamp(timestamp); + }), + ...Array(3) + .fill(0) + .map(() => { + const index = Math.floor(Math.random() * 3); + const logMessage = MESSAGE_LOG_LEVELS[index]; - return log - .create({ isLogsDb: false }) - .service(SYNTH_NODE_TRACE_LOGS) - .message(logMessage.message) - .logLevel(logMessage.level) - .setGeoLocation([1]) - .setHostIp('223.72.43.22') - .defaults({ - 'agent.name': 'nodejs', - }) - .timestamp(timestamp); - }); + return log + .create({ isLogsDb: false }) + .service(SERVICE_LOGS_ONLY) + .message(logMessage.message) + .logLevel(logMessage.level) + .setGeoLocation([1]) + .setHostIp('223.72.43.22') + .defaults({ + 'agent.name': 'nodejs', + }) + .timestamp(timestamp); + }), + ]; }); } diff --git a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts index c9d341c70896..fdb68826e9dc 100644 --- a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts +++ b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts @@ -110,6 +110,18 @@ describe('Home page', () => { cy.url().should('include', '/app/apm/services/synth-node-trace-logs/overview'); }); + it('Navigates to apm when clicking on a logs only service', () => { + cy.intercept('GET', '/internal/entities/managed/enablement', { + fixture: 'eem_enabled.json', + }).as('getEEMStatus'); + cy.visitKibana('/app/inventory'); + cy.wait('@getEEMStatus'); + cy.contains('service').click(); + cy.contains('service-logs-only').click(); + cy.url().should('include', '/app/apm/services/service-logs-only/overview'); + cy.contains('Detect and resolve issues faster with deep visibility into your application'); + }); + it('Navigates to hosts when clicking on a host type entity', () => { cy.intercept('GET', '/internal/entities/managed/enablement', { fixture: 'eem_enabled.json', @@ -213,7 +225,7 @@ describe('Home page', () => { cy.getByTestSubj('inventoryEntityActionOpenInDiscover').click(); cy.url().should( 'include', - "query:'container.id:%20foo%20AND%20entity.definition_id%20:%20builtin*" + "query:'container.id:%20%22foo%22%20AND%20entity.definition_id%20:%20builtin*" ); }); }); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/grid_columns.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/grid_columns.tsx index be5c50eba9c0..867425b9e0ae 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/grid_columns.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/grid_columns.tsx @@ -116,9 +116,7 @@ export const getColumns = ({ id: 'actions' as const, // keep it for accessibility purposes displayAsText: entityActionsLabel, - display: ( - <CustomHeaderCell title={entityActionsLabel} tooltipContent={entityActionsLabel} /> - ), + display: <span>{entityActionsLabel}</span>, initialWidth: 100, }, ] diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx index ff4329955773..dd421d30a3a9 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx @@ -160,7 +160,7 @@ export function EntitiesGrid({ <EuiText size="s"> <FormattedMessage id="xpack.inventory.entitiesGrid.euiDataGrid.headerLeft" - defaultMessage="Showing {currentItems} of {total} {boldEntites}" + defaultMessage="Showing {currentItems} of {total} {boldEntities}" values={{ currentItems: ( <strong> @@ -169,10 +169,10 @@ export function EntitiesGrid({ </strong> ), total: entities.length, - boldEntites: ( + boldEntities: ( <strong> {i18n.translate( - 'xpack.inventory.entitiesGrid.euiDataGrid.headerLeft.entites', + 'xpack.inventory.entitiesGrid.euiDataGrid.headerLeft.entities', { defaultMessage: 'Entities' } )} </strong> diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entity_icon/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entity_icon/index.tsx index 4da8fd3103c4..239d441b5d4e 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entity_icon/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entity_icon/index.tsx @@ -45,7 +45,7 @@ export function EntityIcon({ entity }: EntityIconProps) { return <AgentIcon agentName={castArray(entity.agent?.name)[0]} role="presentation" />; } - if (entity.entityType.startsWith('kubernetes')) { + if (entity.entityType.startsWith('k8s')) { return <EuiIcon type="logoKubernetes" size="l" />; } diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_detail_view_redirect.test.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_detail_view_redirect.test.ts index 233c1a1076b7..c5b35b521b3b 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_detail_view_redirect.test.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_detail_view_redirect.test.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useDetailViewRedirect } from './use_detail_view_redirect'; import { useKibana } from './use_kibana'; import { CONTAINER_ID, - ENTITY_TYPES, + BUILT_IN_ENTITY_TYPES, HOST_NAME, SERVICE_NAME, } from '@kbn/observability-shared-plugin/common'; @@ -134,15 +134,30 @@ describe('useDetailViewRedirect', () => { }); [ - [ENTITY_TYPES.KUBERNETES.CLUSTER.ecs, 'kubernetes-f4dc26db-1b53-4ea2-a78b-1bfab8ea267c'], - [ENTITY_TYPES.KUBERNETES.CLUSTER.semconv, 'kubernetes_otel-cluster-overview'], - [ENTITY_TYPES.KUBERNETES.CRONJOB.ecs, 'kubernetes-0a672d50-bcb1-11ec-b64f-7dd6e8e82013'], - [ENTITY_TYPES.KUBERNETES.DAEMONSET.ecs, 'kubernetes-85879010-bcb1-11ec-b64f-7dd6e8e82013'], - [ENTITY_TYPES.KUBERNETES.DEPLOYMENT.ecs, 'kubernetes-5be46210-bcb1-11ec-b64f-7dd6e8e82013'], - [ENTITY_TYPES.KUBERNETES.JOB.ecs, 'kubernetes-9bf990a0-bcb1-11ec-b64f-7dd6e8e82013'], - [ENTITY_TYPES.KUBERNETES.NODE.ecs, 'kubernetes-b945b7b0-bcb1-11ec-b64f-7dd6e8e82013'], - [ENTITY_TYPES.KUBERNETES.POD.ecs, 'kubernetes-3d4d9290-bcb1-11ec-b64f-7dd6e8e82013'], - [ENTITY_TYPES.KUBERNETES.STATEFULSET.ecs, 'kubernetes-21694370-bcb2-11ec-b64f-7dd6e8e82013'], + [ + BUILT_IN_ENTITY_TYPES.KUBERNETES.CLUSTER.ecs, + 'kubernetes-f4dc26db-1b53-4ea2-a78b-1bfab8ea267c', + ], + [BUILT_IN_ENTITY_TYPES.KUBERNETES.CLUSTER.semconv, 'kubernetes_otel-cluster-overview'], + [ + BUILT_IN_ENTITY_TYPES.KUBERNETES.CRONJOB.ecs, + 'kubernetes-0a672d50-bcb1-11ec-b64f-7dd6e8e82013', + ], + [ + BUILT_IN_ENTITY_TYPES.KUBERNETES.DAEMONSET.ecs, + 'kubernetes-85879010-bcb1-11ec-b64f-7dd6e8e82013', + ], + [ + BUILT_IN_ENTITY_TYPES.KUBERNETES.DEPLOYMENT.ecs, + 'kubernetes-5be46210-bcb1-11ec-b64f-7dd6e8e82013', + ], + [BUILT_IN_ENTITY_TYPES.KUBERNETES.JOB.ecs, 'kubernetes-9bf990a0-bcb1-11ec-b64f-7dd6e8e82013'], + [BUILT_IN_ENTITY_TYPES.KUBERNETES.NODE.ecs, 'kubernetes-b945b7b0-bcb1-11ec-b64f-7dd6e8e82013'], + [BUILT_IN_ENTITY_TYPES.KUBERNETES.POD.ecs, 'kubernetes-3d4d9290-bcb1-11ec-b64f-7dd6e8e82013'], + [ + BUILT_IN_ENTITY_TYPES.KUBERNETES.STATEFULSET.ecs, + 'kubernetes-21694370-bcb2-11ec-b64f-7dd6e8e82013', + ], ].forEach(([entityType, dashboardId]) => { it(`getEntityRedirectUrl should return the correct URL for ${entityType} entity`, () => { const entity: InventoryEntity = { diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_detail_view_redirect.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_detail_view_redirect.ts index 4df4fa4ca1f9..02a77b8f2afa 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_detail_view_redirect.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_detail_view_redirect.ts @@ -6,7 +6,7 @@ */ import { ASSET_DETAILS_LOCATOR_ID, - ENTITY_TYPES, + BUILT_IN_ENTITY_TYPES, SERVICE_OVERVIEW_LOCATOR_ID, type AssetDetailsLocatorParams, type ServiceOverviewParams, @@ -20,15 +20,19 @@ import type { InventoryEntity } from '../../common/entities'; import { useKibana } from './use_kibana'; const KUBERNETES_DASHBOARDS_IDS: Record<string, string> = { - [ENTITY_TYPES.KUBERNETES.CLUSTER.ecs]: 'kubernetes-f4dc26db-1b53-4ea2-a78b-1bfab8ea267c', - [ENTITY_TYPES.KUBERNETES.CLUSTER.semconv]: 'kubernetes_otel-cluster-overview', - [ENTITY_TYPES.KUBERNETES.CRONJOB.ecs]: 'kubernetes-0a672d50-bcb1-11ec-b64f-7dd6e8e82013', - [ENTITY_TYPES.KUBERNETES.DAEMONSET.ecs]: 'kubernetes-85879010-bcb1-11ec-b64f-7dd6e8e82013', - [ENTITY_TYPES.KUBERNETES.DEPLOYMENT.ecs]: 'kubernetes-5be46210-bcb1-11ec-b64f-7dd6e8e82013', - [ENTITY_TYPES.KUBERNETES.JOB.ecs]: 'kubernetes-9bf990a0-bcb1-11ec-b64f-7dd6e8e82013', - [ENTITY_TYPES.KUBERNETES.NODE.ecs]: 'kubernetes-b945b7b0-bcb1-11ec-b64f-7dd6e8e82013', - [ENTITY_TYPES.KUBERNETES.POD.ecs]: 'kubernetes-3d4d9290-bcb1-11ec-b64f-7dd6e8e82013', - [ENTITY_TYPES.KUBERNETES.STATEFULSET.ecs]: 'kubernetes-21694370-bcb2-11ec-b64f-7dd6e8e82013', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.CLUSTER.ecs]: 'kubernetes-f4dc26db-1b53-4ea2-a78b-1bfab8ea267c', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.CLUSTER.semconv]: 'kubernetes_otel-cluster-overview', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.CRONJOB.ecs]: 'kubernetes-0a672d50-bcb1-11ec-b64f-7dd6e8e82013', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.DAEMONSET.ecs]: + 'kubernetes-85879010-bcb1-11ec-b64f-7dd6e8e82013', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.DEPLOYMENT.ecs]: + 'kubernetes-5be46210-bcb1-11ec-b64f-7dd6e8e82013', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.JOB.ecs]: 'kubernetes-9bf990a0-bcb1-11ec-b64f-7dd6e8e82013', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.NODE.ecs]: 'kubernetes-b945b7b0-bcb1-11ec-b64f-7dd6e8e82013', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.POD.ecs]: 'kubernetes-3d4d9290-bcb1-11ec-b64f-7dd6e8e82013', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.SERVICE.ecs]: 'kubernetes-ff1b3850-bcb1-11ec-b64f-7dd6e8e82013', + [BUILT_IN_ENTITY_TYPES.KUBERNETES.STATEFULSET.ecs]: + 'kubernetes-21694370-bcb2-11ec-b64f-7dd6e8e82013', }; export const useDetailViewRedirect = () => { @@ -61,7 +65,9 @@ export const useDetailViewRedirect = () => { if (isBuiltinEntityOfType('service', entity)) { return serviceOverviewLocator?.getRedirectUrl({ serviceName: identityFieldsValue[identityFields[0]], - environment: entity.service?.environment, + environment: entity.service?.environment + ? castArray(entity.service?.environment)[0] + : undefined, }); } diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.test.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.test.ts index 61306a0b66a3..4706301eb0fc 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.test.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useIsLoadingComplete } from './use_is_loading_complete'; describe('useIsLoadingComplete', () => { diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_groups.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_groups.ts index 87d0c375149e..816b3c6af6ec 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_groups.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_groups.ts @@ -22,7 +22,7 @@ export async function getEntityGroupsBy({ inventoryEsClient: ObservabilityElasticsearchClient; field: string; esQuery?: QueryDslQueryContainer; -}) { +}): Promise<EntityGroup[]> { const from = `FROM ${ENTITIES_LATEST_ALIAS}`; const where = [getBuiltinEntityDefinitionIdESQLWhereClause()]; @@ -31,8 +31,14 @@ export async function getEntityGroupsBy({ const limit = `LIMIT ${MAX_NUMBER_OF_ENTITIES}`; const query = [from, ...where, group, sort, limit].join(' | '); - return inventoryEsClient.esql<EntityGroup>('get_entities_groups', { - query, - filter: esQuery, - }); + const { hits } = await inventoryEsClient.esql<EntityGroup, { transform: 'plain' }>( + 'get_entities_groups', + { + query, + filter: esQuery, + }, + { transform: 'plain' } + ); + + return hits; } diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_types.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_types.ts index e944e27379ab..c1f7894a178b 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_types.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_types.ts @@ -7,7 +7,6 @@ import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client'; import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; -import type { EntityInstance } from '@kbn/entities-schema'; import { ENTITIES_LATEST_ALIAS } from '../../../common/entities'; import { getBuiltinEntityDefinitionIdESQLWhereClause } from './query_helper'; @@ -16,14 +15,21 @@ export async function getEntityTypes({ }: { inventoryEsClient: ObservabilityElasticsearchClient; }) { - const entityTypesEsqlResponse = await inventoryEsClient.esql<{ - entity: Pick<EntityInstance['entity'], 'type'>; - }>('get_entity_types', { - query: `FROM ${ENTITIES_LATEST_ALIAS} + const entityTypesEsqlResponse = await inventoryEsClient.esql< + { + 'entity.type': string; + }, + { transform: 'plain' } + >( + 'get_entity_types', + { + query: `FROM ${ENTITIES_LATEST_ALIAS} | ${getBuiltinEntityDefinitionIdESQLWhereClause()} | STATS count = COUNT(${ENTITY_TYPE}) BY ${ENTITY_TYPE} `, - }); + }, + { transform: 'plain' } + ); - return entityTypesEsqlResponse.map((response) => response.entity.type); + return entityTypesEsqlResponse.hits.map((response) => response['entity.type']); } diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts index c4bf13c5ec14..9dcf17250ad6 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts @@ -6,13 +6,13 @@ */ import type { QueryDslQueryContainer, ScalarValue } from '@elastic/elasticsearch/lib/api/types'; -import type { EntityInstance } from '@kbn/entities-schema'; import { ENTITY_DISPLAY_NAME, ENTITY_LAST_SEEN, ENTITY_TYPE, } from '@kbn/observability-shared-plugin/common'; import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client'; +import { unflattenObject } from '@kbn/observability-utils-common/object/unflatten_object'; import { ENTITIES_LATEST_ALIAS, InventoryEntity, @@ -62,17 +62,38 @@ export async function getLatestEntities({ const query = [from, ...where, sort, limit].join(' | '); - const latestEntitiesEsqlResponse = await inventoryEsClient.esql<EntityInstance>( + const latestEntitiesEsqlResponse = await inventoryEsClient.esql< + { + 'entity.id': string; + 'entity.type': string; + 'entity.definition_id': string; + 'entity.display_name': string; + 'entity.identity_fields': string | string[]; + 'entity.last_seen_timestamp': string; + 'entity.definition_version': string; + 'entity.schema_version': string; + } & Record<string, ScalarValue | ScalarValue[]>, + { transform: 'plain' } + >( 'get_latest_entities', { query, filter: esQuery, params, - } + }, + { transform: 'plain' } ); - return latestEntitiesEsqlResponse.map((lastestEntity) => { - const { entity, ...metadata } = lastestEntity; + return latestEntitiesEsqlResponse.hits.map((latestEntity) => { + Object.keys(latestEntity).forEach((key) => { + const keyOfObject = key as keyof typeof latestEntity; + // strip out multi-field aliases + if (keyOfObject.endsWith('.text') || keyOfObject.endsWith('.keyword')) { + delete latestEntity[keyOfObject]; + } + }); + + const { entity, ...metadata } = unflattenObject(latestEntity); return { entityId: entity.id, diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/has_data/get_has_data.ts b/x-pack/plugins/observability_solution/inventory/server/routes/has_data/get_has_data.ts index 2f59478f17c0..c3fd3971f09d 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/has_data/get_has_data.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/has_data/get_has_data.ts @@ -17,14 +17,18 @@ export async function getHasData({ logger: Logger; }) { try { - const esqlResults = await inventoryEsClient.esql<{ _count: number }>('get_has_data', { - query: `FROM ${ENTITIES_LATEST_ALIAS} + const esqlResults = await inventoryEsClient.esql<{ _count: number }, { transform: 'plain' }>( + 'get_has_data', + { + query: `FROM ${ENTITIES_LATEST_ALIAS} | ${getBuiltinEntityDefinitionIdESQLWhereClause()} | STATS _count = COUNT(*) | LIMIT 1`, - }); + }, + { transform: 'plain' } + ); - const totalCount = esqlResults[0]._count; + const totalCount = esqlResults.hits[0]._count; return { hasData: totalCount > 0 }; } catch (e) { diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/types.ts b/x-pack/plugins/observability_solution/inventory/server/routes/types.ts index 397710509dab..646d34888ae9 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/types.ts @@ -30,10 +30,8 @@ export interface InventoryRouteHandlerResources { } export interface InventoryRouteCreateOptions { - options: { - timeout?: { - idleSocket?: number; - }; - tags: Array<'access:inventory'>; + timeout?: { + idleSocket?: number; }; + tags: Array<'access:inventory'>; } diff --git a/x-pack/plugins/observability_solution/investigate_app/server/routes/types.ts b/x-pack/plugins/observability_solution/investigate_app/server/routes/types.ts index afb022cdc9b7..0cee1b701539 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/routes/types.ts @@ -53,10 +53,7 @@ export interface InvestigateAppRouteHandlerResources { } export interface InvestigateAppRouteCreateOptions { - options: { - timeout?: { - idleSocket?: number; - }; - tags: []; + timeout?: { + idleSocket?: number; }; } diff --git a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts index 1521aa67e3d9..cd2354994db2 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts @@ -106,7 +106,7 @@ const resolveDataViewReference = async ( }); return { - indices: dataView.title, + indices: dataView.getIndexPattern(), timestampField: dataView.timeFieldName ?? TIMESTAMP_FIELD, tiebreakerField: TIEBREAKER_FIELD, messageField: ['message'], diff --git a/x-pack/plugins/observability_solution/logs_shared/public/containers/logs/log_summary/log_summary.test.tsx b/x-pack/plugins/observability_solution/logs_shared/public/containers/logs/log_summary/log_summary.test.tsx index 183950edf977..8635df14731a 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/containers/logs/log_summary/log_summary.test.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/containers/logs/log_summary/log_summary.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; // We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; @@ -56,14 +56,14 @@ describe('useLogSummary hook', () => { .mockResolvedValueOnce(firstMockResponse) .mockResolvedValueOnce(secondMockResponse); - const { result, waitForNextUpdate, rerender } = renderHook( + const { result, rerender } = renderHook( ({ logViewReference }) => useLogSummary(logViewReference, startTimestamp, endTimestamp, null), { initialProps: { logViewReference: LOG_VIEW_REFERENCE }, } ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(fetchLogSummaryMock).toHaveBeenCalledTimes(1); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( @@ -75,7 +75,7 @@ describe('useLogSummary hook', () => { expect(result.current.buckets).toEqual(firstMockResponse.data.buckets); rerender({ logViewReference: CHANGED_LOG_VIEW_REFERENCE }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(fetchLogSummaryMock).toHaveBeenCalledTimes(2); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( @@ -101,7 +101,7 @@ describe('useLogSummary hook', () => { .mockResolvedValueOnce(firstMockResponse) .mockResolvedValueOnce(secondMockResponse); - const { result, waitForNextUpdate, rerender } = renderHook( + const { result, rerender } = renderHook( ({ filterQuery }) => useLogSummary(LOG_VIEW_REFERENCE, startTimestamp, endTimestamp, filterQuery), { @@ -109,7 +109,7 @@ describe('useLogSummary hook', () => { } ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(fetchLogSummaryMock).toHaveBeenCalledTimes(1); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( @@ -121,7 +121,7 @@ describe('useLogSummary hook', () => { expect(result.current.buckets).toEqual(firstMockResponse.data.buckets); rerender({ filterQuery: 'CHANGED_FILTER_QUERY' }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(fetchLogSummaryMock).toHaveBeenCalledTimes(2); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( @@ -139,7 +139,7 @@ describe('useLogSummary hook', () => { .mockResolvedValueOnce(createMockResponse([])); const firstRange = createMockDateRange(); - const { waitForNextUpdate, rerender } = renderHook( + const { rerender } = renderHook( ({ startTimestamp, endTimestamp }) => useLogSummary(LOG_VIEW_REFERENCE, startTimestamp, endTimestamp, null), { @@ -147,7 +147,7 @@ describe('useLogSummary hook', () => { } ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(fetchLogSummaryMock).toHaveBeenCalledTimes(1); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( expect.objectContaining({ @@ -160,7 +160,7 @@ describe('useLogSummary hook', () => { const secondRange = createMockDateRange('now-20s', 'now'); rerender(secondRange); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(fetchLogSummaryMock).toHaveBeenCalledTimes(2); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( @@ -176,7 +176,7 @@ describe('useLogSummary hook', () => { fetchLogSummaryMock.mockResolvedValueOnce(createMockResponse([])); const firstRange = createMockDateRange(); - const { waitForNextUpdate, rerender } = renderHook( + const { rerender } = renderHook( ({ startTimestamp, endTimestamp }) => useLogSummary(LOG_VIEW_REFERENCE, startTimestamp, endTimestamp, null), { @@ -188,7 +188,7 @@ describe('useLogSummary hook', () => { // intentionally don't wait for an update to test the throttling rerender(secondRange); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); expect(fetchLogSummaryMock).toHaveBeenCalledTimes(1); expect(fetchLogSummaryMock).toHaveBeenLastCalledWith( diff --git a/x-pack/plugins/observability_solution/logs_shared/public/utils/data_search/use_data_search_request.test.tsx b/x-pack/plugins/observability_solution/logs_shared/public/utils/data_search/use_data_search_request.test.tsx index 33369c1125e1..08907b162708 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/utils/data_search/use_data_search_request.test.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/utils/data_search/use_data_search_request.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import React from 'react'; import { firstValueFrom, Observable, of, Subject } from 'rxjs'; import type { ISearchGeneric, IKibanaSearchResponse } from '@kbn/search-types'; diff --git a/x-pack/plugins/observability_solution/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.test.tsx b/x-pack/plugins/observability_solution/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.test.tsx index a4b01be03d80..24433f23bc67 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.test.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/utils/data_search/use_latest_partial_data_search_response.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; import { IKibanaSearchRequest } from '@kbn/search-types'; import { ParsedDataSearchRequestDescriptor, ParsedKibanaSearchResponse } from './types'; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts b/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts index 246c398ea5a6..7d067993531e 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/log_view_saved_object.ts @@ -40,5 +40,4 @@ export const logViewSavedObjectType: SavedObjectsType = { }, }, }, - migrations: {}, }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts b/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts index ec22fe1df6f9..d9da73336bf0 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts @@ -7,7 +7,7 @@ import { useContainerMetricsTable } from './use_container_metrics_table'; import { useInfrastructureNodeMetrics } from '../shared'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createMetricsClientMock } from '../test_helpers'; jest.mock('../shared', () => ({ @@ -33,6 +33,12 @@ describe('useContainerMetricsTable hook', () => { }, }; + // include this to prevent rendering error in test + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + }); + renderHook(() => useContainerMetricsTable({ timerange: { from: 'now-30d', to: 'now' }, diff --git a/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts b/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts index f34fccc6e442..ab21b0c1ca76 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts @@ -7,7 +7,7 @@ import { useHostMetricsTable } from './use_host_metrics_table'; import { useInfrastructureNodeMetrics } from '../shared'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createMetricsClientMock } from '../test_helpers'; jest.mock('../shared', () => ({ @@ -40,6 +40,12 @@ describe('useHostMetricsTable hook', () => { }, }; + // include this to prevent rendering error in test + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + }); + renderHook(() => useHostMetricsTable({ timerange: { from: 'now-30d', to: 'now' }, diff --git a/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts b/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts index fc6d14b03d08..14f3330e856a 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts @@ -7,7 +7,7 @@ import { usePodMetricsTable } from './use_pod_metrics_table'; import { useInfrastructureNodeMetrics } from '../shared'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createMetricsClientMock } from '../test_helpers'; jest.mock('../shared', () => ({ @@ -33,6 +33,12 @@ describe('usePodMetricsTable hook', () => { }, }; + // include this to prevent rendering error in test + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + }); + renderHook(() => usePodMetricsTable({ timerange: { from: 'now-30d', to: 'now' }, diff --git a/x-pack/plugins/observability_solution/metrics_data_access/public/pages/link_to/use_asset_detail_redirect.test.tsx b/x-pack/plugins/observability_solution/metrics_data_access/public/pages/link_to/use_asset_detail_redirect.test.tsx index cf783e78bd67..1183979d6058 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/public/pages/link_to/use_asset_detail_redirect.test.tsx +++ b/x-pack/plugins/observability_solution/metrics_data_access/public/pages/link_to/use_asset_detail_redirect.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { coreMock } from '@kbn/core/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; diff --git a/x-pack/plugins/observability_solution/observability/kibana.jsonc b/x-pack/plugins/observability_solution/observability/kibana.jsonc index 1c09efd7dd6e..3a888ce14e5e 100644 --- a/x-pack/plugins/observability_solution/observability/kibana.jsonc +++ b/x-pack/plugins/observability_solution/observability/kibana.jsonc @@ -56,7 +56,8 @@ "serverless", "guidedOnboarding", "observabilityAIAssistant", - "investigate" + "investigate", + "streams" ], "requiredBundles": [ "data", @@ -70,4 +71,4 @@ "common" ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts index 2c04cdf8f076..7d5086323e90 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts @@ -15,7 +15,6 @@ import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; import type { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; -import type { OsqueryPluginStart } from '@kbn/osquery-plugin/public'; import { ALERT_GROUP, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; @@ -72,7 +71,6 @@ export interface InfraClientStartDeps { lens: LensPublicStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; - osquery?: OsqueryPluginStart; share: SharePluginStart; spaces: SpacesPluginStart; storage: IStorageWrapper; diff --git a/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts b/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts index 07bb33ebb5a9..16718648c972 100644 --- a/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts +++ b/x-pack/plugins/observability_solution/observability/public/navigation_tree.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import type { NavigationTreeDefinition } from '@kbn/core-chrome-browser'; import type { AddSolutionNavigationArg } from '@kbn/navigation-plugin/public'; -import { of } from 'rxjs'; +import { map, of } from 'rxjs'; import type { ObservabilityPublicPluginsStart } from './plugin'; const title = i18n.translate( @@ -18,7 +18,7 @@ const title = i18n.translate( ); const icon = 'logoObservability'; -export function createNavTree(pluginsStart: ObservabilityPublicPluginsStart) { +function createNavTree({ streamsAvailable }: { streamsAvailable?: boolean }) { const navTree: NavigationTreeDefinition = { body: [ { @@ -87,6 +87,13 @@ export function createNavTree(pluginsStart: ObservabilityPublicPluginsStart) { link: 'inventory', spaceBefore: 'm', }, + ...(streamsAvailable + ? [ + { + link: 'streams' as const, + }, + ] + : []), { id: 'apm', title: i18n.translate('xpack.observability.obltNav.applications', { @@ -558,6 +565,8 @@ export const createDefinition = ( title, icon: 'logoObservability', homePage: 'observabilityOnboarding', - navigationTree$: of(createNavTree(pluginsStart)), + navigationTree$: (pluginsStart.streams?.status$ || of({ status: 'disabled' as const })).pipe( + map(({ status }) => createNavTree({ streamsAvailable: status === 'enabled' })) + ), dataTestSubj: 'observabilitySideNav', }); diff --git a/x-pack/plugins/observability_solution/observability/public/plugin.ts b/x-pack/plugins/observability_solution/observability/public/plugin.ts index 5866a082556b..c37f3cc2f624 100644 --- a/x-pack/plugins/observability_solution/observability/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability/public/plugin.ts @@ -70,6 +70,7 @@ import type { import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import type { StreamsPluginStart, StreamsPluginSetup } from '@kbn/streams-plugin/public'; import { observabilityAppId, observabilityFeatureId } from '../common'; import { ALERTS_PATH, @@ -124,6 +125,7 @@ export interface ObservabilityPublicPluginsSetup { licensing: LicensingPluginSetup; serverless?: ServerlessPluginSetup; presentationUtil?: PresentationUtilPluginStart; + streams?: StreamsPluginSetup; } export interface ObservabilityPublicPluginsStart { actionTypeRegistry: ActionTypeRegistryContract; @@ -162,6 +164,7 @@ export interface ObservabilityPublicPluginsStart { dataViewFieldEditor: DataViewFieldEditorStart; toastNotifications: ToastsStart; investigate?: InvestigatePublicStart; + streams?: StreamsPluginStart; } export type ObservabilityPublicStart = ReturnType<Plugin['start']>; diff --git a/x-pack/plugins/observability_solution/observability/server/lib/annotations/register_annotation_apis.ts b/x-pack/plugins/observability_solution/observability/server/lib/annotations/register_annotation_apis.ts index 59ae964ce883..5df2ed8d0e68 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/annotations/register_annotation_apis.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/annotations/register_annotation_apis.ts @@ -95,6 +95,12 @@ export function registerAnnotationAPIs({ router.post( { path: '/api/observability/annotation', + security: { + authz: { + enabled: false, + reason: 'This route delegates authorization to Elasticsearch', + }, + }, validate: { body: unknowns, }, @@ -110,6 +116,12 @@ export function registerAnnotationAPIs({ router.put( { path: '/api/observability/annotation/{id}', + security: { + authz: { + enabled: false, + reason: 'This route delegates authorization to Elasticsearch', + }, + }, validate: { body: unknowns, }, @@ -125,6 +137,12 @@ export function registerAnnotationAPIs({ router.delete( { path: '/api/observability/annotation/{id}', + security: { + authz: { + enabled: false, + reason: 'This route delegates authorization to Elasticsearch', + }, + }, validate: { params: unknowns, }, @@ -140,6 +158,12 @@ export function registerAnnotationAPIs({ router.get( { path: '/api/observability/annotation/{id}', + security: { + authz: { + enabled: false, + reason: 'This route delegates authorization to Elasticsearch', + }, + }, validate: { params: unknowns, }, @@ -155,6 +179,12 @@ export function registerAnnotationAPIs({ router.get( { path: '/api/observability/annotation/find', + security: { + authz: { + enabled: false, + reason: 'This route delegates authorization to Elasticsearch', + }, + }, validate: { query: unknowns, }, @@ -170,6 +200,12 @@ export function registerAnnotationAPIs({ router.get( { path: '/api/observability/annotation/permissions', + security: { + authz: { + enabled: false, + reason: 'This route delegates authorization to Elasticsearch', + }, + }, validate: { query: unknowns, }, diff --git a/x-pack/plugins/observability_solution/observability/server/plugin.ts b/x-pack/plugins/observability_solution/observability/server/plugin.ts index b98fe316c712..4d0e809f882e 100644 --- a/x-pack/plugins/observability_solution/observability/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability/server/plugin.ts @@ -195,7 +195,6 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> { void core.getStartServices().then(([coreStart, pluginStart]) => { registerRoutes({ core, - config, dependencies: { pluginsSetup: { ...plugins, diff --git a/x-pack/plugins/observability_solution/observability/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/observability/server/routes/register_routes.ts index 5599039a5ce6..9ce2d7c9f182 100644 --- a/x-pack/plugins/observability_solution/observability/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_solution/observability/server/routes/register_routes.ts @@ -4,29 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { errors } from '@elastic/elasticsearch'; -import Boom from '@hapi/boom'; import { RulesClientApi } from '@kbn/alerting-plugin/server/types'; -import { CoreSetup, KibanaRequest, Logger, RouteRegistrar } from '@kbn/core/server'; +import { CoreSetup, KibanaRequest, Logger } from '@kbn/core/server'; import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import { RuleDataPluginService } from '@kbn/rule-registry-plugin/server'; -import { - IoTsParamsObject, - decodeRequestParams, - parseEndpoint, - passThroughValidationObject, - stripNullishRequestParameters, -} from '@kbn/server-route-repository'; +import { registerRoutes as registerServerRoutes } from '@kbn/server-route-repository'; import { SpacesPluginStart } from '@kbn/spaces-plugin/server'; -import axios from 'axios'; -import * as t from 'io-ts'; -import { ObservabilityConfig } from '..'; import { AlertDetailsContextualInsightsService } from '../services'; -import { ObservabilityRequestHandlerContext } from '../types'; import { AbstractObservabilityServerRouteRepository } from './types'; interface RegisterRoutes { - config: ObservabilityConfig; core: CoreSetup; repository: AbstractObservabilityServerRouteRepository; logger: Logger; @@ -46,81 +33,11 @@ export interface RegisterRoutesDependencies { getRulesClientWithRequest: (request: KibanaRequest) => RulesClientApi; } -export function registerRoutes({ config, repository, core, logger, dependencies }: RegisterRoutes) { - const routes = Object.values(repository); - - const router = core.http.createRouter(); - - routes.forEach((route) => { - const { endpoint, options, handler, params } = route; - const { pathname, method } = parseEndpoint(endpoint); - - (router[method] as RouteRegistrar<typeof method, ObservabilityRequestHandlerContext>)( - { - path: pathname, - validate: passThroughValidationObject, - options, - }, - async (context, request, response) => { - try { - const decodedParams = decodeRequestParams( - stripNullishRequestParameters({ - params: request.params, - body: request.body, - query: request.query, - }), - (params as IoTsParamsObject) ?? t.strict({}) - ); - - const data = await handler({ - config, - context, - request, - logger, - params: decodedParams, - dependencies, - }); - - if (data === undefined) { - return response.noContent(); - } - - return response.ok({ body: data }); - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error(error); - return response.customError({ - statusCode: error.response?.status || 500, - body: { - message: error.message, - }, - }); - } - - if (Boom.isBoom(error)) { - logger.error(error.output.payload.message); - return response.customError({ - statusCode: error.output.statusCode, - body: { message: error.output.payload.message }, - }); - } - - logger.error(error); - const opts = { - statusCode: 500, - body: { - message: error.message, - }, - }; - - if (error instanceof errors.RequestAbortedError) { - opts.statusCode = 499; - opts.body.message = 'Client closed request'; - } - - return response.customError(opts); - } - } - ); +export function registerRoutes({ repository, core, logger, dependencies }: RegisterRoutes) { + registerServerRoutes({ + core, + dependencies: { dependencies }, + logger, + repository, }); } diff --git a/x-pack/plugins/observability_solution/observability/server/routes/types.ts b/x-pack/plugins/observability_solution/observability/server/routes/types.ts index 394025313764..111bc4e71411 100644 --- a/x-pack/plugins/observability_solution/observability/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/observability/server/routes/types.ts @@ -13,7 +13,6 @@ import { } from './get_global_observability_server_route_repository'; import { ObservabilityRequestHandlerContext } from '../types'; import { RegisterRoutesDependencies } from './register_routes'; -import { ObservabilityConfig } from '..'; export type { ObservabilityServerRouteRepository, APIEndpoint }; @@ -22,14 +21,11 @@ export interface ObservabilityRouteHandlerResources { dependencies: RegisterRoutesDependencies; logger: Logger; request: KibanaRequest; - config: ObservabilityConfig; } export interface ObservabilityRouteCreateOptions { - options: { - tags: string[]; - access?: 'public' | 'internal'; - }; + tags: string[]; + access?: 'public' | 'internal'; } export type AbstractObservabilityServerRouteRepository = ServerRouteRepository; diff --git a/x-pack/plugins/observability_solution/observability/tsconfig.json b/x-pack/plugins/observability_solution/observability/tsconfig.json index 83c14047131b..b3c18dc24b8e 100644 --- a/x-pack/plugins/observability_solution/observability/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability/tsconfig.json @@ -66,7 +66,6 @@ "@kbn/discover-plugin", "@kbn/embeddable-plugin", "@kbn/lens-plugin", - "@kbn/osquery-plugin", "@kbn/ui-actions-plugin", "@kbn/unified-search-plugin", "@kbn/lens-embeddable-utils", @@ -112,6 +111,7 @@ "@kbn/core-ui-settings-server-mocks", "@kbn/es-types", "@kbn/logging-mocks", + "@kbn/streams-plugin", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts index 80ddf3cbc0a0..1bf892c0d40e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts @@ -34,7 +34,7 @@ export function registerContextFunction({ visibility: FunctionVisibility.Internal, }, async ({ messages, screenContexts, chat }, signal) => { - const { analytics } = (await resources.context.core).coreStart; + const { analytics } = await resources.plugins.core.start(); async function getContext() { const screenDescription = compact( diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/index.ts index 57cac3a4e0c0..77ba9afb1826 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/index.ts @@ -48,7 +48,7 @@ export function registerGetDatasetInfoFunction({ try { const body = await esClient.asCurrentUser.indices.resolveIndex({ - name: index === '' ? '*' : index.split(','), + name: index === '' ? ['*', '*:*'] : index.split(','), expand_wildcards: 'open', }); indices = [ diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts index f693fa53c06c..7949276ac6ab 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts @@ -111,7 +111,18 @@ export class ObservabilityAIAssistantPlugin ]; }), }; - }) as ObservabilityAIAssistantRouteHandlerResources['plugins']; + }) as Pick< + ObservabilityAIAssistantRouteHandlerResources['plugins'], + keyof ObservabilityAIAssistantPluginStartDependencies + >; + + const withCore = { + ...routeHandlerPlugins, + core: { + setup: core, + start: () => core.getStartServices().then(([coreStart]) => coreStart), + }, + }; const service = (this.service = new ObservabilityAIAssistantService({ logger: this.logger.get('service'), @@ -133,7 +144,7 @@ export class ObservabilityAIAssistantPlugin core, logger: this.logger, dependencies: { - plugins: routeHandlerPlugins, + plugins: withCore, service: this.service, }, }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts index a6fe57cb58ad..e80e6fa156b0 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts @@ -194,7 +194,7 @@ const chatRecallRoute = createObservabilityAIAssistantServerRoute({ const response$ = from( recallAndScore({ - analytics: (await resources.context.core).coreStart.analytics, + analytics: (await resources.plugins.core.start()).analytics, chat: (name, params) => client .chat(name, { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/register_routes.ts index 1a6140968c92..27c7361e8a7f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/register_routes.ts @@ -6,7 +6,7 @@ */ import type { CoreSetup } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { registerRoutes } from '@kbn/server-route-repository'; +import { DefaultRouteHandlerResources, registerRoutes } from '@kbn/server-route-repository'; import { getGlobalObservabilityAIAssistantServerRouteRepository } from './get_global_observability_ai_assistant_route_repository'; import type { ObservabilityAIAssistantRouteHandlerResources } from './types'; import { ObservabilityAIAssistantPluginStartDependencies } from '../types'; @@ -20,7 +20,7 @@ export function registerServerRoutes({ logger: Logger; dependencies: Omit< ObservabilityAIAssistantRouteHandlerResources, - 'request' | 'context' | 'logger' | 'params' + keyof DefaultRouteHandlerResources >; }) { registerRoutes({ diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts index b817328d22c6..62365536f382 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts @@ -6,32 +6,36 @@ */ import type { + CoreSetup, CoreStart, CustomRequestHandlerContext, IScopedClusterClient, IUiSettingsClient, - KibanaRequest, SavedObjectsClientContract, } from '@kbn/core/server'; -import type { Logger } from '@kbn/logging'; import type { LicensingApiRequestHandlerContext } from '@kbn/licensing-plugin/server/types'; import type { RacApiRequestHandlerContext } from '@kbn/rule-registry-plugin/server'; import type { RulesClientApi } from '@kbn/alerting-plugin/server/types'; +import { DefaultRouteHandlerResources } from '@kbn/server-route-repository-utils'; import type { ObservabilityAIAssistantService } from '../service'; import type { ObservabilityAIAssistantPluginSetupDependencies, ObservabilityAIAssistantPluginStartDependencies, } from '../types'; +type ObservabilityAIAssistantRequestHandlerContextBase = CustomRequestHandlerContext<{ + licensing: Pick<LicensingApiRequestHandlerContext, 'license' | 'featureUsage'>; + // these two are here for compatibility with APM functions + rac: Pick<RacApiRequestHandlerContext, 'getAlertsClient'>; + alerting: { + getRulesClient: () => RulesClientApi; + }; +}>; + +// this is the type used across methods, it's stripped down for compatibility +// with the context that's available when executing as an action export type ObservabilityAIAssistantRequestHandlerContext = Omit< - CustomRequestHandlerContext<{ - licensing: Pick<LicensingApiRequestHandlerContext, 'license' | 'featureUsage'>; - // these two are here for compatibility with APM functions - rac: Pick<RacApiRequestHandlerContext, 'getAlertsClient'>; - alerting: { - getRulesClient: () => RulesClientApi; - }; - }>, + ObservabilityAIAssistantRequestHandlerContextBase, 'core' | 'resolve' > & { core: Promise<{ @@ -45,32 +49,41 @@ export type ObservabilityAIAssistantRequestHandlerContext = Omit< savedObjects: { client: SavedObjectsClientContract; }; - coreStart: CoreStart; }>; }; -export interface ObservabilityAIAssistantRouteHandlerResources { - request: KibanaRequest; +interface PluginContractResolveCore { + core: { + setup: CoreSetup<ObservabilityAIAssistantPluginStartDependencies>; + start: () => Promise<CoreStart>; + }; +} + +type PluginContractResolveDependenciesStart = { + [key in keyof ObservabilityAIAssistantPluginStartDependencies]: { + start: () => Promise<Required<ObservabilityAIAssistantPluginStartDependencies>[key]>; + }; +}; + +type PluginContractResolveDependenciesSetup = { + [key in keyof ObservabilityAIAssistantPluginSetupDependencies]: { + setup: Required<ObservabilityAIAssistantPluginSetupDependencies>[key]; + }; +}; + +export interface ObservabilityAIAssistantRouteHandlerResources + extends Omit<DefaultRouteHandlerResources, 'context' | 'response'> { context: ObservabilityAIAssistantRequestHandlerContext; - logger: Logger; service: ObservabilityAIAssistantService; - plugins: { - [key in keyof ObservabilityAIAssistantPluginSetupDependencies]: { - setup: Required<ObservabilityAIAssistantPluginSetupDependencies>[key]; - }; - } & { - [key in keyof ObservabilityAIAssistantPluginStartDependencies]: { - start: () => Promise<Required<ObservabilityAIAssistantPluginStartDependencies>[key]>; - }; - }; + plugins: PluginContractResolveCore & + PluginContractResolveDependenciesSetup & + PluginContractResolveDependenciesStart; } export interface ObservabilityAIAssistantRouteCreateOptions { - options: { - timeout?: { - payload?: number; - idleSocket?: number; - }; - tags: Array<'access:ai_assistant'>; + timeout?: { + payload?: number; + idleSocket?: number; }; + tags: Array<'access:ai_assistant'>; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json index d5acd7a365b5..709b3117d575 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json @@ -47,6 +47,7 @@ "@kbn/ai-assistant-common", "@kbn/inference-common", "@kbn/core-lifecycle-server", + "@kbn/server-route-repository-utils", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx index a570d4ba0276..9a4cf790b85c 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx @@ -127,13 +127,13 @@ export function VisualizeESQL({ ( isLoading: boolean, adapters: InlineEditLensEmbeddableContext['lensEvent']['adapters'] | undefined, - lensEmbeddableOutput$?: InlineEditLensEmbeddableContext['lensEvent']['embeddableOutput$'] + dataLoading$?: InlineEditLensEmbeddableContext['lensEvent']['dataLoading$'] ) => { const adapterTables = adapters?.tables?.tables; if (adapterTables && !isLoading) { setLensLoadEvent({ adapters, - embeddableOutput$: lensEmbeddableOutput$, + dataLoading$, }); } }, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/plugin.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/plugin.ts index 63e06818a2b7..97fdc01069b9 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/plugin.ts @@ -17,7 +17,6 @@ import { ObservabilityAIAssistantRequestHandlerContext, ObservabilityAIAssistantRouteHandlerResources, } from '@kbn/observability-ai-assistant-plugin/server/routes/types'; -import { ObservabilityAIAssistantPluginStartDependencies } from '@kbn/observability-ai-assistant-plugin/server/types'; import { mapValues } from 'lodash'; import { firstValueFrom } from 'rxjs'; import type { ObservabilityAIAssistantAppConfig } from './config'; @@ -59,13 +58,22 @@ export class ObservabilityAIAssistantAppPlugin setup: value, start: () => core.getStartServices().then((services) => { - const [, pluginsStartContracts] = services; + const [_, pluginsStartContracts] = services; + return pluginsStartContracts[ - key as keyof ObservabilityAIAssistantPluginStartDependencies + key as keyof ObservabilityAIAssistantAppPluginStartDependencies ]; }), }; - }) as ObservabilityAIAssistantRouteHandlerResources['plugins']; + }) as Omit<ObservabilityAIAssistantRouteHandlerResources['plugins'], 'core'>; + + const withCore = { + ...routeHandlerPlugins, + core: { + setup: core, + start: () => core.getStartServices().then(([coreStart]) => coreStart), + }, + }; const initResources = async ( request: KibanaRequest @@ -90,7 +98,6 @@ export class ObservabilityAIAssistantAppPlugin }; }), core: Promise.resolve({ - coreStart, elasticsearch: { client: coreStart.elasticsearch.client.asScoped(request), }, @@ -110,7 +117,7 @@ export class ObservabilityAIAssistantAppPlugin context, service: plugins.observabilityAIAssistant.service, logger: this.logger.get('connector'), - plugins: routeHandlerPlugins, + plugins: withCore, }; }; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts index de02e4cf841c..04fd10c3e506 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.test.ts @@ -94,12 +94,14 @@ describe('observabilityAIAssistant rule_connector', () => { getAdhocInstructions: () => [], }), }, - context: { - core: Promise.resolve({ - coreStart: { http: { basePath: { publicBaseUrl: 'http://kibana.com' } } }, - }), - }, + context: {}, plugins: { + core: { + start: () => + Promise.resolve({ + http: { basePath: { publicBaseUrl: 'http://kibana.com' } }, + }), + }, actions: { start: async () => { return { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts index 33f3bdd2c98f..1f5a097f8f7c 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/rule_connector/index.ts @@ -248,7 +248,7 @@ If available, include the link of the conversation at the end of your answer.` isPublic: true, connectorId: execOptions.params.connector, signal: new AbortController().signal, - kibanaPublicUrl: (await resources.context.core).coreStart.http.basePath.publicBaseUrl, + kibanaPublicUrl: (await resources.plugins.core.start()).http.basePath.publicBaseUrl, instructions: [backgroundInstruction], messages: [ { diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/state_machines/observability_logs_explorer/src/notifications.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/state_machines/observability_logs_explorer/src/notifications.tsx deleted file mode 100644 index 79ad5820ff27..000000000000 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/public/state_machines/observability_logs_explorer/src/notifications.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { IToasts } from '@kbn/core-notifications-browser'; -import { mountReactNode } from '@kbn/core-mount-utils-browser-internal'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; - -export const createRequestFeedbackNotifier = (toasts: IToasts) => () => { - toasts.addInfo({ - title: i18n.translate('xpack.observabilityLogsExplorer.feedbackToast.title', { - defaultMessage: 'Tell us what you think!', - }), - text: mountReactNode(<></>), - iconType: 'editorComment', - }); -}; diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json index 5a2f18aa4249..443d4ef8f0de 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json @@ -13,7 +13,6 @@ "kbn_references": [ "@kbn/config-schema", "@kbn/core", - "@kbn/core-mount-utils-browser-internal", "@kbn/core-notifications-browser", "@kbn/data-plugin", "@kbn/deeplinks-observability", diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/footer/footer.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/footer/footer.tsx index dae5f70bf3db..8f8b0ec853dc 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/footer/footer.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/footer/footer.tsx @@ -43,6 +43,7 @@ export const Footer: FunctionComponent = () => { { defaultMessage: 'Explore demo' } ), link: URL_DEMO_ENV, + testSubject: 'observabilityOnboardingFooterExploreDemoLink', }, { iconUrl: forumIconUrl, @@ -65,6 +66,7 @@ export const Footer: FunctionComponent = () => { { defaultMessage: 'Open Elastic Discuss forum' } ), link: URL_FORUM, + testSubject: 'observabilityOnboardingFooterDiscussForumLink', }, { iconUrl: docsIconUrl, @@ -87,6 +89,7 @@ export const Footer: FunctionComponent = () => { { defaultMessage: 'Learn more about all Elastic features' } ), link: docLinks.links.observability.guide, + testSubject: 'observabilityOnboardingFooterLearnMoreLink', }, { iconUrl: supportIconUrl, @@ -105,6 +108,7 @@ export const Footer: FunctionComponent = () => { { defaultMessage: 'Open Support Hub' } ), link: helpSupportUrl, + testSubject: 'observabilityOnboardingFooterOpenSupportHubLink', }, ]; @@ -127,7 +131,7 @@ export const Footer: FunctionComponent = () => { <EuiText size="xs"> <p> <EuiLink - data-test-subj="observabilityOnboardingFooterLearnMoreLink" + data-test-subj={section.testSubject} aria-label={section.linkARIALabel} href={section.link} target="_blank" diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/auto_detect/use_auto_detect_telemetry.test.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/auto_detect/use_auto_detect_telemetry.test.ts index d296e598c9e1..f77db3b8aad3 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/auto_detect/use_auto_detect_telemetry.test.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/auto_detect/use_auto_detect_telemetry.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useAutoDetectTelemetry } from './use_auto_detect_telemetry'; import { ObservabilityOnboardingFlowStatus } from './get_onboarding_status'; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.test.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.test.tsx index aede0d27bd4e..45ca17d87899 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.test.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useFlowProgressTelemetry } from './use_flow_progress_telemetry'; import { useKibana } from './use_kibana'; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/plugin.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/plugin.ts index ccb260a002cf..30aaaf258838 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/server/plugin.ts @@ -14,8 +14,8 @@ import type { } from '@kbn/core/server'; import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { DefaultRouteHandlerResources, registerRoutes } from '@kbn/server-route-repository'; import { getObservabilityOnboardingServerRouteRepository } from './routes'; -import { registerRoutes } from './routes/register_routes'; import { ObservabilityOnboardingRouteHandlerResources } from './routes/types'; import { ObservabilityOnboardingPluginSetup, @@ -71,16 +71,28 @@ export class ObservabilityOnboardingPlugin }) as ObservabilityOnboardingRouteHandlerResources['plugins']; const config = this.initContext.config.get<ObservabilityOnboardingConfig>(); - registerRoutes({ - core, - logger: this.logger, - repository: getObservabilityOnboardingServerRouteRepository(), - plugins: resourcePlugins, + + const dependencies: Omit< + ObservabilityOnboardingRouteHandlerResources, + keyof DefaultRouteHandlerResources + > = { config, kibanaVersion: this.initContext.env.packageInfo.version, + plugins: resourcePlugins, services: { esLegacyConfigService: this.esLegacyConfigService, }, + core: { + setup: core, + start: () => core.getStartServices().then(([coreStart]) => coreStart), + }, + }; + + registerRoutes({ + core, + logger: this.logger, + repository: getObservabilityOnboardingServerRouteRepository(), + dependencies, }); plugins.customIntegrations.registerCustomIntegration({ diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts index 372eaf797237..738c9f1fefd5 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts @@ -13,7 +13,8 @@ import { type PackageClient, } from '@kbn/fleet-plugin/server'; import { dump } from 'js-yaml'; -import { PackageDataStreamTypes } from '@kbn/fleet-plugin/common/types'; +import { PackageDataStreamTypes, Output } from '@kbn/fleet-plugin/common/types'; +import { transformOutputToFullPolicyOutput } from '@kbn/fleet-plugin/server/services/output_client'; import { getObservabilityOnboardingFlow, saveObservabilityOnboardingFlow } from '../../lib/state'; import type { SavedObservabilityOnboardingFlow } from '../../saved_objects/observability_onboarding_status'; import { ObservabilityOnboardingFlow } from '../../saved_objects/observability_onboarding_status'; @@ -21,7 +22,6 @@ import { createObservabilityOnboardingServerRoute } from '../create_observabilit import { getHasLogs } from './get_has_logs'; import { getKibanaUrl } from '../../lib/get_fallback_urls'; import { getAgentVersionInfo } from '../../lib/get_agent_version'; -import { getFallbackESUrl } from '../../lib/get_fallback_urls'; import { ElasticAgentStepPayload, InstalledIntegration, StepProgressPayloadRT } from '../types'; import { createShipperApiKey } from '../../lib/api_key/create_shipper_api_key'; import { createInstallApiKey } from '../../lib/api_key/create_install_api_key'; @@ -329,6 +329,13 @@ const integrationsInstallRoute = createObservabilityOnboardingServerRoute({ throw Boom.notFound(`Onboarding session '${params.path.onboardingId}' not found.`); } + const outputClient = await fleetStart.createOutputClient(request); + const defaultOutputId = await outputClient.getDefaultDataOutputId(); + if (!defaultOutputId) { + throw Boom.notFound('Default data output not found'); + } + const output = await outputClient.get(defaultOutputId); + const integrationsToInstall = parseIntegrationsTSV(params.body); if (!integrationsToInstall.length) { return response.badRequest({ @@ -381,15 +388,11 @@ const integrationsInstallRoute = createObservabilityOnboardingServerRoute({ }, }); - const elasticsearchUrl = plugins.cloud?.setup?.elasticsearchUrl - ? [plugins.cloud?.setup?.elasticsearchUrl] - : await getFallbackESUrl(services.esLegacyConfigService); - return response.ok({ headers: { 'content-type': 'application/x-tar', }, - body: generateAgentConfigTar({ elasticsearchUrl, installedIntegrations }), + body: generateAgentConfigTar(output, installedIntegrations), }); }, }); @@ -565,14 +568,9 @@ function parseRegistryIntegrationMetadata( } } -const generateAgentConfigTar = ({ - elasticsearchUrl, - installedIntegrations, -}: { - elasticsearchUrl: string[]; - installedIntegrations: InstalledIntegration[]; -}) => { +function generateAgentConfigTar(output: Output, installedIntegrations: InstalledIntegration[]) { const now = new Date(); + return makeTar([ { type: 'File', @@ -581,11 +579,7 @@ const generateAgentConfigTar = ({ mtime: now, data: dump({ outputs: { - default: { - type: 'elasticsearch', - hosts: elasticsearchUrl, - api_key: '${API_KEY}', // Placeholder to be replaced by bash script with the actual API key - }, + default: transformOutputToFullPolicyOutput(output, undefined, true), }, }), }, @@ -603,7 +597,7 @@ const generateAgentConfigTar = ({ data: integration.config, })), ]); -}; +} export const flowRouteRepository = { ...createFlowRoute, diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/register_routes.ts deleted file mode 100644 index 8fe51623510e..000000000000 --- a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/register_routes.ts +++ /dev/null @@ -1,126 +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 { errors } from '@elastic/elasticsearch'; -import Boom from '@hapi/boom'; -import type { IKibanaResponse } from '@kbn/core/server'; -import { CoreSetup, Logger, RouteRegistrar } from '@kbn/core/server'; -import { - IoTsParamsObject, - ServerRouteRepository, - decodeRequestParams, - stripNullishRequestParameters, - parseEndpoint, - passThroughValidationObject, -} from '@kbn/server-route-repository'; -import * as t from 'io-ts'; -import { ObservabilityOnboardingConfig } from '..'; -import { EsLegacyConfigService } from '../services/es_legacy_config_service'; -import { ObservabilityOnboardingRequestHandlerContext } from '../types'; -import { ObservabilityOnboardingRouteHandlerResources } from './types'; - -interface RegisterRoutes { - core: CoreSetup; - repository: ServerRouteRepository; - logger: Logger; - plugins: ObservabilityOnboardingRouteHandlerResources['plugins']; - config: ObservabilityOnboardingConfig; - kibanaVersion: string; - services: { - esLegacyConfigService: EsLegacyConfigService; - }; -} - -export function registerRoutes({ - repository, - core, - logger, - plugins, - config, - kibanaVersion, - services, -}: RegisterRoutes) { - const routes = Object.values(repository); - - const router = core.http.createRouter(); - - routes.forEach((route) => { - const { endpoint, options, handler, params } = route; - const { pathname, method } = parseEndpoint(endpoint); - - (router[method] as RouteRegistrar<typeof method, ObservabilityOnboardingRequestHandlerContext>)( - { - path: pathname, - validate: passThroughValidationObject, - options, - }, - async (context, request, response) => { - try { - const decodedParams = decodeRequestParams( - stripNullishRequestParameters({ - params: request.params, - body: request.body, - query: request.query, - }), - (params as IoTsParamsObject) ?? t.strict({}) - ); - - const data = (await handler({ - context, - request, - response, - logger, - params: decodedParams, - plugins, - core: { - setup: core, - start: async () => { - const [coreStart] = await core.getStartServices(); - return coreStart; - }, - }, - config, - kibanaVersion, - services, - })) as any; - - if (data === undefined) { - return response.noContent(); - } - - if (data instanceof response.noContent().constructor) { - return data as IKibanaResponse; - } - - return response.ok({ body: data }); - } catch (error) { - if (Boom.isBoom(error)) { - logger.error(error.output.payload.message); - return response.customError({ - statusCode: error.output.statusCode, - body: { message: error.output.payload.message }, - }); - } - - logger.error(error); - const opts = { - statusCode: 500, - body: { - message: error.message, - }, - }; - - if (error instanceof errors.RequestAbortedError) { - opts.statusCode = 499; - opts.body.message = 'Client closed request'; - } - - return response.customError(opts); - } - } - ); - }); -} diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/types.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/types.ts index 4b35272eaa33..689ab1473981 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/types.ts @@ -46,10 +46,8 @@ export interface ObservabilityOnboardingRouteHandlerResources { } export interface ObservabilityOnboardingRouteCreateOptions { - options: { - tags: string[]; - xsrfRequired?: boolean; - }; + tags: string[]; + xsrfRequired?: boolean; } export const IntegrationRT = t.intersection([ diff --git a/x-pack/plugins/observability_solution/observability_shared/common/entity/entity_types.ts b/x-pack/plugins/observability_solution/observability_shared/common/entity/entity_types.ts index 4d8be9efc59c..db3e91fbf493 100644 --- a/x-pack/plugins/observability_solution/observability_shared/common/entity/entity_types.ts +++ b/x-pack/plugins/observability_solution/observability_shared/common/entity/entity_types.ts @@ -6,11 +6,11 @@ */ const createKubernetesEntity = <T extends string>(base: T) => ({ - ecs: `kubernetes_${base}_ecs` as const, - semconv: `kubernetes_${base}_semconv` as const, + ecs: `k8s.${base}.ecs` as const, + semconv: `k8s.${base}.semconv` as const, }); -export const ENTITY_TYPES = { +export const BUILT_IN_ENTITY_TYPES = { HOST: 'host', CONTAINER: 'container', SERVICE: 'service', @@ -18,12 +18,13 @@ export const ENTITY_TYPES = { CLUSTER: createKubernetesEntity('cluster'), CONTAINER: createKubernetesEntity('container'), CRONJOB: createKubernetesEntity('cron_job'), - DAEMONSET: createKubernetesEntity('daemon_set'), + DAEMONSET: createKubernetesEntity('daemonset'), DEPLOYMENT: createKubernetesEntity('deployment'), JOB: createKubernetesEntity('job'), NAMESPACE: createKubernetesEntity('namespace'), NODE: createKubernetesEntity('node'), POD: createKubernetesEntity('pod'), - STATEFULSET: createKubernetesEntity('stateful_set'), + SERVICE: createKubernetesEntity('service'), + STATEFULSET: createKubernetesEntity('statefulset'), }, } as const; diff --git a/x-pack/plugins/observability_solution/observability_shared/common/entity/index.ts b/x-pack/plugins/observability_solution/observability_shared/common/entity/index.ts index adc07a2931b6..92ae5701f800 100644 --- a/x-pack/plugins/observability_solution/observability_shared/common/entity/index.ts +++ b/x-pack/plugins/observability_solution/observability_shared/common/entity/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { ENTITY_TYPES } from './entity_types'; +export { BUILT_IN_ENTITY_TYPES } from './entity_types'; export { EntityDataStreamType } from './entity_data_stream_types'; diff --git a/x-pack/plugins/observability_solution/observability_shared/common/index.ts b/x-pack/plugins/observability_solution/observability_shared/common/index.ts index f483bcc5dc26..a8e26366ab4b 100644 --- a/x-pack/plugins/observability_solution/observability_shared/common/index.ts +++ b/x-pack/plugins/observability_solution/observability_shared/common/index.ts @@ -219,4 +219,4 @@ export { export { COMMON_OBSERVABILITY_GROUPING } from './embeddable_grouping'; -export { ENTITY_TYPES, EntityDataStreamType } from './entity'; +export { BUILT_IN_ENTITY_TYPES, EntityDataStreamType } from './entity'; diff --git a/x-pack/plugins/observability_solution/observability_shared/kibana.jsonc b/x-pack/plugins/observability_solution/observability_shared/kibana.jsonc index a5cde081c7c5..8e5a4c25af48 100644 --- a/x-pack/plugins/observability_solution/observability_shared/kibana.jsonc +++ b/x-pack/plugins/observability_solution/observability_shared/kibana.jsonc @@ -33,4 +33,4 @@ "common" ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_breadcrumbs.test.tsx index c166057df030..8407fd876419 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_breadcrumbs.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import React, { ReactNode } from 'react'; +import { renderHook } from '@testing-library/react'; +import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { CoreStart } from '@kbn/core/public'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; @@ -28,7 +28,7 @@ const kibanaServices = { } as unknown as Partial<CoreStart>; const KibanaContext = createKibanaReactContext(kibanaServices); -function Wrapper({ children }: { children?: ReactNode }) { +function Wrapper({ children }: React.PropsWithChildren) { return ( <MemoryRouter> <KibanaContext.Provider>{children}</KibanaContext.Provider> @@ -38,18 +38,20 @@ function Wrapper({ children }: { children?: ReactNode }) { describe('useBreadcrumbs', () => { afterEach(() => { - setBreadcrumbs.mockClear(); - setTitle.mockClear(); + jest.clearAllMocks(); }); describe('when setBreadcrumbs and setTitle are not defined', () => { it('does not set breadcrumbs or the title', () => { renderHook(() => useBreadcrumbs([]), { - wrapper: ({ children }: React.PropsWithChildren<{}>) => ( + wrapper: ({ children }: React.PropsWithChildren) => ( <MemoryRouter> <KibanaContext.Provider services={ - { ...kibanaServices, chrome: { docTitle: {} } } as unknown as Partial<CoreStart> + { + ...kibanaServices, + chrome: { ...kibanaServices.chrome, docTitle: {}, setBreadcrumbs: null }, + } as unknown as Partial<CoreStart> } > {children} diff --git a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_breadcrumbs.ts index 5c9c0d3981bb..81ee8857e03f 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_breadcrumbs.ts @@ -97,7 +97,7 @@ export const useBreadcrumbs = ( const setBreadcrumbs = useMemo(() => { if (!serverless?.setBreadcrumbs) { return (breadcrumbs: ChromeBreadcrumb[]) => - chromeSetBreadcrumbs( + chromeSetBreadcrumbs?.( breadcrumbs, !classicOnly ? { diff --git a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_link_props.test.tsx b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_link_props.test.tsx index 69ad845939e4..4c6a4f4a06ff 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_link_props.test.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_link_props.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import React, { PropsWithChildren } from 'react'; import { Router } from '@kbn/shared-ux-router'; diff --git a/x-pack/plugins/observability_solution/profiling/server/lib/setup/get_has_setup_privileges.ts b/x-pack/plugins/observability_solution/profiling/server/lib/setup/get_has_setup_privileges.ts index 83bd21b1740b..8696c97dabd3 100644 --- a/x-pack/plugins/observability_solution/profiling/server/lib/setup/get_has_setup_privileges.ts +++ b/x-pack/plugins/observability_solution/profiling/server/lib/setup/get_has_setup_privileges.ts @@ -7,6 +7,7 @@ import { KibanaRequest } from '@kbn/core/server'; import { INTEGRATIONS_PLUGIN_ID, PLUGIN_ID as FLEET_PLUGIN_ID } from '@kbn/fleet-plugin/common'; +import { ApiOperation } from '@kbn/security-plugin-types-server'; import { ProfilingPluginStartDeps } from '../../types'; export async function getHasSetupPrivileges({ @@ -31,8 +32,11 @@ export async function getHasSetupPrivileges({ }, }, kibana: [ - securityPluginStart.authz.actions.api.get(`${FLEET_PLUGIN_ID}-all`), - securityPluginStart.authz.actions.api.get(`${INTEGRATIONS_PLUGIN_ID}-all`), + securityPluginStart.authz.actions.api.get(ApiOperation.Manage, `${FLEET_PLUGIN_ID}-all`), + securityPluginStart.authz.actions.api.get( + ApiOperation.Manage, + `${INTEGRATIONS_PLUGIN_ID}-all` + ), ], }); return hasAllRequested; diff --git a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts index 7ad001831c0e..4d5a7cca0ff7 100644 --- a/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts +++ b/x-pack/plugins/observability_solution/profiling/server/routes/apm.ts @@ -52,9 +52,7 @@ export function registerTopNFunctionsAPMTransactionsRoute({ }); } const core = await context.core; - const { transaction: transactionIndices } = await apmDataAccess.getApmIndices( - core.savedObjects.client - ); + const { transaction: transactionIndices } = await apmDataAccess.getApmIndices(); const esClient = await getClient(context); diff --git a/x-pack/plugins/observability_solution/profiling/tsconfig.json b/x-pack/plugins/observability_solution/profiling/tsconfig.json index 937eee96641c..b89d34bb8442 100644 --- a/x-pack/plugins/observability_solution/profiling/tsconfig.json +++ b/x-pack/plugins/observability_solution/profiling/tsconfig.json @@ -54,7 +54,8 @@ "@kbn/management-settings-components-field-row", "@kbn/deeplinks-observability", "@kbn/react-kibana-context-render", - "@kbn/apm-data-access-plugin" + "@kbn/apm-data-access-plugin", + "@kbn/security-plugin-types-server" // add references to other TypeScript projects the plugin depends on // requiredPlugins from ./kibana.json diff --git a/x-pack/plugins/observability_solution/slo/emotion.d.ts b/x-pack/plugins/observability_solution/slo/emotion.d.ts new file mode 100644 index 000000000000..213178080e53 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/emotion.d.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import '@emotion/react'; +import type { UseEuiTheme } from '@elastic/eui'; + +declare module '@emotion/react' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Theme extends UseEuiTheme {} +} diff --git a/x-pack/plugins/observability_solution/slo/public/application.tsx b/x-pack/plugins/observability_solution/slo/public/application.tsx index 79160de114cd..12019ae1fdf7 100644 --- a/x-pack/plugins/observability_solution/slo/public/application.tsx +++ b/x-pack/plugins/observability_solution/slo/public/application.tsx @@ -5,16 +5,14 @@ * 2.0. */ -import { AppMountParameters, APP_WRAPPER_CLASS, CoreStart } from '@kbn/core/public'; +import { APP_WRAPPER_CLASS, AppMountParameters, CoreStart } from '@kbn/core/public'; import { PerformanceContextProvider } from '@kbn/ebt-tools'; import { i18n } from '@kbn/i18n'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { ObservabilityRuleTypeRegistry } from '@kbn/observability-plugin/public'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { Route, Router, Routes } from '@kbn/shared-ux-router'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; @@ -25,7 +23,7 @@ import { ExperimentalFeatures } from '../common/config'; import { PluginContext } from './context/plugin_context'; import { usePluginContext } from './hooks/use_plugin_context'; import { getRoutes } from './routes/routes'; -import { SLORepositoryClient, SLOPublicPluginsStart } from './types'; +import { SLOPublicPluginsStart, SLORepositoryClient } from './types'; interface Props { core: CoreStart; @@ -54,8 +52,7 @@ export const renderApp = ({ experimentalFeatures, sloClient, }: Props) => { - const { element, history, theme$ } = appMountParameters; - const isDarkMode = core.theme.getTheme().darkMode; + const { element, history } = appMountParameters; // ensure all divs are .kbnAppWrappers element.classList.add(APP_WRAPPER_CLASS); @@ -92,44 +89,40 @@ export const renderApp = ({ ReactDOM.render( <KibanaRenderContextProvider {...core}> <ApplicationUsageTrackingProvider> - <KibanaThemeProvider {...{ theme: { theme$ } }}> - <CloudProvider> - <KibanaContextProvider - services={{ - ...core, - ...plugins, - storage: new Storage(localStorage), + <CloudProvider> + <KibanaContextProvider + services={{ + ...core, + ...plugins, + storage: new Storage(localStorage), + isDev, + kibanaVersion, + isServerless, + }} + > + <PluginContext.Provider + value={{ isDev, - kibanaVersion, isServerless, + appMountParameters, + ObservabilityPageTemplate, + observabilityRuleTypeRegistry, + experimentalFeatures, + sloClient, }} > - <PluginContext.Provider - value={{ - isDev, - isServerless, - appMountParameters, - ObservabilityPageTemplate, - observabilityRuleTypeRegistry, - experimentalFeatures, - sloClient, - }} - > - <Router history={history}> - <EuiThemeProvider darkMode={isDarkMode}> - <RedirectAppLinks coreStart={core} data-test-subj="observabilityMainContainer"> - <PerformanceContextProvider> - <QueryClientProvider client={queryClient}> - <App /> - </QueryClientProvider> - </PerformanceContextProvider> - </RedirectAppLinks> - </EuiThemeProvider> - </Router> - </PluginContext.Provider> - </KibanaContextProvider> - </CloudProvider> - </KibanaThemeProvider> + <Router history={history}> + <RedirectAppLinks coreStart={core} data-test-subj="observabilityMainContainer"> + <PerformanceContextProvider> + <QueryClientProvider client={queryClient}> + <App /> + </QueryClientProvider> + </PerformanceContextProvider> + </RedirectAppLinks> + </Router> + </PluginContext.Provider> + </KibanaContextProvider> + </CloudProvider> </ApplicationUsageTrackingProvider> </KibanaRenderContextProvider>, element diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_alerts_wrapper.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_alerts_wrapper.tsx index d6c58315cc1a..b46506ea73d9 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_alerts_wrapper.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_alerts_wrapper.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; import type { TimeRange } from '@kbn/es-query'; import { Subject } from 'rxjs'; -import styled from 'styled-components'; +import { css } from '@emotion/react'; import { observabilityPaths } from '@kbn/observability-plugin/common'; import { FetchContext } from '@kbn/presentation-publishing'; import { SloIncludedCount } from './components/slo_included_count'; @@ -72,14 +72,10 @@ export function SloAlertsWrapper({ } }, [isSummaryLoaded, isTableLoaded, onRenderComplete]); const handleGoToAlertsClick = () => { - let kuery = ''; - slos.map((slo, index) => { - const shouldAddOr = index < slos.length - 1; - kuery += `(slo.id:"${slo.id}" and slo.instanceId:"${slo.instanceId}")`; - if (shouldAddOr) { - kuery += ' or '; - } - }); + const kuery = slos + .map((slo) => `(slo.id:"${slo.id}" and slo.instanceId:"${slo.instanceId}")`) + .join(' or '); + navigateToUrl( `${basePath.prepend(observabilityPaths.alerts)}?_a=(kuery:'${kuery}',rangeFrom:${ timeRange.from @@ -87,12 +83,17 @@ export function SloAlertsWrapper({ ); }; return ( - <Wrapper> + <div + css={css` + width: 100%; + overflow: scroll; + `} + > <EuiFlexGroup data-shared-item="" justifyContent="flexEnd" wrap - css={` + css={css` margin: 0 35px; `} > @@ -150,11 +151,6 @@ export function SloAlertsWrapper({ /> </EuiFlexItem> </EuiFlexGroup> - </Wrapper> + </div> ); } - -const Wrapper = styled.div` - width: 100%; - overflow: scroll; -`; diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_configuration.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_configuration.tsx index 979162aee40b..07d55b02fb27 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_configuration.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_configuration.tsx @@ -5,24 +5,24 @@ * 2.0. */ -import React, { useState } from 'react'; import { - EuiFlyout, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiTitle, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, EuiSpacer, EuiSwitch, + EuiTitle, } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { ALL_VALUE } from '@kbn/slo-schema'; - +import React, { useState } from 'react'; import { SloSelector } from './slo_selector'; import type { EmbeddableSloProps, SloItem } from './types'; @@ -47,7 +47,7 @@ export function SloConfiguration({ initialInput, onCreate, onCancel }: SloConfig return ( <EuiFlyout onClose={onCancel} - css={` + css={css` min-width: 550px; `} > diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate.tsx index bb9236335911..43491ff036a3 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate.tsx @@ -5,8 +5,15 @@ * 2.0. */ -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLink, EuiLoadingChart } from '@elastic/eui'; -import { css } from '@emotion/css'; +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiLoadingChart, + UseEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; @@ -41,12 +48,7 @@ export function BurnRate({ sloId, sloInstanceId, duration, reloadSubject }: Embe if (isLoading || !slo) { return ( - <EuiFlexGroup - direction="column" - alignItems="center" - justifyContent="center" - className={container} - > + <EuiFlexGroup direction="column" alignItems="center" justifyContent="center" css={container}> <EuiFlexItem grow={false}> <EuiLoadingChart /> </EuiFlexItem> @@ -56,12 +58,7 @@ export function BurnRate({ sloId, sloInstanceId, duration, reloadSubject }: Embe if (isSloNotFound) { return ( - <EuiFlexGroup - direction="column" - alignItems="center" - justifyContent="center" - className={container} - > + <EuiFlexGroup direction="column" alignItems="center" justifyContent="center" css={container}> <EuiFlexItem grow={false}> {i18n.translate('xpack.slo.sloEmbeddable.overview.sloNotFoundText', { defaultMessage: @@ -84,7 +81,7 @@ export function BurnRate({ sloId, sloInstanceId, duration, reloadSubject }: Embe <EuiFlexItem> <EuiLink data-test-subj="sloBurnRateLink" - className={link} + css={link} color="text" onClick={() => { setSelectedSlo(slo); @@ -146,7 +143,7 @@ const container = css` height: 100%; `; -const link = css` - font-size: 16px; - font-weight: 700; +const link = ({ euiTheme }: UseEuiTheme) => css` + font-size: ${euiTheme.size.base}; + font-weight: ${euiTheme.font.weight.bold}; `; diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx index e74ba591e716..57de17419497 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, UseEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; import { i18n } from '@kbn/i18n'; @@ -21,7 +22,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { createBrowserHistory } from 'history'; import React, { useEffect } from 'react'; import { BehaviorSubject, Subject } from 'rxjs'; -import styled from 'styled-components'; import { PluginContext } from '../../../context/plugin_context'; import type { SLOPublicPluginsStart, SLORepositoryClient } from '../../../types'; import { SLO_OVERVIEW_EMBEDDABLE_ID } from './constants'; @@ -165,11 +165,21 @@ export const getOverviewEmbeddableFactory = ({ const kqlQuery = groupFilters?.kqlQuery ?? ''; const groups = groupFilters?.groups ?? []; return ( - <Wrapper> + <div + css={({ euiTheme }: UseEuiTheme) => css` + width: 100%; + padding: ${euiTheme.size.xs} ${euiTheme.size.base}; + overflow: scroll; + + .euiAccordion__buttonContent { + min-width: ${euiTheme.base * 6}px; + } + `} + > <EuiFlexGroup data-test-subj="sloGroupOverviewPanel" data-shared-item=""> <EuiFlexItem - css={` - margin-top: 20px; + css={({ euiTheme }: UseEuiTheme) => css` + margin-top: ${euiTheme.base * 1.25}px; `} > <GroupSloView @@ -182,7 +192,7 @@ export const getOverviewEmbeddableFactory = ({ /> </EuiFlexItem> </EuiFlexGroup> - </Wrapper> + </div> ); } else { return ( @@ -230,13 +240,3 @@ export const getOverviewEmbeddableFactory = ({ }; return factory; }; - -const Wrapper = styled.div` - width: 100%; - padding: 5px 15px; - overflow: scroll; - - .euiAccordion__buttonContent { - min-width: 100px; - } -`; diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/query_search_bar.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/query_search_bar.tsx index e7e61adfc1cc..629d6e9ec459 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/query_search_bar.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/query_search_bar.tsx @@ -6,16 +6,16 @@ */ import { EuiFormRow } from '@elastic/eui'; -import { Controller, useFormContext } from 'react-hook-form'; -import { fromKueryExpression, Query, TimeRange, toElasticsearchQuery } from '@kbn/es-query'; +import { css } from '@emotion/react'; +import { Query, TimeRange, fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { observabilityAppId } from '@kbn/observability-shared-plugin/common'; import { kqlQuerySchema, kqlWithFiltersSchema } from '@kbn/slo-schema'; import React, { memo } from 'react'; -import styled from 'styled-components'; -import { observabilityAppId } from '@kbn/observability-shared-plugin/common'; -import { SearchBarProps } from './query_builder'; +import { Controller, useFormContext } from 'react-hook-form'; import { useKibana } from '../../../../hooks/use_kibana'; import { CreateSLOForm } from '../../types'; import { OptionalText } from './optional_text'; +import { SearchBarProps } from './query_builder'; export const QuerySearchBar = memo( ({ @@ -90,7 +90,13 @@ export const QuerySearchBar = memo( error={fieldState.error?.message} fullWidth > - <Container> + <div + css={css` + .uniSearchBar { + padding: 0; + } + `} + > <SearchBar appName={observabilityAppId} dataTestSubj={dataTestSubj} @@ -153,7 +159,7 @@ export const QuerySearchBar = memo( onClearSavedQuery={() => {}} filters={kqlQuerySchema.is(field.value) ? [] : field.value?.filters ?? []} /> - </Container> + </div> </EuiFormRow> ); }} @@ -161,9 +167,3 @@ export const QuerySearchBar = memo( ); } ); - -const Container = styled.div` - .uniSearchBar { - padding: 0; - } -`; diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item_actions.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item_actions.tsx index 53a8da22db2a..9df624edc1e6 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item_actions.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item_actions.tsx @@ -4,39 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; -import { SLOWithSummaryResponse } from '@kbn/slo-schema'; -import styled from 'styled-components'; import { useEuiShadow } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; +import React from 'react'; import { BurnRateRuleParams } from '../../../../typings'; import { SloItemActions } from '../slo_item_actions'; -type PopoverPosition = 'relative' | 'default'; - -interface ActionContainerProps { - boxShadow: string; - position: PopoverPosition; -} - -const Container = styled.div<ActionContainerProps>` - ${({ position }) => - position === 'relative' - ? // custom styles used to overlay the popover button on `MetricItem` - ` - display: inline-block; - position: relative; - bottom: 42px; - left: 12px; - z-index: 1; -` - : // otherwise, no custom position needed - ''} - - border-radius: ${({ theme }) => theme.eui.euiBorderRadius}; - ${({ boxShadow, position }) => (position === 'relative' ? boxShadow : '')} -`; - interface Props { slo: SLOWithSummaryResponse; isActionsPopoverOpen: boolean; @@ -50,10 +25,19 @@ interface Props { } export function SloCardItemActions(props: Props) { - const euiShadow = useEuiShadow('l'); - + const shadow = useEuiShadow('l'); return ( - <Container boxShadow={euiShadow} position={'relative'}> + <div + css={({ euiTheme }) => css` + display: inline-block; + position: relative; + bottom: ${euiTheme.size.xxl}; + left: ${euiTheme.size.m}; + z-index: 1; + border-radius: ${euiTheme.border.radius.medium}; + ${shadow} + `} + > <SloItemActions {...props} btnProps={{ @@ -62,6 +46,6 @@ export function SloCardItemActions(props: Props) { display: 'empty', }} /> - </Container> + </div> ); } diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item_badges.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item_badges.tsx index 5166baaf7d31..7f1888e182f3 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item_badges.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/card_view/slo_card_item_badges.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import React, { useCallback } from 'react'; -import styled from 'styled-components'; +import { css } from '@emotion/react'; import { SloIndicatorTypeBadge } from '../badges/slo_indicator_type_badge'; import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge'; import { BurnRateRuleParams } from '../../../../typings'; @@ -29,11 +29,6 @@ interface Props { handleCreateRule?: () => void; } -const Container = styled.div` - display: inline-block; - margin-top: 5px; -`; - export function SloCardItemBadges({ slo, activeAlerts, rules, handleCreateRule }: Props) { const { onStateChange } = useUrlSearchState(); @@ -52,10 +47,15 @@ export function SloCardItemBadges({ slo, activeAlerts, rules, handleCreateRule } const numberOfTagsToDisplay = !isRemote || (rules ?? []).length > 0 ? 2 : 1; return ( - <Container + <div + css={({ euiTheme }) => css` + display: inline-block; + margin-top: ${euiTheme.size.xs}; + `} onClick={(evt) => { evt.stopPropagation(); }} + aria-hidden="true" > <EuiFlexGroup direction="row" responsive={false} gutterSize="xs" alignItems="center" wrap> {!slo.summary ? ( @@ -78,6 +78,6 @@ export function SloCardItemBadges({ slo, activeAlerts, rules, handleCreateRule } </> )} </EuiFlexGroup> - </Container> + </div> ); } diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/quick_filters.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/quick_filters.tsx index ad350a3d17fb..3924b55c0f09 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/quick_filters.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/quick_filters.tsx @@ -5,14 +5,14 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; -import { skip } from 'rxjs'; -import React, { useEffect, useState } from 'react'; +import { css } from '@emotion/react'; import { ControlGroupRenderer, ControlGroupRendererApi } from '@kbn/controls-plugin/public'; import { DataView } from '@kbn/data-views-plugin/common'; -import styled from 'styled-components'; import { Filter } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { skip } from 'rxjs'; import { SearchState } from '../../hooks/use_url_search_state'; interface Props { @@ -53,7 +53,17 @@ export function QuickFilters({ } return ( - <Container> + <div + css={css` + .controlsWrapper { + align-items: flex-start; + min-height: initial; + } + .controlGroup { + min-height: initial; + } + `} + > <ControlGroupRenderer onApiAvailable={setControlGroupAPI} getCreationOptions={async (initialState, builder) => { @@ -94,7 +104,7 @@ export function QuickFilters({ timeRange={{ from: 'now-24h', to: 'now' }} compressed={false} /> - </Container> + </div> ); } @@ -114,16 +124,6 @@ export const getSelectedOptions = (filter?: Filter) => { return []; }; -const Container = styled.div` - .controlsWrapper { - align-items: flex-start; - min-height: initial; - } - .controlGroup { - min-height: initial; - } -`; - const TAGS_LABEL = i18n.translate('xpack.slo.list.tags', { defaultMessage: 'Tags', }); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx index 1a99f8ff354d..37c48b2bf888 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx @@ -14,15 +14,15 @@ import { EuiPopover, useEuiShadow, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import React from 'react'; -import styled from 'styled-components'; -import { usePermissions } from '../../../hooks/use_permissions'; import { useCloneSlo } from '../../../hooks/use_clone_slo'; -import { BurnRateRuleParams } from '../../../typings'; import { useKibana } from '../../../hooks/use_kibana'; +import { usePermissions } from '../../../hooks/use_permissions'; +import { BurnRateRuleParams } from '../../../typings'; import { useSloActions } from '../../slo_details/hooks/use_slo_actions'; interface Props { @@ -37,24 +37,22 @@ interface Props { btnProps?: Partial<EuiButtonIconProps>; rules?: Array<Rule<BurnRateRuleParams>>; } -const CustomShadowPanel = styled(EuiPanel)<{ shadow: string }>` - ${(props) => props.shadow} -`; -function IconPanel({ children, hasPanel }: { children: JSX.Element; hasPanel: boolean }) { +function IconPanel({ children }: { children: JSX.Element }) { const shadow = useEuiShadow('s'); - if (!hasPanel) return children; return ( - <CustomShadowPanel + <EuiPanel color="plain" element="button" grow={false} paddingSize="none" hasShadow={false} - shadow={shadow} + css={css` + ${shadow} + `} > {children} - </CustomShadowPanel> + </EuiPanel> ); } @@ -161,7 +159,7 @@ export function SloItemActions({ return ( <EuiPopover anchorPosition="downLeft" - button={btnProps ? <IconPanel hasPanel={true}>{btn}</IconPanel> : btn} + button={btnProps ? <IconPanel>{btn}</IconPanel> : btn} panelPaddingSize="m" closePopover={handleClickActions} isOpen={isActionsPopoverOpen} diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_list_search_bar.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_list_search_bar.tsx index 16c635a2da20..d6d501494bc4 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_list_search_bar.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_list_search_bar.tsx @@ -5,11 +5,11 @@ * 2.0. */ +import { css } from '@emotion/react'; import { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { observabilityAppId } from '@kbn/observability-plugin/public'; import React, { useEffect } from 'react'; -import styled from 'styled-components'; import { useKibana } from '../../../hooks/use_kibana'; import { useSloCrudLoading } from '../hooks/use_crud_loading'; import { useSloSummaryDataView } from '../hooks/use_summary_dataview'; @@ -42,7 +42,13 @@ export function SloListSearchBar() { }, [onStateChange, query]); return ( - <Container> + <div + css={css` + .uniSearchBar { + padding: 0; + } + `} + > <SearchBar appName={observabilityAppId} placeholder={PLACEHOLDER} @@ -77,16 +83,10 @@ export function SloListSearchBar() { }); }} /> - </Container> + </div> ); } -const Container = styled.div` - .uniSearchBar { - padding: 0; - } -`; - const PLACEHOLDER = i18n.translate('xpack.slo.list.search', { defaultMessage: 'Search your SLOs ...', }); diff --git a/x-pack/plugins/observability_solution/slo/public/rules/register_burn_rate_rule_type.ts b/x-pack/plugins/observability_solution/slo/public/rules/register_burn_rate_rule_type.ts index cea53c96ab0a..cd0b5ec47836 100644 --- a/x-pack/plugins/observability_solution/slo/public/rules/register_burn_rate_rule_type.ts +++ b/x-pack/plugins/observability_solution/slo/public/rules/register_burn_rate_rule_type.ts @@ -67,7 +67,9 @@ export const registerBurnRateRuleType = ( documentationUrl(docLinks) { return `${docLinks.links.observability.sloBurnRateRule}`; }, - ruleParamsExpression: lazy(() => import('../components/burn_rate_rule_editor')), + ruleParamsExpression: lazyWithContextProviders( + lazy(() => import('../components/burn_rate_rule_editor')) + ), validate: validateBurnRateRule, requiresAppContext: false, defaultActionMessage: sloBurnRateDefaultActionMessage, diff --git a/x-pack/plugins/observability_solution/slo/public/utils/test_helper.tsx b/x-pack/plugins/observability_solution/slo/public/utils/test_helper.tsx index fd735d94c4a9..44cd0abceded 100644 --- a/x-pack/plugins/observability_solution/slo/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability_solution/slo/public/utils/test_helper.tsx @@ -9,16 +9,15 @@ import { AppMountParameters } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { createObservabilityRuleTypeRegistryMock } from '@kbn/observability-plugin/public'; +import { DefaultClientOptions, createRepositoryClient } from '@kbn/server-route-repository-client'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render as testLibRender } from '@testing-library/react'; import React from 'react'; -import { DefaultClientOptions, createRepositoryClient } from '@kbn/server-route-repository-client'; -import { PluginContext } from '../context/plugin_context'; import type { SLORouteRepository } from '../../server/routes/get_slo_server_route_repository'; +import { PluginContext } from '../context/plugin_context'; const appMountParameters = { setHeaderActionMenu: () => {} } as unknown as AppMountParameters; const observabilityRuleTypeRegistry = createObservabilityRuleTypeRegistryMock(); @@ -67,9 +66,7 @@ export const render = (component: React.ReactNode) => { sloClient, }} > - <QueryClientProvider client={queryClient}> - <EuiThemeProvider>{component}</EuiThemeProvider> - </QueryClientProvider> + <QueryClientProvider client={queryClient}>{component}</QueryClientProvider> </PluginContext.Provider> </KibanaContextProvider> </IntlProvider> diff --git a/x-pack/plugins/observability_solution/slo/server/plugin.ts b/x-pack/plugins/observability_solution/slo/server/plugin.ts index d7d002d26aa0..7699cbe5f140 100644 --- a/x-pack/plugins/observability_solution/slo/server/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/server/plugin.ts @@ -141,6 +141,7 @@ export class SLOPlugin }, logger: this.logger, repository: getSloServerRouteRepository({ isServerless: this.isServerless }), + isServerless: this.isServerless, }); core diff --git a/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts b/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts index fd0b18c21004..ee58618add23 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/register_routes.ts @@ -6,17 +6,32 @@ */ import { CoreSetup, Logger } from '@kbn/core/server'; import { ServerRoute, registerRoutes } from '@kbn/server-route-repository'; -import { ServerRouteCreateOptions } from '@kbn/server-route-repository-utils'; -import { SLORoutesDependencies } from './types'; +import { SLORequestHandlerContext, SLORoutesDependencies } from './types'; interface RegisterRoutes { core: CoreSetup; - repository: Record<string, ServerRoute<string, any, any, any, ServerRouteCreateOptions>>; + repository: Record<string, ServerRoute<string, any, any, any, any>>; logger: Logger; dependencies: SLORoutesDependencies; + isServerless: boolean; } -export function registerServerRoutes({ repository, core, logger, dependencies }: RegisterRoutes) { +export function registerServerRoutes({ + repository, + core, + logger, + dependencies, + isServerless, +}: RegisterRoutes) { + core.http.registerRouteHandlerContext<SLORequestHandlerContext, 'slo'>( + 'slo', + async (_context, _request) => { + return { + isServerless, + }; + } + ); + registerRoutes<SLORoutesDependencies>({ repository, dependencies, diff --git a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts index 9e63a4b02fe7..2081f38df552 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts @@ -29,7 +29,6 @@ import { updateSLOParamsSchema, } from '@kbn/slo-schema'; import { getOverviewParamsSchema } from '@kbn/slo-schema/src/rest_specs/routes/get_overview'; -import type { IndicatorTypes } from '../../domain/models'; import { executeWithErrorHandler } from '../../errors'; import { CreateSLO, @@ -60,29 +59,10 @@ import { SloDefinitionClient } from '../../services/slo_definition_client'; import { getSloSettings, storeSloSettings } from '../../services/slo_settings'; import { DefaultSummarySearchClient } from '../../services/summary_search_client'; import { DefaultSummaryTransformGenerator } from '../../services/summary_transform_generator/summary_transform_generator'; -import { - ApmTransactionDurationTransformGenerator, - ApmTransactionErrorRateTransformGenerator, - HistogramTransformGenerator, - KQLCustomTransformGenerator, - MetricCustomTransformGenerator, - SyntheticsAvailabilityTransformGenerator, - TimesliceMetricTransformGenerator, - TransformGenerator, -} from '../../services/transform_generators'; +import { createTransformGenerators } from '../../services/transform_generators'; import { createSloServerRoute } from '../create_slo_server_route'; import { SLORoutesDependencies } from '../types'; -const transformGenerators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionDuration': new ApmTransactionDurationTransformGenerator(), - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), - 'sli.synthetics.availability': new SyntheticsAvailabilityTransformGenerator(), - 'sli.kql.custom': new KQLCustomTransformGenerator(), - 'sli.metric.custom': new MetricCustomTransformGenerator(), - 'sli.histogram.custom': new HistogramTransformGenerator(), - 'sli.metric.timeslice': new TimesliceMetricTransformGenerator(), -}; - const assertPlatinumLicense = async (plugins: SLORoutesDependencies['plugins']) => { const licensing = await plugins.licensing.start(); const hasCorrectLicense = (await licensing.getLicense()).hasAtLeast('platinum'); @@ -107,6 +87,7 @@ const createSLORoute = createSloServerRoute({ handler: async ({ context, response, params, logger, request, plugins, corePlugins }) => { await assertPlatinumLicense(plugins); + const sloContext = await context.slo; const dataViews = await plugins.dataViews.start(); const core = await context.core; const scopedClusterClient = core.elasticsearch.client; @@ -119,12 +100,17 @@ const createSLORoute = createSloServerRoute({ getSpaceId(plugins, request), dataViews.dataViewsServiceFactory(soClient, esClient), ]); + + const transformGenerators = createTransformGenerators( + spaceId, + dataViewsService, + sloContext.isServerless + ); + const transformManager = new DefaultTransformManager( transformGenerators, scopedClusterClient, - logger, - spaceId, - dataViewsService + logger ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -156,6 +142,7 @@ const inspectSLORoute = createSloServerRoute({ handler: async ({ context, params, logger, request, plugins, corePlugins }) => { await assertPlatinumLicense(plugins); + const sloContext = await context.slo; const dataViews = await plugins.dataViews.start(); const spaceId = await getSpaceId(plugins, request); const basePath = corePlugins.http.basePath; @@ -165,12 +152,16 @@ const inspectSLORoute = createSloServerRoute({ const soClient = core.savedObjects.client; const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); + + const transformGenerators = createTransformGenerators( + spaceId, + dataViewsService, + sloContext.isServerless + ); const transformManager = new DefaultTransformManager( transformGenerators, scopedClusterClient, - logger, - spaceId, - dataViewsService + logger ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -206,6 +197,7 @@ const updateSLORoute = createSloServerRoute({ const spaceId = await getSpaceId(plugins, request); const dataViews = await plugins.dataViews.start(); + const sloContext = await context.slo; const basePath = corePlugins.http.basePath; const core = await context.core; const scopedClusterClient = core.elasticsearch.client; @@ -213,12 +205,16 @@ const updateSLORoute = createSloServerRoute({ const soClient = core.savedObjects.client; const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); + + const transformGenerators = createTransformGenerators( + spaceId, + dataViewsService, + sloContext.isServerless + ); const transformManager = new DefaultTransformManager( transformGenerators, scopedClusterClient, - logger, - spaceId, - dataViewsService + logger ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -254,6 +250,7 @@ const deleteSLORoute = createSloServerRoute({ const spaceId = await getSpaceId(plugins, request); const dataViews = await plugins.dataViews.start(); + const sloContext = await context.slo; const core = await context.core; const scopedClusterClient = core.elasticsearch.client; const esClient = core.elasticsearch.client.asCurrentUser; @@ -264,13 +261,16 @@ const deleteSLORoute = createSloServerRoute({ const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); + const transformGenerators = createTransformGenerators( + spaceId, + dataViewsService, + sloContext.isServerless + ); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); const transformManager = new DefaultTransformManager( transformGenerators, scopedClusterClient, - logger, - spaceId, - dataViewsService + logger ); const summaryTransformManager = new DefaultSummaryTransformManager( @@ -331,19 +331,24 @@ const enableSLORoute = createSloServerRoute({ const spaceId = await getSpaceId(plugins, request); const dataViews = await plugins.dataViews.start(); - + const sloContext = await context.slo; const core = await context.core; const scopedClusterClient = core.elasticsearch.client; const soClient = core.savedObjects.client; const esClient = core.elasticsearch.client.asCurrentUser; const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); + + const transformGenerators = createTransformGenerators( + spaceId, + dataViewsService, + sloContext.isServerless + ); + const transformManager = new DefaultTransformManager( transformGenerators, scopedClusterClient, - logger, - spaceId, - dataViewsService + logger ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -372,18 +377,23 @@ const disableSLORoute = createSloServerRoute({ const spaceId = await getSpaceId(plugins, request); const dataViews = await plugins.dataViews.start(); + const sloContext = await context.slo; const core = await context.core; const scopedClusterClient = core.elasticsearch.client; const soClient = core.savedObjects.client; const esClient = core.elasticsearch.client.asCurrentUser; const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); + + const transformGenerators = createTransformGenerators( + spaceId, + dataViewsService, + sloContext.isServerless + ); const transformManager = new DefaultTransformManager( transformGenerators, scopedClusterClient, - logger, - spaceId, - dataViewsService + logger ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), @@ -408,6 +418,7 @@ const resetSLORoute = createSloServerRoute({ handler: async ({ context, request, params, logger, plugins, corePlugins }) => { await assertPlatinumLicense(plugins); + const sloContext = await context.slo; const dataViews = await plugins.dataViews.start(); const spaceId = await getSpaceId(plugins, request); const core = await context.core; @@ -418,12 +429,16 @@ const resetSLORoute = createSloServerRoute({ const dataViewsService = await dataViews.dataViewsServiceFactory(soClient, esClient); const repository = new KibanaSavedObjectsSLORepository(soClient, logger); + + const transformGenerators = createTransformGenerators( + spaceId, + dataViewsService, + sloContext.isServerless + ); const transformManager = new DefaultTransformManager( transformGenerators, scopedClusterClient, - logger, - spaceId, - dataViewsService + logger ); const summaryTransformManager = new DefaultSummaryTransformManager( new DefaultSummaryTransformGenerator(), diff --git a/x-pack/plugins/observability_solution/slo/server/routes/types.ts b/x-pack/plugins/observability_solution/slo/server/routes/types.ts index cb5057cee405..37937cf8c568 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CoreSetup } from '@kbn/core/server'; +import { CoreSetup, CustomRequestHandlerContext } from '@kbn/core/server'; import type { DefaultRouteHandlerResources } from '@kbn/server-route-repository'; import { SLOPluginSetupDependencies, SLOPluginStartDependencies } from '../types'; @@ -21,4 +21,15 @@ export interface SLORoutesDependencies { corePlugins: CoreSetup; } -export type SLORouteHandlerResources = SLORoutesDependencies & DefaultRouteHandlerResources; +export type SLORouteHandlerResources = SLORoutesDependencies & + DefaultRouteHandlerResources & { + context: SLORequestHandlerContext; + }; + +export interface SLORouteContext { + isServerless: boolean; +} + +export type SLORequestHandlerContext = CustomRequestHandlerContext<{ + slo: Promise<SLORouteContext>; +}>; diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/synthetics_availability.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/synthetics_availability.test.ts.snap index 3c7184467888..309fdc7ffffb 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/synthetics_availability.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/synthetics_availability.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Synthetics Availability Transform Generator returns the expected transform params 1`] = ` +exports[`Synthetics Availability Transform Generator when serverless is disabled returns the expected transform params 1`] = ` Object { "_meta": Object { "managed": true, diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/transform_generator.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/transform_generator.test.ts.snap index 144a4fa35eda..7d8e989c1140 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/transform_generator.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/transform_generator.test.ts.snap @@ -1,8 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Transform Generator builds common runtime mappings and group by with single group by 1`] = `Object {}`; - -exports[`Transform Generator builds common runtime mappings and group by with single group by 2`] = ` +exports[`Transform Generator buildCommonGroupBy builds common groupBy with single group by 1`] = ` Object { "@timestamp": Object { "date_histogram": Object { @@ -18,9 +16,7 @@ Object { } `; -exports[`Transform Generator builds common runtime mappings and group by with single group by 3`] = `Object {}`; - -exports[`Transform Generator builds common runtime mappings and group by with single group by 4`] = ` +exports[`Transform Generator buildCommonGroupBy builds common groupBy with single group by 2`] = ` Object { "@timestamp": Object { "date_histogram": Object { @@ -36,9 +32,7 @@ Object { } `; -exports[`Transform Generator builds common runtime mappings without multi group by 1`] = `Object {}`; - -exports[`Transform Generator builds common runtime mappings without multi group by 2`] = ` +exports[`Transform Generator buildCommonGroupBy builds common groupBy with single group by 3`] = ` Object { "@timestamp": Object { "date_histogram": Object { @@ -59,9 +53,7 @@ Object { } `; -exports[`Transform Generator builds empty runtime mappings without group by 1`] = `Object {}`; - -exports[`Transform Generator builds empty runtime mappings without group by 2`] = ` +exports[`Transform Generator buildCommonGroupBy builds empty runtime mappings without group by 1`] = ` Object { "@timestamp": Object { "date_histogram": Object { diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_duration.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_duration.test.ts index b764b83ea934..c928c121e3e7 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_duration.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_duration.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; import { ALL_VALUE } from '@kbn/slo-schema'; import { twoMinute } from '../fixtures/duration'; import { @@ -13,15 +14,14 @@ import { createSLOWithTimeslicesBudgetingMethod, } from '../fixtures/slo'; import { ApmTransactionDurationTransformGenerator } from './apm_transaction_duration'; -import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; -const generator = new ApmTransactionDurationTransformGenerator(); -const spaceId = 'custom-space'; +const SPACE_ID = 'custom-space'; +const generator = new ApmTransactionDurationTransformGenerator(SPACE_ID, dataViewsService); describe('APM Transaction Duration Transform Generator', () => { it('returns the expected transform params with every specified indicator params', async () => { const slo = createSLO({ id: 'irrelevant', indicator: createAPMTransactionDurationIndicator() }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform).toMatchSnapshot(); }); @@ -31,7 +31,7 @@ describe('APM Transaction Duration Transform Generator', () => { id: 'irrelevant', indicator: createAPMTransactionDurationIndicator(), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform).toMatchSnapshot(); }); @@ -46,7 +46,7 @@ describe('APM Transaction Duration Transform Generator', () => { timesliceWindow: twoMinute(), }, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform).toMatchSnapshot(); }); @@ -60,7 +60,7 @@ describe('APM Transaction Duration Transform Generator', () => { transactionType: ALL_VALUE, }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); }); @@ -72,7 +72,7 @@ describe('APM Transaction Duration Transform Generator', () => { index, }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.index).toEqual(index); }); @@ -84,7 +84,7 @@ describe('APM Transaction Duration Transform Generator', () => { filter, }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); }); @@ -99,7 +99,7 @@ describe('APM Transaction Duration Transform Generator', () => { }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); expect(transform.pivot?.group_by).toMatchSnapshot(); @@ -115,7 +115,7 @@ describe('APM Transaction Duration Transform Generator', () => { }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); expect(transform.pivot?.group_by).toMatchSnapshot(); @@ -131,7 +131,7 @@ describe('APM Transaction Duration Transform Generator', () => { }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); expect(transform.pivot?.group_by).toMatchSnapshot(); @@ -147,7 +147,7 @@ describe('APM Transaction Duration Transform Generator', () => { }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); expect(transform.pivot?.group_by).toMatchSnapshot(); @@ -163,7 +163,7 @@ describe('APM Transaction Duration Transform Generator', () => { }, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); // @ts-ignore const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_duration.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_duration.ts index b349a5affece..d1f05605dab3 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_duration.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_duration.ts @@ -8,30 +8,29 @@ import { estypes } from '@elastic/elasticsearch'; import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; import { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { DataViewsService } from '@kbn/data-views-plugin/common'; import { ALL_VALUE, apmTransactionDurationIndicatorSchema, timeslicesBudgetingMethodSchema, } from '@kbn/slo-schema'; -import { DataViewsService } from '@kbn/data-views-plugin/common'; -import { getElasticsearchQueryOrThrow, TransformGenerator } from '.'; +import { TransformGenerator, getElasticsearchQueryOrThrow } from '.'; import { + SLO_DESTINATION_INDEX_NAME, getSLOPipelineId, getSLOTransformId, - SLO_DESTINATION_INDEX_NAME, } from '../../../common/constants'; import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_transform_template'; import { APMTransactionDurationIndicator, SLODefinition } from '../../domain/models'; import { InvalidTransformError } from '../../errors'; -import { parseIndex } from './common'; -import { getTimesliceTargetComparator, getFilterRange } from './common'; +import { getFilterRange, getTimesliceTargetComparator, parseIndex } from './common'; export class ApmTransactionDurationTransformGenerator extends TransformGenerator { - public async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService) { + super(spaceId, dataViewService); + } + + public async getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { if (!apmTransactionDurationIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -39,7 +38,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator return getSLOTransformTemplate( this.buildTransformId(slo), this.buildDescription(slo), - await this.buildSource(slo, slo.indicator, dataViewService), + await this.buildSource(slo, slo.indicator), this.buildDestination(slo), this.buildGroupBy(slo, slo.indicator), this.buildAggregations(slo, slo.indicator), @@ -75,11 +74,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator return this.buildCommonGroupBy(slo, '@timestamp', extraGroupByFields); } - private async buildSource( - slo: SLODefinition, - indicator: APMTransactionDurationIndicator, - dataViewService: DataViewsService - ) { + private async buildSource(slo: SLODefinition, indicator: APMTransactionDurationIndicator) { const queryFilter: estypes.QueryDslQueryContainer[] = [getFilterRange(slo, '@timestamp')]; if (indicator.params.service !== ALL_VALUE) { @@ -113,10 +108,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator }, }); } - const dataView = await this.getIndicatorDataView({ - dataViewService, - dataViewId: indicator.params.dataViewId, - }); + const dataView = await this.getIndicatorDataView(indicator.params.dataViewId); if (!!indicator.params.filter) { queryFilter.push(getElasticsearchQueryOrThrow(indicator.params.filter, dataView)); @@ -124,7 +116,7 @@ export class ApmTransactionDurationTransformGenerator extends TransformGenerator return { index: parseIndex(indicator.params.index), - runtime_mappings: this.buildCommonRuntimeMappings(slo, dataView), + runtime_mappings: this.buildCommonRuntimeMappings(dataView), query: { bool: { filter: [ diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_error_rate.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_error_rate.test.ts index 13c73443960a..6b71e37ec4b9 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_error_rate.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_error_rate.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; import { ALL_VALUE } from '@kbn/slo-schema'; import { oneMinute, twoMinute } from '../fixtures/duration'; import { @@ -13,10 +14,9 @@ import { createSLOWithTimeslicesBudgetingMethod, } from '../fixtures/slo'; import { ApmTransactionErrorRateTransformGenerator } from './apm_transaction_error_rate'; -import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; -const generator = new ApmTransactionErrorRateTransformGenerator(); -const spaceId = 'custom-space'; +const SPACE_ID = 'custom-space'; +const generator = new ApmTransactionErrorRateTransformGenerator(SPACE_ID, dataViewsService); describe('APM Transaction Error Rate Transform Generator', () => { it('returns the expected transform params with every specified indicator params', async () => { @@ -24,7 +24,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { id: 'irrelevant', indicator: createAPMTransactionErrorRateIndicator(), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform).toMatchSnapshot(); }); @@ -34,7 +34,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { id: 'irrelevant', indicator: createAPMTransactionErrorRateIndicator(), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform).toMatchSnapshot(); }); @@ -49,7 +49,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { timesliceWindow: twoMinute(), }, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform).toMatchSnapshot(); }); @@ -63,7 +63,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { transactionType: ALL_VALUE, }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); }); @@ -75,7 +75,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { index, }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.index).toEqual(index); }); @@ -87,7 +87,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { filter, }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); }); @@ -102,7 +102,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); expect(transform.pivot?.group_by).toMatchSnapshot(); @@ -118,7 +118,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); expect(transform.pivot?.group_by).toMatchSnapshot(); @@ -134,7 +134,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); expect(transform.pivot?.group_by).toMatchSnapshot(); @@ -150,7 +150,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { }), }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.source.query).toMatchSnapshot(); expect(transform.pivot?.group_by).toMatchSnapshot(); @@ -166,7 +166,7 @@ describe('APM Transaction Error Rate Transform Generator', () => { }, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); // @ts-ignore const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_error_rate.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_error_rate.ts index 3aa0d4507e8a..6adbd1d3eae9 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_error_rate.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/apm_transaction_error_rate.ts @@ -13,11 +13,11 @@ import { apmTransactionErrorRateIndicatorSchema, timeslicesBudgetingMethodSchema, } from '@kbn/slo-schema'; -import { getElasticsearchQueryOrThrow, TransformGenerator } from '.'; +import { TransformGenerator, getElasticsearchQueryOrThrow } from '.'; import { + SLO_DESTINATION_INDEX_NAME, getSLOPipelineId, getSLOTransformId, - SLO_DESTINATION_INDEX_NAME, } from '../../../common/constants'; import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_transform_template'; import { APMTransactionErrorRateIndicator, SLODefinition } from '../../domain/models'; @@ -25,11 +25,11 @@ import { InvalidTransformError } from '../../errors'; import { getFilterRange, getTimesliceTargetComparator, parseIndex } from './common'; export class ApmTransactionErrorRateTransformGenerator extends TransformGenerator { - public async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService) { + super(spaceId, dataViewService); + } + + public async getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { if (!apmTransactionErrorRateIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -37,7 +37,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato return getSLOTransformTemplate( this.buildTransformId(slo), this.buildDescription(slo), - await this.buildSource(slo, slo.indicator, dataViewService), + await this.buildSource(slo, slo.indicator), this.buildDestination(slo), this.buildGroupBy(slo, slo.indicator), this.buildAggregations(slo), @@ -73,11 +73,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato return this.buildCommonGroupBy(slo, '@timestamp', extraGroupByFields); } - private async buildSource( - slo: SLODefinition, - indicator: APMTransactionErrorRateIndicator, - dataViewService: DataViewsService - ) { + private async buildSource(slo: SLODefinition, indicator: APMTransactionErrorRateIndicator) { const queryFilter: estypes.QueryDslQueryContainer[] = [getFilterRange(slo, '@timestamp')]; if (indicator.params.service !== ALL_VALUE) { @@ -112,10 +108,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato }); } - const dataView = await this.getIndicatorDataView({ - dataViewService, - dataViewId: indicator.params.dataViewId, - }); + const dataView = await this.getIndicatorDataView(indicator.params.dataViewId); if (indicator.params.filter) { queryFilter.push(getElasticsearchQueryOrThrow(indicator.params.filter, dataView)); @@ -123,7 +116,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato return { index: parseIndex(indicator.params.index), - runtime_mappings: this.buildCommonRuntimeMappings(slo, dataView), + runtime_mappings: this.buildCommonRuntimeMappings(dataView), query: { bool: { filter: [ diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/histogram.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/histogram.test.ts index 2de75b8f7d86..5410efb048dc 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/histogram.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/histogram.test.ts @@ -14,8 +14,8 @@ import { import { HistogramTransformGenerator } from './histogram'; import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; -const generator = new HistogramTransformGenerator(); -const spaceId = 'custom-space'; +const SPACE_ID = 'custom-space'; +const generator = new HistogramTransformGenerator(SPACE_ID, dataViewsService); describe('Histogram Transform Generator', () => { describe('validation', () => { @@ -32,9 +32,7 @@ describe('Histogram Transform Generator', () => { }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL: foo:/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL: foo:/); }); it('throws when the total filter is invalid', async () => { @@ -48,24 +46,20 @@ describe('Histogram Transform Generator', () => { }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL: foo:/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL: foo:/); }); it('throws when the query_filter is invalid', async () => { const anSLO = createSLO({ indicator: createHistogramIndicator({ filter: '{ kql.query: invalid' }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL/); }); }); it('returns the expected transform params with every specified indicator params', async () => { const anSLO = createSLO({ id: 'irrelevant', indicator: createHistogramIndicator() }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -75,7 +69,7 @@ describe('Histogram Transform Generator', () => { id: 'irrelevant', indicator: createHistogramIndicator(), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -90,7 +84,7 @@ describe('Histogram Transform Generator', () => { timesliceWindow: twoMinute(), }, }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -99,7 +93,7 @@ describe('Histogram Transform Generator', () => { const anSLO = createSLO({ indicator: createHistogramIndicator({ filter: 'labels.groupId: group-4' }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.source.query).toMatchSnapshot(); }); @@ -108,7 +102,7 @@ describe('Histogram Transform Generator', () => { const anSLO = createSLO({ indicator: createHistogramIndicator({ index: 'my-own-index*' }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.source.index).toBe('my-own-index*'); }); @@ -119,7 +113,7 @@ describe('Histogram Transform Generator', () => { timestampField: 'my-date-field', }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.sync?.time?.field).toBe('my-date-field'); // @ts-ignore @@ -130,7 +124,7 @@ describe('Histogram Transform Generator', () => { const anSLO = createSLO({ indicator: createHistogramIndicator(), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); @@ -147,7 +141,7 @@ describe('Histogram Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); @@ -156,7 +150,7 @@ describe('Histogram Transform Generator', () => { const anSLO = createSLO({ indicator: createHistogramIndicator(), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); }); @@ -171,7 +165,7 @@ describe('Histogram Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); }); @@ -186,7 +180,7 @@ describe('Histogram Transform Generator', () => { }, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); // @ts-ignore const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/histogram.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/histogram.ts index b19f9a48e70f..805e18c9c31d 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/histogram.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/histogram.ts @@ -6,18 +6,17 @@ */ import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; +import { DataViewsService } from '@kbn/data-views-plugin/common'; import { HistogramIndicator, histogramIndicatorSchema, timeslicesBudgetingMethodSchema, } from '@kbn/slo-schema'; - -import { DataViewsService } from '@kbn/data-views-plugin/common'; -import { getElasticsearchQueryOrThrow, parseIndex, TransformGenerator } from '.'; +import { TransformGenerator, getElasticsearchQueryOrThrow, parseIndex } from '.'; import { + SLO_DESTINATION_INDEX_NAME, getSLOPipelineId, getSLOTransformId, - SLO_DESTINATION_INDEX_NAME, } from '../../../common/constants'; import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_transform_template'; import { SLODefinition } from '../../domain/models'; @@ -26,11 +25,11 @@ import { GetHistogramIndicatorAggregation } from '../aggregations'; import { getFilterRange, getTimesliceTargetComparator } from './common'; export class HistogramTransformGenerator extends TransformGenerator { - public async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService) { + super(spaceId, dataViewService); + } + + public async getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { if (!histogramIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -38,7 +37,7 @@ export class HistogramTransformGenerator extends TransformGenerator { return getSLOTransformTemplate( this.buildTransformId(slo), this.buildDescription(slo), - await this.buildSource(slo, slo.indicator, dataViewService), + await this.buildSource(slo, slo.indicator), this.buildDestination(slo), this.buildCommonGroupBy(slo, slo.indicator.params.timestampField), this.buildAggregations(slo, slo.indicator), @@ -51,19 +50,12 @@ export class HistogramTransformGenerator extends TransformGenerator { return getSLOTransformId(slo.id, slo.revision); } - private async buildSource( - slo: SLODefinition, - indicator: HistogramIndicator, - dataViewService: DataViewsService - ) { - const dataView = await this.getIndicatorDataView({ - dataViewService, - dataViewId: indicator.params.index, - }); + private async buildSource(slo: SLODefinition, indicator: HistogramIndicator) { + const dataView = await this.getIndicatorDataView(indicator.params.dataViewId); return { index: parseIndex(indicator.params.index), - runtime_mappings: this.buildCommonRuntimeMappings(slo, dataView), + runtime_mappings: this.buildCommonRuntimeMappings(dataView), query: { bool: { filter: [ diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/index.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/index.ts index c58de27e9b98..9b68c19692ee 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/index.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/index.ts @@ -14,3 +14,4 @@ export * from './metric_custom'; export * from './histogram'; export * from './timeslice_metric'; export * from './common'; +export * from './transform_generators_factory'; diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/kql_custom.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/kql_custom.test.ts index c41e0d4b3df5..e25aefdb3be1 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/kql_custom.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/kql_custom.test.ts @@ -14,8 +14,8 @@ import { import { KQLCustomTransformGenerator } from './kql_custom'; import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; -const generator = new KQLCustomTransformGenerator(); -const spaceId = 'custom-space'; +const SPACE_ID = 'custom-space'; +const generator = new KQLCustomTransformGenerator(SPACE_ID, dataViewsService); describe('KQL Custom Transform Generator', () => { describe('validation', () => { @@ -23,31 +23,25 @@ describe('KQL Custom Transform Generator', () => { const anSLO = createSLO({ indicator: createKQLCustomIndicator({ good: '{ kql.query: invalid' }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL/); }); it('throws when the KQL denominator is invalid', async () => { const anSLO = createSLO({ indicator: createKQLCustomIndicator({ total: '{ kql.query: invalid' }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL/); }); it('throws when the KQL query_filter is invalid', async () => { const anSLO = createSLO({ indicator: createKQLCustomIndicator({ filter: '{ kql.query: invalid' }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL/); }); }); it('returns the expected transform params with every specified indicator params', async () => { const anSLO = createSLO({ id: 'irrelevant', indicator: createKQLCustomIndicator() }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -57,7 +51,7 @@ describe('KQL Custom Transform Generator', () => { id: 'irrelevant', indicator: createKQLCustomIndicator(), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -72,7 +66,7 @@ describe('KQL Custom Transform Generator', () => { timesliceWindow: twoMinute(), }, }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -81,7 +75,7 @@ describe('KQL Custom Transform Generator', () => { const anSLO = createSLO({ indicator: createKQLCustomIndicator({ filter: 'labels.groupId: group-4' }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.source.query).toMatchSnapshot(); }); @@ -90,7 +84,7 @@ describe('KQL Custom Transform Generator', () => { const anSLO = createSLO({ indicator: createKQLCustomIndicator({ index: 'my-own-index*' }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.source.index).toBe('my-own-index*'); }); @@ -101,7 +95,7 @@ describe('KQL Custom Transform Generator', () => { timestampField: 'my-date-field', }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.sync?.time?.field).toBe('my-date-field'); // @ts-ignore @@ -114,7 +108,7 @@ describe('KQL Custom Transform Generator', () => { good: 'latency < 400 and (http.status_code: 2xx or http.status_code: 3xx or http.status_code: 4xx)', }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); @@ -125,7 +119,7 @@ describe('KQL Custom Transform Generator', () => { total: 'http.status_code: *', }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); }); @@ -140,7 +134,7 @@ describe('KQL Custom Transform Generator', () => { }, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); // @ts-ignore const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/kql_custom.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/kql_custom.ts index 0082e13968c8..61238c82ab60 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/kql_custom.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/kql_custom.ts @@ -6,14 +6,13 @@ */ import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; -import { kqlCustomIndicatorSchema, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema'; - import { DataViewsService } from '@kbn/data-views-plugin/common'; -import { getElasticsearchQueryOrThrow, parseIndex, TransformGenerator } from '.'; +import { kqlCustomIndicatorSchema, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema'; +import { TransformGenerator, getElasticsearchQueryOrThrow, parseIndex } from '.'; import { + SLO_DESTINATION_INDEX_NAME, getSLOPipelineId, getSLOTransformId, - SLO_DESTINATION_INDEX_NAME, } from '../../../common/constants'; import { getSLOTransformTemplate } from '../../assets/transform_templates/slo_transform_template'; import { KQLCustomIndicator, SLODefinition } from '../../domain/models'; @@ -21,11 +20,11 @@ import { InvalidTransformError } from '../../errors'; import { getFilterRange, getTimesliceTargetComparator } from './common'; export class KQLCustomTransformGenerator extends TransformGenerator { - public async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService) { + super(spaceId, dataViewService); + } + + public async getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { if (!kqlCustomIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -33,7 +32,7 @@ export class KQLCustomTransformGenerator extends TransformGenerator { return getSLOTransformTemplate( this.buildTransformId(slo), this.buildDescription(slo), - await this.buildSource(slo, slo.indicator, dataViewService), + await this.buildSource(slo, slo.indicator), this.buildDestination(slo), this.buildCommonGroupBy(slo, slo.indicator.params.timestampField), this.buildAggregations(slo, slo.indicator), @@ -46,18 +45,11 @@ export class KQLCustomTransformGenerator extends TransformGenerator { return getSLOTransformId(slo.id, slo.revision); } - private async buildSource( - slo: SLODefinition, - indicator: KQLCustomIndicator, - dataViewService: DataViewsService - ) { - const dataView = await this.getIndicatorDataView({ - dataViewService, - dataViewId: indicator.params.dataViewId, - }); + private async buildSource(slo: SLODefinition, indicator: KQLCustomIndicator) { + const dataView = await this.getIndicatorDataView(indicator.params.dataViewId); return { index: parseIndex(indicator.params.index), - runtime_mappings: this.buildCommonRuntimeMappings(slo, dataView), + runtime_mappings: this.buildCommonRuntimeMappings(dataView), query: { bool: { filter: [ diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/metric_custom.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/metric_custom.test.ts index 85da6de832c9..b725d77af63d 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/metric_custom.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/metric_custom.test.ts @@ -14,8 +14,8 @@ import { import { MetricCustomTransformGenerator } from './metric_custom'; import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; -const generator = new MetricCustomTransformGenerator(); -const spaceId = 'custom-space'; +const SPACE_ID = 'custom-space'; +const generator = new MetricCustomTransformGenerator(SPACE_ID, dataViewsService); describe('Metric Custom Transform Generator', () => { describe('validation', () => { @@ -28,9 +28,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid equation/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid equation/); }); it('throws when the good filter is invalid', async () => { const anSLO = createSLO({ @@ -41,9 +39,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL: foo:/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL: foo:/); }); it('throws when the total equation is invalid', async () => { const anSLO = createSLO({ @@ -54,9 +50,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid equation/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid equation/); }); it('throws when the total filter is invalid', async () => { const anSLO = createSLO({ @@ -67,23 +61,19 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - await expect(() => - generator.getTransformParams(anSLO, spaceId, dataViewsService) - ).rejects.toThrow(/Invalid KQL: foo:/); + await expect(() => generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL: foo:/); }); it('throws when the query_filter is invalid', async () => { const anSLO = createSLO({ indicator: createMetricCustomIndicator({ filter: '{ kql.query: invalid' }), }); - await expect(() => - generator.getTransformParams(anSLO, spaceId, dataViewsService) - ).rejects.toThrow(/Invalid KQL/); + await expect(() => generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL/); }); }); it('returns the expected transform params with every specified indicator params', async () => { const anSLO = createSLO({ id: 'irrelevant', indicator: createMetricCustomIndicator() }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -93,7 +83,7 @@ describe('Metric Custom Transform Generator', () => { id: 'irrelevant', indicator: createMetricCustomIndicator(), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -102,7 +92,7 @@ describe('Metric Custom Transform Generator', () => { const anSLO = createSLO({ indicator: createMetricCustomIndicator({ filter: 'labels.groupId: group-4' }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.source.query).toMatchSnapshot(); }); @@ -111,7 +101,7 @@ describe('Metric Custom Transform Generator', () => { const anSLO = createSLO({ indicator: createMetricCustomIndicator({ index: 'my-own-index*' }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.source.index).toBe('my-own-index*'); }); @@ -122,7 +112,7 @@ describe('Metric Custom Transform Generator', () => { timestampField: 'my-date-field', }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.sync?.time?.field).toBe('my-date-field'); // @ts-ignore @@ -138,7 +128,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); @@ -152,7 +142,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); @@ -168,7 +158,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); @@ -182,7 +172,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); }); @@ -196,7 +186,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); }); @@ -210,7 +200,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); }); @@ -224,7 +214,7 @@ describe('Metric Custom Transform Generator', () => { }, }), }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); }); @@ -239,7 +229,7 @@ describe('Metric Custom Transform Generator', () => { }, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); // @ts-ignore const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/metric_custom.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/metric_custom.ts index e96f252d5ed8..f2259955bdfb 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/metric_custom.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/metric_custom.ts @@ -24,11 +24,11 @@ import { getFilterRange, getTimesliceTargetComparator } from './common'; export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; export class MetricCustomTransformGenerator extends TransformGenerator { - public async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService) { + super(spaceId, dataViewService); + } + + public async getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { if (!metricCustomIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -36,7 +36,7 @@ export class MetricCustomTransformGenerator extends TransformGenerator { return getSLOTransformTemplate( this.buildTransformId(slo), this.buildDescription(slo), - await this.buildSource(slo, slo.indicator, dataViewService), + await this.buildSource(slo, slo.indicator), this.buildDestination(slo), this.buildCommonGroupBy(slo, slo.indicator.params.timestampField), this.buildAggregations(slo, slo.indicator), @@ -49,18 +49,11 @@ export class MetricCustomTransformGenerator extends TransformGenerator { return getSLOTransformId(slo.id, slo.revision); } - private async buildSource( - slo: SLODefinition, - indicator: MetricCustomIndicator, - dataViewService: DataViewsService - ) { - const dataView = await this.getIndicatorDataView({ - dataViewService, - dataViewId: indicator.params.dataViewId, - }); + private async buildSource(slo: SLODefinition, indicator: MetricCustomIndicator) { + const dataView = await this.getIndicatorDataView(indicator.params.dataViewId); return { index: parseIndex(indicator.params.index), - runtime_mappings: this.buildCommonRuntimeMappings(slo, dataView), + runtime_mappings: this.buildCommonRuntimeMappings(dataView), query: { bool: { filter: [ diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts index 565a0d56d1ff..cccb1c9eba3e 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts @@ -12,286 +12,330 @@ import { twoMinute } from '../fixtures/duration'; import { createSLO, createSyntheticsAvailabilityIndicator } from '../fixtures/slo'; import { SyntheticsAvailabilityTransformGenerator } from './synthetics_availability'; -const generator = new SyntheticsAvailabilityTransformGenerator(); +const SPACE_ID = 'custom-space'; describe('Synthetics Availability Transform Generator', () => { - const spaceId = 'custom-space'; - - it('returns the expected transform params', async () => { - const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator() }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); - - expect(transform).toMatchSnapshot(); - expect(transform.source.query?.bool?.filter).toContainEqual({ - term: { - 'summary.final_attempt': true, - }, - }); - }); - - it('groups by config id and observer.name when using default groupings', async () => { - const slo = createSLO({ - id: 'irrelevant', - indicator: createSyntheticsAvailabilityIndicator(), - }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); - - expect(transform.pivot?.group_by).toEqual( - expect.objectContaining({ - 'monitor.config_id': { - terms: { - field: 'config_id', - }, - }, - 'observer.name': { - terms: { - field: 'observer.name', - }, - }, - }) + describe('when serverless is disabled', () => { + const generator = new SyntheticsAvailabilityTransformGenerator( + SPACE_ID, + dataViewsService, + false ); - }); - it('does not include config id and observer.name when using non default groupings', async () => { - const slo = createSLO({ - id: 'irrelevant', - indicator: createSyntheticsAvailabilityIndicator(), - groupBy: ['host.name'], - }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); - - expect(transform.pivot?.group_by).not.toEqual( - expect.objectContaining({ - 'monitor.config_id': { - terms: { - field: 'config_id', - }, - }, - 'observer.name': { - terms: { - field: 'observer.name', - }, - }, - }) - ); + it('returns the expected transform params', async () => { + const slo = createSLO({ + id: 'irrelevant', + indicator: createSyntheticsAvailabilityIndicator(), + }); + const transform = await generator.getTransformParams(slo); - expect(transform.pivot?.group_by).toEqual( - expect.objectContaining({ - 'slo.groupings.host.name': { - terms: { - field: 'host.name', - }, + expect(transform).toMatchSnapshot(); + expect(transform.source.query?.bool?.filter).toContainEqual({ + term: { + 'summary.final_attempt': true, }, - }) - ); - }); + }); + }); - it.each([[[]], [[ALL_VALUE]]])( - 'adds observer.geo.name and monitor.name to groupings key by default, multi group by', - async (groupBy) => { + it('groups by config id and observer.name when using default groupings', async () => { const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator(), - groupBy, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); expect(transform.pivot?.group_by).toEqual( expect.objectContaining({ - 'slo.groupings.monitor.name': { + 'monitor.config_id': { terms: { - field: 'monitor.name', + field: 'config_id', }, }, - 'slo.groupings.observer.geo.name': { + 'observer.name': { terms: { - field: 'observer.geo.name', + field: 'observer.name', }, }, }) ); - } - ); + }); - it.each([[''], [ALL_VALUE]])( - 'adds observer.geo.name and monitor.name to groupings key by default, single group by', - async (groupBy) => { + it('does not include config id and observer.name when using non default groupings', async () => { const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator(), - groupBy, + groupBy: ['host.name'], }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); - expect(transform.pivot?.group_by).toEqual( + expect(transform.pivot?.group_by).not.toEqual( expect.objectContaining({ - 'slo.groupings.monitor.name': { + 'monitor.config_id': { terms: { - field: 'monitor.name', + field: 'config_id', }, }, - 'slo.groupings.observer.geo.name': { + 'observer.name': { terms: { - field: 'observer.geo.name', + field: 'observer.name', }, }, }) ); - } - ); - - it.each([['host.name'], [['host.name']]])('handles custom groupBy', async (groupBy) => { - const slo = createSLO({ - id: 'irrelevant', - indicator: createSyntheticsAvailabilityIndicator(), - groupBy, - }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); - expect(transform.pivot?.group_by).toEqual( - expect.objectContaining({ - 'slo.groupings.host.name': { - terms: { - field: 'host.name', + expect(transform.pivot?.group_by).toEqual( + expect.objectContaining({ + 'slo.groupings.host.name': { + terms: { + field: 'host.name', + }, }, - }, - }) + }) + ); + }); + + it.each([[[]], [[ALL_VALUE]]])( + 'adds observer.geo.name and monitor.name to groupings key by default, multi group by', + async (groupBy) => { + const slo = createSLO({ + id: 'irrelevant', + indicator: createSyntheticsAvailabilityIndicator(), + groupBy, + }); + const transform = await generator.getTransformParams(slo); + + expect(transform.pivot?.group_by).toEqual( + expect.objectContaining({ + 'slo.groupings.monitor.name': { + terms: { + field: 'monitor.name', + }, + }, + 'slo.groupings.observer.geo.name': { + terms: { + field: 'observer.geo.name', + }, + }, + }) + ); + } ); - }); - it('filters by summary.final_attempt', async () => { - const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator() }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + it.each([[''], [ALL_VALUE]])( + 'adds observer.geo.name and monitor.name to groupings key by default, single group by', + async (groupBy) => { + const slo = createSLO({ + id: 'irrelevant', + indicator: createSyntheticsAvailabilityIndicator(), + groupBy, + }); + const transform = await generator.getTransformParams(slo); - expect(transform.source.query?.bool?.filter).toContainEqual({ - term: { - 'summary.final_attempt': true, - }, - }); - }); + expect(transform.pivot?.group_by).toEqual( + expect.objectContaining({ + 'slo.groupings.monitor.name': { + terms: { + field: 'monitor.name', + }, + }, + 'slo.groupings.observer.geo.name': { + terms: { + field: 'observer.geo.name', + }, + }, + }) + ); + } + ); + + it.each([[['host.name']], [['host.name', 'host.region']]])( + 'handles custom groupBy', + async (groupBy) => { + const slo = createSLO({ + id: 'irrelevant', + indicator: createSyntheticsAvailabilityIndicator(), + groupBy, + }); + const transform = await generator.getTransformParams(slo); + + expect(transform.pivot?.group_by).toEqual( + expect.objectContaining({ + 'slo.groupings.host.name': { + terms: { + field: 'host.name', + }, + }, + }) + ); + } + ); - it('adds tag filters', async () => { - const tags = [ - { value: 'tag-1', label: 'tag1' }, - { value: 'tag-2', label: 'tag2' }, - ]; - const indicator = createSyntheticsAvailabilityIndicator(); - const slo = createSLO({ - id: 'irrelevant', - indicator: { - ...indicator, - params: { - ...indicator.params, - tags, + it('filters by summary.final_attempt', async () => { + const slo = createSLO({ + id: 'irrelevant', + indicator: createSyntheticsAvailabilityIndicator(), + }); + const transform = await generator.getTransformParams(slo); + + expect(transform.source.query?.bool?.filter).toContainEqual({ + term: { + 'summary.final_attempt': true, }, - } as SLODefinition['indicator'], + }); }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); - expect(transform.source.query?.bool?.filter).toContainEqual({ - terms: { - tags: ['tag-1', 'tag-2'], - }, - }); - expect(transform.pivot?.group_by?.tags).toEqual({ - terms: { - field: 'tags', - }, - }); - }); + it('adds tag filters', async () => { + const tags = [ + { value: 'tag-1', label: 'tag1' }, + { value: 'tag-2', label: 'tag2' }, + ]; + const indicator = createSyntheticsAvailabilityIndicator(); + const slo = createSLO({ + id: 'irrelevant', + indicator: { + ...indicator, + params: { + ...indicator.params, + tags, + }, + } as SLODefinition['indicator'], + }); + const transform = await generator.getTransformParams(slo); - it('adds monitorId filter', async () => { - const monitorIds = [ - { value: 'id-1', label: 'Monitor name 1' }, - { value: 'id-2', label: 'Monitor name 2' }, - ]; - const indicator = createSyntheticsAvailabilityIndicator(); - const slo = createSLO({ - id: 'irrelevant', - indicator: { - ...indicator, - params: { - ...indicator.params, - monitorIds, + expect(transform.source.query?.bool?.filter).toContainEqual({ + terms: { + tags: ['tag-1', 'tag-2'], }, - } as SLODefinition['indicator'], + }); + expect(transform.pivot?.group_by?.tags).toEqual({ + terms: { + field: 'tags', + }, + }); }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); - expect(transform.source.query?.bool?.filter).toContainEqual({ - terms: { - 'monitor.id': ['id-1', 'id-2'], - }, - }); - expect(transform.pivot?.group_by?.['monitor.id']).toEqual({ - terms: { - field: 'monitor.id', - }, - }); - }); + it('adds monitorId filter', async () => { + const monitorIds = [ + { value: 'id-1', label: 'Monitor name 1' }, + { value: 'id-2', label: 'Monitor name 2' }, + ]; + const indicator = createSyntheticsAvailabilityIndicator(); + const slo = createSLO({ + id: 'irrelevant', + indicator: { + ...indicator, + params: { + ...indicator.params, + monitorIds, + }, + } as SLODefinition['indicator'], + }); + const transform = await generator.getTransformParams(slo); - it('adds project id filter', async () => { - const projects = [ - { value: 'id-1', label: 'Project name 1' }, - { value: 'id-2', label: 'Project name 2' }, - ]; - const indicator = createSyntheticsAvailabilityIndicator(); - const slo = createSLO({ - id: 'irrelevant', - indicator: { - ...indicator, - params: { - ...indicator.params, - projects, + expect(transform.source.query?.bool?.filter).toContainEqual({ + terms: { + 'monitor.id': ['id-1', 'id-2'], + }, + }); + expect(transform.pivot?.group_by?.['monitor.id']).toEqual({ + terms: { + field: 'monitor.id', }, - } as SLODefinition['indicator'], + }); }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); - expect(transform.source.query?.bool?.filter).toContainEqual({ - terms: { - 'monitor.project.id': ['id-1', 'id-2'], - }, + it('adds project id filter', async () => { + const projects = [ + { value: 'id-1', label: 'Project name 1' }, + { value: 'id-2', label: 'Project name 2' }, + ]; + const indicator = createSyntheticsAvailabilityIndicator(); + const slo = createSLO({ + id: 'irrelevant', + indicator: { + ...indicator, + params: { + ...indicator.params, + projects, + }, + } as SLODefinition['indicator'], + }); + const transform = await generator.getTransformParams(slo); + + expect(transform.source.query?.bool?.filter).toContainEqual({ + terms: { + 'monitor.project.id': ['id-1', 'id-2'], + }, + }); + expect(transform.pivot?.group_by?.['monitor.project.id']).toEqual({ + terms: { + field: 'monitor.project.id', + }, + }); }); - expect(transform.pivot?.group_by?.['monitor.project.id']).toEqual({ - terms: { - field: 'monitor.project.id', - }, + + it('filters by space', async () => { + const slo = createSLO({ + id: 'irrelevant', + indicator: createSyntheticsAvailabilityIndicator(), + }); + const transform = await generator.getTransformParams(slo); + + expect(transform.source.query?.bool?.filter).toContainEqual({ + term: { + 'meta.space_id': SPACE_ID, + }, + }); }); - }); - it('filters by space', async () => { - const slo = createSLO({ id: 'irrelevant', indicator: createSyntheticsAvailabilityIndicator() }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + it("overrides the range filter when 'preventInitialBackfill' is true", async () => { + const slo = createSLO({ + indicator: createSyntheticsAvailabilityIndicator(), + settings: { + frequency: twoMinute(), + syncDelay: twoMinute(), + preventInitialBackfill: true, + }, + }); + + const transform = await generator.getTransformParams(slo); + + // @ts-ignore + const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); - expect(transform.source.query?.bool?.filter).toContainEqual({ - term: { - 'meta.space_id': spaceId, - }, + expect(rangeFilter).toEqual({ + range: { + '@timestamp': { + gte: 'now-300s/m', // 2m + 2m + 60s + }, + }, + }); }); - }); - it("overrides the range filter when 'preventInitialBackfill' is true", async () => { - const slo = createSLO({ - indicator: createSyntheticsAvailabilityIndicator(), - settings: { - frequency: twoMinute(), - syncDelay: twoMinute(), - preventInitialBackfill: true, - }, + it("uses the 'event.ingested' as syncField", async () => { + const slo = createSLO({ + indicator: createSyntheticsAvailabilityIndicator(), + }); + const transform = await generator.getTransformParams(slo); + + expect(transform.sync?.time?.field).toEqual('event.ingested'); }); + }); - const transform = await generator.getTransformParams(slo, 'default', dataViewsService); + describe('when serverless is enabled', () => { + const generator = new SyntheticsAvailabilityTransformGenerator( + SPACE_ID, + dataViewsService, + true + ); - // @ts-ignore - const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); + it("overrides the syncField with '@timestamp'", async () => { + const slo = createSLO({ + indicator: createSyntheticsAvailabilityIndicator(), + }); + const transform = await generator.getTransformParams(slo); - expect(rangeFilter).toEqual({ - range: { - '@timestamp': { - gte: 'now-300s/m', // 2m + 2m + 60s - }, - }, + expect(transform.sync?.time?.field).toEqual('@timestamp'); }); }); }); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.ts index e15c1d09a204..65fda9c3fc22 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.ts @@ -28,11 +28,11 @@ import { InvalidTransformError } from '../../errors'; import { getFilterRange } from './common'; export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator { - public async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService, isServerless: boolean) { + super(spaceId, dataViewService, isServerless); + } + + public async getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { if (!syntheticsAvailabilityIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -40,11 +40,11 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator return getSLOTransformTemplate( this.buildTransformId(slo), this.buildDescription(slo), - await this.buildSource(slo, slo.indicator, spaceId, dataViewService), + await this.buildSource(slo, slo.indicator), this.buildDestination(slo), this.buildGroupBy(slo, slo.indicator), this.buildAggregations(slo), - this.buildSettings(slo, 'event.ingested'), + this.buildSettings(slo, this.isServerless ? '@timestamp' : 'event.ingested'), slo ); } @@ -56,7 +56,7 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator private buildGroupBy(slo: SLODefinition, indicator: SyntheticsAvailabilityIndicator) { // These are the group by fields that will be used in `groupings` key // in the summary and rollup documents. For Synthetics, we want to use the - // user-readible `monitor.name` and `observer.geo.name` fields by default, + // user-readable `monitor.name` and `observer.geo.name` fields by default, // unless otherwise specified by the user. const flattenedGroupBy = [slo.groupBy].flat().filter((value) => !!value); const groupings = @@ -107,15 +107,10 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator ); } - private async buildSource( - slo: SLODefinition, - indicator: SyntheticsAvailabilityIndicator, - spaceId: string, - dataViewService: DataViewsService - ) { + private async buildSource(slo: SLODefinition, indicator: SyntheticsAvailabilityIndicator) { const queryFilter: estypes.QueryDslQueryContainer[] = [ { term: { 'summary.final_attempt': true } }, - { term: { 'meta.space_id': spaceId } }, + { term: { 'meta.space_id': this.spaceId } }, getFilterRange(slo, '@timestamp'), ]; const { monitorIds, tags, projects } = buildParamValues({ @@ -152,14 +147,11 @@ export class SyntheticsAvailabilityTransformGenerator extends TransformGenerator queryFilter.push(getElasticsearchQueryOrThrow(indicator.params.filter)); } - const dataView = await this.getIndicatorDataView({ - dataViewService, - dataViewId: indicator.params.dataViewId, - }); + const dataView = await this.getIndicatorDataView(indicator.params.dataViewId); return { index: SYNTHETICS_INDEX_PATTERN, - runtime_mappings: this.buildCommonRuntimeMappings(slo, dataView), + runtime_mappings: this.buildCommonRuntimeMappings(dataView), query: { bool: { filter: queryFilter, diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/timeslice_metric.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/timeslice_metric.test.ts index 02c69f38d670..c8fd371479c2 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/timeslice_metric.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/timeslice_metric.test.ts @@ -5,18 +5,17 @@ * 2.0. */ +import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; import { twoMinute } from '../fixtures/duration'; import { - createTimesliceMetricIndicator, - createSLOWithTimeslicesBudgetingMethod, createSLO, + createSLOWithTimeslicesBudgetingMethod, + createTimesliceMetricIndicator, } from '../fixtures/slo'; import { TimesliceMetricTransformGenerator } from './timeslice_metric'; -import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; - -const generator = new TimesliceMetricTransformGenerator(); -const spaceId = 'custom-space'; +const SPACE_ID = 'custom-space'; +const generator = new TimesliceMetricTransformGenerator(SPACE_ID, dataViewsService); const everythingIndicator = createTimesliceMetricIndicator( [ { name: 'A', aggregation: 'avg', field: 'test.field', filter: 'test.category: "test"' }, @@ -38,7 +37,7 @@ describe('Timeslice Metric Transform Generator', () => { '(A / 200) + A' ), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( + await expect(generator.getTransformParams(anSLO)).rejects.toThrow( 'The sli.metric.timeslice indicator MUST have a timeslice budgeting method.' ); }); @@ -49,9 +48,7 @@ describe('Timeslice Metric Transform Generator', () => { '(a / 200) + A' ), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid equation/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid equation/); }); it('throws when the metric filter is invalid', async () => { const anSLO = createSLOWithTimeslicesBudgetingMethod({ @@ -60,9 +57,7 @@ describe('Timeslice Metric Transform Generator', () => { '(A / 200) + A' ), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL: test:/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL: test:/); }); it('throws when the query_filter is invalid', async () => { const anSLO = createSLOWithTimeslicesBudgetingMethod({ @@ -72,9 +67,7 @@ describe('Timeslice Metric Transform Generator', () => { 'test:' ), }); - await expect(generator.getTransformParams(anSLO, spaceId, dataViewsService)).rejects.toThrow( - /Invalid KQL/ - ); + await expect(generator.getTransformParams(anSLO)).rejects.toThrow(/Invalid KQL/); }); }); @@ -83,7 +76,7 @@ describe('Timeslice Metric Transform Generator', () => { id: 'irrelevant', indicator: everythingIndicator, }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -93,7 +86,7 @@ describe('Timeslice Metric Transform Generator', () => { id: 'irrelevant', indicator: everythingIndicator, }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform).toMatchSnapshot(); }); @@ -102,7 +95,7 @@ describe('Timeslice Metric Transform Generator', () => { const anSLO = createSLOWithTimeslicesBudgetingMethod({ indicator: everythingIndicator, }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.source.query).toMatchSnapshot(); }); @@ -114,7 +107,7 @@ describe('Timeslice Metric Transform Generator', () => { params: { ...everythingIndicator.params, index: 'my-own-index*' }, }, }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.source.index).toBe('my-own-index*'); }); @@ -126,7 +119,7 @@ describe('Timeslice Metric Transform Generator', () => { params: { ...everythingIndicator.params, timestampField: 'my-date-field' }, }, }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.sync?.time?.field).toBe('my-date-field'); // @ts-ignore @@ -137,7 +130,7 @@ describe('Timeslice Metric Transform Generator', () => { const anSLO = createSLOWithTimeslicesBudgetingMethod({ indicator: everythingIndicator, }); - const transform = await generator.getTransformParams(anSLO, spaceId, dataViewsService); + const transform = await generator.getTransformParams(anSLO); expect(transform.pivot!.aggregations!._metric).toEqual({ bucket_script: { @@ -185,7 +178,7 @@ describe('Timeslice Metric Transform Generator', () => { }, }); - const transform = await generator.getTransformParams(slo, spaceId, dataViewsService); + const transform = await generator.getTransformParams(slo); // @ts-ignore const rangeFilter = transform.source.query.bool.filter.find((f) => 'range' in f); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/timeslice_metric.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/timeslice_metric.ts index b0e32f7094a2..e2f305e68fee 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/timeslice_metric.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/timeslice_metric.ts @@ -28,11 +28,11 @@ import { getFilterRange } from './common'; const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; export class TimesliceMetricTransformGenerator extends TransformGenerator { - public async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService) { + super(spaceId, dataViewService); + } + + public async getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { if (!timesliceMetricIndicatorSchema.is(slo.indicator)) { throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } @@ -40,7 +40,7 @@ export class TimesliceMetricTransformGenerator extends TransformGenerator { return getSLOTransformTemplate( this.buildTransformId(slo), this.buildDescription(slo), - await this.buildSource(slo, slo.indicator, dataViewService), + await this.buildSource(slo, slo.indicator), this.buildDestination(slo), this.buildCommonGroupBy(slo, slo.indicator.params.timestampField), this.buildAggregations(slo, slo.indicator), @@ -53,18 +53,12 @@ export class TimesliceMetricTransformGenerator extends TransformGenerator { return getSLOTransformId(slo.id, slo.revision); } - private async buildSource( - slo: SLODefinition, - indicator: TimesliceMetricIndicator, - dataViewService: DataViewsService - ) { - const dataView = await this.getIndicatorDataView({ - dataViewService, - dataViewId: indicator.params.index, - }); + private async buildSource(slo: SLODefinition, indicator: TimesliceMetricIndicator) { + const dataView = await this.getIndicatorDataView(indicator.params.dataViewId); + return { index: parseIndex(indicator.params.index), - runtime_mappings: this.buildCommonRuntimeMappings(slo, dataView), + runtime_mappings: this.buildCommonRuntimeMappings(dataView), query: { bool: { filter: [ diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.test.ts index 9f07c6cfb5af..e70d406d7539 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.test.ts @@ -7,50 +7,42 @@ import { createAPMTransactionErrorRateIndicator, createSLO } from '../fixtures/slo'; import { ApmTransactionErrorRateTransformGenerator } from './apm_transaction_error_rate'; +import { dataViewsService } from '@kbn/data-views-plugin/server/mocks'; -const generator = new ApmTransactionErrorRateTransformGenerator(); +const generator = new ApmTransactionErrorRateTransformGenerator('my-space-id', dataViewsService); describe('Transform Generator', () => { - it('builds empty runtime mappings without group by', async () => { - const slo = createSLO({ - id: 'irrelevant', - indicator: createAPMTransactionErrorRateIndicator(), - }); - const commonRuntime = generator.buildCommonRuntimeMappings(slo); - expect(commonRuntime).toMatchSnapshot(); - - const commonGroupBy = generator.buildCommonGroupBy(slo); - expect(commonGroupBy).toMatchSnapshot(); - }); - - it.each(['example', ['example']])( - 'builds common runtime mappings and group by with single group by', - async (groupBy) => { - const indicator = createAPMTransactionErrorRateIndicator(); + describe('buildCommonGroupBy', () => { + it('builds empty runtime mappings without group by', async () => { const slo = createSLO({ id: 'irrelevant', - groupBy, - indicator, + indicator: createAPMTransactionErrorRateIndicator(), }); - const commonRuntime = generator.buildCommonRuntimeMappings(slo); - expect(commonRuntime).toMatchSnapshot(); const commonGroupBy = generator.buildCommonGroupBy(slo); expect(commonGroupBy).toMatchSnapshot(); - } - ); - - it('builds common runtime mappings without multi group by', async () => { - const indicator = createAPMTransactionErrorRateIndicator(); - const slo = createSLO({ - id: 'irrelevant', - groupBy: ['example1', 'example2'], - indicator, }); - const commonRuntime = generator.buildCommonRuntimeMappings(slo); - expect(commonRuntime).toMatchSnapshot(); - const commonGroupBy = generator.buildCommonGroupBy(slo); - expect(commonGroupBy).toMatchSnapshot(); + it.each(['example', ['example'], ['example1', 'example2']])( + 'builds common groupBy with single group by', + async (groupBy) => { + const indicator = createAPMTransactionErrorRateIndicator(); + const slo = createSLO({ + id: 'irrelevant', + groupBy, + indicator, + }); + + const commonGroupBy = generator.buildCommonGroupBy(slo); + expect(commonGroupBy).toMatchSnapshot(); + } + ); + }); + + describe('buildCommonRuntimeMappings', () => { + it('builds empty runtime mappings without data view', async () => { + const runtimeMappings = generator.buildCommonRuntimeMappings(); + expect(runtimeMappings).toEqual({}); + }); }); }); diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.ts index 8ae6eeb52c9b..6c44471fd656 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generator.ts @@ -9,22 +9,22 @@ import { MappingRuntimeFields, TransformPutTransformRequest, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ALL_VALUE, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema'; import { DataView, DataViewsService } from '@kbn/data-views-plugin/common'; +import { ALL_VALUE, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema'; import { TransformSettings } from '../../assets/transform_templates/slo_transform_template'; import { SLODefinition } from '../../domain/models'; export abstract class TransformGenerator { - public abstract getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest>; + constructor( + protected spaceId: string, + protected dataViewService: DataViewsService, + protected isServerless: boolean = false + ) {} - public buildCommonRuntimeMappings(slo: SLODefinition, dataView?: DataView): MappingRuntimeFields { - return { - ...(dataView?.getRuntimeMappings?.() ?? {}), - }; + public abstract getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest>; + + public buildCommonRuntimeMappings(dataView?: DataView): MappingRuntimeFields { + return dataView?.getRuntimeMappings?.() ?? {}; } public buildDescription(slo: SLODefinition): string { @@ -70,17 +70,11 @@ export abstract class TransformGenerator { }; } - public async getIndicatorDataView({ - dataViewService, - dataViewId, - }: { - dataViewService: DataViewsService; - dataViewId?: string; - }): Promise<DataView | undefined> { + public async getIndicatorDataView(dataViewId?: string): Promise<DataView | undefined> { let dataView: DataView | undefined; if (dataViewId) { try { - dataView = await dataViewService.get(dataViewId); + dataView = await this.dataViewService.get(dataViewId); } catch (e) { // If the data view is not found, we will continue without it } diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generators_factory.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generators_factory.ts new file mode 100644 index 000000000000..1da2ce1eca4e --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/transform_generators_factory.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataViewsService } from '@kbn/data-views-plugin/server'; +import { + ApmTransactionDurationTransformGenerator, + ApmTransactionErrorRateTransformGenerator, + HistogramTransformGenerator, + KQLCustomTransformGenerator, + MetricCustomTransformGenerator, + SyntheticsAvailabilityTransformGenerator, + TimesliceMetricTransformGenerator, + TransformGenerator, +} from '.'; +import { IndicatorTypes } from '../../domain/models'; + +export function createTransformGenerators( + spaceId: string, + dataViewsService: DataViewsService, + isServerless: boolean +): Record<IndicatorTypes, TransformGenerator> { + return { + 'sli.apm.transactionDuration': new ApmTransactionDurationTransformGenerator( + spaceId, + dataViewsService + ), + 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator( + spaceId, + dataViewsService + ), + 'sli.synthetics.availability': new SyntheticsAvailabilityTransformGenerator( + spaceId, + dataViewsService, + isServerless + ), + 'sli.kql.custom': new KQLCustomTransformGenerator(spaceId, dataViewsService), + 'sli.metric.custom': new MetricCustomTransformGenerator(spaceId, dataViewsService), + 'sli.histogram.custom': new HistogramTransformGenerator(spaceId, dataViewsService), + 'sli.metric.timeslice': new TimesliceMetricTransformGenerator(spaceId, dataViewsService), + }; +} diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts index b7b5d7ba4fcd..aa0884860e8e 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.test.ts @@ -44,14 +44,12 @@ describe('TransformManager', () => { it('throws when no generator exists for the slo indicator type', async () => { // @ts-ignore defining only a subset of the possible SLI const generators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionDuration': new DummyTransformGenerator(), + 'sli.apm.transactionDuration': new DummyTransformGenerator(spaceId, dataViewsService), }; const service = new DefaultTransformManager( generators, scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService + loggerMock ); await expect( @@ -62,14 +60,12 @@ describe('TransformManager', () => { it('throws when transform generator fails', async () => { // @ts-ignore defining only a subset of the possible SLI const generators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionDuration': new FailTransformGenerator(), + 'sli.apm.transactionDuration': new FailTransformGenerator(spaceId, dataViewsService), }; const transformManager = new DefaultTransformManager( generators, scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService + loggerMock ); await expect( @@ -83,14 +79,15 @@ describe('TransformManager', () => { it('installs the transform', async () => { // @ts-ignore defining only a subset of the possible SLI const generators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), + 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator( + spaceId, + dataViewsService + ), }; const transformManager = new DefaultTransformManager( generators, scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService + loggerMock ); const slo = createSLO({ indicator: createAPMTransactionErrorRateIndicator() }); @@ -107,14 +104,15 @@ describe('TransformManager', () => { it('previews the transform', async () => { // @ts-ignore defining only a subset of the possible SLI const generators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), + 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator( + spaceId, + dataViewsService + ), }; const transformManager = new DefaultTransformManager( generators, scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService + loggerMock ); await transformManager.preview('slo-transform-id'); @@ -129,14 +127,15 @@ describe('TransformManager', () => { it('starts the transform', async () => { // @ts-ignore defining only a subset of the possible SLI const generators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), + 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator( + spaceId, + dataViewsService + ), }; const transformManager = new DefaultTransformManager( generators, scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService + loggerMock ); await transformManager.start('slo-transform-id'); @@ -151,14 +150,15 @@ describe('TransformManager', () => { it('stops the transform', async () => { // @ts-ignore defining only a subset of the possible SLI const generators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), + 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator( + spaceId, + dataViewsService + ), }; const transformManager = new DefaultTransformManager( generators, scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService + loggerMock ); await transformManager.stop('slo-transform-id'); @@ -173,14 +173,15 @@ describe('TransformManager', () => { it('uninstalls the transform', async () => { // @ts-ignore defining only a subset of the possible SLI const generators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), + 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator( + spaceId, + dataViewsService + ), }; const transformManager = new DefaultTransformManager( generators, scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService + loggerMock ); await transformManager.uninstall('slo-transform-id'); @@ -196,14 +197,15 @@ describe('TransformManager', () => { ); // @ts-ignore defining only a subset of the possible SLI const generators: Record<IndicatorTypes, TransformGenerator> = { - 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator(), + 'sli.apm.transactionErrorRate': new ApmTransactionErrorRateTransformGenerator( + spaceId, + dataViewsService + ), }; const transformManager = new DefaultTransformManager( generators, scopedClusterClientMock, - loggerMock, - spaceId, - dataViewsService + loggerMock ); await transformManager.uninstall('slo-transform-id'); @@ -216,21 +218,20 @@ describe('TransformManager', () => { }); class DummyTransformGenerator extends TransformGenerator { - async getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService) { + super(spaceId, dataViewService); + } + async getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { return {} as TransformPutTransformRequest; } } class FailTransformGenerator extends TransformGenerator { - getTransformParams( - slo: SLODefinition, - spaceId: string, - dataViewService: DataViewsService - ): Promise<TransformPutTransformRequest> { + constructor(spaceId: string, dataViewService: DataViewsService) { + super(spaceId, dataViewService); + } + + getTransformParams(slo: SLODefinition): Promise<TransformPutTransformRequest> { throw new Error('Some error'); } } diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts index 7e5ddce8bcad..464d1f1aeaa5 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_manager.ts @@ -5,11 +5,9 @@ * 2.0. */ -import { IScopedClusterClient, Logger } from '@kbn/core/server'; - import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { DataViewsService } from '@kbn/data-views-plugin/server'; -import { SLODefinition, IndicatorTypes } from '../domain/models'; +import { IScopedClusterClient, Logger } from '@kbn/core/server'; +import { IndicatorTypes, SLODefinition } from '../domain/models'; import { SecurityException } from '../errors'; import { retryTransientEsErrors } from '../utils/retry'; import { TransformGenerator } from './transform_generators'; @@ -29,9 +27,7 @@ export class DefaultTransformManager implements TransformManager { constructor( private generators: Record<IndicatorTypes, TransformGenerator>, private scopedClusterClient: IScopedClusterClient, - private logger: Logger, - private spaceId: string, - private dataViewService: DataViewsService + private logger: Logger ) {} async install(slo: SLODefinition): Promise<TransformId> { @@ -41,11 +37,7 @@ export class DefaultTransformManager implements TransformManager { throw new Error(`Unsupported indicator type [${slo.indicator.type}]`); } - const transformParams = await generator.getTransformParams( - slo, - this.spaceId, - this.dataViewService - ); + const transformParams = await generator.getTransformParams(slo); try { await retryTransientEsErrors( () => this.scopedClusterClient.asSecondaryAuthUser.transform.putTransform(transformParams), @@ -72,7 +64,7 @@ export class DefaultTransformManager implements TransformManager { throw new Error(`Unsupported indicator type [${slo.indicator.type}]`); } - return await generator.getTransformParams(slo, this.spaceId, this.dataViewService); + return await generator.getTransformParams(slo); } async preview(transformId: string): Promise<void> { diff --git a/x-pack/plugins/observability_solution/slo/tsconfig.json b/x-pack/plugins/observability_solution/slo/tsconfig.json index 23efcc39698b..001c835fcb5c 100644 --- a/x-pack/plugins/observability_solution/slo/tsconfig.json +++ b/x-pack/plugins/observability_solution/slo/tsconfig.json @@ -7,7 +7,9 @@ "common/**/*", "public/**/*", "server/**/*", - "../../../typings/**/*" + "../../../typings/**/*", + // Emotion theme typing + "./emotion.d.ts" ], "exclude": [ "target/**/*" @@ -22,7 +24,6 @@ "@kbn/observability-plugin", "@kbn/observability-shared-plugin", "@kbn/kibana-react-plugin", - "@kbn/react-kibana-context-theme", "@kbn/shared-ux-link-redirect-app", "@kbn/kibana-utils-plugin", "@kbn/slo-schema", @@ -98,6 +99,5 @@ "@kbn/observability-alerting-rule-utils", "@kbn/discover-shared-plugin", "@kbn/server-route-repository-client", - "@kbn/server-route-repository-utils" ] } diff --git a/x-pack/plugins/observability_solution/synthetics/common/constants/client_defaults.ts b/x-pack/plugins/observability_solution/synthetics/common/constants/client_defaults.ts index f1098c89b7ca..0f4f2ca9441a 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/constants/client_defaults.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/constants/client_defaults.ts @@ -112,3 +112,18 @@ export const getTimeSpanFilter = () => ({ }, }, }); + +export const getQueryFilters = (query: string) => ({ + query_string: { + query: `${query}`, + fields: [ + 'monitor.name.text', + 'tags', + 'observer.geo.name', + 'observer.name', + 'urls', + 'hosts', + 'monitor.project.id', + ], + }, +}); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.test.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.test.ts new file mode 100644 index 000000000000..830e2bce119c --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.test.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import * as spaceHook from '../../../../../hooks/use_kibana_space'; +import * as paramHook from '../../../hooks/use_url_params'; +import * as redux from 'react-redux'; +import { useMonitorFilters } from './use_monitor_filters'; +import { WrappedHelper } from '../../../utils/testing'; + +describe('useMonitorFilters', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const spaceSpy = jest.spyOn(spaceHook, 'useKibanaSpace'); + const paramSpy = jest.spyOn(paramHook, 'useGetUrlParams'); + const selSPy = jest.spyOn(redux, 'useSelector'); + + it('should return an empty array when no parameters are provided', () => { + const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper }); + + expect(result.current).toEqual([]); + }); + + it('should return filters for allIds and schedules', () => { + spaceSpy.mockReturnValue({} as any); + paramSpy.mockReturnValue({ schedules: 'daily' } as any); + selSPy.mockReturnValue({ status: { allIds: ['id1', 'id2'] } }); + + const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper }); + + expect(result.current).toEqual([{ field: 'monitor.id', values: ['id1', 'id2'] }]); + }); + + it('should return filters for allIds and empty schedules', () => { + spaceSpy.mockReturnValue({} as any); + paramSpy.mockReturnValue({ schedules: [] } as any); + selSPy.mockReturnValue({ status: { allIds: ['id1', 'id2'] } }); + + const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper }); + + expect(result.current).toEqual([]); + }); + + it('should return filters for project IDs', () => { + spaceSpy.mockReturnValue({ space: null } as any); + paramSpy.mockReturnValue({ projects: ['project1', 'project2'] } as any); + selSPy.mockReturnValue({ status: { allIds: [] } }); + + const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper }); + + expect(result.current).toEqual([ + { field: 'monitor.project.id', values: ['project1', 'project2'] }, + ]); + }); + + it('should return filters for tags and locations', () => { + spaceSpy.mockReturnValue({ space: null } as any); + paramSpy.mockReturnValue({ + tags: ['tag1', 'tag2'], + locations: ['location1', 'location2'], + } as any); + selSPy.mockReturnValue({ status: { allIds: [] } }); + + const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper }); + + expect(result.current).toEqual([ + { field: 'tags', values: ['tag1', 'tag2'] }, + { field: 'observer.geo.name', values: ['location1', 'location2'] }, + ]); + }); + + it('should include space filters for alerts', () => { + spaceSpy.mockReturnValue({ space: { id: 'space1' } } as any); + paramSpy.mockReturnValue({} as any); + selSPy.mockReturnValue({ status: { allIds: [] } }); + + const { result } = renderHook(() => useMonitorFilters({ forAlerts: true }), { + wrapper: WrappedHelper, + }); + + expect(result.current).toEqual([{ field: 'kibana.space_ids', values: ['space1'] }]); + }); + + it('should include space filters for non-alerts', () => { + spaceSpy.mockReturnValue({ space: { id: 'space2' } } as any); + paramSpy.mockReturnValue({} as any); + selSPy.mockReturnValue({ status: { allIds: [] } }); + + const { result } = renderHook(() => useMonitorFilters({}), { wrapper: WrappedHelper }); + + expect(result.current).toEqual([{ field: 'meta.space_id', values: ['space2'] }]); + }); + + it('should handle a combination of parameters', () => { + spaceSpy.mockReturnValue({ space: { id: 'space3' } } as any); + paramSpy.mockReturnValue({ + schedules: 'daily', + projects: ['projectA'], + tags: ['tagB'], + locations: ['locationC'], + monitorTypes: 'http', + } as any); + selSPy.mockReturnValue({ status: { allIds: ['id3', 'id4'] } }); + + const { result } = renderHook(() => useMonitorFilters({ forAlerts: false }), { + wrapper: WrappedHelper, + }); + + expect(result.current).toEqual([ + { field: 'monitor.id', values: ['id3', 'id4'] }, + { field: 'monitor.project.id', values: ['projectA'] }, + { field: 'monitor.type', values: ['http'] }, + { field: 'tags', values: ['tagB'] }, + { field: 'observer.geo.name', values: ['locationC'] }, + { field: 'meta.space_id', values: ['space3'] }, + ]); + }); +}); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.ts new file mode 100644 index 000000000000..ed20d021349c --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_filters.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UrlFilter } from '@kbn/exploratory-view-plugin/public'; +import { useSelector } from 'react-redux'; +import { isEmpty } from 'lodash'; +import { useGetUrlParams } from '../../../hooks/use_url_params'; +import { useKibanaSpace } from '../../../../../hooks/use_kibana_space'; +import { selectOverviewStatus } from '../../../state/overview_status'; + +export const useMonitorFilters = ({ forAlerts }: { forAlerts?: boolean }): UrlFilter[] => { + const { space } = useKibanaSpace(); + const { locations, monitorTypes, tags, projects, schedules } = useGetUrlParams(); + const { status: overviewStatus } = useSelector(selectOverviewStatus); + const allIds = overviewStatus?.allIds ?? []; + + return [ + // since schedule isn't available in heartbeat data, in that case we rely on monitor.id + ...(allIds?.length && !isEmpty(schedules) ? [{ field: 'monitor.id', values: allIds }] : []), + ...(projects?.length ? [{ field: 'monitor.project.id', values: getValues(projects) }] : []), + ...(monitorTypes?.length ? [{ field: 'monitor.type', values: getValues(monitorTypes) }] : []), + ...(tags?.length ? [{ field: 'tags', values: getValues(tags) }] : []), + ...(locations?.length ? [{ field: 'observer.geo.name', values: getValues(locations) }] : []), + ...(space + ? [{ field: forAlerts ? 'kibana.space_ids' : 'meta.space_id', values: [space.id] }] + : []), + ]; +}; + +const getValues = (values: string | string[]): string[] => { + return Array.isArray(values) ? values : [values]; +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_query_filters.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_query_filters.ts new file mode 100644 index 000000000000..5ddf208da611 --- /dev/null +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_query_filters.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { useGetUrlParams } from '../../../hooks'; +import { getQueryFilters } from '../../../../../../common/constants/client_defaults'; + +export const useMonitorQueryFilters = () => { + const { query } = useGetUrlParams(); + + return useMemo(() => { + return query ? [getQueryFilters(query)] : undefined; + }, [query]); +}; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.tsx index ae5a035b76b5..a40bb6e37078 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_errors/monitor_async_error.tsx @@ -36,7 +36,7 @@ export const MonitorAsyncError = () => { defaultMessage="There was a problem running your monitors for one or more locations:" /> </p> - <ul> + <ul style={{ maxHeight: 100, overflow: 'auto' }}> {Object.values(syncErrors ?? {}).map((e) => { return ( <li key={e.locationId}> diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx index 67ce536fc3c8..db0e54b07875 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx @@ -73,9 +73,9 @@ export const MonitorStats = ({ <EuiFlexItem css={{ display: 'flex', flexDirection: 'row', gap: euiTheme.size.l, height: '200px' }} > - <MonitorTestRunsCount monitorIds={overviewStatus?.allIds ?? []} /> + <MonitorTestRunsCount /> <EuiFlexItem grow={true}> - <MonitorTestRunsSparkline monitorIds={overviewStatus?.allIds ?? []} /> + <MonitorTestRunsSparkline /> </EuiFlexItem> </EuiFlexItem> </EuiPanel> diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx index 90a7d705b5c3..18eaa74b4cdc 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx @@ -11,31 +11,36 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useTheme } from '@kbn/observability-shared-plugin/public'; import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; +import { useMonitorFilters } from '../../hooks/use_monitor_filters'; import { useRefreshedRange } from '../../../../hooks'; import { ClientPluginsStart } from '../../../../../../plugin'; import * as labels from '../labels'; +import { useMonitorQueryFilters } from '../../hooks/use_monitor_query_filters'; -export const MonitorTestRunsCount = ({ monitorIds }: { monitorIds: string[] }) => { +export const MonitorTestRunsCount = () => { const { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana<ClientPluginsStart>().services; const theme = useTheme(); const { from, to } = useRefreshedRange(30, 'days'); + const filters = useMonitorFilters({}); + const queryFilter = useMonitorQueryFilters(); return ( <ExploratoryViewEmbeddable + dslFilters={queryFilter} align="left" reportType={ReportTypes.SINGLE_METRIC} attributes={[ { + filters, time: { from, to }, reportDefinitions: { - 'monitor.id': monitorIds.length > 0 ? monitorIds : ['false-monitor-id'], // Show no data when monitorIds is empty + 'monitor.type': ['http', 'tcp', 'browser', 'icmp'], }, dataType: 'synthetics', selectedMetricField: 'monitor_total_runs', - filters: [], name: labels.TEST_RUNS_LABEL, color: theme.eui.euiColorVis1, }, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx index c2930a1d22ff..8713dfb77769 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx @@ -10,11 +10,13 @@ import React, { useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useTheme } from '@kbn/observability-shared-plugin/public'; +import { useMonitorQueryFilters } from '../../hooks/use_monitor_query_filters'; +import { useMonitorFilters } from '../../hooks/use_monitor_filters'; import { useRefreshedRange } from '../../../../hooks'; import { ClientPluginsStart } from '../../../../../../plugin'; import * as labels from '../labels'; -export const MonitorTestRunsSparkline = ({ monitorIds }: { monitorIds: string[] }) => { +export const MonitorTestRunsSparkline = () => { const { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana<ClientPluginsStart>().services; @@ -22,6 +24,8 @@ export const MonitorTestRunsSparkline = ({ monitorIds }: { monitorIds: string[] const theme = useTheme(); const { from, to } = useRefreshedRange(30, 'days'); + const filters = useMonitorFilters({}); + const queryFilter = useMonitorQueryFilters(); const attributes = useMemo(() => { return [ @@ -29,18 +33,18 @@ export const MonitorTestRunsSparkline = ({ monitorIds }: { monitorIds: string[] seriesType: 'area' as const, time: { from, to }, reportDefinitions: { - 'monitor.id': monitorIds.length > 0 ? monitorIds : ['false-monitor-id'], // Show no data when monitorIds is empty + 'monitor.type': ['http', 'tcp', 'browser', 'icmp'], }, dataType: 'synthetics' as const, selectedMetricField: 'total_test_runs', - filters: [], + filters, name: labels.TEST_RUNS_LABEL, color: theme.eui.euiColorVis1, operationType: 'count', }, ]; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [from, JSON.stringify({ ids: [...monitorIds].sort() }), theme.eui.euiColorVis1, to]); + }, [from, theme.eui.euiColorVis1, to]); return ( <ExploratoryViewEmbeddable @@ -51,6 +55,7 @@ export const MonitorTestRunsSparkline = ({ monitorIds }: { monitorIds: string[] hideTicks={true} attributes={attributes} customHeight={'68px'} + dslFilters={queryFilter} /> ); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx index e9c86eef3767..8174b7fb63f7 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx @@ -6,19 +6,18 @@ */ import React, { useMemo } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiSkeletonText, - EuiPanel, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useTheme } from '@kbn/observability-shared-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useSelector } from 'react-redux'; import { RECORDS_FIELD } from '@kbn/exploratory-view-plugin/public'; +import { useMonitorQueryFilters } from '../../hooks/use_monitor_query_filters'; +import { + SYNTHETICS_STATUS_RULE, + SYNTHETICS_TLS_RULE, +} from '../../../../../../../common/constants/synthetics_alerts'; +import { useMonitorFilters } from '../../hooks/use_monitor_filters'; import { selectOverviewStatus } from '../../../../state/overview_status'; import { AlertsLink } from '../../../common/links/view_alerts'; import { useRefreshedRange, useGetUrlParams } from '../../../../hooks'; @@ -64,14 +63,10 @@ export const OverviewAlerts = () => { } = useKibana<ClientPluginsStart>().services; const theme = useTheme(); - - const { status } = useSelector(selectOverviewStatus); + const filters = useMonitorFilters({ forAlerts: true }); const { locations } = useGetUrlParams(); - - const loading = !status?.allIds || status?.allIds.length === 0; - - const monitorIds = useMonitorQueryIds(); + const queryFilters = useMonitorQueryFilters(); return ( <EuiPanel hasShadow={false} paddingSize="m" hasBorder> @@ -79,68 +74,70 @@ export const OverviewAlerts = () => { <h3>{headingText}</h3> </EuiTitle> <EuiSpacer size="s" /> - {loading ? ( - <EuiSkeletonText lines={3} /> - ) : ( - <EuiFlexGroup alignItems="center" gutterSize="m"> - <EuiFlexItem grow={false}> - <ExploratoryViewEmbeddable - id="monitorActiveAlertsCount" - dataTestSubj="monitorActiveAlertsCount" - reportType="single-metric" - customHeight="70px" - attributes={[ - { - dataType: 'alerts', - time: { - from, - to, - }, - name: ALERTS_LABEL, - selectedMetricField: RECORDS_FIELD, - reportDefinitions: { - 'kibana.alert.rule.category': ['Synthetics monitor status'], - 'monitor.id': monitorIds, - ...(locations?.length ? { 'observer.geo.name': locations } : {}), - }, - filters: [{ field: 'kibana.alert.status', values: ['active', 'recovered'] }], - color: theme.eui.euiColorVis1, + <EuiFlexGroup alignItems="center" gutterSize="m"> + <EuiFlexItem grow={false}> + <ExploratoryViewEmbeddable + id="monitorActiveAlertsCount" + dataTestSubj="monitorActiveAlertsCount" + reportType="single-metric" + customHeight="70px" + dslFilters={queryFilters} + attributes={[ + { + dataType: 'alerts', + time: { + from, + to, + }, + name: ALERTS_LABEL, + selectedMetricField: RECORDS_FIELD, + reportDefinitions: { + 'kibana.alert.rule.rule_type_id': [SYNTHETICS_STATUS_RULE, SYNTHETICS_TLS_RULE], + ...(locations?.length ? { 'observer.geo.name': locations } : {}), + }, + filters: [ + { field: 'kibana.alert.status', values: ['active', 'recovered'] }, + ...filters, + ], + color: theme.eui.euiColorVis1, + }, + ]} + /> + </EuiFlexItem> + <EuiFlexItem> + <ExploratoryViewEmbeddable + id="monitorActiveAlertsOverTime" + sparklineMode + customHeight="70px" + reportType="kpi-over-time" + dslFilters={queryFilters} + attributes={[ + { + seriesType: 'area', + time: { + from, + to, }, - ]} - /> - </EuiFlexItem> - <EuiFlexItem> - <ExploratoryViewEmbeddable - id="monitorActiveAlertsOverTime" - sparklineMode - customHeight="70px" - reportType="kpi-over-time" - attributes={[ - { - seriesType: 'area', - time: { - from, - to, - }, - reportDefinitions: { - 'kibana.alert.rule.category': ['Synthetics monitor status'], - 'monitor.id': monitorIds, - ...(locations?.length ? { 'observer.geo.name': locations } : {}), - }, - dataType: 'alerts', - selectedMetricField: RECORDS_FIELD, - name: ALERTS_LABEL, - filters: [{ field: 'kibana.alert.status', values: ['active', 'recovered'] }], - color: theme.eui.euiColorVis1_behindText, + reportDefinitions: { + 'kibana.alert.rule.rule_type_id': [SYNTHETICS_STATUS_RULE, SYNTHETICS_TLS_RULE], + ...(locations?.length ? { 'observer.geo.name': locations } : {}), }, - ]} - /> - </EuiFlexItem> - <EuiFlexItem grow={false} css={{ alignSelf: 'center' }}> - <AlertsLink /> - </EuiFlexItem> - </EuiFlexGroup> - )} + dataType: 'alerts', + selectedMetricField: RECORDS_FIELD, + name: ALERTS_LABEL, + filters: [ + { field: 'kibana.alert.status', values: ['active', 'recovered'] }, + ...filters, + ], + color: theme.eui.euiColorVis1_behindText, + }, + ]} + /> + </EuiFlexItem> + <EuiFlexItem grow={false} css={{ alignSelf: 'center' }}> + <AlertsLink /> + </EuiFlexItem> + </EuiFlexGroup> </EuiPanel> ); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx index 34d113da9901..acabd436d83f 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors.tsx @@ -5,62 +5,30 @@ * 2.0. */ -import { - EuiFlexGroup, - EuiFlexItem, - EuiSkeletonText, - EuiPanel, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import React from 'react'; -import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; -import { useMonitorQueryIds } from '../overview_alerts'; -import { selectOverviewStatus } from '../../../../../state/overview_status'; import { OverviewErrorsSparklines } from './overview_errors_sparklines'; -import { useRefreshedRange, useGetUrlParams } from '../../../../../hooks'; +import { useRefreshedRange } from '../../../../../hooks'; import { OverviewErrorsCount } from './overview_errors_count'; export function OverviewErrors() { - const { status } = useSelector(selectOverviewStatus); - - const loading = !status?.allIds || status?.allIds.length === 0; - const { from, to } = useRefreshedRange(6, 'hours'); - const { locations } = useGetUrlParams(); - - const monitorIds = useMonitorQueryIds(); - return ( <EuiPanel hasShadow={false} hasBorder> <EuiTitle size="xs"> <h3>{headingText}</h3> </EuiTitle> <EuiSpacer size="s" /> - {loading ? ( - <EuiSkeletonText lines={3} /> - ) : ( - <EuiFlexGroup gutterSize="xl"> - <EuiFlexItem grow={false}> - <OverviewErrorsCount - from={from} - to={to} - monitorIds={monitorIds} - locations={locations} - /> - </EuiFlexItem> - <EuiFlexItem grow={true}> - <OverviewErrorsSparklines - from={from} - to={to} - monitorIds={monitorIds} - locations={locations} - /> - </EuiFlexItem> - </EuiFlexGroup> - )} + <EuiFlexGroup gutterSize="xl"> + <EuiFlexItem grow={false}> + <OverviewErrorsCount from={from} to={to} /> + </EuiFlexItem> + <EuiFlexItem grow={true}> + <OverviewErrorsSparklines from={from} to={to} /> + </EuiFlexItem> + </EuiFlexGroup> </EuiPanel> ); } diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx index aaebf3e4bb04..e7365ccc7520 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx @@ -8,27 +8,23 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import React, { useMemo } from 'react'; import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; +import { useMonitorFilters } from '../../../hooks/use_monitor_filters'; import { ERRORS_LABEL } from '../../../../monitor_details/monitor_summary/monitor_errors_count'; import { ClientPluginsStart } from '../../../../../../../plugin'; +import { useMonitorQueryFilters } from '../../../hooks/use_monitor_query_filters'; interface MonitorErrorsCountProps { from: string; to: string; - locationLabel?: string; - monitorIds: string[]; - locations?: string[]; } -export const OverviewErrorsCount = ({ - monitorIds, - from, - to, - locations, -}: MonitorErrorsCountProps) => { +export const OverviewErrorsCount = ({ from, to }: MonitorErrorsCountProps) => { const { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana<ClientPluginsStart>().services; + const filters = useMonitorFilters({}); + const time = useMemo(() => ({ from, to }), [from, to]); return ( @@ -36,17 +32,18 @@ export const OverviewErrorsCount = ({ id="overviewErrorsCount" align="left" customHeight="70px" + dslFilters={useMonitorQueryFilters()} reportType={ReportTypes.SINGLE_METRIC} attributes={[ { time, reportDefinitions: { - 'monitor.id': monitorIds.length > 0 ? monitorIds : ['false-monitor-id'], - ...(locations?.length ? { 'observer.geo.name': locations } : {}), + 'monitor.type': ['http', 'tcp', 'browser', 'icmp'], }, dataType: 'synthetics', selectedMetricField: 'monitor_errors', name: ERRORS_LABEL, + filters, }, ]} /> diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx index b97e0eef8bbb..41d6a5bc34d7 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx @@ -8,20 +8,21 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import React, { useMemo } from 'react'; import { useEuiTheme } from '@elastic/eui'; -import { ClientPluginsStart } from '../../../../../../../plugin'; import { ERRORS_LABEL } from '../../../../monitor_details/monitor_summary/monitor_errors_count'; +import { ClientPluginsStart } from '../../../../../../../plugin'; +import { useMonitorFilters } from '../../../hooks/use_monitor_filters'; +import { useMonitorQueryFilters } from '../../../hooks/use_monitor_query_filters'; interface Props { from: string; to: string; - monitorIds: string[]; - locations?: string[]; } -export const OverviewErrorsSparklines = ({ from, to, monitorIds, locations }: Props) => { +export const OverviewErrorsSparklines = ({ from, to }: Props) => { const { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana<ClientPluginsStart>().services; + const filters = useMonitorFilters({}); const { euiTheme } = useEuiTheme(); const time = useMemo(() => ({ from, to }), [from, to]); @@ -33,19 +34,20 @@ export const OverviewErrorsSparklines = ({ from, to, monitorIds, locations }: Pr axisTitlesVisibility={{ x: false, yRight: false, yLeft: false }} legendIsVisible={false} hideTicks={true} + dslFilters={useMonitorQueryFilters()} attributes={[ { time, seriesType: 'area', reportDefinitions: { - 'monitor.id': monitorIds.length > 0 ? monitorIds : ['false-monitor-id'], - ...(locations?.length ? { 'observer.geo.name': locations } : {}), + 'monitor.type': ['http', 'tcp', 'browser', 'icmp'], }, dataType: 'synthetics', selectedMetricField: 'monitor_errors', name: ERRORS_LABEL, color: euiTheme.colors.danger, operationType: 'unique_count', + filters, }, ]} /> diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx index f0612498f866..507b971c6a40 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_grid.tsx @@ -114,7 +114,6 @@ export const OverviewGrid = memo(() => { return acc; }, [monitorsSortedByStatus]); - const listRef: React.LegacyRef<FixedSizeList<ListItem[][]>> | undefined = React.createRef(); useEffect(() => { dispatch(refreshOverviewTrends.get()); }, [dispatch, lastRefresh]); @@ -165,50 +164,52 @@ export const OverviewGrid = memo(() => { minimumBatchSize={MIN_BATCH_SIZE} threshold={LIST_THRESHOLD} > - {({ onItemsRendered }) => ( - <FixedSizeList - // pad computed height to avoid clipping last row's drop shadow - height={listHeight + 16} - width={width} - onItemsRendered={onItemsRendered} - itemSize={ITEM_HEIGHT} - itemCount={listItems.length} - itemData={listItems} - ref={listRef} - > - {({ - index: listIndex, - style, - data: listData, - }: React.PropsWithChildren<ListChildComponentProps<ListItem[][]>>) => { - setCurrentIndex(listIndex); - return ( - <EuiFlexGroup - data-test-subj={`overview-grid-row-${listIndex}`} - gutterSize="m" - style={{ ...style }} - > - {listData[listIndex].map((_, idx) => ( - <EuiFlexItem - data-test-subj="syntheticsOverviewGridItem" - key={listIndex * ROW_COUNT + idx} - > - <MetricItem - monitor={monitorsSortedByStatus[listIndex * ROW_COUNT + idx]} - onClick={setFlyoutConfigCallback} - /> - </EuiFlexItem> - ))} - {listData[listIndex].length % ROW_COUNT !== 0 && - // Adds empty items to fill out row - Array.from({ - length: ROW_COUNT - listData[listIndex].length, - }).map((_, idx) => <EuiFlexItem key={idx} />)} - </EuiFlexGroup> - ); - }} - </FixedSizeList> - )} + {({ onItemsRendered, ref }) => { + return ( + <FixedSizeList + // pad computed height to avoid clipping last row's drop shadow + height={listHeight + 16} + width={width} + onItemsRendered={onItemsRendered} + itemSize={ITEM_HEIGHT} + itemCount={listItems.length} + itemData={listItems} + ref={ref} + > + {({ + index: listIndex, + style, + data: listData, + }: React.PropsWithChildren<ListChildComponentProps<ListItem[][]>>) => { + setCurrentIndex(listIndex); + return ( + <EuiFlexGroup + data-test-subj={`overview-grid-row-${listIndex}`} + gutterSize="m" + style={{ ...style }} + > + {listData[listIndex].map((_, idx) => ( + <EuiFlexItem + data-test-subj="syntheticsOverviewGridItem" + key={listIndex * ROW_COUNT + idx} + > + <MetricItem + monitor={monitorsSortedByStatus[listIndex * ROW_COUNT + idx]} + onClick={setFlyoutConfigCallback} + /> + </EuiFlexItem> + ))} + {listData[listIndex].length % ROW_COUNT !== 0 && + // Adds empty items to fill out row + Array.from({ + length: ROW_COUNT - listData[listIndex].length, + }).map((_, idx) => <EuiFlexItem key={idx} />)} + </EuiFlexGroup> + ); + }} + </FixedSizeList> + ); + }} </InfiniteLoader> )} </EuiAutoSizer> @@ -239,7 +240,6 @@ export const OverviewGrid = memo(() => { data-test-subj="syntheticsOverviewGridButton" onClick={() => { window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); - listRef.current?.scrollToItem(0); }} iconType="sortUp" iconSide="right" diff --git a/x-pack/plugins/observability_solution/synthetics/public/hooks/use_kibana_space.tsx b/x-pack/plugins/observability_solution/synthetics/public/hooks/use_kibana_space.tsx index 6dbc979397b3..0b5291e890ec 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/hooks/use_kibana_space.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/hooks/use_kibana_space.tsx @@ -8,7 +8,7 @@ import type { Space } from '@kbn/spaces-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { useFetcher } from '@kbn/observability-shared-plugin/public'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; -import { ClientPluginsStart } from '../plugin'; +import type { ClientPluginsStart } from '../plugin'; export const useKibanaSpace = () => { const { services } = useKibana<ClientPluginsStart>(); diff --git a/x-pack/plugins/observability_solution/synthetics/server/feature.ts b/x-pack/plugins/observability_solution/synthetics/server/feature.ts index bf86ac7b0c89..5a4c4d508853 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/feature.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/feature.ts @@ -106,6 +106,7 @@ export const syntheticsFeature = { syntheticsSettingsObjectType, syntheticsMonitorType, syntheticsApiKeyObjectType, + privateLocationSavedObjectName, legacyPrivateLocationsSavedObjectName, // uptime settings object is also registered here since feature is shared between synthetics and uptime uptimeSettingsObjectType, diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/common.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/common.ts index 2a906f3cf6a4..24d16d323e48 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/common.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/common.ts @@ -111,7 +111,7 @@ export const getMonitors = async ( sortField: parseMappingKey(sortField), sortOrder, searchFields: SEARCH_FIELDS, - search: query ? `${query}*` : undefined, + search: query, filter: filtersStr, searchAfter, fields, diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/run_once_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/run_once_monitor.ts index 6f7b3427f96f..2af3a10f3975 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/run_once_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/run_once_monitor.ts @@ -6,6 +6,7 @@ */ import { schema } from '@kbn/config-schema'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import { isEmpty } from 'lodash'; import { PrivateLocationAttributes } from '../../runtime_types/private_locations'; import { getPrivateLocationsForMonitor } from '../monitor_cruds/add_monitor/utils'; import { SyntheticsRestApiRouteFactory } from '../types'; @@ -31,6 +32,9 @@ export const runOnceSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () = }): Promise<any> => { const monitor = request.body as MonitorFields; const { monitorId } = request.params; + if (isEmpty(monitor)) { + return response.badRequest({ body: { message: 'Monitor data is empty.' } }); + } const validationResult = validateMonitor(monitor); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/test_now_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/test_now_monitor.ts index 3f878b10ac8f..d1a1513ae85c 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/test_now_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/test_now_monitor.ts @@ -6,6 +6,8 @@ */ import { schema } from '@kbn/config-schema'; import { v4 as uuidv4 } from 'uuid'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; +import { IKibanaResponse } from '@kbn/core-http-server'; import { getDecryptedMonitor } from '../../saved_objects/synthetics_monitor'; import { PrivateLocationAttributes } from '../../runtime_types/private_locations'; import { RouteContext, SyntheticsRestApiRouteFactory } from '../types'; @@ -14,6 +16,7 @@ import { ConfigKey, MonitorFields } from '../../../common/runtime_types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; import { normalizeSecrets } from '../../synthetics_service/utils/secrets'; import { getPrivateLocationsForMonitor } from '../monitor_cruds/add_monitor/utils'; +import { getMonitorNotFoundResponse } from './service_errors'; export const testNowMonitorRoute: SyntheticsRestApiRouteFactory<TestNowResponse> = () => ({ method: 'POST', @@ -33,48 +36,56 @@ export const testNowMonitorRoute: SyntheticsRestApiRouteFactory<TestNowResponse> export const triggerTestNow = async ( monitorId: string, routeContext: RouteContext -): Promise<TestNowResponse> => { - const { server, spaceId, syntheticsMonitorClient, savedObjectsClient } = routeContext; +): Promise<TestNowResponse | IKibanaResponse<any>> => { + const { server, spaceId, syntheticsMonitorClient, savedObjectsClient, response } = routeContext; - const monitorWithSecrets = await getDecryptedMonitor(server, monitorId, spaceId); - const normalizedMonitor = normalizeSecrets(monitorWithSecrets); + try { + const monitorWithSecrets = await getDecryptedMonitor(server, monitorId, spaceId); + const normalizedMonitor = normalizeSecrets(monitorWithSecrets); - const { [ConfigKey.SCHEDULE]: schedule, [ConfigKey.LOCATIONS]: locations } = - monitorWithSecrets.attributes; + const { [ConfigKey.SCHEDULE]: schedule, [ConfigKey.LOCATIONS]: locations } = + monitorWithSecrets.attributes; - const privateLocations: PrivateLocationAttributes[] = await getPrivateLocationsForMonitor( - savedObjectsClient, - normalizedMonitor.attributes - ); - const testRunId = uuidv4(); + const privateLocations: PrivateLocationAttributes[] = await getPrivateLocationsForMonitor( + savedObjectsClient, + normalizedMonitor.attributes + ); + const testRunId = uuidv4(); - const [, errors] = await syntheticsMonitorClient.testNowConfigs( - { - monitor: normalizedMonitor.attributes as MonitorFields, - id: monitorId, - testRunId, - }, - savedObjectsClient, - privateLocations, - spaceId - ); + const [, errors] = await syntheticsMonitorClient.testNowConfigs( + { + monitor: normalizedMonitor.attributes as MonitorFields, + id: monitorId, + testRunId, + }, + savedObjectsClient, + privateLocations, + spaceId + ); + + if (errors && errors?.length > 0) { + return { + errors, + testRunId, + schedule, + locations, + configId: monitorId, + monitor: normalizedMonitor.attributes, + }; + } - if (errors && errors?.length > 0) { return { - errors, testRunId, schedule, locations, configId: monitorId, monitor: normalizedMonitor.attributes, }; - } + } catch (getErr) { + if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) { + return getMonitorNotFoundResponse(response, monitorId); + } - return { - testRunId, - schedule, - locations, - configId: monitorId, - monitor: normalizedMonitor.attributes, - }; + throw getErr; + } }; diff --git a/x-pack/plugins/observability_solution/synthetics/server/server.ts b/x-pack/plugins/observability_solution/synthetics/server/server.ts index 77937c57590e..9ba4341ecad3 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/server.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/server.ts @@ -21,7 +21,7 @@ export const initSyntheticsServer = ( ) => { const { router } = server; syntheticsAppRestApiRoutes.forEach((route) => { - const { method, options, handler, validate, path } = syntheticsRouteWrapper( + const { method, options, handler, validate, path, security } = syntheticsRouteWrapper( createSyntheticsRouteWithAuth(route), server, syntheticsMonitorClient @@ -30,6 +30,7 @@ export const initSyntheticsServer = ( const routeDefinition = { path, validate, + security, options, }; @@ -52,11 +53,8 @@ export const initSyntheticsServer = ( }); syntheticsAppPublicRestApiRoutes.forEach((route) => { - const { method, options, handler, validate, path, validation } = syntheticsRouteWrapper( - createSyntheticsRouteWithAuth(route), - server, - syntheticsMonitorClient - ); + const { method, options, handler, validate, path, validation, security } = + syntheticsRouteWrapper(createSyntheticsRouteWithAuth(route), server, syntheticsMonitorClient); const routeDefinition = { path, @@ -70,13 +68,11 @@ export const initSyntheticsServer = ( .get({ access: 'public', path: routeDefinition.path, - options: { - tags: options?.tags, - }, }) .addVersion( { version: '2023-10-31', + security, validate: validation ?? false, }, handler @@ -87,13 +83,11 @@ export const initSyntheticsServer = ( .put({ access: 'public', path: routeDefinition.path, - options: { - tags: options?.tags, - }, }) .addVersion( { version: '2023-10-31', + security, validate: validation ?? false, }, handler @@ -104,13 +98,11 @@ export const initSyntheticsServer = ( .post({ access: 'public', path: routeDefinition.path, - options: { - tags: options?.tags, - }, }) .addVersion( { version: '2023-10-31', + security, validate: validation ?? false, }, handler @@ -128,6 +120,7 @@ export const initSyntheticsServer = ( .addVersion( { version: '2023-10-31', + security, validate: validation ?? false, }, handler diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_route_wrapper.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_route_wrapper.ts index a0785b448691..697cf93d7421 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_route_wrapper.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_route_wrapper.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { KibanaResponse } from '@kbn/core-http-router-server-internal'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { isEmpty } from 'lodash'; +import { isKibanaResponse } from '@kbn/core-http-server'; import { isTestUser, SyntheticsEsClient } from './lib'; import { checkIndicesReadPrivileges } from './synthetics_service/authentication/check_has_privilege'; import { SYNTHETICS_INDEX_PATTERN } from '../common/constants'; @@ -20,9 +20,13 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( ) => ({ ...uptimeRoute, options: { - tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])], ...(uptimeRoute.options ?? {}), }, + security: { + authz: { + requiredPrivileges: ['uptime-read', ...(uptimeRoute?.writeAccess ? ['uptime-write'] : [])], + }, + }, handler: async (context, request, response) => { const { elasticsearch, savedObjects, uiSettings } = await context.core; @@ -56,7 +60,7 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( spaceId, syntheticsMonitorClient, }); - if (res instanceof KibanaResponse) { + if (isKibanaResponse(res)) { return res; } diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/utils/fake_kibana_request.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/utils/fake_kibana_request.ts index d57fc23cd5c7..a156b0473ad9 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/utils/fake_kibana_request.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/utils/fake_kibana_request.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { FakeRawRequest, Headers } from '@kbn/core/server'; -import { CoreKibanaRequest } from '@kbn/core/server'; +import { type FakeRawRequest, type Headers } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; export function getFakeKibanaRequest(apiKey: { id: string; api_key: string }) { const requestHeaders: Headers = {}; @@ -20,5 +20,5 @@ export function getFakeKibanaRequest(apiKey: { id: string; api_key: string }) { path: '/', }; - return CoreKibanaRequest.from(fakeRawRequest); + return kibanaRequestFactory(fakeRawRequest); } diff --git a/x-pack/plugins/observability_solution/synthetics/tsconfig.json b/x-pack/plugins/observability_solution/synthetics/tsconfig.json index 32d15f8080ff..c82f6a5c3036 100644 --- a/x-pack/plugins/observability_solution/synthetics/tsconfig.json +++ b/x-pack/plugins/observability_solution/synthetics/tsconfig.json @@ -45,7 +45,6 @@ "@kbn/core-http-browser", "@kbn/core-notifications-browser", "@kbn/rison", - "@kbn/core-http-router-server-internal", "@kbn/licensing-plugin", "@kbn/rule-registry-plugin", "@kbn/encrypted-saved-objects-plugin", @@ -106,7 +105,8 @@ "@kbn/alerting-types", "@kbn/core-chrome-browser", "@kbn/core-rendering-browser", - "@kbn/index-lifecycle-management-common-shared" + "@kbn/index-lifecycle-management-common-shared", + "@kbn/core-http-server-utils" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/routes/uptime_route_wrapper.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/routes/uptime_route_wrapper.ts index 2590db852410..a4ef42c07f02 100644 --- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/routes/uptime_route_wrapper.ts +++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/routes/uptime_route_wrapper.ts @@ -5,18 +5,19 @@ * 2.0. */ -import { KibanaResponse } from '@kbn/core-http-router-server-internal'; -import { UMKibanaRouteWrapper } from './types'; +import { isKibanaResponse } from '@kbn/core-http-server'; +import type { UMKibanaRouteWrapper } from './types'; import { UptimeEsClient } from '../lib/lib'; export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute, server) => ({ ...uptimeRoute, options: { - tags: [ - 'oas-tag:uptime', - 'access:uptime-read', - ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : []), - ], + tags: ['oas-tag:uptime'], + }, + security: { + authz: { + requiredPrivileges: ['uptime-read', ...(uptimeRoute?.writeAccess ? ['uptime-write'] : [])], + }, }, handler: async (context, request, response) => { const coreContext = await context.core; @@ -41,7 +42,7 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute, server) => server, }); - if (res instanceof KibanaResponse) { + if (isKibanaResponse(res)) { return res; } diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/uptime_server.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/uptime_server.ts index b8a9b56c2a90..32feb62fcb0f 100644 --- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/uptime_server.ts +++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/uptime_server.ts @@ -41,7 +41,7 @@ export const initUptimeServer = ( router: UptimeRouter ) => { legacyUptimeRestApiRoutes.forEach((route) => { - const { method, options, handler, validate, path } = uptimeRouteWrapper( + const { method, options, handler, validate, path, security } = uptimeRouteWrapper( createRouteWithAuth(libs, route), server ); @@ -50,6 +50,7 @@ export const initUptimeServer = ( path, validate, options, + security, }; switch (method) { @@ -71,26 +72,20 @@ export const initUptimeServer = ( }); legacyUptimePublicRestApiRoutes.forEach((route) => { - const { method, options, handler, path, ...rest } = uptimeRouteWrapper( + const { method, options, handler, path, security, ...rest } = uptimeRouteWrapper( createRouteWithAuth(libs, route), server ); const validate = rest.validate ? getRequestValidation(rest.validate) : rest.validate; - const routeDefinition = { - path, - validate, - options, - }; - switch (method) { case 'GET': router.versioned .get({ access: 'public', description: `Get uptime settings`, - path: routeDefinition.path, + path, options: { tags: options?.tags, }, @@ -98,6 +93,7 @@ export const initUptimeServer = ( .addVersion( { version: INITIAL_REST_VERSION, + security, validate: { request: { body: validate ? validate?.body : undefined, @@ -117,7 +113,7 @@ export const initUptimeServer = ( .put({ access: 'public', description: `Update uptime settings`, - path: routeDefinition.path, + path, options: { tags: options?.tags, }, @@ -125,6 +121,7 @@ export const initUptimeServer = ( .addVersion( { version: INITIAL_REST_VERSION, + security, validate: { request: { body: validate ? validate?.body : undefined, diff --git a/x-pack/plugins/observability_solution/uptime/tsconfig.json b/x-pack/plugins/observability_solution/uptime/tsconfig.json index 22b5e9afb738..1d60bc456170 100644 --- a/x-pack/plugins/observability_solution/uptime/tsconfig.json +++ b/x-pack/plugins/observability_solution/uptime/tsconfig.json @@ -68,7 +68,6 @@ "@kbn/safer-lodash-set", "@kbn/core-elasticsearch-client-server-mocks", "@kbn/core-http-server", - "@kbn/core-http-router-server-internal", "@kbn/actions-plugin", "@kbn/core-saved-objects-server", "@kbn/observability-ai-assistant-plugin", diff --git a/x-pack/plugins/observability_solution/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.test.ts b/x-pack/plugins/observability_solution/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.test.ts index f4e733baf6ef..025481c6b879 100644 --- a/x-pack/plugins/observability_solution/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.test.ts +++ b/x-pack/plugins/observability_solution/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { mockLayerList } from './__mocks__/regions_layer.mock'; import { useLayerList } from './use_layer_list'; diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_response_actions_form.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_response_actions_form.cy.ts index efb725bff3d5..4d8a83d8d6b5 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_response_actions_form.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_response_actions_form.cy.ts @@ -24,227 +24,232 @@ import { import { clickRuleName, inputQuery, typeInECSFieldInput } from '../../tasks/live_query'; import { closeDateTabIfVisible, closeToastIfVisible } from '../../tasks/integrations'; -describe('Alert Event Details - Response Actions Form', { tags: ['@ess', '@serverless'] }, () => { - let multiQueryPackId: string; - let multiQueryPackName: string; - let ruleId: string; - let ruleName: string; - let packId: string; - let packName: string; - const packData = packFixture(); - const multiQueryPackData = multiQueryPackFixture(); - before(() => { - initializeDataViews(); - }); - beforeEach(() => { - loadPack(packData).then((data) => { - packId = data.saved_object_id; - packName = data.name; - }); - loadPack(multiQueryPackData).then((data) => { - multiQueryPackId = data.saved_object_id; - multiQueryPackName = data.name; - }); - loadRule().then((data) => { - ruleId = data.id; - ruleName = data.name; - }); - }); - afterEach(() => { - cleanupPack(packId); - cleanupPack(multiQueryPackId); - cleanupRule(ruleId); - }); - - it('adds response actions with osquery with proper validation and form values', () => { - cy.visit('/app/security/rules'); - clickRuleName(ruleName); - cy.getBySel('globalLoadingIndicator').should('not.exist'); - cy.getBySel('editRuleSettingsLink').click(); - cy.getBySel('globalLoadingIndicator').should('not.exist'); - closeDateTabIfVisible(); - cy.getBySel('edit-rule-actions-tab').click(); - cy.getBySel('globalLoadingIndicator').should('not.exist'); - cy.contains('Response actions are run on each rule execution.'); - cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click(); - - cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { - cy.contains('Query is a required field'); - cy.contains('The timeout value must be 60 seconds or higher.').should('not.exist'); - }); - - // check if changing error state of one input doesn't clear other errors - START - cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { - cy.contains('Advanced').click(); - cy.getBySel('timeout-input').clear(); - cy.contains('The timeout value must be 60 seconds or higher.'); - }); - - cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { - cy.contains('Query is a required field'); - cy.contains('The timeout value must be 60 seconds or higher.'); - }); - - cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { - cy.getBySel('timeout-input').type('6'); - cy.contains('The timeout value must be 60 seconds or higher.'); - }); - cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { - cy.contains('Query is a required field'); - cy.contains('The timeout value must be 60 seconds or higher.'); - }); - cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { - cy.getBySel('timeout-input').type('6'); - cy.contains('The timeout value must be 60 seconds or higher.').should('not.exist'); - }); - cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { - cy.contains('Query is a required field'); - }); - cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { - cy.getBySel('timeout-input').type('6'); - }); - cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { - cy.contains('Query is a required field'); - cy.contains('The timeout value must be 60 seconds or higher.').should('not.exist'); - }); - // check if changing error state of one input doesn't clear other errors - END +// FLAKY: https://github.com/elastic/kibana/issues/169785 +describe.skip( + 'Alert Event Details - Response Actions Form', + { tags: ['@ess', '@serverless'] }, + () => { + let multiQueryPackId: string; + let multiQueryPackName: string; + let ruleId: string; + let ruleName: string; + let packId: string; + let packName: string; + const packData = packFixture(); + const multiQueryPackData = multiQueryPackFixture(); + before(() => { + initializeDataViews(); + }); + beforeEach(() => { + loadPack(packData).then((data) => { + packId = data.saved_object_id; + packName = data.name; + }); + loadPack(multiQueryPackData).then((data) => { + multiQueryPackId = data.saved_object_id; + multiQueryPackName = data.name; + }); + loadRule().then((data) => { + ruleId = data.id; + ruleName = data.name; + }); + }); + afterEach(() => { + cleanupPack(packId); + cleanupPack(multiQueryPackId); + cleanupRule(ruleId); + }); + + it('adds response actions with osquery with proper validation and form values', () => { + cy.visit('/app/security/rules'); + clickRuleName(ruleName); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel('editRuleSettingsLink').click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + closeDateTabIfVisible(); + cy.getBySel('edit-rule-actions-tab').click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.contains('Response actions are run on each rule execution.'); + cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click(); + + cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { + cy.contains('Query is a required field'); + cy.contains('The timeout value must be 60 seconds or higher.').should('not.exist'); + }); - cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { - cy.contains('Query is a required field'); - inputQuery('select * from uptime1'); - }); - cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click(); - cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => { - cy.contains('Run a set of queries in a pack').click(); - }); - cy.getBySel(RESPONSE_ACTIONS_ERRORS) - .within(() => { - cy.contains('Pack is a required field'); - }) - .should('exist'); - cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => { - cy.contains('Pack is a required field'); - cy.getBySel('comboBoxInput').click(); - cy.getBySel('comboBoxInput').type(`${packName}`); - cy.contains(`doesn't match any options`).should('not.exist'); - cy.getBySel('comboBoxInput').type('{downArrow}{enter}'); - }); + // check if changing error state of one input doesn't clear other errors - START + cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { + cy.contains('Advanced').click(); + cy.getBySel('timeout-input').clear(); + cy.contains('The timeout value must be 60 seconds or higher.'); + }); - cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click(); + cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { + cy.contains('Query is a required field'); + cy.contains('The timeout value must be 60 seconds or higher.'); + }); + + cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { + cy.getBySel('timeout-input').type('6'); + cy.contains('The timeout value must be 60 seconds or higher.'); + }); + cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { + cy.contains('Query is a required field'); + cy.contains('The timeout value must be 60 seconds or higher.'); + }); + cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { + cy.getBySel('timeout-input').type('6'); + cy.contains('The timeout value must be 60 seconds or higher.').should('not.exist'); + }); + cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { + cy.contains('Query is a required field'); + }); + cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { + cy.getBySel('timeout-input').type('6'); + }); + cy.getBySel(RESPONSE_ACTIONS_ERRORS).within(() => { + cy.contains('Query is a required field'); + cy.contains('The timeout value must be 60 seconds or higher.').should('not.exist'); + }); + // check if changing error state of one input doesn't clear other errors - END - cy.getBySel(RESPONSE_ACTIONS_ITEM_2) - .within(() => { + cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { cy.contains('Query is a required field'); - inputQuery('select * from uptime'); - cy.contains('Query is a required field').should('not.exist'); - cy.contains('Advanced').click(); - typeInECSFieldInput('label{downArrow}{enter}'); - cy.getBySel('osqueryColumnValueSelect').type('days{downArrow}{enter}'); - }) - .clickOutside(); - - cy.getBySel('ruleEditSubmitButton').click(); - cy.contains(`${ruleName} was saved`).should('exist'); - closeToastIfVisible(); - - cy.getBySel('globalLoadingIndicator').should('not.exist'); - cy.getBySel('editRuleSettingsLink').click(); - cy.getBySel('globalLoadingIndicator').should('not.exist'); - cy.getBySel('edit-rule-actions-tab').click(); - cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { - cy.contains('select * from uptime1'); - }); - cy.getBySel(RESPONSE_ACTIONS_ITEM_2).within(() => { - cy.contains('select * from uptime'); - cy.contains('Custom key/value pairs. e.g. {"application":"foo-bar","env":"production"}'); - cy.contains('Days of uptime'); - }); - cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => { - cy.getBySel('comboBoxSearchInput').should('have.value', packName); - cy.getBySel('comboBoxInput').type('{selectall}{backspace}{enter}'); - }); - cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { - cy.contains('select * from uptime1'); - cy.getBySel('remove-response-action').click(); - }); - cy.getBySel(RESPONSE_ACTIONS_ITEM_0) - .within(() => { - cy.getBySel('comboBoxSearchInput').click(); - cy.contains('Search for a pack to run'); + inputQuery('select * from uptime1'); + }); + cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click(); + cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => { + cy.contains('Run a set of queries in a pack').click(); + }); + cy.getBySel(RESPONSE_ACTIONS_ERRORS) + .within(() => { + cy.contains('Pack is a required field'); + }) + .should('exist'); + cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => { cy.contains('Pack is a required field'); - cy.getBySel('comboBoxInput').type(`${packName}{downArrow}{enter}`); - cy.contains(packName); - }) - .clickOutside(); - cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => { - cy.contains('select * from uptime'); - cy.contains('Custom key/value pairs. e.g. {"application":"foo-bar","env":"production"}'); - cy.contains('Days of uptime'); - }); - - cy.intercept('PUT', '/api/detection_engine/rules').as('saveRuleSingleQuery'); - - cy.getBySel('ruleEditSubmitButton').click(); - cy.wait('@saveRuleSingleQuery', { timeout: 15000 }).should(({ request }) => { - const oneQuery = [ - { - interval: 3600, - query: 'select * from uptime;', - id: Object.keys(packData.queries)[0], - }, - ]; - expect(request.body.response_actions[0].params.queries).to.deep.equal(oneQuery); - }); - - cy.contains(`${ruleName} was saved`).should('exist'); - closeToastIfVisible(); - - cy.getBySel('globalLoadingIndicator').should('not.exist'); - cy.getBySel('editRuleSettingsLink').click(); - cy.getBySel('globalLoadingIndicator').should('not.exist'); - - cy.getBySel('edit-rule-actions-tab').click(); - cy.getBySel(RESPONSE_ACTIONS_ITEM_0) - .within(() => { + cy.getBySel('comboBoxInput').click(); + cy.getBySel('comboBoxInput').type(`${packName}`); + cy.contains(`doesn't match any options`).should('not.exist'); + cy.getBySel('comboBoxInput').type('{downArrow}{enter}'); + }); + + cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click(); + + cy.getBySel(RESPONSE_ACTIONS_ITEM_2) + .within(() => { + cy.contains('Query is a required field'); + inputQuery('select * from uptime'); + cy.contains('Query is a required field').should('not.exist'); + cy.contains('Advanced').click(); + typeInECSFieldInput('label{downArrow}{enter}'); + cy.getBySel('osqueryColumnValueSelect').type('days{downArrow}{enter}'); + }) + .clickOutside(); + + cy.getBySel('ruleEditSubmitButton').click(); + cy.contains(`${ruleName} was saved`).should('exist'); + closeToastIfVisible(); + + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel('editRuleSettingsLink').click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel('edit-rule-actions-tab').click(); + cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { + cy.contains('select * from uptime1'); + }); + cy.getBySel(RESPONSE_ACTIONS_ITEM_2).within(() => { + cy.contains('select * from uptime'); + cy.contains('Custom key/value pairs. e.g. {"application":"foo-bar","env":"production"}'); + cy.contains('Days of uptime'); + }); + cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => { cy.getBySel('comboBoxSearchInput').should('have.value', packName); - cy.getBySel('comboBoxInput').type( - `{selectall}{backspace}${multiQueryPackName}{downArrow}{enter}` - ); - cy.contains('SELECT * FROM memory_info;'); - cy.contains('SELECT * FROM system_info;'); - }) - .clickOutside(); - - cy.getBySel(RESPONSE_ACTIONS_ITEM_1) - .within(() => { + cy.getBySel('comboBoxInput').type('{selectall}{backspace}{enter}'); + }); + cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => { + cy.contains('select * from uptime1'); + cy.getBySel('remove-response-action').click(); + }); + cy.getBySel(RESPONSE_ACTIONS_ITEM_0) + .within(() => { + cy.getBySel('comboBoxSearchInput').click(); + cy.contains('Search for a pack to run'); + cy.contains('Pack is a required field'); + cy.getBySel('comboBoxInput').type(`${packName}{downArrow}{enter}`); + cy.contains(packName); + }) + .clickOutside(); + cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => { cy.contains('select * from uptime'); cy.contains('Custom key/value pairs. e.g. {"application":"foo-bar","env":"production"}'); cy.contains('Days of uptime'); - }) - .clickOutside(); - cy.intercept('PUT', '/api/detection_engine/rules').as('saveRuleMultiQuery'); - - cy.contains('Save changes').click(); - cy.wait('@saveRuleMultiQuery', { timeout: 15000 }).should(({ request }) => { - const threeQueries = [ - { - interval: 3600, - query: 'SELECT * FROM memory_info;', - platform: 'linux', - id: Object.keys(multiQueryPackData.queries)[0], - }, - { - interval: 3600, - query: 'SELECT * FROM system_info;', - id: Object.keys(multiQueryPackData.queries)[1], - }, - { - interval: 10, - query: 'select opera_extensions.* from users join opera_extensions using (uid);', - id: Object.keys(multiQueryPackData.queries)[2], - }, - ]; - expect(request.body.response_actions[0].params.queries).to.deep.equal(threeQueries); - }); - }); -}); + }); + + cy.intercept('PUT', '/api/detection_engine/rules').as('saveRuleSingleQuery'); + + cy.getBySel('ruleEditSubmitButton').click(); + cy.wait('@saveRuleSingleQuery', { timeout: 15000 }).should(({ request }) => { + const oneQuery = [ + { + interval: 3600, + query: 'select * from uptime;', + id: Object.keys(packData.queries)[0], + }, + ]; + expect(request.body.response_actions[0].params.queries).to.deep.equal(oneQuery); + }); + + cy.contains(`${ruleName} was saved`).should('exist'); + closeToastIfVisible(); + + cy.getBySel('globalLoadingIndicator').should('not.exist'); + cy.getBySel('editRuleSettingsLink').click(); + cy.getBySel('globalLoadingIndicator').should('not.exist'); + + cy.getBySel('edit-rule-actions-tab').click(); + cy.getBySel(RESPONSE_ACTIONS_ITEM_0) + .within(() => { + cy.getBySel('comboBoxSearchInput').should('have.value', packName); + cy.getBySel('comboBoxInput').type( + `{selectall}{backspace}${multiQueryPackName}{downArrow}{enter}` + ); + cy.contains('SELECT * FROM memory_info;'); + cy.contains('SELECT * FROM system_info;'); + }) + .clickOutside(); + + cy.getBySel(RESPONSE_ACTIONS_ITEM_1) + .within(() => { + cy.contains('select * from uptime'); + cy.contains('Custom key/value pairs. e.g. {"application":"foo-bar","env":"production"}'); + cy.contains('Days of uptime'); + }) + .clickOutside(); + cy.intercept('PUT', '/api/detection_engine/rules').as('saveRuleMultiQuery'); + + cy.contains('Save changes').click(); + cy.wait('@saveRuleMultiQuery', { timeout: 15000 }).should(({ request }) => { + const threeQueries = [ + { + interval: 3600, + query: 'SELECT * FROM memory_info;', + platform: 'linux', + id: Object.keys(multiQueryPackData.queries)[0], + }, + { + interval: 3600, + query: 'SELECT * FROM system_info;', + id: Object.keys(multiQueryPackData.queries)[1], + }, + { + interval: 10, + query: 'select opera_extensions.* from users join opera_extensions using (uid);', + id: Object.keys(multiQueryPackData.queries)[2], + }, + ]; + expect(request.body.response_actions[0].params.queries).to.deep.equal(threeQueries); + }); + }); + } +); diff --git a/x-pack/plugins/osquery/kibana.jsonc b/x-pack/plugins/osquery/kibana.jsonc index cd8e929da43d..3cd4cfc7f005 100644 --- a/x-pack/plugins/osquery/kibana.jsonc +++ b/x-pack/plugins/osquery/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/osquery-plugin", "owner": "@elastic/security-defend-workflows", + "group": "platform", + "visibility": "shared", "plugin": { "id": "osquery", "server": true, diff --git a/x-pack/plugins/osquery/server/routes/asset/get_assets_status_route.ts b/x-pack/plugins/osquery/server/routes/asset/get_assets_status_route.ts index f217b60bf245..ab7a52c6fca6 100644 --- a/x-pack/plugins/osquery/server/routes/asset/get_assets_status_route.ts +++ b/x-pack/plugins/osquery/server/routes/asset/get_assets_status_route.ts @@ -23,7 +23,11 @@ export const getAssetsStatusRoute = (router: IRouter, osqueryContext: OsqueryApp .get({ access: 'internal', path: '/internal/osquery/assets', - options: { tags: [`access:${PLUGIN_ID}-writePacks`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-writePacks`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts b/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts index 690ea4206b84..7c8ccc5bb0ba 100644 --- a/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts +++ b/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts @@ -28,7 +28,11 @@ export const updateAssetsRoute = (router: IRouter, osqueryContext: OsqueryAppCon .post({ access: 'internal', path: '/internal/osquery/assets/update', - options: { tags: [`access:${PLUGIN_ID}-writePacks`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-writePacks`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_details.ts b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_details.ts index c1d445fd4018..2bf8eea1811c 100644 --- a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_details.ts +++ b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_details.ts @@ -17,7 +17,11 @@ export const getAgentDetailsRoute = (router: IRouter, osqueryContext: OsqueryApp .get({ access: 'internal', path: '/internal/osquery/fleet_wrapper/agents/{id}', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts index 64b10f0a8248..e347299b42d3 100644 --- a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts +++ b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policies.ts @@ -21,7 +21,11 @@ export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAp .get({ access: 'internal', path: '/internal/osquery/fleet_wrapper/agent_policies', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policy.ts b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policy.ts index bad5b01289d5..9535b36ed3e2 100644 --- a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policy.ts +++ b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_policy.ts @@ -18,7 +18,11 @@ export const getAgentPolicyRoute = (router: IRouter, osqueryContext: OsqueryAppC .get({ access: 'internal', path: '/internal/osquery/fleet_wrapper/agent_policies/{id}', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_status_for_agent_policy.ts b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_status_for_agent_policy.ts index 64cd9d9f8ddd..bfc77e9d6d6a 100644 --- a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_status_for_agent_policy.ts +++ b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agent_status_for_agent_policy.ts @@ -28,7 +28,11 @@ export const getAgentStatusForAgentPolicyRoute = ( .get({ access: 'internal', path: '/internal/osquery/fleet_wrapper/agent_status', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agents.ts b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agents.ts index 195e550077aa..a04967a3e3c7 100644 --- a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agents.ts +++ b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_agents.ts @@ -26,7 +26,11 @@ export const getAgentsRoute = (router: IRouter, osqueryContext: OsqueryAppContex .get({ access: 'internal', path: '/internal/osquery/fleet_wrapper/agents', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_package_policies.ts b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_package_policies.ts index 86719125b97e..748c9102d636 100644 --- a/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_package_policies.ts +++ b/x-pack/plugins/osquery/server/routes/fleet_wrapper/get_package_policies.ts @@ -17,7 +17,11 @@ export const getPackagePoliciesRoute = (router: IRouter, osqueryContext: Osquery .get({ access: 'internal', path: '/internal/osquery/fleet_wrapper/package_policies', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/live_query/find_live_query_route.ts b/x-pack/plugins/osquery/server/routes/live_query/find_live_query_route.ts index 008964f2468b..b89a69e5aeb2 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/find_live_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/find_live_query_route.ts @@ -29,7 +29,12 @@ export const findLiveQueryRoute = (router: IRouter<DataRequestHandlerContext>) = .get({ access: 'public', path: '/api/osquery/live_queries', - options: { tags: ['api', `access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, + options: { tags: ['api'] }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/live_query/get_live_query_details_route.ts b/x-pack/plugins/osquery/server/routes/live_query/get_live_query_details_route.ts index 2b32a3269b69..7406887ecb59 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/get_live_query_details_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/get_live_query_details_route.ts @@ -34,7 +34,11 @@ export const getLiveQueryDetailsRoute = (router: IRouter<DataRequestHandlerConte .get({ access: 'public', path: '/api/osquery/live_queries/{id}', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/live_query/get_live_query_results_route.ts b/x-pack/plugins/osquery/server/routes/live_query/get_live_query_results_route.ts index d73b44a79434..8c3b2a6d19a9 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/get_live_query_results_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/get_live_query_results_route.ts @@ -36,7 +36,11 @@ export const getLiveQueryResultsRoute = (router: IRouter<DataRequestHandlerConte .get({ access: 'public', path: '/api/osquery/live_queries/{id}/results/{actionId}', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts index edb2293d5bce..28c8a6e98da7 100644 --- a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts @@ -40,7 +40,11 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte .post({ access: 'public', path: '/api/osquery/packs', - options: { tags: [`access:${PLUGIN_ID}-writePacks`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-writePacks`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/pack/delete_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/delete_pack_route.ts index e4f7a9e5f77c..36d88bc9fb8f 100644 --- a/x-pack/plugins/osquery/server/routes/pack/delete_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/delete_pack_route.ts @@ -25,7 +25,11 @@ export const deletePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte .delete({ access: 'public', path: '/api/osquery/packs/{id}', - options: { tags: [`access:${PLUGIN_ID}-writePacks`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-writePacks`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts index 450f3ff805ac..4b2bf6b21ddf 100644 --- a/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts @@ -23,7 +23,11 @@ export const findPackRoute = (router: IRouter) => { .get({ access: 'public', path: '/api/osquery/packs', - options: { tags: [`access:${PLUGIN_ID}-readPacks`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-readPacks`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts index 724deedf1984..4ec3e6806a55 100644 --- a/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts @@ -25,7 +25,11 @@ export const readPackRoute = (router: IRouter) => { .get({ access: 'public', path: '/api/osquery/packs/{id}', - options: { tags: [`access:${PLUGIN_ID}-readPacks`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-readPacks`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts index 532aa2c77273..0872a16c5bb0 100644 --- a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts @@ -44,7 +44,11 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte .put({ access: 'public', path: '/api/osquery/packs/{id}', - options: { tags: [`access:${PLUGIN_ID}-writePacks`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-writePacks`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts b/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts index b31da0c0e24d..2470b2f0c418 100644 --- a/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts +++ b/x-pack/plugins/osquery/server/routes/privileges_check/privileges_check_route.ts @@ -15,8 +15,10 @@ export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryApp .get({ access: 'internal', path: '/internal/osquery/privileges_check', - options: { - tags: [`access:${PLUGIN_ID}-readLiveQueries`], + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-readLiveQueries`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts index 1be4cb24a2ea..35d3b34b1ca8 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts @@ -23,7 +23,11 @@ export const createSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp .post({ access: 'public', path: '/api/osquery/saved_queries', - options: { tags: [`access:${PLUGIN_ID}-writeSavedQueries`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-writeSavedQueries`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/saved_query/delete_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/delete_saved_query_route.ts index f33040e167c6..1d76bcf2d471 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/delete_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/delete_saved_query_route.ts @@ -20,7 +20,11 @@ export const deleteSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp .delete({ access: 'public', path: '/api/osquery/saved_queries/{id}', - options: { tags: [`access:${PLUGIN_ID}-writeSavedQueries`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-writeSavedQueries`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts index 88ccc120d0fd..02d4b0229fd1 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts @@ -25,7 +25,11 @@ export const findSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppC .get({ access: 'public', path: '/api/osquery/saved_queries', - options: { tags: [`access:${PLUGIN_ID}-readSavedQueries`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-readSavedQueries`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts index 706304300c40..e3100baa4b3f 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts @@ -23,7 +23,11 @@ export const readSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppC .get({ access: 'public', path: '/api/osquery/saved_queries/{id}', - options: { tags: [`access:${PLUGIN_ID}-readSavedQueries`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-readSavedQueries`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts index 4225f0f223cd..12dd5b2bf73d 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts @@ -30,7 +30,11 @@ export const updateSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp .put({ access: 'public', path: '/api/osquery/saved_queries/{id}', - options: { tags: [`access:${PLUGIN_ID}-writeSavedQueries`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-writeSavedQueries`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/osquery/server/routes/status/create_status_route.ts b/x-pack/plugins/osquery/server/routes/status/create_status_route.ts index 8b6f75100a37..b6e6f988f454 100644 --- a/x-pack/plugins/osquery/server/routes/status/create_status_route.ts +++ b/x-pack/plugins/osquery/server/routes/status/create_status_route.ts @@ -27,7 +27,11 @@ export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppCon .get({ access: 'internal', path: '/internal/osquery/status', - options: { tags: [`access:${PLUGIN_ID}-read`] }, + security: { + authz: { + requiredPrivileges: [`${PLUGIN_ID}-read`], + }, + }, }) .addVersion( { diff --git a/x-pack/plugins/reporting/server/config/index.test.ts b/x-pack/plugins/reporting/server/config/index.test.ts index a517affac54e..d6b0f31ddc5f 100644 --- a/x-pack/plugins/reporting/server/config/index.test.ts +++ b/x-pack/plugins/reporting/server/config/index.test.ts @@ -54,19 +54,8 @@ describe('deprecations', () => { `); }); - it('logs a warning if csv.enablePanelActionDownload: true is set', () => { - const { messages } = applyReportingDeprecations({ csv: { enablePanelActionDownload: true } }); - expect(messages).toMatchInlineSnapshot(` - Array [ - "The default mechanism for Reporting privileges will work differently in future versions, which will affect the behavior of this cluster. Set \\"xpack.reporting.roles.enabled\\" to \\"false\\" to adopt the future behavior before upgrading.", - "The \\"xpack.reporting.csv.enablePanelActionDownload\\" setting is deprecated.", - ] - `); - }); - it('does not log a warning recommended settings are used', () => { const { messages } = applyReportingDeprecations({ - csv: { enablePanelActionDownload: false }, roles: { enabled: false }, }); expect(messages).toMatchInlineSnapshot(`Array []`); diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index 709d072a9035..c99e2667eb6d 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -13,10 +13,7 @@ import { ConfigSchema, ReportingConfigType } from '@kbn/reporting-server'; export const config: PluginConfigDescriptor<ReportingConfigType> = { exposeToBrowser: { - csv: { - enablePanelActionDownload: true, - scroll: true, - }, + csv: { scroll: true }, poll: true, roles: true, export_types: true, @@ -69,44 +66,11 @@ export const config: PluginConfigDescriptor<ReportingConfigType> = { }, }); } - - if (reporting?.csv?.enablePanelActionDownload === true) { - addDeprecation({ - configPath: `${fromPath}.csv.enablePanelActionDownload`, - title: i18n.translate('xpack.reporting.deprecations.csvPanelActionDownload.title', { - defaultMessage: - 'The setting to enable CSV Download from saved search panels in dashboards is deprecated.', - }), - level: 'warning', - message: i18n.translate('xpack.reporting.deprecations.csvPanelActionDownload.message', { - defaultMessage: `The "{enablePanelActionDownload}" setting is deprecated.`, - values: { - enablePanelActionDownload: `${fromPath}.csv.enablePanelActionDownload`, - }, - }), - correctiveActions: { - manualSteps: [ - i18n.translate('xpack.reporting.deprecations.csvPanelActionDownload.manualStep1', { - defaultMessage: - 'Remove "{enablePanelActionDownload}" from `kibana.yml` or change the setting to `false`.', - values: { - enablePanelActionDownload: `${fromPath}.csv.enablePanelActionDownload`, - }, - }), - i18n.translate('xpack.reporting.deprecations.csvPanelActionDownload.manualStep2', { - defaultMessage: - 'Use the replacement panel action to generate CSV reports from saved search panels in the Dashboard application.', - }), - ], - }, - }); - } }, ], exposeToUsage: { capture: { maxAttempts: true }, csv: { - enablePanelActionDownload: true, maxSizeBytes: true, scroll: { size: true, duration: true }, }, diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts index cb3f9b37541a..b76a1e99f6d1 100644 --- a/x-pack/plugins/rollup/server/types.ts +++ b/x-pack/plugins/rollup/server/types.ts @@ -5,24 +5,22 @@ * 2.0. */ -import { IRouter } from '@kbn/core/server'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; -import { VisTypeTimeseriesSetup } from '@kbn/vis-type-timeseries-plugin/server'; +import type { IRouter } from '@kbn/core/server'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; -import { getCapabilitiesForRollupIndices } from '@kbn/data-plugin/server'; -import { IndexManagementPluginSetup } from '@kbn/index-management-plugin/server'; -import { FeaturesPluginSetup } from '@kbn/features-plugin/server'; -import { DataViewsServerPluginSetup } from '@kbn/data-views-plugin/server'; -import { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; -import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; -import { License } from './services'; -import { IndexPatternsFetcher } from './shared_imports'; -import { handleEsError } from './shared_imports'; -import { formatEsError } from './lib/format_es_error'; +import type { getCapabilitiesForRollupIndices } from '@kbn/data-plugin/server'; +import type { IndexManagementPluginSetup } from '@kbn/index-management-plugin/server'; +import type { FeaturesPluginSetup } from '@kbn/features-plugin/server'; +import type { DataViewsServerPluginSetup } from '@kbn/data-views-plugin/server'; +import type { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; +import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; +import type { License } from './services'; +import type { IndexPatternsFetcher } from './shared_imports'; +import type { handleEsError } from './shared_imports'; +import type { formatEsError } from './lib/format_es_error'; export interface Dependencies { indexManagement?: IndexManagementPluginSetup; - visTypeTimeseries?: VisTypeTimeseriesSetup; usageCollection?: UsageCollectionSetup; licensing: LicensingPluginSetup; features: FeaturesPluginSetup; diff --git a/x-pack/plugins/rollup/tsconfig.json b/x-pack/plugins/rollup/tsconfig.json index 2957a4bcb17c..a74f689515de 100644 --- a/x-pack/plugins/rollup/tsconfig.json +++ b/x-pack/plugins/rollup/tsconfig.json @@ -19,7 +19,6 @@ "@kbn/home-plugin", "@kbn/index-management-plugin", "@kbn/usage-collection-plugin", - "@kbn/vis-type-timeseries-plugin", // required bundles "@kbn/kibana-utils-plugin", "@kbn/kibana-react-plugin", diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.ts index 0ecab0aaa094..ca5e72c529d7 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.ts @@ -7,7 +7,7 @@ import { IRouter } from '@kbn/core/server'; import * as t from 'io-ts'; -import { id as _id } from '@kbn/securitysolution-io-ts-list-types'; +import { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; import { transformError } from '@kbn/securitysolution-es-utils'; import { RacRequestHandlerContext } from '../types'; @@ -23,7 +23,7 @@ export const getAlertByIdRoute = (router: IRouter<RacRequestHandlerContext>) => t.intersection([ t.exact( t.type({ - id: _id, + id: NonEmptyString, }) ), t.exact( diff --git a/x-pack/plugins/rule_registry/tsconfig.json b/x-pack/plugins/rule_registry/tsconfig.json index 8c244ed95e01..794917b37012 100644 --- a/x-pack/plugins/rule_registry/tsconfig.json +++ b/x-pack/plugins/rule_registry/tsconfig.json @@ -24,7 +24,6 @@ "@kbn/spaces-plugin", "@kbn/securitysolution-es-utils", "@kbn/securitysolution-io-ts-types", - "@kbn/securitysolution-io-ts-list-types", "@kbn/es-types", "@kbn/core-elasticsearch-client-server-mocks", "@kbn/logging-mocks", diff --git a/x-pack/plugins/search_connectors/kibana.jsonc b/x-pack/plugins/search_connectors/kibana.jsonc index 83d052ac232a..6290b9692b38 100644 --- a/x-pack/plugins/search_connectors/kibana.jsonc +++ b/x-pack/plugins/search_connectors/kibana.jsonc @@ -2,8 +2,11 @@ "type": "plugin", "id": "@kbn/search-connectors-plugin", "owner": "@elastic/search-kibana", - "group": "platform", - "visibility": "shared", + // TODO this is currently used from Observability too, must be refactored before solution-specific builds + // see x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/search_connector_tab.tsx + // cc sphilipse + "group": "search", + "visibility": "private", "description": "Plugin hosting shared features for connectors", "plugin": { "id": "searchConnectors", diff --git a/x-pack/plugins/search_indices/public/components/quick_stats/quick_stat.tsx b/x-pack/plugins/search_indices/public/components/quick_stats/quick_stat.tsx index cdd5f1240848..9df28ccec4bd 100644 --- a/x-pack/plugins/search_indices/public/components/quick_stats/quick_stat.tsx +++ b/x-pack/plugins/search_indices/public/components/quick_stats/quick_stat.tsx @@ -18,6 +18,7 @@ import { EuiText, useEuiTheme, useGeneratedHtmlId, + EuiIconTip, } from '@elastic/eui'; interface BaseQuickStatProps { @@ -33,6 +34,7 @@ interface BaseQuickStatProps { }>; setOpen: (open: boolean) => void; first?: boolean; + tooltipContent?: string; } export const QuickStat: React.FC<BaseQuickStatProps> = ({ @@ -45,6 +47,7 @@ export const QuickStat: React.FC<BaseQuickStatProps> = ({ secondaryTitle, iconColor, content, + tooltipContent, ...rest }) => { const { euiTheme } = useEuiTheme(); @@ -93,6 +96,11 @@ export const QuickStat: React.FC<BaseQuickStatProps> = ({ {secondaryTitle} </EuiText> </EuiFlexItem> + {tooltipContent && ( + <EuiFlexItem> + <EuiIconTip content={tooltipContent} /> + </EuiFlexItem> + )} </EuiFlexGroup> </EuiPanel> } diff --git a/x-pack/plugins/search_indices/public/components/quick_stats/quick_stats.tsx b/x-pack/plugins/search_indices/public/components/quick_stats/quick_stats.tsx index e051fee17d2e..de1ebe0a8dcc 100644 --- a/x-pack/plugins/search_indices/public/components/quick_stats/quick_stats.tsx +++ b/x-pack/plugins/search_indices/public/components/quick_stats/quick_stats.tsx @@ -28,6 +28,7 @@ export interface QuickStatsProps { index: Index; mappings: Mappings; indexDocuments: IndexDocuments; + tooltipContent?: string; } export const SetupAISearchButton: React.FC = () => { @@ -107,6 +108,10 @@ export const QuickStats: React.FC<QuickStatsProps> = ({ index, mappings, indexDo description: index.size ?? '0b', }, ]} + tooltipContent={i18n.translate('xpack.searchIndices.quickStats.documentCountTooltip', { + defaultMessage: + 'This excludes nested documents, which Elasticsearch uses internally to store chunks of vectors.', + })} first /> </EuiFlexItem> diff --git a/x-pack/plugins/search_inference_endpoints/common/doc_links.ts b/x-pack/plugins/search_inference_endpoints/common/doc_links.ts index bb426180a36e..483803e970d8 100644 --- a/x-pack/plugins/search_inference_endpoints/common/doc_links.ts +++ b/x-pack/plugins/search_inference_endpoints/common/doc_links.ts @@ -15,7 +15,7 @@ class InferenceEndpointsDocLinks { constructor() {} setDocLinks(newDocLinks: DocLinks) { - this.createInferenceEndpoint = newDocLinks.enterpriseSearch.inferenceApiCreate; + this.createInferenceEndpoint = newDocLinks.inferenceManagement.inferenceAPIDocumentation; this.semanticSearchElser = newDocLinks.enterpriseSearch.elser; this.semanticSearchE5 = newDocLinks.enterpriseSearch.e5Model; } diff --git a/x-pack/plugins/search_inference_endpoints/common/translations.ts b/x-pack/plugins/search_inference_endpoints/common/translations.ts index 0f1aa4a8abcf..8b63725c59f9 100644 --- a/x-pack/plugins/search_inference_endpoints/common/translations.ts +++ b/x-pack/plugins/search_inference_endpoints/common/translations.ts @@ -26,49 +26,6 @@ export const MANAGE_INFERENCE_ENDPOINTS_LABEL = i18n.translate( } ); -export const CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription', - { - defaultMessage: - "Inference endpoints enable you to perform inference tasks using NLP models provided by third-party services or Elastic's built-in models like ELSER and E5. Set up tasks such as text embedding, completions, reranking, and more by using the Create Inference API.", - } -); - -export const START_WITH_PREPARED_ENDPOINTS_LABEL = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel', - { - defaultMessage: 'Learn more about built-in NLP models:', - } -); - -export const ELSER_TITLE = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle', - { - defaultMessage: 'ELSER', - } -); - -export const LEARN_HOW_TO_CREATE_INFERENCE_ENDPOINTS_LINK = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.learnHowToCreateInferenceEndpoints', - { - defaultMessage: 'Learn how to create inference endpoints', - } -); - -export const SEMANTIC_SEARCH_WITH_ELSER_LINK = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithElser', - { - defaultMessage: 'Semantic search with ELSER', - } -); - -export const SEMANTIC_SEARCH_WITH_E5_LINK = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithE5', - { - defaultMessage: 'Semantic search with E5 Multilingual', - } -); - export const VIEW_YOUR_MODELS_LINK = i18n.translate( 'xpack.searchInferenceEndpoints.viewYourModels', { @@ -83,25 +40,6 @@ export const API_DOCUMENTATION_LINK = i18n.translate( } ); -export const ELSER_DESCRIPTION = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription', - { - defaultMessage: "ELSER is Elastic's sparse vector NLP model for semantic search in English.", - } -); - -export const E5_TITLE = i18n.translate('xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title', { - defaultMessage: 'E5 Multilingual', -}); - -export const E5_DESCRIPTION = i18n.translate( - 'xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description', - { - defaultMessage: - 'E5 is a third-party NLP model that enables you to perform multilingual semantic search by using dense vector representations.', - } -); - export const ERROR_TITLE = i18n.translate('xpack.searchInferenceEndpoints.inferenceId.errorTitle', { defaultMessage: 'Error adding inference endpoint', }); diff --git a/x-pack/plugins/search_inference_endpoints/kibana.jsonc b/x-pack/plugins/search_inference_endpoints/kibana.jsonc index f535a9df27e9..25b7b391b955 100644 --- a/x-pack/plugins/search_inference_endpoints/kibana.jsonc +++ b/x-pack/plugins/search_inference_endpoints/kibana.jsonc @@ -24,6 +24,7 @@ "optionalPlugins": [ "cloud", "console", + "serverless" ], "requiredBundles": [ "kibanaReact" diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts index 37c65ebb9a31..931994c46afc 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts @@ -34,8 +34,4 @@ export const DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE: AllInferenceEndpointsTable }; export const PIPELINE_URL = 'ingest/ingest_pipelines'; - -export const PRECONFIGURED_ENDPOINTS = { - ELSER: '.elser-2-elasticsearch', - E5: '.multilingual-e5-small-elasticsearch', -}; +export const SERVERLESS_INDEX_MANAGEMENT_URL = 'index_details'; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/multi_select_filter.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/multi_select_filter.tsx index 790bb5ec0991..371b204e0acd 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/multi_select_filter.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/multi_select_filter.tsx @@ -33,6 +33,7 @@ interface UseFilterParams { options: MultiSelectFilterOption[]; renderOption?: (option: MultiSelectFilterOption) => React.ReactNode; selectedOptionKeys?: string[]; + dataTestSubj?: string; } export const MultiSelectFilter: React.FC<UseFilterParams> = ({ @@ -41,6 +42,7 @@ export const MultiSelectFilter: React.FC<UseFilterParams> = ({ options: rawOptions, selectedOptionKeys = [], renderOption, + dataTestSubj, }) => { const { euiTheme } = useEuiTheme(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -55,7 +57,7 @@ export const MultiSelectFilter: React.FC<UseFilterParams> = ({ ); return ( - <EuiFilterGroup> + <EuiFilterGroup data-test-subj={dataTestSubj}> <EuiPopover ownFocus button={ diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/service_provider_filter.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/service_provider_filter.tsx index 3d7f9568428e..56420f98bfac 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/service_provider_filter.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/service_provider_filter.tsx @@ -38,6 +38,7 @@ export const ServiceProviderFilter: React.FC<Props> = ({ optionKeys, onChange }) options={options} renderOption={(option) => option.label} selectedOptionKeys={optionKeys} + dataTestSubj="service-field-endpoints" /> ); }; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/task_type_filter.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/task_type_filter.tsx index e9c32503dba7..071069a880b3 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/task_type_filter.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/filter/task_type_filter.tsx @@ -38,6 +38,7 @@ export const TaskTypeFilter: React.FC<Props> = ({ optionKeys, onChange }) => { options={options} renderOption={(option) => option.label} selectedOptionKeys={optionKeys} + dataTestSubj="type-field-endpoints" /> ); }; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.test.tsx new file mode 100644 index 000000000000..bfdc1edd31bd --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.test.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, fireEvent, screen } from '@testing-library/react'; +import React from 'react'; + +import { IndexItem } from './index_item'; +import { InferenceUsageInfo } from '../../../../types'; +import { useKibana } from '../../../../../../hooks/use_kibana'; + +jest.mock('../../../../../../hooks/use_kibana'); +const mockUseKibana = useKibana as jest.Mock; +const mockNavigateToApp = jest.fn(); + +describe('Index Item', () => { + const item: InferenceUsageInfo = { + id: 'index-1', + type: 'Index', + }; + beforeEach(() => { + mockUseKibana.mockReturnValue({ + services: { + application: { + navigateToApp: mockNavigateToApp, + }, + }, + }); + + render(<IndexItem usageItem={item} />); + }); + + it('renders', () => { + expect(screen.getByText('index-1')).toBeInTheDocument(); + expect(screen.getByText('Index')).toBeInTheDocument(); + }); + + it('opens index in a new tab', () => { + fireEvent.click(screen.getByRole('button')); + expect(mockNavigateToApp).toHaveBeenCalledWith('enterpriseSearchContent', { + openInNewTab: true, + path: 'search_indices/index-1', + }); + }); +}); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.tsx similarity index 77% rename from x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.tsx rename to x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.tsx index 577b9f8aa0e2..a62a9b9f3cae 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/index_item.tsx @@ -16,34 +16,35 @@ import { EuiIcon, EuiSpacer, } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback } from 'react'; import { ENTERPRISE_SEARCH_CONTENT_APP_ID } from '@kbn/deeplinks-search'; -import { MANAGEMENT_APP_ID } from '@kbn/deeplinks-management/constants'; + +import { SEARCH_INDICES } from '@kbn/deeplinks-search/constants'; import { useKibana } from '../../../../../../hooks/use_kibana'; import { InferenceUsageInfo } from '../../../../types'; -import { PIPELINE_URL } from '../../../../constants'; +import { SERVERLESS_INDEX_MANAGEMENT_URL } from '../../../../constants'; interface UsageProps { usageItem: InferenceUsageInfo; } -export const UsageItem: React.FC<UsageProps> = ({ usageItem }) => { +export const IndexItem: React.FC<UsageProps> = ({ usageItem }) => { const { - services: { application }, + services: { application, serverless }, } = useKibana(); - const handleNavigateToIndex = () => { - if (usageItem.type === 'Index') { - application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, { - path: `search_indices/${usageItem.id}`, + const navigateToIndex = useCallback(() => { + if (serverless) { + application?.navigateToApp(SEARCH_INDICES, { + path: `${SERVERLESS_INDEX_MANAGEMENT_URL}/${usageItem.id}/data`, openInNewTab: true, }); - } else if (usageItem.type === 'Pipeline') { - application?.navigateToApp(MANAGEMENT_APP_ID, { - path: `${PIPELINE_URL}?pipeline=${usageItem.id}`, + } else { + application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, { + path: `search_indices/${usageItem.id}`, openInNewTab: true, }); } - }; + }, [application, serverless, usageItem.id]); return ( <EuiFlexGroup gutterSize="s" direction="column" data-test-subj="usageItem"> @@ -62,7 +63,7 @@ export const UsageItem: React.FC<UsageProps> = ({ usageItem }) => { </EuiFlexGroup> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiLink data-test-subj="navigateToIndexPage" onClick={handleNavigateToIndex}> + <EuiLink data-test-subj="navigateToIndexPage" onClick={navigateToIndex}> <EuiIcon size="s" type="popout" /> </EuiLink> </EuiFlexItem> diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/list_usage_results.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/list_usage_results.tsx index d42b0f673525..05aaaa8bb9ea 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/list_usage_results.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/list_usage_results.tsx @@ -10,7 +10,8 @@ import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { InferenceUsageInfo } from '../../../../types'; import * as i18n from '../delete/confirm_delete_endpoint/translations'; -import { UsageItem } from './usage_item'; +import { IndexItem } from './index_item'; +import { PipelineItem } from './pipeline_item'; interface ListUsageResultsProps { list: InferenceUsageInfo[]; @@ -35,9 +36,13 @@ export const ListUsageResults: React.FC<ListUsageResultsProps> = ({ list }) => { <EuiFlexItem> {list .filter((item) => item.id.toLowerCase().includes(term.toLowerCase())) - .map((item, id) => ( - <UsageItem usageItem={item} key={id} /> - ))} + .map((item, id) => { + if (item.type === 'Pipeline') { + return <PipelineItem usageItem={item} key={id} />; + } else { + return <IndexItem usageItem={item} key={id} />; + } + })} </EuiFlexItem> </EuiFlexGroup> ); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.test.tsx similarity index 59% rename from x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.test.tsx rename to x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.test.tsx index 6c6899c71922..8a1bc7a78cab 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/usage_item.test.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.test.tsx @@ -8,7 +8,7 @@ import { render, fireEvent, screen } from '@testing-library/react'; import React from 'react'; -import { UsageItem } from './usage_item'; +import { PipelineItem } from './pipeline_item'; import { InferenceUsageInfo } from '../../../../types'; import { useKibana } from '../../../../../../hooks/use_kibana'; @@ -16,7 +16,11 @@ jest.mock('../../../../../../hooks/use_kibana'); const mockUseKibana = useKibana as jest.Mock; const mockNavigateToApp = jest.fn(); -describe('UsageItem', () => { +describe('Pipeline item', () => { + const item: InferenceUsageInfo = { + id: 'pipeline-1', + type: 'Pipeline', + }; beforeEach(() => { mockUseKibana.mockReturnValue({ services: { @@ -25,41 +29,10 @@ describe('UsageItem', () => { }, }, }); - }); - - describe('index', () => { - const item: InferenceUsageInfo = { - id: 'index-1', - type: 'Index', - }; - - beforeEach(() => { - render(<UsageItem usageItem={item} />); - }); - - it('renders', () => { - expect(screen.getByText('index-1')).toBeInTheDocument(); - expect(screen.getByText('Index')).toBeInTheDocument(); - }); - - it('opens index in a new tab', () => { - fireEvent.click(screen.getByRole('button')); - expect(mockNavigateToApp).toHaveBeenCalledWith('enterpriseSearchContent', { - openInNewTab: true, - path: 'search_indices/index-1', - }); - }); + render(<PipelineItem usageItem={item} />); }); describe('pipeline', () => { - const item: InferenceUsageInfo = { - id: 'pipeline-1', - type: 'Pipeline', - }; - - beforeEach(() => { - render(<UsageItem usageItem={item} />); - }); it('renders', () => { expect(screen.getByText('pipeline-1')).toBeInTheDocument(); expect(screen.getByText('Pipeline')).toBeInTheDocument(); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.tsx new file mode 100644 index 000000000000..1a2e8ead2908 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/pipeline_item.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiText, + EuiTextTruncate, + EuiIcon, + EuiSpacer, +} from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { MANAGEMENT_APP_ID } from '@kbn/deeplinks-management/constants'; + +import { useKibana } from '../../../../../../hooks/use_kibana'; +import { InferenceUsageInfo } from '../../../../types'; +import { PIPELINE_URL } from '../../../../constants'; + +interface UsageProps { + usageItem: InferenceUsageInfo; +} +export const PipelineItem: React.FC<UsageProps> = ({ usageItem }) => { + const { + services: { application }, + } = useKibana(); + const navigateToPipeline = useCallback(() => { + application?.navigateToApp(MANAGEMENT_APP_ID, { + path: `${PIPELINE_URL}?pipeline=${usageItem.id}`, + openInNewTab: true, + }); + }, [application, usageItem.id]); + + return ( + <EuiFlexGroup gutterSize="s" direction="column" data-test-subj="usageItem"> + <EuiFlexItem grow={false}> + <EuiFlexGroup> + <EuiFlexItem> + <EuiFlexGroup gutterSize="xs" justifyContent="spaceBetween"> + <EuiFlexItem> + <EuiText size="s"> + <EuiTextTruncate text={usageItem.id} /> + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiBadge color="hollow">{usageItem.type}</EuiBadge> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiLink data-test-subj="navigateToPipelinePage" onClick={navigateToPipeline}> + <EuiIcon size="s" type="popout" /> + </EuiLink> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + <EuiHorizontalRule margin="none" /> + <EuiSpacer size="s" /> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/scan_usage_results.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/scan_usage_results.tsx index 33d7a4dae891..cab278f5e1ed 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/scan_usage_results.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/component/scan_usage_results.tsx @@ -19,6 +19,7 @@ import { euiThemeVars } from '@kbn/ui-theme'; import { css } from '@emotion/react'; import { ENTERPRISE_SEARCH_CONTENT_APP_ID } from '@kbn/deeplinks-search'; +import { SEARCH_INDICES } from '@kbn/deeplinks-search/constants'; import { InferenceUsageInfo } from '../../../../types'; import { useKibana } from '../../../../../../hooks/use_kibana'; import { RenderMessageWithIcon } from './render_message_with_icon'; @@ -37,13 +38,19 @@ export const ScanUsageResults: React.FC<ScanUsageResultsProps> = ({ onIgnoreWarningCheckboxChange, }) => { const { - services: { application }, + services: { application, serverless }, } = useKibana(); const handleNavigateToIndexManagement = () => { - application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, { - path: 'search_indices', - openInNewTab: true, - }); + if (serverless) { + application?.navigateToApp(SEARCH_INDICES, { + openInNewTab: true, + }); + } else { + application?.navigateToApp(ENTERPRISE_SEARCH_CONTENT_APP_ID, { + path: `search_indices`, + openInNewTab: true, + }); + } }; return ( diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.tsx index 06bd585c0eb2..345f0f81b092 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/render_table_columns/render_actions/actions/delete/confirm_delete_endpoint/index.tsx @@ -86,6 +86,7 @@ export const ConfirmDeleteEndpointModal: React.FC<ConfirmDeleteEndpointModalProp font-family: ${euiThemeVars.euiCodeFontFamily}; font-weight: ${euiThemeVars.euiCodeFontWeightBold}; `} + data-test-subj="deleteModalInferenceEndpointName" > {inferenceEndpoint.endpoint} </EuiText> diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx index 4cf4b3112396..0ea17fa6408a 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx @@ -52,6 +52,7 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints }) { field: 'endpoint', name: i18n.ENDPOINT, + 'data-test-subj': 'endpointCell', render: (endpoint: string) => { if (endpoint) { return <EndpointInfo inferenceId={endpoint} />; @@ -65,6 +66,7 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints }) { field: 'provider', name: i18n.SERVICE_PROVIDER, + 'data-test-subj': 'providerCell', render: (provider: InferenceAPIConfigResponse) => { if (provider) { return <ServiceProvider providerEndpoint={provider} />; @@ -78,6 +80,7 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints }) { field: 'type', name: i18n.TASK_TYPE, + 'data-test-subj': 'typeCell', render: (type: TaskTypes) => { if (type) { return <TaskType type={type} />; @@ -149,6 +152,7 @@ export const TabularPage: React.FC<TabularPageProps> = ({ inferenceEndpoints }) onChange={handleTableChange} pagination={pagination} sorting={sorting} + data-test-subj="inferenceEndpointTable" /> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss deleted file mode 100644 index b85859948b0b..000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss +++ /dev/null @@ -1,3 +0,0 @@ -.addEmptyPrompt { - max-width: 860px; -} \ No newline at end of file diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx deleted file mode 100644 index 755c1f0a1de5..000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { screen } from '@testing-library/react'; -import { AddEmptyPrompt } from './add_empty_prompt'; - -import { renderReactTestingLibraryWithI18n as render } from '@kbn/test-jest-helpers'; -import '@testing-library/jest-dom'; - -describe('When empty prompt is loaded', () => { - beforeEach(() => { - render(<AddEmptyPrompt />); - }); - - it('should display the description for creation of the first inference endpoint', () => { - expect( - screen.getByText( - /Inference endpoints enable you to perform inference tasks using NLP models provided by third-party services/ - ) - ).toBeInTheDocument(); - }); - - it('should have a learn-more link', () => { - const learnMoreLink = screen.getByTestId('learn-how-to-create-inference-endpoints'); - expect(learnMoreLink).toBeInTheDocument(); - }); - - it('should have a view-your-models link', () => { - const learnMoreLink = screen.getByTestId('view-your-models'); - expect(learnMoreLink).toBeInTheDocument(); - }); - - it('should have a semantic-search-with-elser link', () => { - const learnMoreLink = screen.getByTestId('semantic-search-with-elser'); - expect(learnMoreLink).toBeInTheDocument(); - }); - - it('should have a semantic-search-with-e5 link', () => { - const learnMoreLink = screen.getByTestId('semantic-search-with-e5'); - expect(learnMoreLink).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx deleted file mode 100644 index 782994975ada..000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx +++ /dev/null @@ -1,103 +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 React from 'react'; - -import { - EuiPageTemplate, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiSpacer, - EuiLink, -} from '@elastic/eui'; -import { docLinks } from '../../../common/doc_links'; - -import * as i18n from '../../../common/translations'; - -import inferenceEndpoint from '../../assets/images/inference_endpoint.svg'; - -import { EndpointPrompt } from './endpoint_prompt'; -import { useTrainedModelPageUrl } from '../../hooks/use_trained_model_page_url'; - -import './add_empty_prompt.scss'; - -export const AddEmptyPrompt: React.FC = () => { - const trainedModelPageUrl = useTrainedModelPageUrl(); - - return ( - <EuiPageTemplate.EmptyPrompt - layout="horizontal" - restrictWidth - color="plain" - hasShadow - icon={<EuiImage size="fullWidth" src={inferenceEndpoint} alt="" />} - title={<h2>{i18n.INFERENCE_ENDPOINT_LABEL}</h2>} - body={ - <EuiFlexGroup direction="column"> - <EuiFlexItem data-test-subj="createFirstInferenceEndpointDescription"> - {i18n.CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION} - </EuiFlexItem> - <EuiFlexItem> - <EuiLink - href={docLinks.createInferenceEndpoint} - target="_blank" - data-test-subj="learn-how-to-create-inference-endpoints" - > - {i18n.LEARN_HOW_TO_CREATE_INFERENCE_ENDPOINTS_LINK} - </EuiLink> - </EuiFlexItem> - <EuiFlexItem> - <EuiLink href={trainedModelPageUrl} target="_blank" data-test-subj="view-your-models"> - {i18n.VIEW_YOUR_MODELS_LINK} - </EuiLink> - </EuiFlexItem> - </EuiFlexGroup> - } - footer={ - <EuiFlexGroup gutterSize="xs" direction="column"> - <EuiFlexItem> - <strong>{i18n.START_WITH_PREPARED_ENDPOINTS_LABEL}</strong> - </EuiFlexItem> - <EuiSpacer size="s" /> - <EuiFlexGroup> - <EuiFlexItem> - <EndpointPrompt - title={i18n.ELSER_TITLE} - description={i18n.ELSER_DESCRIPTION} - footer={ - <EuiLink - href={docLinks.semanticSearchElser} - target="_blank" - data-test-subj="semantic-search-with-elser" - > - {i18n.SEMANTIC_SEARCH_WITH_ELSER_LINK} - </EuiLink> - } - /> - </EuiFlexItem> - <EuiFlexItem> - <EndpointPrompt - title={i18n.E5_TITLE} - description={i18n.E5_DESCRIPTION} - footer={ - <EuiLink - href={docLinks.semanticSearchE5} - target="_blank" - data-test-subj="semantic-search-with-e5" - > - {i18n.SEMANTIC_SEARCH_WITH_E5_LINK} - </EuiLink> - } - /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexGroup> - } - /> - ); -}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/endpoint_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/endpoint_prompt.tsx deleted file mode 100644 index aa6ff9d582e1..000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/endpoint_prompt.tsx +++ /dev/null @@ -1,27 +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 React from 'react'; -import { EuiCard } from '@elastic/eui'; - -interface EndpointPromptProps { - title: string; - description: string; - footer: React.ReactElement; -} - -export const EndpointPrompt: React.FC<EndpointPromptProps> = ({ title, description, footer }) => ( - <EuiCard - display="plain" - textAlign="left" - data-test-subj="multilingualE5PromptForEmptyState" - title={title} - titleSize="xs" - description={description} - footer={footer} - /> -); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx index e1dae72d402f..c39bc69fc300 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx @@ -11,7 +11,6 @@ import { EuiPageTemplate } from '@elastic/eui'; import { useQueryInferenceEndpoints } from '../hooks/use_inference_endpoints'; import { TabularPage } from './all_inference_endpoints/tabular_page'; -import { AddEmptyPrompt } from './empty_prompt/add_empty_prompt'; import { InferenceEndpointsHeader } from './inference_endpoints_header'; export const InferenceEndpoints: React.FC = () => { @@ -21,13 +20,9 @@ export const InferenceEndpoints: React.FC = () => { return ( <> - {inferenceEndpoints.length > 0 && <InferenceEndpointsHeader />} - <EuiPageTemplate.Section className="eui-yScroll"> - {inferenceEndpoints.length === 0 ? ( - <AddEmptyPrompt /> - ) : ( - <TabularPage inferenceEndpoints={inferenceEndpoints} /> - )} + <InferenceEndpointsHeader /> + <EuiPageTemplate.Section className="eui-yScroll" data-test-subj="inferenceManagementPage"> + <TabularPage inferenceEndpoints={inferenceEndpoints} /> </EuiPageTemplate.Section> </> ); diff --git a/x-pack/plugins/search_inference_endpoints/public/locators.ts b/x-pack/plugins/search_inference_endpoints/public/locators.ts new file mode 100644 index 000000000000..5b32e5648cc7 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/locators.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LocatorDefinition } from '@kbn/share-plugin/common'; +import type { SharePluginSetup } from '@kbn/share-plugin/public'; +import type { SerializableRecord } from '@kbn/utility-types'; + +import { PLUGIN_ID } from '../common/constants'; +import { SEARCH_INFERENCE_ENDPOINTS_PATH } from './routes'; + +export function registerLocators(share: SharePluginSetup) { + share.url.locators.create<SerializableRecord>(new SearchInferenceEndpointLocatorDefinition()); +} + +class SearchInferenceEndpointLocatorDefinition implements LocatorDefinition<SerializableRecord> { + public readonly getLocation = async () => { + return { + app: PLUGIN_ID, + path: SEARCH_INFERENCE_ENDPOINTS_PATH, + state: {}, + }; + }; + + public readonly id = 'SEARCH_INFERENCE_ENDPOINTS'; +} diff --git a/x-pack/plugins/search_inference_endpoints/public/plugin.ts b/x-pack/plugins/search_inference_endpoints/public/plugin.ts index a864b7c0fcdd..cb60f611b3bb 100644 --- a/x-pack/plugins/search_inference_endpoints/public/plugin.ts +++ b/x-pack/plugins/search_inference_endpoints/public/plugin.ts @@ -12,16 +12,20 @@ import { Plugin, PluginInitializerContext, } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; import { PLUGIN_ID, PLUGIN_NAME } from '../common/constants'; import { docLinks } from '../common/doc_links'; import { InferenceEndpoints, getInferenceEndpointsProvider } from './embeddable'; import { + AppPluginSetupDependencies, AppPluginStartDependencies, SearchInferenceEndpointsConfigType, SearchInferenceEndpointsPluginSetup, SearchInferenceEndpointsPluginStart, } from './types'; import { INFERENCE_ENDPOINTS_UI_FLAG } from '.'; +import { registerLocators } from './locators'; +import { INFERENCE_ENDPOINTS_PATH } from './components/routes'; export class SearchInferenceEndpointsPlugin implements Plugin<SearchInferenceEndpointsPluginSetup, SearchInferenceEndpointsPluginStart> @@ -33,7 +37,8 @@ export class SearchInferenceEndpointsPlugin } public setup( - core: CoreSetup<AppPluginStartDependencies, SearchInferenceEndpointsPluginStart> + core: CoreSetup<AppPluginStartDependencies, SearchInferenceEndpointsPluginStart>, + plugins: AppPluginSetupDependencies ): SearchInferenceEndpointsPluginSetup { if ( !this.config.ui?.enabled && @@ -42,7 +47,16 @@ export class SearchInferenceEndpointsPlugin return {}; core.application.register({ id: PLUGIN_ID, - appRoute: '/app/search_inference_endpoints', + appRoute: '/app/elasticsearch/relevance', + deepLinks: [ + { + id: 'inferenceEndpoints', + path: `/${INFERENCE_ENDPOINTS_PATH}`, + title: i18n.translate('xpack.searchInferenceEndpoints.InferenceEndpointsLinkLabel', { + defaultMessage: 'Inference Endpoints', + }), + }, + ], title: PLUGIN_NAME, async mount({ element, history }: AppMountParameters) { const { renderApp } = await import('./application'); @@ -56,6 +70,8 @@ export class SearchInferenceEndpointsPlugin }, }); + registerLocators(plugins.share); + return {}; } diff --git a/x-pack/plugins/search_inference_endpoints/public/types.ts b/x-pack/plugins/search_inference_endpoints/public/types.ts index 4bd83521cf8d..9f73d0d0033b 100644 --- a/x-pack/plugins/search_inference_endpoints/public/types.ts +++ b/x-pack/plugins/search_inference_endpoints/public/types.ts @@ -5,12 +5,14 @@ * 2.0. */ -import type { ConsolePluginStart } from '@kbn/console-plugin/public'; +import type { ConsolePluginSetup, ConsolePluginStart } from '@kbn/console-plugin/public'; import { HttpStart } from '@kbn/core-http-browser'; import { AppMountParameters } from '@kbn/core/public'; import { MlPluginStart } from '@kbn/ml-plugin/public'; -import { SharePluginStart } from '@kbn/share-plugin/public'; +import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import React from 'react'; + +import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { App } from './components/app'; import type { InferenceEndpointsProvider } from './providers/inference_endpoints_provider'; @@ -27,12 +29,21 @@ export interface AppPluginStartDependencies { history: AppMountParameters['history']; share: SharePluginStart; console?: ConsolePluginStart; + serverless?: ServerlessPluginStart; +} + +export interface AppPluginSetupDependencies { + history: AppMountParameters['history']; + share: SharePluginSetup; + console?: ConsolePluginSetup; } export interface AppServicesContext { http: HttpStart; ml?: MlPluginStart; console?: ConsolePluginStart; + serverless?: ServerlessPluginStart; + share: SharePluginStart; } export interface InferenceUsageResponse { diff --git a/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.test.ts b/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.test.ts index b853109352dd..9bb3505fffbe 100644 --- a/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.test.ts +++ b/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.test.ts @@ -5,16 +5,15 @@ * 2.0. */ -import { PRECONFIGURED_ENDPOINTS } from '../components/all_inference_endpoints/constants'; import { isEndpointPreconfigured } from './preconfigured_endpoint_helper'; describe('Preconfigured Endpoint helper', () => { it('return true for preconfigured elser', () => { - expect(isEndpointPreconfigured(PRECONFIGURED_ENDPOINTS.ELSER)).toEqual(true); + expect(isEndpointPreconfigured('.preconfigured_elser')).toEqual(true); }); it('return true for preconfigured e5', () => { - expect(isEndpointPreconfigured(PRECONFIGURED_ENDPOINTS.E5)).toEqual(true); + expect(isEndpointPreconfigured('.preconfigured_e5')).toEqual(true); }); it('return false for other endpoints', () => { diff --git a/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.ts b/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.ts index 418e7e95319e..213a03c0d85a 100644 --- a/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.ts +++ b/x-pack/plugins/search_inference_endpoints/public/utils/preconfigured_endpoint_helper.ts @@ -5,7 +5,4 @@ * 2.0. */ -import { PRECONFIGURED_ENDPOINTS } from '../components/all_inference_endpoints/constants'; - -export const isEndpointPreconfigured = (endpoint: string) => - Object.values(PRECONFIGURED_ENDPOINTS).includes(endpoint); +export const isEndpointPreconfigured = (endpoint: string) => endpoint.startsWith('.'); diff --git a/x-pack/plugins/search_inference_endpoints/tsconfig.json b/x-pack/plugins/search_inference_endpoints/tsconfig.json index d454be99b65f..9a5b160779e7 100644 --- a/x-pack/plugins/search_inference_endpoints/tsconfig.json +++ b/x-pack/plugins/search_inference_endpoints/tsconfig.json @@ -33,7 +33,9 @@ "@kbn/features-plugin", "@kbn/ui-theme", "@kbn/deeplinks-search", - "@kbn/deeplinks-management" + "@kbn/deeplinks-management", + "@kbn/serverless", + "@kbn/utility-types" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/search_playground/public/components/select_indices_flyout.test.tsx b/x-pack/plugins/search_playground/public/components/select_indices_flyout.test.tsx index 246d7b2ebf24..59a606fddafd 100644 --- a/x-pack/plugins/search_playground/public/components/select_indices_flyout.test.tsx +++ b/x-pack/plugins/search_playground/public/components/select_indices_flyout.test.tsx @@ -49,6 +49,7 @@ describe('SelectIndicesFlyout', () => { mockedUseQueryIndices.mockReturnValue({ indices: ['index1', 'index2', 'index3'], isLoading: false, + isFetched: true, }); }); diff --git a/x-pack/plugins/search_playground/public/components/select_indices_flyout.tsx b/x-pack/plugins/search_playground/public/components/select_indices_flyout.tsx index d98a6fc9de8b..fa3868cb392c 100644 --- a/x-pack/plugins/search_playground/public/components/select_indices_flyout.tsx +++ b/x-pack/plugins/search_playground/public/components/select_indices_flyout.tsx @@ -35,7 +35,7 @@ interface SelectIndicesFlyout { export const SelectIndicesFlyout: React.FC<SelectIndicesFlyout> = ({ onClose }) => { const [query, setQuery] = useState<string>(''); - const { indices, isLoading: isIndicesLoading } = useQueryIndices(query); + const { indices, isLoading: isIndicesLoading } = useQueryIndices({ query }); const { indices: selectedIndices, setIndices: setSelectedIndices } = useSourceIndicesFields(); const [selectedTempIndices, setSelectedTempIndices] = useState<string[]>(selectedIndices); const handleSelectOptions = (options: EuiSelectableOption[]) => { diff --git a/x-pack/plugins/search_playground/public/hooks/use_indices_validation.ts b/x-pack/plugins/search_playground/public/hooks/use_indices_validation.ts new file mode 100644 index 000000000000..45b3848bb85e --- /dev/null +++ b/x-pack/plugins/search_playground/public/hooks/use_indices_validation.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useState } from 'react'; +import { useQueryIndices } from './use_query_indices'; + +export const useIndicesValidation = (unvalidatedIndices: string[]) => { + const [isValidated, setIsValidated] = useState<boolean>(false); + const [validIndices, setValidIndices] = useState<string[]>([]); + const { indices, isFetched: isIndicesLoaded } = useQueryIndices({ + query: unvalidatedIndices.join(','), + exact: true, + }); + + useEffect(() => { + if (isIndicesLoaded) { + setValidIndices(indices.filter((index) => unvalidatedIndices.includes(index))); + setIsValidated(true); + } + }, [unvalidatedIndices, indices, isIndicesLoaded]); + + return { isValidated, validIndices }; +}; diff --git a/x-pack/plugins/search_playground/public/hooks/use_query_indices.ts b/x-pack/plugins/search_playground/public/hooks/use_query_indices.ts index d011b471a68d..cc5812306097 100644 --- a/x-pack/plugins/search_playground/public/hooks/use_query_indices.ts +++ b/x-pack/plugins/search_playground/public/hooks/use_query_indices.ts @@ -11,26 +11,41 @@ import { useKibana } from './use_kibana'; import { APIRoutes } from '../types'; export const useQueryIndices = ( - query: string = '' -): { indices: IndexName[]; isLoading: boolean } => { + { + query, + exact, + }: { + query?: string; + exact?: boolean; + } = { query: '', exact: false } +): { indices: IndexName[]; isLoading: boolean; isFetched: boolean } => { const { services } = useKibana(); - const { data, isLoading } = useQuery({ + const { data, isLoading, isFetched } = useQuery({ queryKey: ['indices', query], queryFn: async () => { - const response = await services.http.get<{ - indices: string[]; - }>(APIRoutes.GET_INDICES, { - query: { - search_query: query, - size: 10, - }, - }); + try { + const response = await services.http.get<{ + indices: string[]; + }>(APIRoutes.GET_INDICES, { + query: { + search_query: query, + exact, + size: 50, + }, + }); - return response.indices; + return response.indices; + } catch (err) { + if (err?.response?.status === 404) { + return []; + } + + throw err; + } }, initialData: [], }); - return { indices: data, isLoading }; + return { indices: data, isLoading, isFetched }; }; diff --git a/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx b/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx index 96c86ad18493..e0205f01f1e6 100644 --- a/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx +++ b/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx @@ -19,6 +19,9 @@ jest.mock('../hooks/use_llms_models'); jest.mock('react-router-dom-v5-compat', () => ({ useSearchParams: jest.fn(() => [{ get: jest.fn() }]), })); +jest.mock('../hooks/use_indices_validation', () => ({ + useIndicesValidation: jest.fn((indices) => ({ isValidated: true, validIndices: indices })), +})); let formHookSpy: jest.SpyInstance; @@ -216,6 +219,7 @@ describe('FormProvider', () => { }); it('updates indices from search params', async () => { + expect.assertions(1); const mockSearchParams = new URLSearchParams(); mockSearchParams.get = jest.fn().mockReturnValue('new-index'); mockUseSearchParams.mockReturnValue([mockSearchParams]); @@ -237,10 +241,12 @@ describe('FormProvider', () => { </FormProvider> ); - const { getValues } = formHookSpy.mock.results[0].value; + await act(async () => { + const { getValues } = formHookSpy.mock.results[0].value; - await waitFor(() => { - expect(getValues(ChatFormFields.indices)).toEqual(['new-index']); + await waitFor(() => { + expect(getValues(ChatFormFields.indices)).toEqual(['new-index']); + }); }); }); }); diff --git a/x-pack/plugins/search_playground/public/providers/form_provider.tsx b/x-pack/plugins/search_playground/public/providers/form_provider.tsx index f352688fe89f..e1b27e66a98d 100644 --- a/x-pack/plugins/search_playground/public/providers/form_provider.tsx +++ b/x-pack/plugins/search_playground/public/providers/form_provider.tsx @@ -7,6 +7,7 @@ import { FormProvider as ReactHookFormProvider, useForm } from 'react-hook-form'; import React, { useEffect, useMemo } from 'react'; import { useSearchParams } from 'react-router-dom-v5-compat'; +import { useIndicesValidation } from '../hooks/use_indices_validation'; import { useLoadFieldsByIndices } from '../hooks/use_load_fields_by_indices'; import { ChatForm, ChatFormFields } from '../types'; import { useLLMsModels } from '../hooks/use_llms_models'; @@ -53,16 +54,27 @@ export const FormProvider: React.FC<React.PropsWithChildren<FormProviderProps>> }) => { const models = useLLMsModels(); const [searchParams] = useSearchParams(); - const index = useMemo(() => searchParams.get('default-index'), [searchParams]); + const defaultIndex = useMemo(() => { + const index = searchParams.get('default-index'); + + return index ? [index] : null; + }, [searchParams]); const sessionState = useMemo(() => getLocalSession(storage), [storage]); const form = useForm<ChatForm>({ defaultValues: { ...sessionState, - indices: index ? [index] : sessionState.indices, + indices: [], search_query: '', }, }); - useLoadFieldsByIndices({ watch: form.watch, setValue: form.setValue, getValues: form.getValues }); + const { isValidated: isValidatedIndices, validIndices } = useIndicesValidation( + defaultIndex || sessionState.indices || [] + ); + useLoadFieldsByIndices({ + watch: form.watch, + setValue: form.setValue, + getValues: form.getValues, + }); useEffect(() => { const subscription = form.watch((values) => @@ -80,5 +92,11 @@ export const FormProvider: React.FC<React.PropsWithChildren<FormProviderProps>> } }, [form, models]); + useEffect(() => { + if (isValidatedIndices) { + form.setValue(ChatFormFields.indices, validIndices); + } + }, [form, isValidatedIndices, validIndices]); + return <ReactHookFormProvider {...form}>{children}</ReactHookFormProvider>; }; diff --git a/x-pack/plugins/search_playground/server/lib/fetch_indices.ts b/x-pack/plugins/search_playground/server/lib/fetch_indices.ts index 51b72790028c..c60e6b508261 100644 --- a/x-pack/plugins/search_playground/server/lib/fetch_indices.ts +++ b/x-pack/plugins/search_playground/server/lib/fetch_indices.ts @@ -21,11 +21,12 @@ function isClosed(index: IndicesIndexState): boolean { export const fetchIndices = async ( client: ElasticsearchClient, - searchQuery: string | undefined + searchQuery: string | undefined, + { exact }: { exact?: boolean } = { exact: false } ): Promise<{ indexNames: string[]; }> => { - const indexPattern = searchQuery ? `*${searchQuery}*` : '*'; + const indexPattern = exact && searchQuery ? searchQuery : searchQuery ? `*${searchQuery}*` : '*'; const allIndexMatches = await client.indices.get({ expand_wildcards: ['open'], // for better performance only compute aliases and settings of indices but not mappings diff --git a/x-pack/plugins/search_playground/server/routes.ts b/x-pack/plugins/search_playground/server/routes.ts index 3cdebe11c02c..115b55d34146 100644 --- a/x-pack/plugins/search_playground/server/routes.ts +++ b/x-pack/plugins/search_playground/server/routes.ts @@ -198,17 +198,17 @@ export function defineRoutes({ query: schema.object({ search_query: schema.maybe(schema.string()), size: schema.number({ defaultValue: 10, min: 0 }), + exact: schema.maybe(schema.boolean({ defaultValue: false })), }), }, }, errorHandler(logger)(async (context, request, response) => { - const { search_query: searchQuery, size } = request.query; + const { search_query: searchQuery, exact, size } = request.query; const { client: { asCurrentUser }, } = (await context.core).elasticsearch; - const { indexNames } = await fetchIndices(asCurrentUser, searchQuery); - + const { indexNames } = await fetchIndices(asCurrentUser, searchQuery, { exact }); const indexNameSlice = indexNames.slice(0, size).filter(isNotNullish); return response.ok({ diff --git a/x-pack/plugins/search_solution/search_navigation/README.mdx b/x-pack/plugins/search_solution/search_navigation/README.mdx new file mode 100644 index 000000000000..13ece425b680 --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/README.mdx @@ -0,0 +1,3 @@ +# Search Navigation + +The Search Navigation plugin is used to handle navigation for search solution plugins across both stack and serverless. diff --git a/x-pack/plugins/lens/public/embeddable/index.ts b/x-pack/plugins/search_solution/search_navigation/common/index.ts similarity index 72% rename from x-pack/plugins/lens/public/embeddable/index.ts rename to x-pack/plugins/search_solution/search_navigation/common/index.ts index 50ee0f582a2f..8a5bd50a5bc3 100644 --- a/x-pack/plugins/lens/public/embeddable/index.ts +++ b/x-pack/plugins/search_solution/search_navigation/common/index.ts @@ -5,6 +5,5 @@ * 2.0. */ -export * from './embeddable'; - -export { type LensApi, isLensApi } from './interfaces/lens_api'; +export const PLUGIN_ID = 'searchNavigation'; +export const PLUGIN_NAME = 'searchNavigation'; diff --git a/x-pack/plugins/search_solution/search_navigation/jest.config.js b/x-pack/plugins/search_solution/search_navigation/jest.config.js new file mode 100644 index 000000000000..e86a30c14324 --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['<rootDir>/x-pack/plugins/search_solution/search_navigation'], + coverageDirectory: + '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/search_solution/search_navigation', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '<rootDir>/x-pack/plugins/search_solution/search_navigation/{public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/search_solution/search_navigation/kibana.jsonc b/x-pack/plugins/search_solution/search_navigation/kibana.jsonc new file mode 100644 index 000000000000..4b10b5f3a5a7 --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/kibana.jsonc @@ -0,0 +1,21 @@ +{ + "type": "plugin", + "id": "@kbn/search-navigation", + "owner": "@elastic/search-kibana", + "group": "search", + "visibility": "private", + "plugin": { + "id": "searchNavigation", + "server": false, + "browser": true, + "configPath": [ + "xpack", + "searchNavigation" + ], + "requiredPlugins": [], + "optionalPlugins": [ + "serverless" + ], + "requiredBundles": [] + } +} diff --git a/x-pack/plugins/search_solution/search_navigation/public/classic_navigation.test.ts b/x-pack/plugins/search_solution/search_navigation/public/classic_navigation.test.ts new file mode 100644 index 000000000000..1b17296f54c7 --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/public/classic_navigation.test.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 { CoreStart, ScopedHistory } from '@kbn/core/public'; +import type { ChromeNavLink } from '@kbn/core-chrome-browser'; + +import { classicNavigationFactory } from './classic_navigation'; +import { ClassicNavItem } from './types'; + +describe('classicNavigationFactory', function () { + const mockedNavLinks: Array<Partial<ChromeNavLink>> = [ + { + id: 'enterpriseSearch', + url: '/app/enterprise_search/overview', + title: 'Overview', + }, + { + id: 'enterpriseSearchContent:searchIndices', + title: 'Indices', + url: '/app/enterprise_search/content/search_indices', + }, + { + id: 'enterpriseSearchContent:connectors', + title: 'Connectors', + url: '/app/enterprise_search/content/connectors', + }, + { + id: 'enterpriseSearchContent:webCrawlers', + title: 'Web crawlers', + url: '/app/enterprise_search/content/crawlers', + }, + ]; + const mockedCoreStart = { + chrome: { + navLinks: { + getAll: () => mockedNavLinks, + }, + }, + }; + const core = mockedCoreStart as unknown as CoreStart; + const mockHistory = { + location: { + pathname: '/', + }, + createHref: jest.fn(), + }; + const history = mockHistory as unknown as ScopedHistory; + + beforeEach(() => { + jest.clearAllMocks(); + mockHistory.location.pathname = '/'; + mockHistory.createHref.mockReturnValue('/'); + }); + + it('can render top-level items', () => { + const items: ClassicNavItem[] = [ + { + id: 'unit-test', + deepLink: { + link: 'enterpriseSearch', + }, + }, + ]; + expect(classicNavigationFactory(items, core, history)).toEqual({ + icon: 'logoEnterpriseSearch', + items: [ + { + href: '/app/enterprise_search/overview', + id: 'unit-test', + isSelected: false, + name: 'Overview', + onClick: expect.any(Function), + }, + ], + name: 'Elasticsearch', + }); + }); + + it('will set isSelected', () => { + mockHistory.location.pathname = '/overview'; + mockHistory.createHref.mockReturnValue('/app/enterprise_search/overview'); + + const items: ClassicNavItem[] = [ + { + id: 'unit-test', + deepLink: { + link: 'enterpriseSearch', + }, + }, + ]; + + const solutionNav = classicNavigationFactory(items, core, history); + expect(solutionNav!.items).toEqual([ + { + href: '/app/enterprise_search/overview', + id: 'unit-test', + isSelected: true, + name: 'Overview', + onClick: expect.any(Function), + }, + ]); + }); + it('can render items with children', () => { + const items: ClassicNavItem[] = [ + { + id: 'searchContent', + name: 'Content', + items: [ + { + id: 'searchIndices', + deepLink: { + link: 'enterpriseSearchContent:searchIndices', + }, + }, + { + id: 'searchConnectors', + deepLink: { + link: 'enterpriseSearchContent:connectors', + }, + }, + ], + }, + ]; + + const solutionNav = classicNavigationFactory(items, core, history); + expect(solutionNav!.items).toEqual([ + { + id: 'searchContent', + items: [ + { + href: '/app/enterprise_search/content/search_indices', + id: 'searchIndices', + isSelected: false, + name: 'Indices', + onClick: expect.any(Function), + }, + { + href: '/app/enterprise_search/content/connectors', + id: 'searchConnectors', + isSelected: false, + name: 'Connectors', + onClick: expect.any(Function), + }, + ], + name: 'Content', + }, + ]); + }); + it('returns name if provided over the deeplink title', () => { + const items: ClassicNavItem[] = [ + { + id: 'searchIndices', + deepLink: { + link: 'enterpriseSearchContent:searchIndices', + }, + name: 'Index Management', + }, + ]; + const solutionNav = classicNavigationFactory(items, core, history); + expect(solutionNav!.items).toEqual([ + { + href: '/app/enterprise_search/content/search_indices', + id: 'searchIndices', + isSelected: false, + name: 'Index Management', + onClick: expect.any(Function), + }, + ]); + }); + it('removes item if deeplink not defined', () => { + const items: ClassicNavItem[] = [ + { + id: 'unit-test', + deepLink: { + link: 'enterpriseSearch', + }, + }, + { + id: 'serverlessElasticsearch', + deepLink: { + link: 'serverlessElasticsearch', + }, + }, + ]; + + const solutionNav = classicNavigationFactory(items, core, history); + expect(solutionNav!.items).toEqual([ + { + href: '/app/enterprise_search/overview', + id: 'unit-test', + isSelected: false, + name: 'Overview', + onClick: expect.any(Function), + }, + ]); + }); +}); diff --git a/x-pack/plugins/search_solution/search_navigation/public/classic_navigation.ts b/x-pack/plugins/search_solution/search_navigation/public/classic_navigation.ts new file mode 100644 index 000000000000..99a6b33c4bcc --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/public/classic_navigation.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { type MouseEvent } from 'react'; +import { i18n } from '@kbn/i18n'; +import type { CoreStart, ScopedHistory } from '@kbn/core/public'; +import type { ChromeNavLink, EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; +import type { SolutionNavProps } from '@kbn/shared-ux-page-solution-nav'; + +import type { ClassicNavItem, ClassicNavItemDeepLink, ClassicNavigationFactoryFn } from './types'; +import { stripTrailingSlash } from './utils'; + +type DeepLinksMap = Record<string, ChromeNavLink | undefined>; +type SolutionNavItems = SolutionNavProps['items']; + +export const classicNavigationFactory: ClassicNavigationFactoryFn = ( + classicItems: ClassicNavItem[], + core: CoreStart, + history: ScopedHistory<unknown> +): SolutionNavProps | undefined => { + const navLinks = core.chrome.navLinks.getAll(); + const deepLinks = navLinks.reduce((links: DeepLinksMap, link: ChromeNavLink) => { + links[link.id] = link; + return links; + }, {}); + + const currentPath = stripTrailingSlash(history.location.pathname); + const currentLocation = history.createHref({ pathname: currentPath }); + const items: SolutionNavItems = generateSideNavItems( + classicItems, + core, + deepLinks, + currentLocation + ); + + return { + items, + icon: 'logoEnterpriseSearch', + name: i18n.translate('xpack.searchNavigation.classicNav.name', { + defaultMessage: 'Elasticsearch', + }), + }; +}; + +function generateSideNavItems( + classicItems: ClassicNavItem[], + core: CoreStart, + deepLinks: DeepLinksMap, + currentLocation: string +): SolutionNavItems { + const result: SolutionNavItems = []; + + for (const navItem of classicItems) { + let children: SolutionNavItems | undefined; + + const { deepLink, items, ...rest } = navItem; + if (items) { + children = generateSideNavItems(items, core, deepLinks, currentLocation); + } + + let item: EuiSideNavItemTypeEnhanced<{}> | undefined; + if (deepLink) { + const sideNavProps = getSideNavItemLinkProps(deepLink, deepLinks, core, currentLocation); + if (sideNavProps) { + const { name, ...linkProps } = sideNavProps; + item = { + ...rest, + ...linkProps, + name: navItem?.name ?? name, + }; + } + } else { + item = { + ...rest, + items: children, + name: navItem.name, + }; + } + + if (isValidSideNavItem(item)) { + result.push(item); + } + } + + return result; +} + +function isValidSideNavItem( + item: EuiSideNavItemTypeEnhanced<unknown> | undefined +): item is EuiSideNavItemTypeEnhanced<unknown> { + if (item === undefined) return false; + if (item.href || item.onClick) return true; + if (item?.items?.length ?? 0 > 0) return true; + + return false; +} + +function getSideNavItemLinkProps( + { link, shouldShowActiveForSubroutes }: ClassicNavItemDeepLink, + deepLinks: DeepLinksMap, + core: CoreStart, + currentLocation: string +) { + const deepLink = deepLinks[link]; + if (!deepLink || !deepLink.url) return undefined; + const isSelected = Boolean( + deepLink.url === currentLocation || + (shouldShowActiveForSubroutes && currentLocation.startsWith(deepLink.url)) + ); + + return { + onClick: (e: MouseEvent) => { + e.preventDefault(); + core.application.navigateToUrl(deepLink.url); + }, + href: deepLink.url, + name: deepLink.title, + isSelected, + }; +} diff --git a/x-pack/plugins/search_solution/search_navigation/public/index.ts b/x-pack/plugins/search_solution/search_navigation/public/index.ts new file mode 100644 index 000000000000..f5fc03e088a2 --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/public/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializerContext } from '@kbn/core-plugins-browser'; +import { SearchNavigationPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new SearchNavigationPlugin(initializerContext); +} + +export type { + SearchNavigationPluginSetup, + SearchNavigationPluginStart, + ClassicNavItem, + ClassicNavItemDeepLink, +} from './types'; diff --git a/x-pack/plugins/search_solution/search_navigation/public/plugin.ts b/x-pack/plugins/search_solution/search_navigation/public/plugin.ts new file mode 100644 index 000000000000..4b618a6245c4 --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/public/plugin.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, + ScopedHistory, +} from '@kbn/core/public'; +import type { ChromeStyle } from '@kbn/core-chrome-browser'; +import type { Logger } from '@kbn/logging'; +import type { + SearchNavigationPluginSetup, + SearchNavigationPluginStart, + ClassicNavItem, + ClassicNavigationFactoryFn, +} from './types'; + +export class SearchNavigationPlugin + implements Plugin<SearchNavigationPluginSetup, SearchNavigationPluginStart> +{ + private readonly logger: Logger; + private currentChromeStyle: ChromeStyle | undefined = undefined; + private baseClassicNavItemsFn: (() => ClassicNavItem[]) | undefined = undefined; + private coreStart: CoreStart | undefined = undefined; + private classicNavFactory: ClassicNavigationFactoryFn | undefined = undefined; + private onAppMountHandlers: Array<() => Promise<void>> = []; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.logger = this.initializerContext.logger.get(); + } + + public setup(_core: CoreSetup): SearchNavigationPluginSetup { + return {}; + } + + public start(core: CoreStart): SearchNavigationPluginStart { + this.coreStart = core; + core.chrome.getChromeStyle$().subscribe((value) => { + this.currentChromeStyle = value; + }); + + import('./classic_navigation').then(({ classicNavigationFactory }) => { + this.classicNavFactory = classicNavigationFactory; + }); + + return { + handleOnAppMount: this.handleOnAppMount.bind(this), + registerOnAppMountHandler: this.registerOnAppMountHandler.bind(this), + setGetBaseClassicNavItems: this.setGetBaseClassicNavItems.bind(this), + useClassicNavigation: this.useClassicNavigation.bind(this), + }; + } + + public stop() {} + + private async handleOnAppMount() { + if (this.onAppMountHandlers.length === 0) return; + + try { + await Promise.all(this.onAppMountHandlers); + } catch (e) { + this.logger.warn('Error handling app mount functions for search navigation'); + this.logger.warn(e); + } + } + + private registerOnAppMountHandler(handler: () => Promise<void>) { + this.onAppMountHandlers.push(handler); + } + + private setGetBaseClassicNavItems(classicNavItemsFn: () => ClassicNavItem[]) { + this.baseClassicNavItemsFn = classicNavItemsFn; + } + + private useClassicNavigation(history: ScopedHistory<unknown>) { + if ( + this.baseClassicNavItemsFn === undefined || + this.classicNavFactory === undefined || + this.coreStart === undefined || + this.currentChromeStyle !== 'classic' + ) + return undefined; + + return this.classicNavFactory(this.baseClassicNavItemsFn(), this.coreStart, history); + } +} diff --git a/x-pack/plugins/search_solution/search_navigation/public/types.ts b/x-pack/plugins/search_solution/search_navigation/public/types.ts new file mode 100644 index 000000000000..91e8cc73524e --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/public/types.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 type { ReactNode } from 'react'; +import type { AppDeepLinkId } from '@kbn/core-chrome-browser'; +import type { CoreStart, ScopedHistory } from '@kbn/core/public'; +import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import type { SolutionNavProps } from '@kbn/shared-ux-page-solution-nav'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchNavigationPluginSetup {} + +export interface SearchNavigationPluginStart { + registerOnAppMountHandler: (onAppMount: () => Promise<void>) => void; + handleOnAppMount: () => Promise<void>; + // This is temporary until we can migrate building the class nav item list out of `enterprise_search` plugin + setGetBaseClassicNavItems: (classicNavItemsFn: () => ClassicNavItem[]) => void; + useClassicNavigation: (history: ScopedHistory<unknown>) => SolutionNavProps | undefined; +} + +export interface AppPluginSetupDependencies { + serverless?: ServerlessPluginSetup; +} + +export interface AppPluginStartDependencies { + serverless?: ServerlessPluginStart; +} + +export interface ClassicNavItemDeepLink { + link: AppDeepLinkId; + shouldShowActiveForSubroutes?: boolean; +} + +export interface ClassicNavItem { + 'data-test-subj'?: string; + deepLink?: ClassicNavItemDeepLink; + iconToString?: string; + id: string; + items?: ClassicNavItem[]; + name?: ReactNode; +} + +export type ClassicNavigationFactoryFn = ( + items: ClassicNavItem[], + core: CoreStart, + history: ScopedHistory<unknown> +) => SolutionNavProps | undefined; diff --git a/x-pack/plugins/search_solution/search_navigation/public/utils.ts b/x-pack/plugins/search_solution/search_navigation/public/utils.ts new file mode 100644 index 000000000000..fb80778977b1 --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/public/utils.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Helpers for stripping trailing or leading slashes from URLs or paths + * (usually ones that come in from React Router or API endpoints) + */ + +export const stripTrailingSlash = (url: string): string => { + return url && url.endsWith('/') ? url.slice(0, -1) : url; +}; + +export const stripLeadingSlash = (path: string): string => { + return path && path.startsWith('/') ? path.substring(1) : path; +}; diff --git a/x-pack/plugins/search_solution/search_navigation/tsconfig.json b/x-pack/plugins/search_solution/search_navigation/tsconfig.json new file mode 100644 index 000000000000..6d61fbb24ec8 --- /dev/null +++ b/x-pack/plugins/search_solution/search_navigation/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "__mocks__/**/*", + "common/**/*", + "public/**/*", + "server/**/*", + "../../../../typings/**/*" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/i18n", + "@kbn/core-chrome-browser", + "@kbn/shared-ux-page-solution-nav", + "@kbn/logging", + "@kbn/serverless", + "@kbn/core-plugins-browser", + ], + "exclude": ["target/**/*"] +} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_table.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_table.tsx index f536b5838bab..8ceff7f1460f 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_table.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_table.tsx @@ -274,6 +274,7 @@ export const ApiKeysTable: FunctionComponent<ApiKeysTableProps> = ({ <EuiSearchBar query={query} box={{ + 'data-test-subj': 'apiKeysSearchBar', incremental: true, schema: { strict: true, @@ -393,6 +394,7 @@ export const TypesFilterButton: FunctionComponent<CustomComponentProps> = ({ que onFilterChange({ ...filters, type: filters.type === 'rest' ? undefined : 'rest' }); }} withNext={types.includes('cross_cluster') || types.includes('managed')} + data-test-subj="personalFilterButton" > <FormattedMessage id="xpack.security.accountManagement.apiKeyBadge.restTitle" @@ -412,6 +414,7 @@ export const TypesFilterButton: FunctionComponent<CustomComponentProps> = ({ que }); }} withNext={types.includes('managed')} + data-test-subj="crossClusterFilterButton" > <FormattedMessage id="xpack.security.accountManagement.apiKeyBadge.crossClusterLabel" @@ -430,6 +433,7 @@ export const TypesFilterButton: FunctionComponent<CustomComponentProps> = ({ que type: filters.type === 'managed' ? undefined : 'managed', }); }} + data-test-subj="managedFilterButton" > <FormattedMessage id="xpack.security.accountManagement.apiKeyBadge.managedTitle" @@ -463,6 +467,7 @@ export const ExpiredFilterButton: FunctionComponent<CustomComponentProps> = ({ } }} withNext={true} + data-test-subj="activeFilterButton" > <FormattedMessage id="xpack.security.management.apiKeys.table.activeFilter" @@ -478,6 +483,7 @@ export const ExpiredFilterButton: FunctionComponent<CustomComponentProps> = ({ onFilterChange({ ...filters, expired: true }); } }} + data-test-subj="expiredFilterButton" > <FormattedMessage id="xpack.security.management.apiKeys.table.expiredFilter" @@ -520,6 +526,7 @@ export const UsersFilterButton: FunctionComponent<CustomComponentProps> = ({ que numFilters={usernames.length} hasActiveFilters={numActiveFilters ? true : false} numActiveFilters={numActiveFilters} + data-test-subj="ownerFilterButton" > <FormattedMessage id="xpack.security.management.apiKeys.table.ownerFilter" diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx index ad6415a1f4fe..b9f4fd8e474a 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx @@ -226,6 +226,7 @@ export class SimplePrivilegeSection extends Component<Props, State> { privilegeIndex={this.props.role.kibana.findIndex((k) => isGlobalPrivilegeDefinition(k) )} + showAdditionalPermissionsMessage={true} canCustomizeSubFeaturePrivileges={this.props.canCustomizeSubFeaturePrivileges} allSpacesSelected disabled={!this.props.editable} diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx index fb472191b561..751db03939dd 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx @@ -19,7 +19,6 @@ import type { Space } from '@kbn/spaces-plugin/public'; import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { PrivilegeSpaceForm } from './privilege_space_form'; -import { SpaceSelector } from './space_selector'; import type { Role } from '../../../../../../../common'; const createRole = (kibana: Role['kibana'] = []): Role => { @@ -57,7 +56,7 @@ const renderComponent = (props: React.ComponentProps<typeof PrivilegeSpaceForm>) }; describe('PrivilegeSpaceForm', () => { - it('renders an empty form when the role contains no Kibana privileges', () => { + it('renders no form when no role is selected', () => { const role = createRole(); const kibanaPrivileges = createKibanaPrivileges(kibanaFeatures); @@ -71,40 +70,9 @@ describe('PrivilegeSpaceForm', () => { onCancel: jest.fn(), }); - expect( - wrapper.find(EuiButtonGroup).filter('[name="basePrivilegeButtonGroup"]').props().idSelected - ).toEqual(`basePrivilege_custom`); - expect(wrapper.find(FeatureTable).props().disabled).toEqual(true); - expect(getDisplayedFeaturePrivileges(wrapper)).toMatchInlineSnapshot(` - Object { - "excluded_from_base": Object { - "primaryFeaturePrivilege": "none", - "subFeaturePrivileges": Array [], - }, - "no_sub_features": Object { - "primaryFeaturePrivilege": "none", - "subFeaturePrivileges": Array [], - }, - "with_excluded_sub_features": Object { - "primaryFeaturePrivilege": "none", - "subFeaturePrivileges": Array [], - }, - "with_require_all_spaces_for_feature_and_sub_features": Object { - "primaryFeaturePrivilege": "none", - "subFeaturePrivileges": Array [], - }, - "with_require_all_spaces_sub_features": Object { - "primaryFeaturePrivilege": "none", - "subFeaturePrivileges": Array [], - }, - "with_sub_features": Object { - "primaryFeaturePrivilege": "none", - "subFeaturePrivileges": Array [], - }, - } - `); - - expect(findTestSubject(wrapper, 'spaceFormGlobalPermissionsSupersedeWarning')).toHaveLength(0); + expect(wrapper.find(EuiButtonGroup).filter('[name="basePrivilegeButtonGroup"]')).toHaveLength( + 0 + ); }); it('renders when a base privilege is selected', () => { @@ -232,43 +200,6 @@ describe('PrivilegeSpaceForm', () => { expect(findTestSubject(wrapper, 'spaceFormGlobalPermissionsSupersedeWarning')).toHaveLength(0); }); - it('renders a warning when configuring a global privilege after space privileges are already defined', () => { - const role = createRole([ - { - base: [], - feature: { - with_sub_features: ['read'], - }, - spaces: ['foo'], - }, - { - base: [], - feature: { - with_sub_features: ['all'], - }, - spaces: ['*'], - }, - ]); - - const kibanaPrivileges = createKibanaPrivileges(kibanaFeatures); - - const wrapper = renderComponent({ - role, - spaces: displaySpaces, - kibanaPrivileges, - privilegeIndex: -1, - canCustomizeSubFeaturePrivileges: true, - onChange: jest.fn(), - onCancel: jest.fn(), - }); - - wrapper.find(SpaceSelector).props().onChange(['*']); - - wrapper.update(); - - expect(findTestSubject(wrapper, 'globalPrivilegeWarning')).toHaveLength(1); - }); - it('renders a warning when space privileges are less permissive than configured global privileges', () => { const role = createRole([ { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index 8275a7b1203a..5946197d35d6 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import type { EuiButtonColor } from '@elastic/eui'; import { EuiButton, EuiButtonEmpty, @@ -21,7 +20,6 @@ import { EuiForm, EuiFormRow, EuiSpacer, - EuiText, EuiTitle, } from '@elastic/eui'; import { remove } from 'lodash'; @@ -105,20 +103,19 @@ export class PrivilegeSpaceForm extends Component<Props, State> { <EuiFlyoutHeader hasBorder> <EuiTitle size="m"> <h2> - <FormattedMessage - id="xpack.security.management.editRole.spacePrivilegeForm.modalTitle" - defaultMessage="Assign role to space" - /> + {this.state.mode === 'create' ? ( + <FormattedMessage + id="xpack.security.management.editRole.spacePrivilegeForm.modalTitleCreate" + defaultMessage="Assign role to spaces" + /> + ) : ( + <FormattedMessage + id="xpack.security.management.editRole.spacePrivilegeForm.modalTitleUpdate" + defaultMessage="Edit role privileges for spaces" + /> + )} </h2> </EuiTitle> - <EuiText size="s"> - <p> - <FormattedMessage - id="xpack.security.management.editRole.spacePrivilegeForm.modalHeadline" - defaultMessage="This role will be granted access to the following spaces" - /> - </p> - </EuiText> </EuiFlyoutHeader> <EuiFlyoutBody> <EuiErrorBoundary>{this.getForm()}</EuiErrorBoundary> @@ -180,14 +177,13 @@ export class PrivilegeSpaceForm extends Component<Props, State> { label={i18n.translate( 'xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormLabel', { - defaultMessage: 'Spaces', + defaultMessage: 'Select spaces', } )} helpText={i18n.translate( 'xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormHelpText', { - defaultMessage: - 'Select one or more Kibana spaces to which you wish to assign privileges.', + defaultMessage: 'Users assigned to this role will gain access to selected spaces.', } )} > @@ -198,99 +194,85 @@ export class PrivilegeSpaceForm extends Component<Props, State> { /> </EuiFormRow> - {this.getPrivilegeCallout()} - - <EuiFormRow - fullWidth - label={i18n.translate( - 'xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormLabel', - { - defaultMessage: 'Privileges for all features', - } - )} - helpText={i18n.translate( - 'xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormHelpText', - { - defaultMessage: - 'Assign the privilege level you wish to grant to all present and future features across this space.', - } - )} - > - <EuiButtonGroup - name={`basePrivilegeButtonGroup`} - data-test-subj={`basePrivilegeButtonGroup`} - isFullWidth={true} - color={'primary'} - options={[ - { - id: 'basePrivilege_all', - label: 'All', - ['data-test-subj']: 'basePrivilege_all', - }, - { - id: 'basePrivilege_read', - label: 'Read', - ['data-test-subj']: 'basePrivilege_read', - }, - { - id: 'basePrivilege_custom', - label: 'Customize', - ['data-test-subj']: 'basePrivilege_custom', - }, - ]} - idSelected={this.getDisplayedBasePrivilege()} - isDisabled={!hasSelectedSpaces} - onChange={this.onSpaceBasePrivilegeChange} - legend={i18n.translate( - 'xpack.security.management.editRole.spacePrivilegeForm.basePrivilegeControlLegend', - { - defaultMessage: 'Privileges for all features', - } - )} - /> - </EuiFormRow> - - <EuiSpacer /> - - <EuiTitle size="xxs"> - <h3>{this.getFeatureListLabel(this.state.selectedBasePrivilege.length > 0)}</h3> - </EuiTitle> - - <EuiSpacer size="xs" /> - - <EuiText size="s"> - <p>{this.getFeatureListDescription(this.state.selectedBasePrivilege.length > 0)}</p> - </EuiText> - - <EuiSpacer size="l" /> - - <KibanaPrivilegeTable - role={this.state.role} - privilegeCalculator={this.state.privilegeCalculator} - onChange={this.onFeaturePrivilegesChange} - onChangeAll={this.onChangeAllFeaturePrivileges} - kibanaPrivileges={this.props.kibanaPrivileges} - privilegeIndex={this.state.privilegeIndex} - canCustomizeSubFeaturePrivileges={this.props.canCustomizeSubFeaturePrivileges} - disabled={this.state.selectedBasePrivilege.length > 0 || !hasSelectedSpaces} - allSpacesSelected={this.state.selectedSpaceIds.includes(ALL_SPACES_ID)} - /> - - {this.requiresGlobalPrivilegeWarning() && ( - <Fragment> - <EuiSpacer size="l" /> - <EuiCallOut - color="warning" - iconType="warning" - data-test-subj="globalPrivilegeWarning" - title={ - <FormattedMessage - id="xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeWarning" - defaultMessage="Creating a global privilege might impact your other space privileges." - /> - } + {Boolean(this.state.selectedSpaceIds.length) && ( + <> + <EuiFormRow fullWidth> + <EuiCallOut + color="primary" + iconType="iInCircle" + size="s" + title={i18n.translate( + 'xpack.security.management.editRole.spacePrivilegeForm.privilegeCombinationMsg.title', + { + defaultMessage: `The user's resulting access depends on a combination of their role's global space privileges and specific privileges applied to this space.`, + } + )} + /> + </EuiFormRow> + + <EuiFormRow + fullWidth + label={i18n.translate( + 'xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormLabel', + { + defaultMessage: 'Define privileges', + } + )} + helpText={i18n.translate( + 'xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormHelpText', + { + defaultMessage: + 'Assign the privilege level you wish to grant to all present and future features across this space.', + } + )} + > + <EuiButtonGroup + name={`basePrivilegeButtonGroup`} + data-test-subj={`basePrivilegeButtonGroup`} + isFullWidth={true} + color={'primary'} + options={[ + { + id: 'basePrivilege_all', + label: 'All', + ['data-test-subj']: 'basePrivilege_all', + }, + { + id: 'basePrivilege_read', + label: 'Read', + ['data-test-subj']: 'basePrivilege_read', + }, + { + id: 'basePrivilege_custom', + label: 'Customize', + ['data-test-subj']: 'basePrivilege_custom', + }, + ]} + idSelected={this.getDisplayedBasePrivilege()} + isDisabled={!hasSelectedSpaces} + onChange={this.onSpaceBasePrivilegeChange} + legend={i18n.translate( + 'xpack.security.management.editRole.spacePrivilegeForm.basePrivilegeControlLegend', + { + defaultMessage: 'Privileges for all features', + } + )} + /> + </EuiFormRow> + + <KibanaPrivilegeTable + role={this.state.role} + privilegeCalculator={this.state.privilegeCalculator} + onChange={this.onFeaturePrivilegesChange} + onChangeAll={this.onChangeAllFeaturePrivileges} + kibanaPrivileges={this.props.kibanaPrivileges} + privilegeIndex={this.state.privilegeIndex} + showAdditionalPermissionsMessage={true} + canCustomizeSubFeaturePrivileges={this.props.canCustomizeSubFeaturePrivileges} + disabled={this.state.selectedBasePrivilege.length > 0 || !hasSelectedSpaces} + allSpacesSelected={this.state.selectedSpaceIds.includes(ALL_SPACES_ID)} /> - </Fragment> + </> )} </EuiForm> ); @@ -298,58 +280,34 @@ export class PrivilegeSpaceForm extends Component<Props, State> { private getSaveButton = () => { const { mode } = this.state; - const isGlobal = this.isDefiningGlobalPrivilege(); let buttonText; switch (mode) { case 'create': - if (isGlobal) { - buttonText = ( - <FormattedMessage - id="xpack.security.management.editRolespacePrivilegeForm.createGlobalPrivilegeButton" - defaultMessage="Create global privilege" - /> - ); - } else { - buttonText = ( - <FormattedMessage - id="xpack.security.management.editRolespacePrivilegeForm.createPrivilegeButton" - defaultMessage="Add Kibana privilege" - /> - ); - } + buttonText = ( + <FormattedMessage + id="xpack.security.management.editRolespacePrivilegeForm.createPrivilegeButton" + defaultMessage="Assign role" + /> + ); break; case 'update': - if (isGlobal) { - buttonText = ( - <FormattedMessage - id="xpack.security.management.editRolespacePrivilegeForm.updateGlobalPrivilegeButton" - defaultMessage="Update global privilege" - /> - ); - } else { - buttonText = ( - <FormattedMessage - id="xpack.security.management.editRolespacePrivilegeForm.updatePrivilegeButton" - defaultMessage="Update space privilege" - /> - ); - } + buttonText = ( + <FormattedMessage + id="xpack.security.management.editRolespacePrivilegeForm.updatePrivilegeButton" + defaultMessage="Update role privileges" + /> + ); break; default: throw new Error(`Unsupported mode: ${mode}`); } - let buttonColor: EuiButtonColor = 'primary'; - if (this.requiresGlobalPrivilegeWarning()) { - buttonColor = 'warning'; - } - return ( <EuiButton onClick={this.onSaveClick} fill disabled={!this.canSave()} - color={buttonColor} + color="primary" data-test-subj={'createSpacePrivilegeButton'} > {buttonText} @@ -357,65 +315,6 @@ export class PrivilegeSpaceForm extends Component<Props, State> { ); }; - private getFeatureListLabel = (disabled: boolean) => { - if (disabled) { - return i18n.translate( - 'xpack.security.management.editRole.spacePrivilegeForm.summaryOfFeaturePrivileges', - { - defaultMessage: 'Summary of feature privileges', - } - ); - } else { - return i18n.translate( - 'xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivileges', - { - defaultMessage: 'Customize by feature', - } - ); - } - }; - - private getFeatureListDescription = (disabled: boolean) => { - if (disabled) { - return i18n.translate( - 'xpack.security.management.editRole.spacePrivilegeForm.featurePrivilegeSummaryDescription', - { - defaultMessage: - 'Some features might be hidden by the space or affected by a global space privilege.', - } - ); - } else { - return i18n.translate( - 'xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivilegeDescription', - { - defaultMessage: - 'Increase privilege levels on a per feature basis. Some features might be hidden by the space or affected by a global space privilege.', - } - ); - } - }; - - private getPrivilegeCallout = () => { - if (this.isDefiningGlobalPrivilege()) { - return ( - <EuiFormRow fullWidth> - <EuiCallOut - color="primary" - iconType="iInCircle" - title={i18n.translate( - 'xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeNotice', - { - defaultMessage: 'These privileges will apply to all current and future spaces.', - } - )} - /> - </EuiFormRow> - ); - } - - return null; - }; - private closeFlyout = () => { this.props.onCancel(); }; @@ -594,13 +493,4 @@ export class PrivilegeSpaceForm extends Component<Props, State> { }; private isDefiningGlobalPrivilege = () => this.state.selectedSpaceIds.includes('*'); - - private requiresGlobalPrivilegeWarning = () => { - const hasOtherSpacePrivilegesDefined = this.props.role.kibana.length > 0; - return ( - this.state.mode === 'create' && - this.isDefiningGlobalPrivilege() && - hasOtherSpacePrivilegesDefined - ); - }; } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx index 99e9edb48d55..d7edbdfa59e8 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_selector.tsx @@ -57,6 +57,9 @@ export class SpaceSelector extends Component<Props, {}> { aria-label={i18n.translate('xpack.security.management.editRole.spaceSelectorLabel', { defaultMessage: 'Spaces', })} + placeholder={i18n.translate('xpack.security.management.editRole.spaceSelectorPlaceholder', { + defaultMessage: 'Add spaces...', + })} fullWidth options={this.getOptions()} renderOption={renderOption} diff --git a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts index ad0a9b79fff4..f82f83c2f21f 100644 --- a/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts +++ b/x-pack/plugins/security/server/anonymous_access/anonymous_access_service.ts @@ -5,16 +5,12 @@ * 2.0. */ -import type { - Capabilities, - CapabilitiesStart, - FakeRawRequest, - IBasePath, - IClusterClient, - KibanaRequest, - Logger, -} from '@kbn/core/server'; -import { CoreKibanaRequest } from '@kbn/core/server'; +import type { Capabilities } from '@kbn/core-capabilities-common'; +import type { CapabilitiesStart } from '@kbn/core-capabilities-server'; +import type { IClusterClient } from '@kbn/core-elasticsearch-server'; +import type { FakeRawRequest, IBasePath, KibanaRequest } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; +import type { Logger } from '@kbn/logging'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; import type { SpacesServiceStart } from '@kbn/spaces-plugin/server'; @@ -176,6 +172,6 @@ export class AnonymousAccessService { auth: { isAuthenticated: authenticateRequest }, path: '/', }; - return CoreKibanaRequest.from(fakeRawRequest); + return kibanaRequestFactory(fakeRawRequest); } } diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts index 8df7450317df..4bb33c5d1c2f 100644 --- a/x-pack/plugins/security/server/audit/audit_service.test.ts +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -8,14 +8,11 @@ import type { Socket } from 'net'; import { lastValueFrom, Observable, of } from 'rxjs'; -import type { FakeRawRequest } from '@kbn/core/server'; -import { CoreKibanaRequest } from '@kbn/core/server'; -import { - coreMock, - httpServerMock, - httpServiceMock, - loggingSystemMock, -} from '@kbn/core/server/mocks'; +import { coreMock } from '@kbn/core/server/mocks'; +import type { FakeRawRequest } from '@kbn/core-http-server'; +import { httpServerMock, httpServiceMock } from '@kbn/core-http-server-mocks'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import type { AuditEvent } from '@kbn/security-plugin-types-server'; import { @@ -233,7 +230,7 @@ describe('#asScoped', () => { headers: {}, path: '/', }; - const request = CoreKibanaRequest.from(fakeRawRequest); + const request = kibanaRequestFactory(fakeRawRequest); await auditSetup.asScoped(request).log({ message: 'MESSAGE', diff --git a/x-pack/plugins/security/server/authentication/api_keys/fake_kibana_request.ts b/x-pack/plugins/security/server/authentication/api_keys/fake_kibana_request.ts index d57fc23cd5c7..c00dc6a44fa7 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/fake_kibana_request.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/fake_kibana_request.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { FakeRawRequest, Headers } from '@kbn/core/server'; -import { CoreKibanaRequest } from '@kbn/core/server'; +import type { FakeRawRequest, Headers } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; export function getFakeKibanaRequest(apiKey: { id: string; api_key: string }) { const requestHeaders: Headers = {}; @@ -20,5 +20,5 @@ export function getFakeKibanaRequest(apiKey: { id: string; api_key: string }) { path: '/', }; - return CoreKibanaRequest.from(fakeRawRequest); + return kibanaRequestFactory(fakeRawRequest); } diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index e1890273469e..04991f4aeefd 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -473,6 +473,7 @@ describe('Session index', () => { expect(mockElasticsearchClient.search).toHaveBeenCalledTimes(1); expect(mockElasticsearchClient.search).toHaveBeenCalledWith({ _source_includes: 'usernameHash,provider', + allow_partial_search_results: true, sort: '_shard_doc', track_total_hits: false, search_after: undefined, @@ -555,6 +556,7 @@ describe('Session index', () => { expect(mockElasticsearchClient.search).toHaveBeenCalledTimes(1); expect(mockElasticsearchClient.search).toHaveBeenCalledWith({ _source_includes: 'usernameHash,provider', + allow_partial_search_results: true, sort: '_shard_doc', track_total_hits: false, search_after: undefined, @@ -649,6 +651,7 @@ describe('Session index', () => { expect(mockElasticsearchClient.search).toHaveBeenCalledTimes(1); expect(mockElasticsearchClient.search).toHaveBeenCalledWith({ _source_includes: 'usernameHash,provider', + allow_partial_search_results: true, sort: '_shard_doc', track_total_hits: false, search_after: undefined, @@ -737,6 +740,7 @@ describe('Session index', () => { expect(mockElasticsearchClient.search).toHaveBeenCalledTimes(1); expect(mockElasticsearchClient.search).toHaveBeenCalledWith({ _source_includes: 'usernameHash,provider', + allow_partial_search_results: true, sort: '_shard_doc', track_total_hits: false, search_after: undefined, @@ -850,6 +854,7 @@ describe('Session index', () => { expect(mockElasticsearchClient.search).toHaveBeenCalledTimes(1); expect(mockElasticsearchClient.search).toHaveBeenCalledWith({ _source_includes: 'usernameHash,provider', + allow_partial_search_results: true, sort: '_shard_doc', track_total_hits: false, search_after: undefined, diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 9f11e9224243..9166ec9deb91 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -857,6 +857,7 @@ export class SessionIndex { size: SESSION_INDEX_CLEANUP_BATCH_SIZE, sort: '_shard_doc', track_total_hits: false, // for performance + allow_partial_search_results: true, }); const { hits } = searchResponse.hits; if (hits.length > 0) { diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index 4837d3729e3f..bfbf5df12759 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -88,6 +88,9 @@ "@kbn/core-http-router-server-mocks", "@kbn/security-authorization-core-common", "@kbn/doc-links", + "@kbn/core-capabilities-server", + "@kbn/core-elasticsearch-server", + "@kbn/core-http-server-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types.ts index 5138e94b78db..6262fd0e579e 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_field_types.ts @@ -9,14 +9,17 @@ import { z } from '@kbn/zod'; import { BuildingBlockType, DataViewId, + EventCategoryOverride, IndexPatternArray, KqlQueryLanguage, RuleFilterArray, RuleNameOverride, RuleQuery, SavedQueryId, + TiebreakerField, TimelineTemplateId, TimelineTemplateTitle, + TimestampField, TimestampOverride, TimestampOverrideFallbackDisabled, } from '../../../../model/rule_schema'; @@ -78,6 +81,9 @@ export const RuleEqlQuery = z.object({ query: RuleQuery, language: z.literal('eql'), filters: RuleFilterArray, + event_category_override: EventCategoryOverride.optional(), + timestamp_field: TimestampField.optional(), + tiebreaker_field: TiebreakerField.optional(), }); export type RuleEsqlQuery = z.infer<typeof RuleEsqlQuery>; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts index 374c6ff492e8..38331d3a01c6 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/diffable_rule/diffable_rule.ts @@ -9,7 +9,6 @@ import { z } from '@kbn/zod'; import { AlertSuppression, AnomalyThreshold, - EventCategoryOverride, HistoryWindowStart, InvestigationFields, InvestigationGuide, @@ -37,8 +36,6 @@ import { ThreatMapping, Threshold, ThresholdAlertSuppression, - TiebreakerField, - TimestampField, } from '../../../../model/rule_schema'; import { @@ -113,9 +110,6 @@ export const DiffableEqlFields = z.object({ type: z.literal('eql'), eql_query: RuleEqlQuery, // NOTE: new field data_source: RuleDataSource.optional(), // NOTE: new field - event_category_override: EventCategoryOverride.optional(), - timestamp_field: TimestampField.optional(), - tiebreaker_field: TiebreakerField.optional(), alert_suppression: AlertSuppression.optional(), }); diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts index f0d8445c41ee..5a1cf49baecd 100644 --- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts +++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts @@ -299,14 +299,8 @@ import type { CreateTimelinesRequestBodyInput, CreateTimelinesResponse, } from './timeline/create_timelines/create_timelines_route.gen'; -import type { - DeleteNoteRequestBodyInput, - DeleteNoteResponse, -} from './timeline/delete_note/delete_note_route.gen'; -import type { - DeleteTimelinesRequestBodyInput, - DeleteTimelinesResponse, -} from './timeline/delete_timelines/delete_timelines_route.gen'; +import type { DeleteNoteRequestBodyInput } from './timeline/delete_note/delete_note_route.gen'; +import type { DeleteTimelinesRequestBodyInput } from './timeline/delete_timelines/delete_timelines_route.gen'; import type { ExportTimelinesRequestQueryInput, ExportTimelinesRequestBodyInput, @@ -768,7 +762,7 @@ If a record already exists for the specified entity, that record is overwritten async deleteNote(props: DeleteNoteProps) { this.log.info(`${new Date().toISOString()} Calling API DeleteNote`); return this.kbnClient - .request<DeleteNoteResponse>({ + .request({ path: '/api/note', headers: { [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31', @@ -801,7 +795,7 @@ If a record already exists for the specified entity, that record is overwritten async deleteTimelines(props: DeleteTimelinesProps) { this.log.info(`${new Date().toISOString()} Calling API DeleteTimelines`); return this.kbnClient - .request<DeleteTimelinesResponse>({ + .request({ path: '/api/timeline', headers: { [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31', diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.gen.ts index d98455c1fdb5..f0ee5665b9f7 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.gen.ts @@ -30,8 +30,3 @@ export const DeleteNoteRequestBody = z.union([ .nullable(), ]); export type DeleteNoteRequestBodyInput = z.input<typeof DeleteNoteRequestBody>; - -export type DeleteNoteResponse = z.infer<typeof DeleteNoteResponse>; -export const DeleteNoteResponse = z.object({ - data: z.object({}).optional(), -}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml index e79cb9aab65a..672488e4ff6c 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml @@ -37,10 +37,3 @@ paths: responses: '200': description: Indicates the note was successfully deleted. - content: - application/json: - schema: - type: object - properties: - data: - type: object diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.gen.ts index 5b63249843f4..98761d710f03 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.gen.ts @@ -25,10 +25,3 @@ export const DeleteTimelinesRequestBody = z.object({ searchIds: z.array(z.string()).optional(), }); export type DeleteTimelinesRequestBodyInput = z.input<typeof DeleteTimelinesRequestBody>; - -export type DeleteTimelinesResponse = z.infer<typeof DeleteTimelinesResponse>; -export const DeleteTimelinesResponse = z.object({ - data: z.object({ - deleteTimeline: z.boolean(), - }), -}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml index bb6674fa6587..4dd5105a737a 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml @@ -36,15 +36,3 @@ paths: responses: '200': description: Indicates the Timeline was successfully deleted. - content: - application/json: - schema: - type: object - required: [data] - properties: - data: - type: object - required: [deleteTimeline] - properties: - deleteTimeline: - type: boolean diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts index 0ee6445dd71e..c8ef00e3c526 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts @@ -60,4 +60,4 @@ export const GetNotesRequestQuery = z.object({ export type GetNotesRequestQueryInput = z.input<typeof GetNotesRequestQuery>; export type GetNotesResponse = z.infer<typeof GetNotesResponse>; -export const GetNotesResponse = z.union([GetNotesResult, z.object({})]); +export const GetNotesResponse = GetNotesResult; diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml index e14212681770..601fd8bfc996 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml @@ -66,9 +66,7 @@ paths: content: application/json: schema: - oneOf: - - $ref: '#/components/schemas/GetNotesResult' - - type: object + $ref: '#/components/schemas/GetNotesResult' components: schemas: diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts index 7a4178807752..72df3caf8c6d 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts @@ -32,11 +32,4 @@ export const GetTimelineRequestQuery = z.object({ export type GetTimelineRequestQueryInput = z.input<typeof GetTimelineRequestQuery>; export type GetTimelineResponse = z.infer<typeof GetTimelineResponse>; -export const GetTimelineResponse = z.union([ - z.object({ - data: z.object({ - getOneTimeline: TimelineResponse, - }), - }), - z.object({}).strict(), -]); +export const GetTimelineResponse = TimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml index 9b5d3fedfd59..40022f34b6ea 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml @@ -32,15 +32,4 @@ paths: content: application/json: schema: - oneOf: - - type: object - required: [data] - properties: - data: - type: object - required: [getOneTimeline] - properties: - getOneTimeline: - $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' - - type: object - additionalProperties: false + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts index 93d627f53263..6de7425d226e 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts @@ -309,8 +309,6 @@ export type FavoriteTimelineResponse = z.infer<typeof FavoriteTimelineResponse>; export const FavoriteTimelineResponse = z.object({ savedObjectId: z.string(), version: z.string(), - code: z.number().nullable().optional(), - message: z.string().nullable().optional(), templateTimelineId: z.string().nullable().optional(), templateTimelineVersion: z.number().nullable().optional(), timelineType: TimelineType.optional(), @@ -318,13 +316,7 @@ export const FavoriteTimelineResponse = z.object({ }); export type PersistTimelineResponse = z.infer<typeof PersistTimelineResponse>; -export const PersistTimelineResponse = z.object({ - data: z.object({ - persistTimeline: z.object({ - timeline: TimelineResponse, - }), - }), -}); +export const PersistTimelineResponse = TimelineResponse; export type BareNoteWithoutExternalRefs = z.infer<typeof BareNoteWithoutExternalRefs>; export const BareNoteWithoutExternalRefs = z.object({ diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml index 568eec197576..fb5b4a964788 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml @@ -225,12 +225,6 @@ components: type: string version: type: string - code: - type: number - nullable: true - message: - type: string - nullable: true templateTimelineId: type: string nullable: true @@ -244,19 +238,7 @@ components: items: $ref: '#/components/schemas/FavoriteTimelineResult' PersistTimelineResponse: - type: object - required: [data] - properties: - data: - type: object - required: [persistTimeline] - properties: - persistTimeline: - type: object - required: [timeline] - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' + $ref: '#/components/schemas/TimelineResponse' ColumnHeaderResult: type: object properties: diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.gen.ts index c3c5f16cf2e3..675ad647c692 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.gen.ts @@ -28,8 +28,4 @@ export const PersistFavoriteRouteRequestBody = z.object({ export type PersistFavoriteRouteRequestBodyInput = z.input<typeof PersistFavoriteRouteRequestBody>; export type PersistFavoriteRouteResponse = z.infer<typeof PersistFavoriteRouteResponse>; -export const PersistFavoriteRouteResponse = z.object({ - data: z.object({ - persistFavorite: FavoriteTimelineResponse, - }), -}); +export const PersistFavoriteRouteResponse = FavoriteTimelineResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml index 3a57dd066d3b..c3a0def405a6 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml @@ -39,15 +39,8 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistFavorite] - properties: - persistFavorite: - $ref: '../model/components.schema.yaml#/components/schemas/FavoriteTimelineResponse' + $ref: '../model/components.schema.yaml#/components/schemas/FavoriteTimelineResponse' + '403': description: Indicates the user does not have the required permissions to persist the favorite status. content: diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.gen.ts index 36def713d499..a24428d64c2b 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.gen.ts @@ -20,8 +20,6 @@ import { BareNote, Note } from '../model/components.gen'; export type ResponseNote = z.infer<typeof ResponseNote>; export const ResponseNote = z.object({ - code: z.number(), - message: z.string(), note: Note, }); @@ -38,8 +36,4 @@ export const PersistNoteRouteRequestBody = z.object({ export type PersistNoteRouteRequestBodyInput = z.input<typeof PersistNoteRouteRequestBody>; export type PersistNoteRouteResponse = z.infer<typeof PersistNoteRouteResponse>; -export const PersistNoteRouteResponse = z.object({ - data: z.object({ - persistNote: ResponseNote, - }), -}); +export const PersistNoteRouteResponse = ResponseNote; diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml index 640e75171c61..c5b3cee08166 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml @@ -50,24 +50,12 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistNote] - properties: - persistNote: - $ref: '#/components/schemas/ResponseNote' + $ref: '#/components/schemas/ResponseNote' components: schemas: ResponseNote: type: object - required: [code, message, note] + required: [note] properties: - code: - type: number - message: - type: string note: $ref: '../model/components.schema.yaml#/components/schemas/Note' diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.gen.ts index 6fd628e5a258..8dd99913c5e3 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.gen.ts @@ -18,16 +18,12 @@ import { z } from '@kbn/zod'; import { PinnedEvent } from '../model/components.gen'; -export type PinnedEventBaseResponseBody = z.infer<typeof PinnedEventBaseResponseBody>; -export const PinnedEventBaseResponseBody = z.object({ - code: z.number(), - message: z.string().optional(), -}); - export type PersistPinnedEventResponse = z.infer<typeof PersistPinnedEventResponse>; export const PersistPinnedEventResponse = z.union([ - PinnedEvent.merge(PinnedEventBaseResponseBody), - z.object({}).nullable(), + PinnedEvent, + z.object({ + unpinned: z.boolean(), + }), ]); export type PersistPinnedEventRouteRequestBody = z.infer<typeof PersistPinnedEventRouteRequestBody>; @@ -41,8 +37,4 @@ export type PersistPinnedEventRouteRequestBodyInput = z.input< >; export type PersistPinnedEventRouteResponse = z.infer<typeof PersistPinnedEventRouteResponse>; -export const PersistPinnedEventRouteResponse = z.object({ - data: z.object({ - persistPinnedEventOnTimeline: PersistPinnedEventResponse, - }), -}); +export const PersistPinnedEventRouteResponse = PersistPinnedEventResponse; diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml index 3b697e957ad8..3059dec00331 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml @@ -37,30 +37,15 @@ paths: content: application/json: schema: - type: object - required: [data] - properties: - data: - type: object - required: [persistPinnedEventOnTimeline] - properties: - persistPinnedEventOnTimeline: - $ref: '#/components/schemas/PersistPinnedEventResponse' + $ref: '#/components/schemas/PersistPinnedEventResponse' components: schemas: PersistPinnedEventResponse: oneOf: - - allOf: - - $ref: '../model/components.schema.yaml#/components/schemas/PinnedEvent' - - $ref: '#/components/schemas/PinnedEventBaseResponseBody' + - $ref: '../model/components.schema.yaml#/components/schemas/PinnedEvent' - type: object - nullable: true - PinnedEventBaseResponseBody: - type: object - required: [code] - properties: - code: - type: number - message: - type: string + required: [unpinned] + properties: + unpinned: + type: boolean diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts index d4c79eec50b2..d245dcf4a16a 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts @@ -32,9 +32,4 @@ export const ResolveTimelineRequestQuery = z.object({ export type ResolveTimelineRequestQueryInput = z.input<typeof ResolveTimelineRequestQuery>; export type ResolveTimelineResponse = z.infer<typeof ResolveTimelineResponse>; -export const ResolveTimelineResponse = z.union([ - z.object({ - data: ResolvedTimeline, - }), - z.object({}).strict(), -]); +export const ResolveTimelineResponse = ResolvedTimeline; diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml index b06969e28cad..ce0cfdc81f52 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml @@ -28,14 +28,7 @@ paths: content: application/json: schema: - oneOf: - - type: object - required: [data] - properties: - data: - $ref: '../model/components.schema.yaml#/components/schemas/ResolvedTimeline' - - type: object - additionalProperties: false + $ref: '../model/components.schema.yaml#/components/schemas/ResolvedTimeline' '400': description: The request is missing parameters diff --git a/x-pack/plugins/security_solution/common/api/timeline/routes.ts b/x-pack/plugins/security_solution/common/api/timeline/routes.ts index 70b339c92f19..d5996d970670 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/routes.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/routes.ts @@ -5,17 +5,14 @@ * 2.0. */ -export { - DeleteTimelinesRequestBody, - DeleteTimelinesResponse, -} from './delete_timelines/delete_timelines_route.gen'; +export { DeleteTimelinesRequestBody } from './delete_timelines/delete_timelines_route.gen'; export { PersistNoteRouteRequestBody, PersistNoteRouteResponse, ResponseNote, } from './persist_note/persist_note_route.gen'; -export { DeleteNoteRequestBody, DeleteNoteResponse } from './delete_note/delete_note_route.gen'; +export { DeleteNoteRequestBody } from './delete_note/delete_note_route.gen'; export { CleanDraftTimelinesResponse, diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index b366a0e55535..265af5a47e1f 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -138,6 +138,8 @@ export const APP_BLOCKLIST_PATH = `${APP_PATH}${BLOCKLIST_PATH}` as const; export const APP_RESPONSE_ACTIONS_HISTORY_PATH = `${APP_PATH}${RESPONSE_ACTIONS_HISTORY_PATH}` as const; export const NOTES_PATH = `${MANAGEMENT_PATH}/notes` as const; +export const SIEM_MIGRATIONS_PATH = '/siem_migrations' as const; +export const SIEM_MIGRATIONS_RULES_PATH = `${SIEM_MIGRATIONS_PATH}/rules` as const; // cloud logs to exclude from default index pattern export const EXCLUDE_ELASTIC_CLOUD_INDICES = ['-*elastic-cloud-logs-*']; diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts index 882dcae3e36a..95ceb5c71882 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts @@ -175,11 +175,15 @@ const extractDiffableEqlFieldsFromRuleObject = ( ): RequiredOptional<DiffableEqlFields> => { return { type: rule.type, - eql_query: extractRuleEqlQuery(rule.query, rule.language, rule.filters), + eql_query: extractRuleEqlQuery({ + query: rule.query, + language: rule.language, + filters: rule.filters, + eventCategoryOverride: rule.event_category_override, + timestampField: rule.timestamp_field, + tiebreakerField: rule.tiebreaker_field, + }), data_source: extractRuleDataSource(rule.index, rule.data_view_id), - event_category_override: rule.event_category_override, - timestamp_field: rule.timestamp_field, - tiebreaker_field: rule.tiebreaker_field, alert_suppression: rule.alert_suppression, }; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts index 4f4164c6a008..99bb27b99357 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts @@ -8,9 +8,12 @@ import type { EqlQueryLanguage, EsqlQueryLanguage, + EventCategoryOverride, KqlQueryLanguage, RuleFilterArray, RuleQuery, + TiebreakerField, + TimestampField, } from '../../../api/detection_engine/model/rule_schema'; import type { InlineKqlQuery, @@ -49,15 +52,23 @@ export const extractInlineKqlQuery = ( }; }; -export const extractRuleEqlQuery = ( - query: RuleQuery, - language: EqlQueryLanguage, - filters: RuleFilterArray | undefined -): RuleEqlQuery => { +interface ExtractRuleEqlQueryParams { + query: RuleQuery; + language: EqlQueryLanguage; + filters: RuleFilterArray | undefined; + eventCategoryOverride: EventCategoryOverride | undefined; + timestampField: TimestampField | undefined; + tiebreakerField: TiebreakerField | undefined; +} + +export const extractRuleEqlQuery = (params: ExtractRuleEqlQueryParams): RuleEqlQuery => { return { - query, - language, - filters: filters ?? [], + query: params.query, + language: params.language, + filters: params.filters ?? [], + event_category_override: params.eventCategoryOverride, + timestamp_field: params.timestampField, + tiebreaker_field: params.tiebreakerField, }; }; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 7fcdabad3b36..369735936561 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -15,7 +15,7 @@ export const allowedExperimentalValues = Object.freeze({ // FIXME:PT delete? excludePoliciesInFilterEnabled: false, - kubernetesEnabled: true, + kubernetesEnabled: false, donutChartEmbeddablesEnabled: false, // Depends on https://github.com/elastic/kibana/issues/136409 item 2 - 6 /** diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts index 10f993b46818..ce7805f89db1 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts @@ -7,7 +7,7 @@ export type { TimelineEqlResponse, - EqlOptionsData, - EqlOptionsSelected, + EqlFieldsComboBoxOptions, + EqlOptions, FieldsEqlOptions, } from '@kbn/timelines-plugin/common'; diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts index ac15080f2e0a..36728e0e928a 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts @@ -22,9 +22,8 @@ import { ElasticRulePartial, RuleMigrationTranslationResult, RuleMigrationComments, - RuleMigrationAllTaskStats, - RuleMigration, RuleMigrationTaskStats, + RuleMigration, RuleMigrationResourceData, RuleMigrationResourceType, RuleMigrationResource, @@ -44,7 +43,7 @@ export const CreateRuleMigrationResponse = z.object({ }); export type GetAllStatsRuleMigrationResponse = z.infer<typeof GetAllStatsRuleMigrationResponse>; -export const GetAllStatsRuleMigrationResponse = RuleMigrationAllTaskStats; +export const GetAllStatsRuleMigrationResponse = z.array(RuleMigrationTaskStats); export type GetRuleMigrationRequestParams = z.infer<typeof GetRuleMigrationRequestParams>; export const GetRuleMigrationRequestParams = z.object({ diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml index 778530467112..fdb589e7b45c 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml @@ -93,7 +93,9 @@ paths: content: application/json: schema: - $ref: '../../rule_migration.schema.yaml#/components/schemas/RuleMigrationAllTaskStats' + type: array + items: + $ref: '../../rule_migration.schema.yaml#/components/schemas/RuleMigrationTaskStats' ## Specific rule migration APIs diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts index 0554ef18a13f..2260b83190e2 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts @@ -88,6 +88,10 @@ export const ElasticRule = z.object({ * The Elastic prebuilt rule id matched. */ prebuilt_rule_id: NonEmptyString.optional(), + /** + * The Elastic integration IDs related to the rule. + */ + integration_ids: z.array(z.string()).optional(), /** * The Elastic rule id installed as a result. */ @@ -187,6 +191,10 @@ export const RuleMigration = z */ export type RuleMigrationTaskStats = z.infer<typeof RuleMigrationTaskStats>; export const RuleMigrationTaskStats = z.object({ + /** + * The migration id + */ + id: NonEmptyString, /** * Indicates if the migration task status. */ @@ -216,24 +224,16 @@ export const RuleMigrationTaskStats = z.object({ */ failed: z.number().int(), }), + /** + * The moment the migration was created. + */ + created_at: z.string(), /** * The moment of the last update. */ - last_updated_at: z.string().optional(), + last_updated_at: z.string(), }); -export type RuleMigrationAllTaskStats = z.infer<typeof RuleMigrationAllTaskStats>; -export const RuleMigrationAllTaskStats = z.array( - RuleMigrationTaskStats.merge( - z.object({ - /** - * The migration id - */ - migration_id: NonEmptyString, - }) - ) -); - /** * The type of the rule migration resource. */ diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml index 95ff05df39a1..17c70665b9ad 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml @@ -73,6 +73,11 @@ components: prebuilt_rule_id: description: The Elastic prebuilt rule id matched. $ref: './common.schema.yaml#/components/schemas/NonEmptyString' + integration_ids: + type: array + items: + type: string + description: The Elastic integration IDs related to the rule. id: description: The Elastic rule id installed as a result. $ref: './common.schema.yaml#/components/schemas/NonEmptyString' @@ -140,9 +145,15 @@ components: type: object description: The rule migration task stats object. required: + - id - status - rules + - created_at + - last_updated_at properties: + id: + description: The migration id + $ref: './common.schema.yaml#/components/schemas/NonEmptyString' status: type: string description: Indicates if the migration task status. @@ -176,23 +187,13 @@ components: failed: type: integer description: The number of rules that have failed migration. + created_at: + type: string + description: The moment the migration was created. last_updated_at: type: string description: The moment of the last update. - RuleMigrationAllTaskStats: - type: array - items: - allOf: - - $ref: '#/components/schemas/RuleMigrationTaskStats' - - type: object - required: - - migration_id - properties: - migration_id: - description: The migration id - $ref: './common.schema.yaml#/components/schemas/NonEmptyString' - RuleMigrationTranslationResult: type: string description: The rule translation result. diff --git a/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts b/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts deleted file mode 100644 index 33d0c226fc44..000000000000 --- a/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EventHit } from '../search_strategy'; -import { getDataFromFieldsHits, getDataSafety } from './field_formatters'; -import { eventDetailsFormattedFields, eventHit } from '@kbn/securitysolution-t-grid'; - -describe('Events Details Helpers', () => { - const fields: EventHit['fields'] = eventHit.fields; - const resultFields = eventDetailsFormattedFields; - describe('#getDataFromFieldsHits', () => { - it('happy path', () => { - const result = getDataFromFieldsHits(fields); - expect(result).toEqual(resultFields); - }); - it('lets get weird', () => { - const whackFields = { - 'crazy.pants': [ - { - 'matched.field': ['matched_field'], - first_seen: ['2021-02-22T17:29:25.195Z'], - provider: ['yourself'], - type: ['custom'], - 'matched.atomic': ['matched_atomic'], - lazer: [ - { - 'great.field': ['grrrrr'], - lazer: [ - { - lazer: [ - { - cool: true, - lazer: [ - { - lazer: [ - { - lazer: [ - { - lazer: [ - { - whoa: false, - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - lazer: [ - { - cool: false, - }, - ], - }, - ], - }, - { - 'great.field': ['grrrrr_2'], - }, - ], - }, - ], - }; - const whackResultFields = [ - { - category: 'crazy', - field: 'crazy.pants', - values: [ - '{"matched.field":["matched_field"],"first_seen":["2021-02-22T17:29:25.195Z"],"provider":["yourself"],"type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"],"lazer":[{"lazer":[{"cool":true,"lazer":[{"lazer":[{"lazer":[{"lazer":[{"whoa":false}]}]}]}]}]},{"lazer":[{"cool":false}]}]},{"great.field":["grrrrr_2"]}]}', - ], - originalValue: [ - '{"matched.field":["matched_field"],"first_seen":["2021-02-22T17:29:25.195Z"],"provider":["yourself"],"type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"],"lazer":[{"lazer":[{"cool":true,"lazer":[{"lazer":[{"lazer":[{"lazer":[{"whoa":false}]}]}]}]}]},{"lazer":[{"cool":false}]}]},{"great.field":["grrrrr_2"]}]}', - ], - isObjectArray: true, - }, - ]; - const result = getDataFromFieldsHits(whackFields); - expect(result).toEqual(whackResultFields); - }); - }); - it('#getDataSafety', async () => { - const result = await getDataSafety(getDataFromFieldsHits, fields); - expect(result).toEqual(resultFields); - }); -}); diff --git a/x-pack/plugins/security_solution/common/utils/field_formatters.ts b/x-pack/plugins/security_solution/common/utils/field_formatters.ts deleted file mode 100644 index 1d8c05ec9ccf..000000000000 --- a/x-pack/plugins/security_solution/common/utils/field_formatters.ts +++ /dev/null @@ -1,148 +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 { ecsFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/ecs_field_map'; -import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; -import { technicalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/technical_rule_field_map'; -import { isEmpty } from 'lodash/fp'; -import { ENRICHMENT_DESTINATION_PATH } from '../constants'; - -import type { Fields, TimelineEventsDetailsItem } from '../search_strategy'; -import { toObjectArrayOfStrings, toStringArray } from './to_array'; - -export const baseCategoryFields = ['@timestamp', 'labels', 'message', 'tags']; - -export const getFieldCategory = (field: string): string => { - const fieldCategory = field.split('.')[0]; - if (!isEmpty(fieldCategory) && baseCategoryFields.includes(fieldCategory)) { - return 'base'; - } - return fieldCategory; -}; - -export const formatGeoLocation = (item: unknown[]) => { - const itemGeo = item.length > 0 ? (item[0] as { coordinates: number[] }) : null; - if (itemGeo != null && !isEmpty(itemGeo.coordinates)) { - try { - return toStringArray({ - lon: itemGeo.coordinates[0], - lat: itemGeo.coordinates[1], - }); - } catch { - return toStringArray(item); - } - } - return toStringArray(item); -}; - -export const isGeoField = (field: string) => - field.includes('geo.location') || field.includes('geoip.location'); - -export const isThreatEnrichmentFieldOrSubfield = (field: string, prependField?: string) => - prependField?.includes(ENRICHMENT_DESTINATION_PATH) || field === ENRICHMENT_DESTINATION_PATH; - -export const getDataFromFieldsHits = ( - fields: Fields, - prependField?: string, - prependFieldCategory?: string -): TimelineEventsDetailsItem[] => - Object.keys(fields).reduce<TimelineEventsDetailsItem[]>((accumulator, field) => { - const item: unknown[] = fields[field]; - - const fieldCategory = - prependFieldCategory != null ? prependFieldCategory : getFieldCategory(field); - if (isGeoField(field)) { - return [ - ...accumulator, - { - category: fieldCategory, - field, - values: formatGeoLocation(item), - originalValue: formatGeoLocation(item), - isObjectArray: true, // important for UI - }, - ]; - } - - const objArrStr = toObjectArrayOfStrings(item); - const strArr = objArrStr.map(({ str }) => str); - const isObjectArray = objArrStr.some((o) => o.isObjectArray); - const dotField = prependField ? `${prependField}.${field}` : field; - - // return simple field value (non-esc object, non-array) - if ( - !isObjectArray || - Object.keys({ ...ecsFieldMap, ...technicalRuleFieldMap, ...legacyExperimentalFieldMap }).find( - (ecsField) => ecsField === field - ) === undefined - ) { - return [ - ...accumulator, - { - category: fieldCategory, - field: dotField, - values: strArr, - originalValue: strArr, - isObjectArray, - }, - ]; - } - - const threatEnrichmentObject = isThreatEnrichmentFieldOrSubfield(field, prependField) - ? [ - { - category: fieldCategory, - field: dotField, - values: strArr, - originalValue: strArr, - isObjectArray, - }, - ] - : []; - - // format nested fields - const nestedFields = Array.isArray(item) - ? item - .reduce<TimelineEventsDetailsItem[][]>((acc, curr) => { - acc.push(getDataFromFieldsHits(curr as Fields, dotField, fieldCategory)); - return acc; - }, []) - .flat() - : getDataFromFieldsHits(item, prependField, fieldCategory); - - // combine duplicate fields - const flat: Record<string, TimelineEventsDetailsItem> = [ - ...accumulator, - ...nestedFields, - ...threatEnrichmentObject, - ].reduce( - (acc, f) => ({ - ...acc, - // acc/flat is hashmap to determine if we already have the field or not without an array iteration - // its converted back to array in return with Object.values - ...(acc[f.field] != null - ? { - [f.field]: { - ...f, - originalValue: acc[f.field].originalValue.includes(f.originalValue[0]) - ? acc[f.field].originalValue - : [...acc[f.field].originalValue, ...f.originalValue], - values: acc[f.field].values?.includes(f.values?.[0] || '') - ? acc[f.field].values - : [...(acc[f.field].values || []), ...(f.values || [])], - }, - } - : { [f.field]: f }), - }), - {} as Record<string, TimelineEventsDetailsItem> - ); - - return Object.values(flat); - }, []); - -export const getDataSafety = <A, T>(fn: (args: A) => T, args: A): Promise<T> => - new Promise((resolve) => setTimeout(() => resolve(fn(args)))); diff --git a/x-pack/plugins/security_solution/common/utils/to_array.ts b/x-pack/plugins/security_solution/common/utils/to_array.ts deleted file mode 100644 index b6945708ff0d..000000000000 --- a/x-pack/plugins/security_solution/common/utils/to_array.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const toArray = <T = string>(value: T | T[] | null | undefined): T[] => - Array.isArray(value) ? value : value == null ? [] : [value]; - -export const toStringArray = <T = string>(value: T | T[] | null): string[] => { - if (Array.isArray(value)) { - return value.reduce<string[]>((acc, v) => { - if (v != null) { - switch (typeof v) { - case 'number': - case 'boolean': - return [...acc, v.toString()]; - case 'object': - try { - return [...acc, JSON.stringify(v)]; - } catch { - return [...acc, 'Invalid Object']; - } - case 'string': - return [...acc, v]; - default: - return [...acc, `${v}`]; - } - } - return acc; - }, []); - } else if (value == null) { - return []; - } else if (!Array.isArray(value) && typeof value === 'object') { - try { - return [JSON.stringify(value)]; - } catch { - return ['Invalid Object']; - } - } else { - return [`${value}`]; - } -}; - -export const toObjectArrayOfStrings = <T = string>( - value: T | T[] | null -): Array<{ - str: string; - isObjectArray?: boolean; -}> => { - if (Array.isArray(value)) { - return value.reduce< - Array<{ - str: string; - isObjectArray?: boolean; - }> - >((acc, v) => { - if (v != null) { - switch (typeof v) { - case 'number': - case 'boolean': - return [...acc, { str: v.toString() }]; - case 'object': - try { - return [...acc, { str: JSON.stringify(v), isObjectArray: true }]; // need to track when string is not a simple value - } catch { - return [...acc, { str: 'Invalid Object' }]; - } - case 'string': - return [...acc, { str: v }]; - default: - return [...acc, { str: `${v}` }]; - } - } - return acc; - }, []); - } else if (value == null) { - return []; - } else if (!Array.isArray(value) && typeof value === 'object') { - try { - return [{ str: JSON.stringify(value), isObjectArray: true }]; - } catch { - return [{ str: 'Invalid Object' }]; - } - } else { - return [{ str: `${value}` }]; - } -}; diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml index 562bf9b80d3e..351e53f2fd01 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -43,13 +43,6 @@ paths: required: true responses: '200': - content: - application/json: - schema: - type: object - properties: - data: - type: object description: Indicates the note was successfully deleted. summary: Delete a note tags: @@ -111,9 +104,7 @@ paths: content: application/json: schema: - oneOf: - - $ref: '#/components/schemas/GetNotesResult' - - type: object + $ref: '#/components/schemas/GetNotesResult' description: Indicates the requested notes were returned. summary: Get notes tags: @@ -157,17 +148,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistNote: - $ref: '#/components/schemas/ResponseNote' - required: - - persistNote - required: - - data + $ref: '#/components/schemas/ResponseNote' description: Indicates the note was successfully created. summary: Add or update a note tags: @@ -200,17 +181,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistPinnedEventOnTimeline: - $ref: '#/components/schemas/PersistPinnedEventResponse' - required: - - persistPinnedEventOnTimeline - required: - - data + $ref: '#/components/schemas/PersistPinnedEventResponse' description: Indicates the event was successfully pinned to the Timeline. summary: Pin an event tags: @@ -243,20 +214,6 @@ paths: required: true responses: '200': - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - deleteTimeline: - type: boolean - required: - - deleteTimeline - required: - - data description: Indicates the Timeline was successfully deleted. summary: Delete Timelines or Timeline templates tags: @@ -281,20 +238,7 @@ paths: content: application/json: schema: - oneOf: - - type: object - properties: - data: - type: object - properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - getOneTimeline - required: - - data - - additionalProperties: false - type: object + $ref: '#/components/schemas/TimelineResponse' description: Indicates that the (template) Timeline was found and returned. summary: Get Timeline or Timeline template details tags: @@ -636,17 +580,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistFavorite: - $ref: '#/components/schemas/FavoriteTimelineResponse' - required: - - persistFavorite - required: - - data + $ref: '#/components/schemas/FavoriteTimelineResponse' description: Indicates the favorite status was successfully updated. '403': content: @@ -811,15 +745,7 @@ paths: content: application/json: schema: - oneOf: - - type: object - properties: - data: - $ref: '#/components/schemas/ResolvedTimeline' - required: - - data - - additionalProperties: false - type: object + $ref: '#/components/schemas/ResolvedTimeline' description: The (template) Timeline has been found '400': description: The request is missing parameters @@ -1089,16 +1015,10 @@ components: FavoriteTimelineResponse: type: object properties: - code: - nullable: true - type: number favorite: items: $ref: '#/components/schemas/FavoriteTimelineResult' type: array - message: - nullable: true - type: string savedObjectId: type: string templateTimelineId: @@ -1267,28 +1187,15 @@ components: - version PersistPinnedEventResponse: oneOf: - - allOf: - - $ref: '#/components/schemas/PinnedEvent' - - $ref: '#/components/schemas/PinnedEventBaseResponseBody' - - nullable: true - type: object - PersistTimelineResponse: - type: object - properties: - data: - type: object + - $ref: '#/components/schemas/PinnedEvent' + - type: object properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline + unpinned: + type: boolean required: - - persistTimeline - required: - - data + - unpinned + PersistTimelineResponse: + $ref: '#/components/schemas/TimelineResponse' PinnedEvent: allOf: - $ref: '#/components/schemas/BarePinnedEvent' @@ -1301,15 +1208,6 @@ components: required: - pinnedEventId - version - PinnedEventBaseResponseBody: - type: object - properties: - code: - type: number - message: - type: string - required: - - code QueryMatchResult: type: object properties: @@ -1350,15 +1248,9 @@ components: ResponseNote: type: object properties: - code: - type: number - message: - type: string note: $ref: '#/components/schemas/Note' required: - - code - - message - note RowRendererId: enum: diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml index a68919aa0e1f..410dd9962f40 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -43,13 +43,6 @@ paths: required: true responses: '200': - content: - application/json: - schema: - type: object - properties: - data: - type: object description: Indicates the note was successfully deleted. summary: Delete a note tags: @@ -111,9 +104,7 @@ paths: content: application/json: schema: - oneOf: - - $ref: '#/components/schemas/GetNotesResult' - - type: object + $ref: '#/components/schemas/GetNotesResult' description: Indicates the requested notes were returned. summary: Get notes tags: @@ -157,17 +148,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistNote: - $ref: '#/components/schemas/ResponseNote' - required: - - persistNote - required: - - data + $ref: '#/components/schemas/ResponseNote' description: Indicates the note was successfully created. summary: Add or update a note tags: @@ -200,17 +181,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistPinnedEventOnTimeline: - $ref: '#/components/schemas/PersistPinnedEventResponse' - required: - - persistPinnedEventOnTimeline - required: - - data + $ref: '#/components/schemas/PersistPinnedEventResponse' description: Indicates the event was successfully pinned to the Timeline. summary: Pin an event tags: @@ -243,20 +214,6 @@ paths: required: true responses: '200': - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - deleteTimeline: - type: boolean - required: - - deleteTimeline - required: - - data description: Indicates the Timeline was successfully deleted. summary: Delete Timelines or Timeline templates tags: @@ -281,20 +238,7 @@ paths: content: application/json: schema: - oneOf: - - type: object - properties: - data: - type: object - properties: - getOneTimeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - getOneTimeline - required: - - data - - additionalProperties: false - type: object + $ref: '#/components/schemas/TimelineResponse' description: Indicates that the (template) Timeline was found and returned. summary: Get Timeline or Timeline template details tags: @@ -636,17 +580,7 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: object - properties: - persistFavorite: - $ref: '#/components/schemas/FavoriteTimelineResponse' - required: - - persistFavorite - required: - - data + $ref: '#/components/schemas/FavoriteTimelineResponse' description: Indicates the favorite status was successfully updated. '403': content: @@ -811,15 +745,7 @@ paths: content: application/json: schema: - oneOf: - - type: object - properties: - data: - $ref: '#/components/schemas/ResolvedTimeline' - required: - - data - - additionalProperties: false - type: object + $ref: '#/components/schemas/ResolvedTimeline' description: The (template) Timeline has been found '400': description: The request is missing parameters @@ -1089,16 +1015,10 @@ components: FavoriteTimelineResponse: type: object properties: - code: - nullable: true - type: number favorite: items: $ref: '#/components/schemas/FavoriteTimelineResult' type: array - message: - nullable: true - type: string savedObjectId: type: string templateTimelineId: @@ -1267,28 +1187,15 @@ components: - version PersistPinnedEventResponse: oneOf: - - allOf: - - $ref: '#/components/schemas/PinnedEvent' - - $ref: '#/components/schemas/PinnedEventBaseResponseBody' - - nullable: true - type: object - PersistTimelineResponse: - type: object - properties: - data: - type: object + - $ref: '#/components/schemas/PinnedEvent' + - type: object properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '#/components/schemas/TimelineResponse' - required: - - timeline + unpinned: + type: boolean required: - - persistTimeline - required: - - data + - unpinned + PersistTimelineResponse: + $ref: '#/components/schemas/TimelineResponse' PinnedEvent: allOf: - $ref: '#/components/schemas/BarePinnedEvent' @@ -1301,15 +1208,6 @@ components: required: - pinnedEventId - version - PinnedEventBaseResponseBody: - type: object - properties: - code: - type: number - message: - type: string - required: - - code QueryMatchResult: type: object properties: @@ -1350,15 +1248,9 @@ components: ResponseNote: type: object properties: - code: - type: number - message: - type: string note: $ref: '#/components/schemas/Note' required: - - code - - message - note RowRendererId: enum: diff --git a/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/lens/add_to_timeline.test.ts b/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/lens/add_to_timeline.test.ts index b1a272de4d37..ef920458f51d 100644 --- a/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/lens/add_to_timeline.test.ts +++ b/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/lens/add_to_timeline.test.ts @@ -4,10 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import type { CellValueContext, EmbeddableInput, IEmbeddable } from '@kbn/embeddable-plugin/public'; import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { LENS_EMBEDDABLE_TYPE } from '@kbn/lens-plugin/public'; import type { SecurityAppStore } from '../../../../common/store/types'; import { createAddToTimelineLensAction, getInvestigatedValue } from './add_to_timeline'; import { KibanaServices } from '../../../../common/lib/kibana'; @@ -16,6 +15,9 @@ import type { DataProvider } from '../../../../../common/types'; import { TimelineId, EXISTS_OPERATOR } from '../../../../../common/types'; import { addProvider } from '../../../../timelines/store/actions'; import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import type { Query, Filter, AggregateQuery, TimeRange } from '@kbn/es-query'; +import type { LensApi } from '@kbn/lens-plugin/public'; +import { getLensApiMock } from '@kbn/lens-plugin/public/react_embeddable/mocks'; jest.mock('../../../../common/lib/kibana'); const currentAppId$ = new Subject<string | undefined>(); @@ -29,16 +31,32 @@ const store = { dispatch: mockDispatch, } as unknown as SecurityAppStore; -class MockEmbeddable { - public type; - constructor(type: string) { - this.type = type; - } - getFilters() {} - getQuery() {} -} +const getMockLensApi = ( + { from, to = 'now' }: { from: string; to: string } = { from: 'now-24h', to: 'now' } +): LensApi => + getLensApiMock({ + timeRange$: new BehaviorSubject<TimeRange | undefined>({ from, to }), + getViewUnderlyingDataArgs: jest.fn(() => ({ + dataViewSpec: { id: 'index-pattern-id' }, + timeRange: { from: 'now-7d', to: 'now' }, + filters: [], + query: undefined, + columns: [], + })), + saveToLibrary: jest.fn(async () => 'saved-id'), + }); + +const getMockEmbeddable = (type: string): IEmbeddable => + ({ + type, + filters$: new BehaviorSubject<Filter[] | undefined>([]), + query$: new BehaviorSubject<Query | AggregateQuery | undefined>({ + query: 'test', + language: 'kuery', + }), + } as unknown as IEmbeddable); -const lensEmbeddable = new MockEmbeddable(LENS_EMBEDDABLE_TYPE) as unknown as IEmbeddable; +const lensEmbeddable = getMockLensApi(); const columnMeta = { field: 'user.name', @@ -85,7 +103,7 @@ describe('createAddToTimelineLensAction', () => { expect( await addToTimelineAction.isCompatible({ ...context, - embeddable: new MockEmbeddable('not_lens') as unknown as IEmbeddable, + embeddable: getMockEmbeddable('not_lens') as unknown as IEmbeddable, }) ).toEqual(false); }); diff --git a/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/lens/add_to_timeline.ts b/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/lens/add_to_timeline.ts index 84c95fd659fb..3ccbd30efd61 100644 --- a/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/lens/add_to_timeline.ts +++ b/x-pack/plugins/security_solution/public/app/actions/add_to_timeline/lens/add_to_timeline.ts @@ -6,14 +6,16 @@ */ import type { CellValueContext, IEmbeddable } from '@kbn/embeddable-plugin/public'; -import { isErrorEmbeddable, isFilterableEmbeddable } from '@kbn/embeddable-plugin/public'; +import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; import { createAction } from '@kbn/ui-actions-plugin/public'; +import { apiPublishesUnifiedSearch } from '@kbn/presentation-publishing'; +import { isLensApi } from '@kbn/lens-plugin/public'; import { KibanaServices } from '../../../../common/lib/kibana'; import type { SecurityAppStore } from '../../../../common/store/types'; import { addProvider } from '../../../../timelines/store/actions'; import type { DataProvider } from '../../../../../common/types'; import { EXISTS_OPERATOR, TimelineId } from '../../../../../common/types'; -import { fieldHasCellActions, isInSecurityApp, isLensEmbeddable } from '../../utils'; +import { fieldHasCellActions, isInSecurityApp } from '../../utils'; import { ADD_TO_TIMELINE, ADD_TO_TIMELINE_FAILED_TEXT, @@ -83,8 +85,8 @@ export const createAddToTimelineLensAction = ({ getDisplayName: () => ADD_TO_TIMELINE, isCompatible: async ({ embeddable, data }) => !isErrorEmbeddable(embeddable as IEmbeddable) && - isLensEmbeddable(embeddable as IEmbeddable) && - isFilterableEmbeddable(embeddable as IEmbeddable) && + isLensApi(embeddable) && + apiPublishesUnifiedSearch(embeddable) && isDataColumnsFilterable(data) && isInSecurityApp(currentAppId), execute: async ({ data }) => { diff --git a/x-pack/plugins/security_solution/public/app/actions/copy_to_clipboard/lens/copy_to_clipboard.test.ts b/x-pack/plugins/security_solution/public/app/actions/copy_to_clipboard/lens/copy_to_clipboard.test.ts index bb036e3f12e0..0ec4e0084834 100644 --- a/x-pack/plugins/security_solution/public/app/actions/copy_to_clipboard/lens/copy_to_clipboard.test.ts +++ b/x-pack/plugins/security_solution/public/app/actions/copy_to_clipboard/lens/copy_to_clipboard.test.ts @@ -7,12 +7,14 @@ import type { CellValueContext, EmbeddableInput, IEmbeddable } from '@kbn/embeddable-plugin/public'; import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { LENS_EMBEDDABLE_TYPE } from '@kbn/lens-plugin/public'; +import type { LensApi } from '@kbn/lens-plugin/public'; import { createCopyToClipboardLensAction } from './copy_to_clipboard'; import { KibanaServices } from '../../../../common/lib/kibana'; import { APP_UI_ID } from '../../../../../common/constants'; -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import type { TimeRange } from '@kbn/es-query'; +import { getLensApiMock } from '@kbn/lens-plugin/public/react_embeddable/mocks'; jest.mock('../../../../common/lib/kibana'); const currentAppId$ = new Subject<string | undefined>(); @@ -23,14 +25,29 @@ KibanaServices.get().notifications.toasts.addSuccess = mockSuccessToast; const mockCopy = jest.fn((text: string) => true); jest.mock('copy-to-clipboard', () => (text: string) => mockCopy(text)); -class MockEmbeddable { - public type; - constructor(type: string) { - this.type = type; - } - getFilters() {} - getQuery() {} -} +const getMockLensApi = ( + { from, to = 'now' }: { from: string; to: string } = { from: 'now-24h', to: 'now' } +): LensApi => + getLensApiMock({ + timeRange$: new BehaviorSubject<TimeRange | undefined>({ from, to }), + getViewUnderlyingDataArgs: jest.fn(() => ({ + dataViewSpec: { id: 'index-pattern-id' }, + timeRange: { from: 'now-7d', to: 'now' }, + filters: [], + query: undefined, + columns: [], + })), + saveToLibrary: jest.fn(async () => 'saved-id'), + }); + +const getMockEmbeddable = (type: string): IEmbeddable => + ({ + type, + getFilters: jest.fn(), + getQuery: jest.fn(), + } as unknown as IEmbeddable); + +const lensEmbeddable = getMockLensApi(); const columnMeta = { field: 'user.name', @@ -39,7 +56,6 @@ const columnMeta = { sourceParams: { indexPatternId: 'some-pattern-id' }, }; const data: CellValueContext['data'] = [{ columnMeta, value: 'the value' }]; -const lensEmbeddable = new MockEmbeddable(LENS_EMBEDDABLE_TYPE) as unknown as IEmbeddable; const context = { data, @@ -76,7 +92,7 @@ describe('createCopyToClipboardLensAction', () => { expect( await copyToClipboardAction.isCompatible({ ...context, - embeddable: new MockEmbeddable('not_lens') as unknown as IEmbeddable, + embeddable: getMockEmbeddable('not_lens') as unknown as IEmbeddable, }) ).toEqual(false); }); diff --git a/x-pack/plugins/security_solution/public/app/actions/utils.ts b/x-pack/plugins/security_solution/public/app/actions/utils.ts index 12c5400dbcf3..d857c54d5091 100644 --- a/x-pack/plugins/security_solution/public/app/actions/utils.ts +++ b/x-pack/plugins/security_solution/public/app/actions/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ import type { IEmbeddable } from '@kbn/embeddable-plugin/public'; -import { LENS_EMBEDDABLE_TYPE, type Embeddable as LensEmbeddable } from '@kbn/lens-plugin/public'; +import { isLensApi } from '@kbn/lens-plugin/public'; import type { Serializable } from '@kbn/utility-types'; import { APP_UI_ID } from '../../../common/constants'; @@ -21,8 +21,10 @@ export const isInSecurityApp = (currentAppId?: string): boolean => { return !!currentAppId && currentAppId === APP_UI_ID; }; -export const isLensEmbeddable = (embeddable: IEmbeddable): embeddable is LensEmbeddable => { - return embeddable.type === LENS_EMBEDDABLE_TYPE; +// @TODO: this is a temporary fix. It needs a better refactor on the consumer side here to +// adapt to the new Embeddable architecture +export const isLensEmbeddable = (embeddable: IEmbeddable): embeddable is IEmbeddable => { + return isLensApi(embeddable); }; export const fieldHasCellActions = (field?: string): boolean => { diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.test.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.test.tsx index c1d3760813a1..49508dd79b1e 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.test.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.test.tsx @@ -22,32 +22,11 @@ jest.mock('./timeline', () => ({ Timeline: () => <div>{'Timeline'}</div>, })); -jest.mock('../../../common/components/navigation/use_security_solution_navigation', () => { - return { - useSecuritySolutionNavigation: () => ({ - icon: 'logoSecurity', - items: [ - { - id: 'investigate', - name: 'Investigate', - items: [ - { - 'data-href': 'some-data-href', - 'data-test-subj': 'navigation-cases', - disabled: false, - href: 'some-href', - id: 'cases', - isSelected: true, - name: 'Cases', - }, - ], - tabIndex: undefined, - }, - ], - name: 'Security', - }), - }; -}); +const navProps = { icon: 'logoSecurity', items: [], name: 'Security' }; +const mockUseSecuritySolutionNavigation = jest.fn(); +jest.mock('../../../common/components/navigation/use_security_solution_navigation', () => ({ + useSecuritySolutionNavigation: () => mockUseSecuritySolutionNavigation(), +})); const mockUseRouteSpy = jest.fn((): [{ pageName: string }] => [ { pageName: SecurityPageName.alerts }, @@ -69,6 +48,39 @@ const renderComponent = ({ describe('SecuritySolutionTemplateWrapper', () => { beforeEach(() => { jest.clearAllMocks(); + mockUseSecuritySolutionNavigation.mockReturnValue(navProps); + }); + + describe('when navigation props are defined (classic nav)', () => { + beforeEach(() => { + mockUseSecuritySolutionNavigation.mockReturnValue(navProps); + }); + it('should render the children', async () => { + const { queryByText } = renderComponent(); + expect(queryByText('child of wrapper')).toBeInTheDocument(); + }); + }); + + describe('when navigation props are null (project nav)', () => { + beforeEach(() => { + mockUseSecuritySolutionNavigation.mockReturnValue(null); + }); + + it('should render the children', async () => { + const { queryByText } = renderComponent(); + expect(queryByText('child of wrapper')).toBeInTheDocument(); + }); + }); + + describe('when navigation props are undefined (loading)', () => { + beforeEach(() => { + mockUseSecuritySolutionNavigation.mockReturnValue(undefined); + }); + + it('should not render the children', async () => { + const { queryByText } = renderComponent(); + expect(queryByText('child of wrapper')).not.toBeInTheDocument(); + }); }); it('Should render with bottom bar when user allowed', async () => { diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx index 19e8d55aa2dd..f547d128ab54 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx @@ -69,8 +69,8 @@ export const SecuritySolutionTemplateWrapper: React.FC<SecuritySolutionTemplateW const { euiTheme, colorMode: globalColorMode } = useEuiTheme(); // There is some logic in the StyledKibanaPageTemplate that checks for children presence, and we dont even need to render the children - // here if isEmptyState is set - const isNotEmpty = !rest.isEmptyState; + // solutionNavProps is momentarily initialized to undefined, this check prevents the children from being re-rendered in the initial load + const renderChildren = !rest.isEmptyState && solutionNavProps !== undefined; /* * StyledKibanaPageTemplate is a styled EuiPageTemplate. Security solution currently passes the header @@ -83,11 +83,11 @@ export const SecuritySolutionTemplateWrapper: React.FC<SecuritySolutionTemplateW theme={euiTheme} $isShowingTimelineOverlay={isShowingTimelineOverlay} paddingSize="none" - solutionNav={solutionNavProps} + solutionNav={solutionNavProps ?? undefined} restrictWidth={false} {...rest} > - {isNotEmpty && ( + {renderChildren && ( <> <GlobalKQLHeader /> <KibanaPageTemplate.Section diff --git a/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/assets_links.ts b/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/assets_links.ts index f4ae848beb25..c77e0fe7a03e 100644 --- a/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/assets_links.ts +++ b/x-pack/plugins/security_solution/public/app/solution_navigation/links/sections/assets_links.ts @@ -34,7 +34,7 @@ const assetsCloudDefendAppLink: LinkItem = { landingIcon: IconEcctlLazy, isBeta: true, hideTimeline: true, - links: [], // cloudDefendPolicies link is added in createAssetsLinkFromManage + links: [], }; export const createAssetsLinkFromManage = (manageLink: LinkItem): LinkItem => { @@ -54,13 +54,7 @@ export const createAssetsLinkFromManage = (manageLink: LinkItem): LinkItem => { assetsSubLinks.push({ ...endpointsLink, links: endpointsSubLinks }); } - const cloudPoliciesLink = manageLink.links?.find( - ({ id }) => id === SecurityPageName.cloudDefendPolicies - ); - if (cloudPoliciesLink) { - // Add cloud defend policies link as cloud defend sub link - assetsSubLinks.push({ ...assetsCloudDefendAppLink, links: [cloudPoliciesLink] }); - } + assetsSubLinks.push(assetsCloudDefendAppLink); return { ...assetsAppLink, diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 709bb5f614f7..1769a805f488 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -101,6 +101,13 @@ export const EXCEPTIONS = i18n.translate('xpack.securitySolution.navigation.exce defaultMessage: 'Shared exception lists', }); +export const SIEM_MIGRATIONS_RULES = i18n.translate( + 'xpack.securitySolution.navigation.siemMigrationsRules', + { + defaultMessage: 'SIEM Rules Migrations', + } +); + export const ALERTS = i18n.translate('xpack.securitySolution.navigation.alerts', { defaultMessage: 'Alerts', }); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx index fff9450b6a1c..594629e9f9e2 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx @@ -58,7 +58,7 @@ describe('AlertsPreview', () => { it('renders', () => { const { getByTestId } = render( <TestProviders> - <AlertsPreview alertsData={mockAlertsData} name="host1" fieldName="host.name" /> + <AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" /> </TestProviders> ); @@ -68,7 +68,7 @@ describe('AlertsPreview', () => { it('renders correct alerts number', () => { const { getByTestId } = render( <TestProviders> - <AlertsPreview alertsData={mockAlertsData} name="host1" fieldName="host.name" /> + <AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" /> </TestProviders> ); @@ -78,7 +78,7 @@ describe('AlertsPreview', () => { it('should render the correct number of distribution bar section based on the number of severities', () => { const { queryAllByTestId } = render( <TestProviders> - <AlertsPreview alertsData={mockAlertsData} name="host1" fieldName="host.name" /> + <AlertsPreview alertsData={mockAlertsData} value="host1" field="host.name" /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx index a5f08527cdc7..8edd0b798193 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx @@ -5,40 +5,21 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { capitalize } from 'lodash'; import type { EuiThemeComputed } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { DistributionBar } from '@kbn/security-solution-distribution-bar'; -import { - buildEntityFlyoutPreviewQuery, - getAbbreviatedNumber, -} from '@kbn/cloud-security-posture-common'; -import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; -import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; -import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; -import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { getAbbreviatedNumber } from '@kbn/cloud-security-posture-common'; import type { AlertsByStatus, ParsedAlertsData, } from '../../../overview/components/detection_response/alerts_by_status/types'; import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel'; import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers'; -import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy'; -import { - buildHostNamesFilter, - buildUserNamesFilter, - RiskScoreEntity, -} from '../../../../common/search_strategy'; -import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score'; -import { FIRST_RECORD_PAGINATION } from '../../../entity_analytics/common'; -import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left'; -import { - EntityDetailsLeftPanelTab, - CspInsightLeftPanelSubTab, -} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; -import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left'; +import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; +import { useNavigateEntityInsight } from '../../hooks/use_entity_insight'; const AlertsCount = ({ alertsTotal, @@ -77,13 +58,13 @@ const AlertsCount = ({ export const AlertsPreview = ({ alertsData, - fieldName, - name, + field, + value, isPreviewMode, }: { alertsData: ParsedAlertsData; - fieldName: string; - name: string; + field: 'host.name' | 'user.name'; + value: string; isPreviewMode?: boolean; }) => { const { euiTheme } = useEuiTheme(); @@ -107,101 +88,14 @@ export const AlertsPreview = ({ const totalAlertsCount = alertStats.reduce((total, item) => total + item.count, 0); - const { data } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery(fieldName, name), - sort: [], - enabled: true, - pageSize: 1, - ignore_unavailable: true, - }); - const isUsingHostName = fieldName === 'host.name'; - const passedFindings = data?.count.passed || 0; - const failedFindings = data?.count.failed || 0; - - const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; - - const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', name), - sort: [], - enabled: true, - pageSize: 1, - }); - - const { - CRITICAL = 0, - HIGH = 0, - MEDIUM = 0, - LOW = 0, - NONE = 0, - } = vulnerabilitiesData?.count || {}; - - const hasVulnerabilitiesFindings = hasVulnerabilitiesData({ - critical: CRITICAL, - high: HIGH, - medium: MEDIUM, - low: LOW, - none: NONE, - }); - - const buildFilterQuery = useMemo( - () => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])), - [isUsingHostName, name] - ); - - const riskScoreState = useRiskScore({ - riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user, - filterQuery: buildFilterQuery, - onlyLatest: false, - pagination: FIRST_RECORD_PAGINATION, - }); - - const { data: hostRisk } = riskScoreState; - - const riskData = hostRisk?.[0]; - - const isRiskScoreExist = isUsingHostName - ? !!(riskData as HostRiskScore)?.host.risk - : !!(riskData as UserRiskScore)?.user.risk; - const hasNonClosedAlerts = totalAlertsCount > 0; - const { openLeftPanel } = useExpandableFlyoutApi(); - - const goToEntityInsightTab = useCallback(() => { - openLeftPanel({ - id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey, - params: isUsingHostName - ? { - name, - isRiskScoreExist, - hasMisconfigurationFindings, - hasVulnerabilitiesFindings, - hasNonClosedAlerts, - path: { - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.ALERTS, - }, - } - : { - user: { name }, - isRiskScoreExist, - hasMisconfigurationFindings, - hasNonClosedAlerts, - path: { - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.ALERTS, - }, - }, - }); - }, [ - hasMisconfigurationFindings, - hasNonClosedAlerts, - hasVulnerabilitiesFindings, - isRiskScoreExist, - isUsingHostName, - name, - openLeftPanel, - ]); + const { goToEntityInsightTab } = useNavigateEntityInsight({ + field, + value, + queryIdExtension: 'ALERTS_PREVIEW', + subTab: CspInsightLeftPanelSubTab.ALERTS, + }); const link = useMemo( () => !isPreviewMode diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx index 966de68e3497..6567fb41a93f 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx @@ -60,7 +60,7 @@ interface AlertsDetailsFields { } export const AlertsDetailsTable = memo( - ({ fieldName, queryName }: { fieldName: 'host.name' | 'user.name'; queryName: string }) => { + ({ field, value }: { field: 'host.name' | 'user.name'; value: string }) => { useEffect(() => { uiMetricService.trackUiMetric( METRIC_TYPE.COUNT, @@ -90,7 +90,7 @@ export const AlertsDetailsTable = memo( const { to, from } = useGlobalTime(); const { signalIndexName } = useSignalIndex(); const { data } = useQueryAlerts({ - query: buildEntityAlertsQuery(fieldName, to, from, queryName, 500), + query: buildEntityAlertsQuery(field, to, from, value, 500), queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS, indexName: signalIndexName, }); @@ -216,11 +216,11 @@ export const AlertsDetailsTable = memo( [ { title: - fieldName === 'host.name' + field === 'host.name' ? OPEN_IN_ALERTS_TITLE_HOSTNAME : OPEN_IN_ALERTS_TITLE_USERNAME, - selectedOptions: [queryName], - fieldName, + selectedOptions: [value], + fieldName: field, }, { title: OPEN_IN_ALERTS_TITLE_STATUS, @@ -230,7 +230,7 @@ export const AlertsDetailsTable = memo( ], true ), - [fieldName, openAlertsPageWithFilters, queryName] + [field, openAlertsPageWithFilters, value] ); return ( diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx index 2e7b4171fd02..84e3ee4faee9 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx @@ -42,7 +42,7 @@ function isCspFlyoutPanelProps( } export const InsightsTabCsp = memo( - ({ name, fieldName }: { name: string; fieldName: 'host.name' | 'user.name' }) => { + ({ value, field }: { value: string; field: 'host.name' | 'user.name' }) => { const panels = useExpandableFlyoutState(); let hasMisconfigurationFindings = false; @@ -150,11 +150,11 @@ export const InsightsTabCsp = memo( /> <EuiSpacer size="xl" /> {activeInsightsId === CspInsightLeftPanelSubTab.MISCONFIGURATIONS ? ( - <MisconfigurationFindingsDetailsTable fieldName={fieldName} queryName={name} /> + <MisconfigurationFindingsDetailsTable field={field} value={value} /> ) : activeInsightsId === CspInsightLeftPanelSubTab.VULNERABILITIES ? ( - <VulnerabilitiesFindingsDetailsTable queryName={name} /> + <VulnerabilitiesFindingsDetailsTable value={value} /> ) : ( - <AlertsDetailsTable fieldName={fieldName} queryName={name} /> + <AlertsDetailsTable field={field} value={value} /> )} </> ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx index 69912c58e4e1..00430c2b8726 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx @@ -59,7 +59,7 @@ const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: numb * Insights view displayed in the document details expandable flyout left section */ export const MisconfigurationFindingsDetailsTable = memo( - ({ fieldName, queryName }: { fieldName: 'host.name' | 'user.name'; queryName: string }) => { + ({ field, value }: { field: 'host.name' | 'user.name'; value: string }) => { useEffect(() => { uiMetricService.trackUiMetric( METRIC_TYPE.COUNT, @@ -68,7 +68,7 @@ export const MisconfigurationFindingsDetailsTable = memo( }, []); const { data } = useMisconfigurationFindings({ - query: buildEntityFlyoutPreviewQuery(fieldName, queryName), + query: buildEntityFlyoutPreviewQuery(field, value), sort: [], enabled: true, pageSize: 1, @@ -183,7 +183,7 @@ export const MisconfigurationFindingsDetailsTable = memo( <EuiPanel hasShadow={false}> <SecuritySolutionLinkAnchor deepLinkId={SecurityPageName.cloudSecurityPostureFindings} - path={`${getFindingsPageUrl(queryName, fieldName)}`} + path={`${getFindingsPageUrl(value, field)}`} target={'_blank'} external={false} onClick={() => { diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx index 82c5f91bf425..155946a791f7 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx @@ -44,7 +44,7 @@ interface VulnerabilitiesPackage extends Vulnerability { }; } -export const VulnerabilitiesFindingsDetailsTable = memo(({ queryName }: { queryName: string }) => { +export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: string }) => { useEffect(() => { uiMetricService.trackUiMetric( METRIC_TYPE.COUNT, @@ -53,7 +53,7 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ queryName }: { queryN }, []); const { data } = useVulnerabilitiesFindings({ - query: buildEntityFlyoutPreviewQuery('host.name', queryName), + query: buildEntityFlyoutPreviewQuery('host.name', value), sort: [], enabled: true, pageSize: 1, @@ -204,7 +204,7 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ queryName }: { queryN <EuiPanel hasShadow={false}> <SecuritySolutionLinkAnchor deepLinkId={SecurityPageName.cloudSecurityPostureFindings} - path={`${getVulnerabilityUrl(queryName, 'host.name')}`} + path={`${getVulnerabilityUrl(value, 'host.name')}`} target={'_blank'} external={false} onClick={() => { diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx index 7139994f7e97..eec66e765371 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx @@ -7,97 +7,56 @@ import { EuiAccordion, EuiHorizontalRule, EuiSpacer, EuiTitle, useEuiTheme } from '@elastic/eui'; -import React, { useMemo } from 'react'; +import React from 'react'; import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; -import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; -import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; -import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; -import { FILTER_CLOSED } from '../../../common/types'; +import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; import { MisconfigurationsPreview } from './misconfiguration/misconfiguration_preview'; import { VulnerabilitiesPreview } from './vulnerabilities/vulnerabilities_preview'; import { AlertsPreview } from './alerts/alerts_preview'; import { useGlobalTime } from '../../common/containers/use_global_time'; -import type { ParsedAlertsData } from '../../overview/components/detection_response/alerts_by_status/types'; import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../overview/components/detection_response/alerts_by_status/types'; -import { useAlertsByStatus } from '../../overview/components/detection_response/alerts_by_status/use_alerts_by_status'; -import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index'; +import { useNonClosedAlerts } from '../hooks/use_non_closed_alerts'; export const EntityInsight = <T,>({ - name, - fieldName, + value, + field, isPreviewMode, }: { - name: string; - fieldName: 'host.name' | 'user.name'; + value: string; + field: 'host.name' | 'user.name'; isPreviewMode?: boolean; }) => { const { euiTheme } = useEuiTheme(); const insightContent: React.ReactElement[] = []; - const { data: dataMisconfiguration } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery(fieldName, name), - sort: [], - enabled: true, - pageSize: 1, - }); - - const passedFindings = dataMisconfiguration?.count.passed || 0; - const failedFindings = dataMisconfiguration?.count.failed || 0; - - const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; - - const { data } = useVulnerabilitiesPreview({ - query: buildEntityFlyoutPreviewQuery(fieldName, name), - sort: [], - enabled: true, - pageSize: 1, - }); - - const { CRITICAL = 0, HIGH = 0, MEDIUM = 0, LOW = 0, NONE = 0 } = data?.count || {}; - - const hasVulnerabilitiesFindings = hasVulnerabilitiesData({ - critical: CRITICAL, - high: HIGH, - medium: MEDIUM, - low: LOW, - none: NONE, - }); - - const isVulnerabilitiesFindingForHost = hasVulnerabilitiesFindings && fieldName === 'host.name'; + const { hasMisconfigurationFindings: showMisconfigurationsPreview } = useHasMisconfigurations( + field, + value + ); - const { signalIndexName } = useSignalIndex(); + const { hasVulnerabilitiesFindings } = useHasVulnerabilities(field, value); - const entityFilter = useMemo(() => ({ field: fieldName, value: name }), [fieldName, name]); + const showVulnerabilitiesPreview = hasVulnerabilitiesFindings && field === 'host.name'; const { to, from } = useGlobalTime(); - const { items: alertsData } = useAlertsByStatus({ - entityFilter, - signalIndexName, - queryId: DETECTION_RESPONSE_ALERTS_BY_STATUS_ID, + const { hasNonClosedAlerts: showAlertsPreview, filteredAlertsData } = useNonClosedAlerts({ + field, + value, to, from, + queryId: DETECTION_RESPONSE_ALERTS_BY_STATUS_ID, }); - const filteredAlertsData: ParsedAlertsData = alertsData - ? Object.fromEntries(Object.entries(alertsData).filter(([key]) => key !== FILTER_CLOSED)) - : {}; - - const alertsOpenCount = filteredAlertsData?.open?.total || 0; - - const alertsAcknowledgedCount = filteredAlertsData?.acknowledged?.total || 0; - - const alertsCount = alertsOpenCount + alertsAcknowledgedCount; - - if (alertsCount > 0) { + if (showAlertsPreview) { insightContent.push( <> <AlertsPreview alertsData={filteredAlertsData} - fieldName={fieldName} - name={name} + field={field} + value={value} isPreviewMode={isPreviewMode} /> <EuiSpacer size="s" /> @@ -105,34 +64,23 @@ export const EntityInsight = <T,>({ ); } - if (hasMisconfigurationFindings) + if (showMisconfigurationsPreview) insightContent.push( <> - <MisconfigurationsPreview - name={name} - fieldName={fieldName} - hasNonClosedAlerts={alertsCount > 0} - isPreviewMode={isPreviewMode} - /> + <MisconfigurationsPreview value={value} field={field} isPreviewMode={isPreviewMode} /> <EuiSpacer size="s" /> </> ); - if (isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings) + if (showVulnerabilitiesPreview) insightContent.push( <> - <VulnerabilitiesPreview - name={name} - isPreviewMode={isPreviewMode} - hasNonClosedAlerts={alertsCount > 0} - /> + <VulnerabilitiesPreview value={value} field={field} isPreviewMode={isPreviewMode} /> <EuiSpacer size="s" /> </> ); return ( <> - {(insightContent.length > 0 || - hasMisconfigurationFindings || - (isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings)) && ( + {insightContent.length > 0 && ( <> <EuiAccordion initialIsOpen={true} diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx index 4b53e86b68e2..a3c6bcd38d26 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx @@ -37,7 +37,7 @@ describe('MisconfigurationsPreview', () => { it('renders', () => { const { getByTestId } = render( <TestProviders> - <MisconfigurationsPreview name="host1" fieldName="host.name" /> + <MisconfigurationsPreview value="host1" field="host.name" /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx index 42a5906ce4e3..c7c1889a5838 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx @@ -5,39 +5,23 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { css } from '@emotion/react'; import type { EuiThemeComputed } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, useEuiTheme, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { DistributionBar } from '@kbn/security-solution-distribution-bar'; -import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; import { i18n } from '@kbn/i18n'; -import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; -import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; -import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; -import { hasVulnerabilitiesData, statusColors } from '@kbn/cloud-security-posture'; +import { statusColors } from '@kbn/cloud-security-posture'; import { METRIC_TYPE } from '@kbn/analytics'; import { ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT, uiMetricService, } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel'; -import { - CspInsightLeftPanelSubTab, - EntityDetailsLeftPanelTab, -} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; -import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left'; -import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left'; -import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score'; -import { RiskScoreEntity } from '../../../../common/entity_analytics/risk_engine'; -import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy'; -import { buildHostNamesFilter, buildUserNamesFilter } from '../../../../common/search_strategy'; - -const FIRST_RECORD_PAGINATION = { - cursorStart: 0, - querySize: 1, -}; +import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; +import { useNavigateEntityInsight } from '../../hooks/use_entity_insight'; export const getFindingsStats = (passedFindingsStats: number, failedFindingsStats: number) => { if (passedFindingsStats === 0 && failedFindingsStats === 0) return []; @@ -101,113 +85,30 @@ const MisconfigurationPreviewScore = ({ }; export const MisconfigurationsPreview = ({ - name, - fieldName, - hasNonClosedAlerts = false, + value, + field, isPreviewMode, }: { - name: string; - fieldName: 'host.name' | 'user.name'; - hasNonClosedAlerts?: boolean; + value: string; + field: 'host.name' | 'user.name'; isPreviewMode?: boolean; }) => { - const { data } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery(fieldName, name), - sort: [], - enabled: true, - pageSize: 1, - ignore_unavailable: true, - }); - const isUsingHostName = fieldName === 'host.name'; - const passedFindings = data?.count.passed || 0; - const failedFindings = data?.count.failed || 0; + const { hasMisconfigurationFindings, passedFindings, failedFindings } = useHasMisconfigurations( + field, + value + ); useEffect(() => { uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT); }, []); const { euiTheme } = useEuiTheme(); - const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; - const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', name), - sort: [], - enabled: true, - pageSize: 1, + const { goToEntityInsightTab } = useNavigateEntityInsight({ + field, + value, + queryIdExtension: 'MISCONFIGURATION_PREVIEW', + subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, }); - - const { - CRITICAL = 0, - HIGH = 0, - MEDIUM = 0, - LOW = 0, - NONE = 0, - } = vulnerabilitiesData?.count || {}; - - const hasVulnerabilitiesFindings = hasVulnerabilitiesData({ - critical: CRITICAL, - high: HIGH, - medium: MEDIUM, - low: LOW, - none: NONE, - }); - - const buildFilterQuery = useMemo( - () => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])), - [isUsingHostName, name] - ); - - const riskScoreState = useRiskScore({ - riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user, - filterQuery: buildFilterQuery, - onlyLatest: false, - pagination: FIRST_RECORD_PAGINATION, - }); - - const { data: hostRisk } = riskScoreState; - - const riskData = hostRisk?.[0]; - - const isRiskScoreExist = isUsingHostName - ? !!(riskData as HostRiskScore)?.host.risk - : !!(riskData as UserRiskScore)?.user.risk; - - const { openLeftPanel } = useExpandableFlyoutApi(); - - const goToEntityInsightTab = useCallback(() => { - openLeftPanel({ - id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey, - params: isUsingHostName - ? { - name, - isRiskScoreExist, - hasMisconfigurationFindings, - hasVulnerabilitiesFindings, - hasNonClosedAlerts, - path: { - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, - }, - } - : { - user: { name }, - isRiskScoreExist, - hasMisconfigurationFindings, - hasNonClosedAlerts, - path: { - tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, - subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, - }, - }, - }); - }, [ - hasMisconfigurationFindings, - hasNonClosedAlerts, - hasVulnerabilitiesFindings, - isRiskScoreExist, - isUsingHostName, - name, - openLeftPanel, - ]); const link = useMemo( () => !isPreviewMode diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx index cc71d1be2158..14a6366fd4ba 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx @@ -37,7 +37,7 @@ describe('VulnerabilitiesPreview', () => { it('renders', () => { const { getByTestId } = render( <TestProviders> - <VulnerabilitiesPreview name="host1" /> + <VulnerabilitiesPreview value="host1" field="host.name" /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx index c4335d921e37..5a5b638abafa 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { css } from '@emotion/react'; import type { EuiThemeComputed } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, useEuiTheme, EuiTitle } from '@elastic/eui'; @@ -17,24 +17,14 @@ import { getAbbreviatedNumber, } from '@kbn/cloud-security-posture-common'; import { getVulnerabilityStats, hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; -import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; -import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; import { ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW, uiMetricService, } from '@kbn/cloud-security-posture-common/utils/ui_metrics'; import { METRIC_TYPE } from '@kbn/analytics'; import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel'; -import { EntityDetailsLeftPanelTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; -import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left'; -import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score'; -import { RiskScoreEntity } from '../../../../common/entity_analytics/risk_engine'; -import { buildHostNamesFilter } from '../../../../common/search_strategy'; - -const FIRST_RECORD_PAGINATION = { - cursorStart: 0, - querySize: 1, -}; +import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; +import { useNavigateEntityInsight } from '../../hooks/use_entity_insight'; const VulnerabilitiesCount = ({ vulnerabilitiesTotal, @@ -70,20 +60,20 @@ const VulnerabilitiesCount = ({ }; export const VulnerabilitiesPreview = ({ - name, + value, + field, isPreviewMode, - hasNonClosedAlerts = false, }: { - name: string; + value: string; + field: 'host.name' | 'user.name'; isPreviewMode?: boolean; - hasNonClosedAlerts?: boolean; }) => { useEffect(() => { uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW); }, []); const { data } = useVulnerabilitiesPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', name), + query: buildEntityFlyoutPreviewQuery(field, value), sort: [], enabled: true, pageSize: 1, @@ -103,49 +93,12 @@ export const VulnerabilitiesPreview = ({ const { euiTheme } = useEuiTheme(); - const { data: dataMisconfiguration } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', name), - sort: [], - enabled: true, - pageSize: 1, - }); - - const passedFindings = dataMisconfiguration?.count.passed || 0; - const failedFindings = dataMisconfiguration?.count.failed || 0; - - const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; - - const buildFilterQuery = useMemo(() => buildHostNamesFilter([name]), [name]); - const riskScoreState = useRiskScore({ - riskEntity: RiskScoreEntity.host, - filterQuery: buildFilterQuery, - onlyLatest: false, - pagination: FIRST_RECORD_PAGINATION, + const { goToEntityInsightTab } = useNavigateEntityInsight({ + field, + value, + queryIdExtension: 'VULNERABILITIES_PREVIEW', + subTab: CspInsightLeftPanelSubTab.VULNERABILITIES, }); - const { data: hostRisk } = riskScoreState; - const riskData = hostRisk?.[0]; - const isRiskScoreExist = riskData?.host.risk; - const { openLeftPanel } = useExpandableFlyoutApi(); - const goToEntityInsightTab = useCallback(() => { - openLeftPanel({ - id: HostDetailsPanelKey, - params: { - name, - isRiskScoreExist, - hasMisconfigurationFindings, - hasVulnerabilitiesFindings, - hasNonClosedAlerts, - path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, subTab: 'vulnerabilitiesTabId' }, - }, - }); - }, [ - hasMisconfigurationFindings, - hasNonClosedAlerts, - hasVulnerabilitiesFindings, - isRiskScoreExist, - name, - openLeftPanel, - ]); const link = useMemo( () => !isPreviewMode diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_entity_insight.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_entity_insight.ts new file mode 100644 index 000000000000..fc35474ffdef --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_entity_insight.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { useCallback } from 'react'; +import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; +import { UserDetailsPanelKey } from '../../flyout/entity_details/user_details_left'; +import { HostDetailsPanelKey } from '../../flyout/entity_details/host_details_left'; +import { EntityDetailsLeftPanelTab } from '../../flyout/entity_details/shared/components/left_panel/left_panel_header'; +import { useGlobalTime } from '../../common/containers/use_global_time'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../overview/components/detection_response/alerts_by_status/types'; +import { useNonClosedAlerts } from './use_non_closed_alerts'; +import { useHasRiskScore } from './use_risk_score_data'; + +export const useNavigateEntityInsight = ({ + field, + value, + subTab, + queryIdExtension, +}: { + field: 'host.name' | 'user.name'; + value: string; + subTab: string; + queryIdExtension: string; +}) => { + const isHostNameField = field === 'host.name'; + const { to, from } = useGlobalTime(); + + const { hasNonClosedAlerts } = useNonClosedAlerts({ + field, + value, + to, + from, + queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}${queryIdExtension}`, + }); + + const { hasVulnerabilitiesFindings } = useHasVulnerabilities(field, value); + + const { hasRiskScore } = useHasRiskScore({ + field, + value, + }); + const { hasMisconfigurationFindings } = useHasMisconfigurations(field, value); + const { openLeftPanel } = useExpandableFlyoutApi(); + + const goToEntityInsightTab = useCallback(() => { + openLeftPanel({ + id: isHostNameField ? HostDetailsPanelKey : UserDetailsPanelKey, + params: isHostNameField + ? { + name: value, + isRiskScoreExist: hasRiskScore, + hasMisconfigurationFindings, + hasVulnerabilitiesFindings, + hasNonClosedAlerts, + path: { + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab, + }, + } + : { + user: { name: value }, + isRiskScoreExist: hasRiskScore, + hasMisconfigurationFindings, + hasNonClosedAlerts, + path: { + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab, + }, + }, + }); + }, [ + openLeftPanel, + isHostNameField, + value, + hasRiskScore, + hasMisconfigurationFindings, + hasVulnerabilitiesFindings, + hasNonClosedAlerts, + subTab, + ]); + + return { goToEntityInsightTab }; +}; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_non_closed_alerts.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_non_closed_alerts.ts new file mode 100644 index 000000000000..598f78cd6840 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_non_closed_alerts.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { FILTER_CLOSED } from '@kbn/securitysolution-data-table/common/types'; +import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index'; +import { useAlertsByStatus } from '../../overview/components/detection_response/alerts_by_status/use_alerts_by_status'; +import type { ParsedAlertsData } from '../../overview/components/detection_response/alerts_by_status/types'; + +export const useNonClosedAlerts = ({ + field, + value, + to, + from, + queryId, +}: { + field: 'host.name' | 'user.name'; + value: string; + to: string; + from: string; + queryId: string; +}) => { + const { signalIndexName } = useSignalIndex(); + + const entityFilter = useMemo(() => ({ field, value }), [field, value]); + + const { items: alertsData } = useAlertsByStatus({ + entityFilter, + signalIndexName, + queryId, + to, + from, + }); + + const filteredAlertsData: ParsedAlertsData = alertsData + ? Object.fromEntries(Object.entries(alertsData).filter(([key]) => key !== FILTER_CLOSED)) + : {}; + + const hasNonClosedAlerts = + (filteredAlertsData?.acknowledged?.total || 0) + (filteredAlertsData?.open?.total || 0) > 0; + + return { hasNonClosedAlerts, filteredAlertsData }; +}; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_risk_score_data.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_risk_score_data.ts new file mode 100644 index 000000000000..b100fe30b105 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/hooks/use_risk_score_data.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { + RiskScoreEntity, + type HostRiskScore, + type UserRiskScore, + buildHostNamesFilter, + buildUserNamesFilter, +} from '../../../common/search_strategy'; +import { useRiskScore } from '../../entity_analytics/api/hooks/use_risk_score'; +import { FIRST_RECORD_PAGINATION } from '../../entity_analytics/common'; + +export const useHasRiskScore = ({ + field, + value, +}: { + field: 'host.name' | 'user.name'; + value: string; +}) => { + const isHostNameField = field === 'host.name'; + const buildFilterQuery = useMemo( + () => (isHostNameField ? buildHostNamesFilter([value]) : buildUserNamesFilter([value])), + [isHostNameField, value] + ); + const { data } = useRiskScore({ + riskEntity: isHostNameField ? RiskScoreEntity.host : RiskScoreEntity.user, + filterQuery: buildFilterQuery, + onlyLatest: false, + pagination: FIRST_RECORD_PAGINATION, + }); + + const riskData = data?.[0]; + + const hasRiskScore = isHostNameField + ? !!(riskData as HostRiskScore)?.host.risk + : !!(riskData as UserRiskScore)?.user.risk; + + return { hasRiskScore }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.test.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.test.ts index 4d1807b91b71..f8b0e1bff944 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.test.ts @@ -4,13 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import type { UseInsightDataProvidersProps, Provider } from './use_insight_data_providers'; + +import { renderHook } from '@testing-library/react'; +import type { Provider } from './use_insight_data_providers'; import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; -import { - useInsightDataProviders, - type UseInsightDataProvidersResult, -} from './use_insight_data_providers'; +import { useInsightDataProviders } from './use_insight_data_providers'; import { mockAlertDetailsData } from '../../../event_details/mocks'; const mockAlertDetailsDataWithIsObject = mockAlertDetailsData.map((detail) => { @@ -103,7 +101,7 @@ const providerWithRange: Provider[][] = [ describe('useInsightDataProviders', () => { it('should return 2 data providers, 1 with a nested provider ANDed to it', () => { - const { result } = renderHook<UseInsightDataProvidersProps, UseInsightDataProvidersResult>(() => + const { result } = renderHook(() => useInsightDataProviders({ providers: nestedAndProvider, alertData: mockAlertDetailsDataWithIsObject, @@ -117,7 +115,7 @@ describe('useInsightDataProviders', () => { }); it('should return 3 data providers without any containing nested ANDs', () => { - const { result } = renderHook<UseInsightDataProvidersProps, UseInsightDataProvidersResult>(() => + const { result } = renderHook(() => useInsightDataProviders({ providers: topLevelOnly, alertData: mockAlertDetailsDataWithIsObject, @@ -130,7 +128,7 @@ describe('useInsightDataProviders', () => { }); it('should use the string literal if no field in the alert matches a bracketed value', () => { - const { result } = renderHook<UseInsightDataProvidersProps, UseInsightDataProvidersResult>(() => + const { result } = renderHook(() => useInsightDataProviders({ providers: nonExistantField, alertData: mockAlertDetailsDataWithIsObject, @@ -145,7 +143,7 @@ describe('useInsightDataProviders', () => { }); it('should use template data providers when called without alertData', () => { - const { result } = renderHook<UseInsightDataProvidersProps, UseInsightDataProvidersResult>(() => + const { result } = renderHook(() => useInsightDataProviders({ providers: nestedAndProvider, }) @@ -159,7 +157,7 @@ describe('useInsightDataProviders', () => { }); it('should return an empty array of dataProviders and populated filters if a provider contains a range type', () => { - const { result } = renderHook<UseInsightDataProvidersProps, UseInsightDataProvidersResult>(() => + const { result } = renderHook(() => useInsightDataProviders({ providers: providerWithRange, }) diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts index a162c625c7ad..98d5e2f3f7df 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts @@ -4,13 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { QueryOperator } from '@kbn/timelines-plugin/common'; import { DataProviderTypeEnum } from '../../../../../../common/api/timeline'; import { useInsightQuery } from './use_insight_query'; import { TestProviders } from '../../../../mock'; -import type { UseInsightQuery, UseInsightQueryResult } from './use_insight_query'; import { IS_OPERATOR } from '../../../../../timelines/components/timeline/data_providers/data_provider'; const mockProvider = { @@ -30,7 +28,7 @@ const mockProvider = { describe('useInsightQuery', () => { it('should return renderable defaults', () => { - const { result } = renderHook<React.PropsWithChildren<UseInsightQuery>, UseInsightQueryResult>( + const { result } = renderHook( () => useInsightQuery({ dataProviders: [mockProvider], diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.test.tsx index 0f59bbbccb8a..0aed8dc19663 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.test.tsx @@ -34,6 +34,20 @@ describe('Security Solution Navigation', () => { beforeEach(() => { jest.clearAllMocks(); }); + describe('while chrome style is undefined', () => { + beforeAll(() => { + mockGetChromeStyle$.mockReturnValue(of()); + }); + + it('should return proper navigation props', async () => { + const { result } = renderHook(useSecuritySolutionNavigation); + expect(result.current).toEqual(undefined); + + // check rendering of SecuritySideNav children + expect(mockSecuritySideNav).not.toHaveBeenCalled(); + }); + }); + describe('when classic navigation is enabled', () => { beforeAll(() => { mockGetChromeStyle$.mockReturnValue(of('classic')); @@ -77,9 +91,9 @@ describe('Security Solution Navigation', () => { mockGetChromeStyle$.mockReturnValue(of('project')); }); - it('should return undefined props when disabled', () => { + it('should return null props when disabled', () => { const { result } = renderHook(useSecuritySolutionNavigation); - expect(result.current).toEqual(undefined); + expect(result.current).toEqual(null); }); it('should initialize breadcrumbs', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx index a2f54c04ca46..9abebcbb359c 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_security_solution_navigation.tsx @@ -23,16 +23,20 @@ const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mai defaultMessage: 'Security', }); -export const useSecuritySolutionNavigation = (): KibanaPageTemplateProps['solutionNav'] => { +export const useSecuritySolutionNavigation = (): KibanaPageTemplateProps['solutionNav'] | null => { const { chrome } = useKibana().services; const chromeStyle$ = useMemo(() => chrome.getChromeStyle$(), [chrome]); - const chromeStyle = useObservable(chromeStyle$, 'classic'); + const chromeStyle = useObservable(chromeStyle$, undefined); useBreadcrumbsNav(); + if (chromeStyle === undefined) { + return undefined; // wait for chromeStyle to be initialized + } + if (chromeStyle === 'project') { - // new shared-ux 'project' navigation enabled, return undefined to disable the 'classic' navigation - return undefined; + // new shared-ux 'project' navigation enabled, return null to disable the 'classic' navigation + return null; } return { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx index 6b264a4dc759..871750d5ad00 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx @@ -19,7 +19,6 @@ import type { TypedLensByValueInput, XYState, } from '@kbn/lens-plugin/public'; -import type { LensBaseEmbeddableInput } from '@kbn/lens-plugin/public/embeddable'; import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { useKibana } from '../../lib/kibana'; import { useLensAttributes } from './use_lens_attributes'; @@ -159,7 +158,7 @@ const LensEmbeddableComponent: React.FC<LensEmbeddableComponentProps> = ({ [dispatch, inputsModelId] ); - const onFilterCallback = useCallback<Required<LensBaseEmbeddableInput>['onFilter']>( + const onFilterCallback = useCallback<Required<TypedLensByValueInput>['onFilter']>( (event) => { if (disableOnClickFilter) { event.preventDefault(); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_embeddable_inspect.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_embeddable_inspect.tsx index ee577d4a310d..152930fc7649 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_embeddable_inspect.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_embeddable_inspect.tsx @@ -5,14 +5,14 @@ * 2.0. */ -import type { LensBaseEmbeddableInput } from '@kbn/lens-plugin/public/embeddable'; +import type { LensEmbeddableInput } from '@kbn/lens-plugin/public'; import { useCallback } from 'react'; import type { OnEmbeddableLoaded, Request } from './types'; import { getRequestsAndResponses } from './utils'; export const useEmbeddableInspect = (onEmbeddableLoad?: OnEmbeddableLoaded) => { - const setInspectData = useCallback<NonNullable<LensBaseEmbeddableInput['onLoad']>>( + const setInspectData = useCallback<NonNullable<LensEmbeddableInput['onLoad']>>( (isLoading, adapters) => { if (!adapters) { return; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx index 8ed7d40519ac..3d6bb712e9d9 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx @@ -22,6 +22,7 @@ import { useSourcererDataView } from '../../../sourcerer/containers'; import { kpiHostMetricLensAttributes } from './lens_attributes/hosts/kpi_host_metric'; import { useRouteSpy } from '../../utils/route/use_route_spy'; import { SecurityPageName } from '../../../app/types'; +import type { Query } from '@kbn/es-query'; import { getEventsHistogramLensAttributes } from './lens_attributes/common/events'; jest.mock('../../../sourcerer/containers'); @@ -147,7 +148,7 @@ describe('useLensAttributes', () => { { wrapper } ); - expect(result?.current?.state.query.query).toEqual(''); + expect((result?.current?.state.query as Query).query).toEqual(''); expect(result?.current?.state.filters).toEqual([ ...getExternalAlertLensAttributes().state.filters, diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.test.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.test.ts index ee7c9a1515f9..a0e47595c5da 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/api.test.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.test.ts @@ -34,7 +34,7 @@ const triggerValidateEql = () => { query: 'any where true', signal, runtimeMappings: undefined, - options: undefined, + eqlOptions: undefined, }); }; diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts index 569344297f31..b586e0593ab6 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts @@ -11,7 +11,7 @@ import type { EqlSearchStrategyRequest, EqlSearchStrategyResponse } from '@kbn/d import { EQL_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { EqlOptionsSelected } from '../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../common/search_strategy'; import { getValidationErrors, isErrorResponse, @@ -31,9 +31,9 @@ interface Params { dataViewTitle: string; query: string; data: DataPublicPluginStart; - signal: AbortSignal; runtimeMappings: estypes.MappingRuntimeFields | undefined; - options: Omit<EqlOptionsSelected, 'query' | 'size'> | undefined; + eqlOptions: Omit<EqlOptions, 'query' | 'size'> | undefined; + signal?: AbortSignal; } export interface EqlResponseError { @@ -51,9 +51,9 @@ export const validateEql = async ({ data, dataViewTitle, query, - signal, runtimeMappings, - options, + eqlOptions, + signal, }: Params): Promise<ValidateEqlResponse> => { try { const { rawResponse: response } = await firstValueFrom( @@ -62,9 +62,12 @@ export const validateEql = async ({ params: { index: dataViewTitle, body: { query, runtime_mappings: runtimeMappings, size: 0 }, - timestamp_field: options?.timestampField, - tiebreaker_field: options?.tiebreakerField || undefined, - event_category_field: options?.eventCategoryField, + // Prevent passing empty string values + timestamp_field: eqlOptions?.timestampField ? eqlOptions.timestampField : undefined, + tiebreaker_field: eqlOptions?.tiebreakerField ? eqlOptions.tiebreakerField : undefined, + event_category_field: eqlOptions?.eventCategoryField + ? eqlOptions.eventCategoryField + : undefined, }, options: { ignore: [400] }, }, @@ -79,19 +82,23 @@ export const validateEql = async ({ valid: false, error: { code: EQL_ERROR_CODES.INVALID_SYNTAX, messages: getValidationErrors(response) }, }; - } else if (isVerificationErrorResponse(response) || isMappingErrorResponse(response)) { + } + + if (isVerificationErrorResponse(response) || isMappingErrorResponse(response)) { return { valid: false, error: { code: EQL_ERROR_CODES.INVALID_EQL, messages: getValidationErrors(response) }, }; - } else if (isErrorResponse(response)) { + } + + if (isErrorResponse(response)) { return { valid: false, error: { code: EQL_ERROR_CODES.FAILED_REQUEST, error: new Error(JSON.stringify(response)) }, }; - } else { - return { valid: true }; } + + return { valid: true }; } catch (error) { if (error instanceof Error && error.message.startsWith('index_not_found_exception')) { return { @@ -99,6 +106,7 @@ export const validateEql = async ({ error: { code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, messages: [error.message] }, }; } + return { valid: false, error: { diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index 6ae63769b114..79bd0eb55868 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -349,7 +349,6 @@ export const mockGlobalState: State = { description: '', eqlOptions: { eventCategoryField: 'event.category', - tiebreakerField: '', timestampField: '@timestamp', }, eventIdToNoteIds: { '1': ['1'] }, diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 406ff1f4ffe3..5feee5adafcd 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -2015,15 +2015,6 @@ export const mockGetOneTimelineResult: TimelineResponse = { version: '1', }; -export const mockTimelineResult = { - data: { - getOneTimeline: mockGetOneTimelineResult, - }, - loading: false, - networkStatus: 7, - stale: false, -}; - export const defaultTimelineProps: CreateTimelineProps = { from: '2018-11-05T18:58:25.937Z', timeline: { @@ -2051,7 +2042,6 @@ export const defaultTimelineProps: CreateTimelineProps = { eventCategoryField: 'event.category', query: '', size: 100, - tiebreakerField: '', timestampField: '@timestamp', }, eventIdToNoteIds: {}, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_overview_link.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_overview_link.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_overview_link.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_overview_link.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx similarity index 64% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx index 67e0e516e22e..0496f08dd264 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.test.tsx @@ -8,9 +8,10 @@ import React from 'react'; import { shallow } from 'enzyme'; import { render, screen, fireEvent, within } from '@testing-library/react'; - +import type { SecuritySolutionDataViewBase } from '../../../../common/types'; import { mockIndexPattern, TestProviders, useFormFieldMock } from '../../../../common/mock'; import { mockQueryBar } from '../../../rule_management_ui/components/rules_table/__mocks__/mock'; +import { selectEuiComboBoxOption } from '../../../../common/test/eui/combobox'; import type { EqlQueryBarProps } from './eql_query_bar'; import { EqlQueryBar } from './eql_query_bar'; import { getEqlValidationError } from './validators.mock'; @@ -115,36 +116,82 @@ describe('EqlQueryBar', () => { }); describe('EQL options interaction', () => { - const mockOptionsData = { - keywordFields: [], - dateFields: [{ label: 'timestamp', value: 'timestamp' }], - nonDateFields: [], + const mockIndexPatternWithEqlOptionsFields: SecuritySolutionDataViewBase = { + fields: [ + { + name: 'category', + searchable: true, + type: 'keyword', + esTypes: ['keyword'], + aggregatable: true, + }, + { + name: 'timestamp', + searchable: true, + type: 'date', + aggregatable: true, + }, + { + name: 'tiebreaker', + searchable: true, + type: 'string', + aggregatable: true, + }, + ], + title: 'test-*', }; - it('invokes onOptionsChange when the EQL options change', () => { - const onOptionsChangeMock = jest.fn(); + it('updates EQL options', async () => { + let eqlOptions = {}; + + const mockEqlOptionsField = useFormFieldMock({ + value: {}, + setValue: (updater) => { + if (typeof updater === 'function') { + eqlOptions = updater(eqlOptions); + } + }, + }); - const { getByTestId, getByText } = render( + const { getByTestId } = render( <TestProviders> <EqlQueryBar dataTestSubj="myQueryBar" field={mockField} + eqlOptionsField={mockEqlOptionsField} isLoading={false} - optionsData={mockOptionsData} - indexPattern={mockIndexPattern} - onOptionsChange={onOptionsChangeMock} + indexPattern={mockIndexPatternWithEqlOptionsFields} /> </TestProviders> ); // open options popover fireEvent.click(getByTestId('eql-settings-trigger')); - // display combobox options - within(getByTestId(`eql-timestamp-field`)).getByRole('combobox').focus(); - // select timestamp - getByText('timestamp').click(); - expect(onOptionsChangeMock).toHaveBeenCalledWith('timestampField', 'timestamp'); + await selectEuiComboBoxOption({ + comboBoxToggleButton: within(getByTestId('eql-event-category-field')).getByRole('combobox'), + optionText: 'category', + }); + + expect(eqlOptions).toEqual({ eventCategoryField: 'category' }); + + await selectEuiComboBoxOption({ + comboBoxToggleButton: within(getByTestId('eql-tiebreaker-field')).getByRole('combobox'), + optionText: 'tiebreaker', + }); + + expect(eqlOptions).toEqual({ eventCategoryField: 'category', tiebreakerField: 'tiebreaker' }); + + await selectEuiComboBoxOption({ + comboBoxToggleButton: within(getByTestId('eql-timestamp-field')).getByRole('combobox'), + optionText: 'timestamp', + }); + + expect(eqlOptions).toEqual({ + eventCategoryField: 'category', + tiebreakerField: 'tiebreaker', + timestampField: 'timestamp', + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx similarity index 83% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx index 607b7a3ef2bb..079735aab3c4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/eql_query_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_bar.tsx @@ -17,16 +17,13 @@ import { FilterManager } from '@kbn/data-plugin/public'; import type { FieldHook } from '../../../../shared_imports'; import { FilterBar } from '../../../../common/components/filter_bar'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; -import * as i18n from './translations'; +import type { EqlOptions } from '../../../../../common/search_strategy'; +import { useKibana } from '../../../../common/lib/kibana'; +import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar'; +import type { EqlQueryBarFooterProps } from './footer'; import { EqlQueryBarFooter } from './footer'; import { getValidationResults } from './validators'; -import type { - EqlOptionsData, - EqlOptionsSelected, - FieldsEqlOptions, -} from '../../../../../common/search_strategy'; -import { useKibana } from '../../../../common/lib/kibana'; +import * as i18n from './translations'; const TextArea = styled(EuiTextArea)` display: block; @@ -60,32 +57,28 @@ const StyledFormRow = styled(EuiFormRow)` export interface EqlQueryBarProps { dataTestSubj: string; - field: FieldHook<DefineStepRule['queryBar']>; - isLoading: boolean; + field: FieldHook<FieldValueQueryBar>; + eqlOptionsField?: FieldHook<EqlOptions>; + isLoading?: boolean; indexPattern: DataViewBase; showFilterBar?: boolean; idAria?: string; - optionsData?: EqlOptionsData; - optionsSelected?: EqlOptionsSelected; isSizeOptionDisabled?: boolean; - onOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; onValidityChange?: (arg: boolean) => void; - onValiditingChange?: (arg: boolean) => void; + onValidatingChange?: (arg: boolean) => void; } export const EqlQueryBar: FC<EqlQueryBarProps> = ({ dataTestSubj, field, + eqlOptionsField, isLoading = false, indexPattern, showFilterBar, idAria, - optionsData, - optionsSelected, isSizeOptionDisabled, - onOptionsChange, onValidityChange, - onValiditingChange, + onValidatingChange, }) => { const { addError } = useAppToasts(); const [errorMessages, setErrorMessages] = useState<string[]>([]); @@ -115,10 +108,10 @@ export const EqlQueryBar: FC<EqlQueryBarProps> = ({ }, [error, addError]); useEffect(() => { - if (onValiditingChange) { - onValiditingChange(isValidating); + if (onValidatingChange) { + onValidatingChange(isValidating); } - }, [isValidating, onValiditingChange]); + }, [isValidating, onValidatingChange]); useEffect(() => { let isSubscribed = true; @@ -156,8 +149,8 @@ export const EqlQueryBar: FC<EqlQueryBarProps> = ({ const handleChange = useCallback( (e: ChangeEvent<HTMLTextAreaElement>) => { const newQuery = e.target.value; - if (onValiditingChange) { - onValiditingChange(true); + if (onValidatingChange) { + onValidatingChange(true); } setErrorMessages([]); setFieldValue({ @@ -169,7 +162,19 @@ export const EqlQueryBar: FC<EqlQueryBarProps> = ({ saved_id: null, }); }, - [fieldValue, setFieldValue, onValiditingChange] + [fieldValue, setFieldValue, onValidatingChange] + ); + + const handleEqlOptionsChange = useCallback< + NonNullable<EqlQueryBarFooterProps['onEqlOptionsChange']> + >( + (eqlOptionsFieldName, value) => { + eqlOptionsField?.setValue((prevEqlOptions) => ({ + ...prevEqlOptions, + [eqlOptionsFieldName]: value, + })); + }, + [eqlOptionsField] ); return ( @@ -195,9 +200,9 @@ export const EqlQueryBar: FC<EqlQueryBarProps> = ({ errors={errorMessages} isLoading={isValidating} isSizeOptionDisabled={isSizeOptionDisabled} - optionsData={optionsData} - optionsSelected={optionsSelected} - onOptionsChange={onOptionsChange} + dataView={indexPattern} + eqlOptions={eqlOptionsField?.value} + onEqlOptionsChange={handleEqlOptionsChange} /> {showFilterBar && ( <> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx new file mode 100644 index 000000000000..5b519cb43c84 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_edit.tsx @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import type { DataViewBase } from '@kbn/es-query'; +import { debounceAsync } from '@kbn/securitysolution-utils'; +import type { FormData, FieldConfig, ValidationFuncArg } from '../../../../shared_imports'; +import { UseMultiFields } from '../../../../shared_imports'; +import type { EqlFieldsComboBoxOptions, EqlOptions } from '../../../../../common/search_strategy'; +import { queryRequiredValidatorFactory } from '../../../rule_creation_ui/validators/query_required_validator_factory'; +import { eqlQueryValidatorFactory } from './eql_query_validator_factory'; +import { EqlQueryBar } from './eql_query_bar'; +import * as i18n from './translations'; +import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar'; + +interface EqlQueryEditProps { + path: string; + eqlOptionsPath: string; + fieldsToValidateOnChange?: string | string[]; + eqlFieldsComboBoxOptions?: EqlFieldsComboBoxOptions; + showEqlSizeOption?: boolean; + showFilterBar?: boolean; + dataView: DataViewBase; + required?: boolean; + loading?: boolean; + disabled?: boolean; + // This is a temporal solution for Prebuilt Customization workflow + skipEqlValidation?: boolean; + onValidityChange?: (arg: boolean) => void; +} + +export function EqlQueryEdit({ + path, + eqlOptionsPath, + fieldsToValidateOnChange, + showEqlSizeOption = false, + showFilterBar = false, + dataView, + required, + loading, + disabled, + skipEqlValidation, + onValidityChange, +}: EqlQueryEditProps): JSX.Element { + const componentProps = useMemo( + () => ({ + isSizeOptionDisabled: !showEqlSizeOption, + isDisabled: disabled, + isLoading: loading, + indexPattern: dataView, + showFilterBar, + idAria: 'ruleEqlQueryBar', + dataTestSubj: 'ruleEqlQueryBar', + onValidityChange, + }), + [showEqlSizeOption, showFilterBar, onValidityChange, dataView, loading, disabled] + ); + const fieldConfig: FieldConfig<FieldValueQueryBar> = useMemo( + () => ({ + label: i18n.EQL_QUERY_BAR_LABEL, + fieldsToValidateOnChange: fieldsToValidateOnChange + ? [path, fieldsToValidateOnChange].flat() + : undefined, + validations: [ + ...(required + ? [ + { + validator: queryRequiredValidatorFactory('eql'), + }, + ] + : []), + ...(!skipEqlValidation + ? [ + { + validator: debounceAsync( + (data: ValidationFuncArg<FormData, FieldValueQueryBar>) => { + const { formData } = data; + const eqlOptions = + eqlOptionsPath && formData[eqlOptionsPath] ? formData[eqlOptionsPath] : {}; + + return eqlQueryValidatorFactory( + dataView.id + ? { + dataViewId: dataView.id, + eqlOptions, + } + : { + indexPatterns: dataView.title.split(','), + eqlOptions, + } + )(data); + }, + 300 + ), + }, + ] + : []), + ], + }), + [ + skipEqlValidation, + eqlOptionsPath, + required, + dataView.id, + dataView.title, + path, + fieldsToValidateOnChange, + ] + ); + + return ( + <UseMultiFields<{ + eqlQuery: FieldValueQueryBar; + eqlOptions: EqlOptions; + }> + fields={{ + eqlQuery: { + path, + config: fieldConfig, + }, + eqlOptions: { + path: eqlOptionsPath, + }, + }} + > + {({ eqlQuery, eqlOptions }) => ( + <EqlQueryBar field={eqlQuery} eqlOptionsField={eqlOptions} {...componentProps} /> + )} + </UseMultiFields> + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts new file mode 100644 index 000000000000..54a0b3e3b6a6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/eql_query_validator_factory.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash'; +import type { FormData, ValidationError, ValidationFunc } from '../../../../shared_imports'; +import { KibanaServices } from '../../../../common/lib/kibana'; +import type { EqlOptions } from '../../../../../common/search_strategy'; +import type { FieldValueQueryBar } from '../../../rule_creation_ui/components/query_bar'; +import type { EqlResponseError } from '../../../../common/hooks/eql/api'; +import { EQL_ERROR_CODES, validateEql } from '../../../../common/hooks/eql/api'; +import { EQL_VALIDATION_REQUEST_ERROR } from './translations'; + +type EqlQueryValidatorFactoryParams = + | { + indexPatterns: string[]; + dataViewId?: never; + eqlOptions: EqlOptions; + } + | { + indexPatterns?: never; + dataViewId: string; + eqlOptions: EqlOptions; + }; + +export function eqlQueryValidatorFactory({ + indexPatterns, + dataViewId, + eqlOptions, +}: EqlQueryValidatorFactoryParams): ValidationFunc<FormData, string, FieldValueQueryBar> { + return async (...args) => { + const [{ value }] = args; + + if (isEmpty(value.query.query)) { + return; + } + + try { + const { data } = KibanaServices.get(); + const dataView = isDataViewIdValid(dataViewId) + ? await data.dataViews.get(dataViewId) + : undefined; + + const dataViewTitle = dataView?.getIndexPattern() ?? indexPatterns?.join(',') ?? ''; + const runtimeMappings = dataView?.getRuntimeMappings() ?? {}; + + const response = await validateEql({ + data, + query: value.query.query as string, + dataViewTitle, + runtimeMappings, + eqlOptions, + }); + + if (response?.valid === false && response.error) { + return transformEqlResponseErrorToValidationError(response.error); + } + } catch (error) { + return { + code: EQL_ERROR_CODES.FAILED_REQUEST, + message: EQL_VALIDATION_REQUEST_ERROR, + error, + }; + } + }; +} + +function transformEqlResponseErrorToValidationError( + responseError: EqlResponseError +): ValidationError<EQL_ERROR_CODES> { + if (responseError.error) { + return { + code: EQL_ERROR_CODES.FAILED_REQUEST, + message: EQL_VALIDATION_REQUEST_ERROR, + error: responseError.error, + }; + } + + return { + code: responseError.code, + message: '', + messages: responseError.messages, + }; +} + +function isDataViewIdValid(dataViewId: unknown): dataViewId is string { + return typeof dataViewId === 'string' && dataViewId !== ''; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/errors_popover.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/errors_popover.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/errors_popover.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/errors_popover.test.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/errors_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/errors_popover.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/errors_popover.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/errors_popover.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx similarity index 83% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx index cb15e5002ca4..ded190cc86de 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.test.tsx @@ -32,7 +32,11 @@ describe('EQL footer', () => { it('EQL settings button is enable when popover is NOT open', () => { const wrapper = mount( <TestProviders> - <EqlQueryBarFooter errors={[]} onOptionsChange={jest.fn()} /> + <EqlQueryBarFooter + errors={[]} + dataView={{ title: '', fields: [] }} + onEqlOptionsChange={jest.fn()} + /> </TestProviders> ); @@ -44,7 +48,11 @@ describe('EQL footer', () => { it('disable EQL settings button when popover is open', () => { const wrapper = mount( <TestProviders> - <EqlQueryBarFooter errors={[]} onOptionsChange={jest.fn()} /> + <EqlQueryBarFooter + errors={[]} + dataView={{ title: '', fields: [] }} + onEqlOptionsChange={jest.fn()} + /> </TestProviders> ); wrapper.find(`[data-test-subj="eql-settings-trigger"]`).first().simulate('click'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx similarity index 73% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx index 45ab4a1c969c..fd9431c8d20f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/footer.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/footer.tsx @@ -20,28 +20,27 @@ import { import type { FC } from 'react'; import React, { useCallback, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; - +import type { DataViewBase } from '@kbn/es-query'; import type { DebouncedFunc } from 'lodash'; -import { debounce } from 'lodash'; -import type { - EqlOptionsData, - EqlOptionsSelected, - FieldsEqlOptions, -} from '../../../../../common/search_strategy'; +import { debounce, isEmpty } from 'lodash'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import * as i18n from './translations'; import { ErrorsPopover } from './errors_popover'; import { EqlOverviewLink } from './eql_overview_link'; -export interface Props { +export interface EqlQueryBarFooterProps { errors: string[]; isLoading?: boolean; isSizeOptionDisabled?: boolean; - optionsData?: EqlOptionsData; - optionsSelected?: EqlOptionsSelected; - onOptionsChange?: (field: FieldsEqlOptions, newValue: string | undefined) => void; + dataView: DataViewBase; + eqlOptions?: EqlOptions; + onEqlOptionsChange?: <Field extends keyof EqlOptions>( + field: Field, + newValue: EqlOptions[Field] + ) => void; } -type SizeVoidFunc = (newSize: string) => void; +type SizeVoidFunc = (newSize: number) => void; const Container = styled(EuiFlexGroup)` border-radius: 0; @@ -69,18 +68,40 @@ const Spinner = styled(EuiLoadingSpinner)` const singleSelection = { asPlainText: true }; -export const EqlQueryBarFooter: FC<Props> = ({ +export const EqlQueryBarFooter: FC<EqlQueryBarFooterProps> = ({ errors, isLoading, isSizeOptionDisabled, - optionsData, - optionsSelected, - onOptionsChange, + dataView, + eqlOptions, + onEqlOptionsChange, }) => { const [openEqlSettings, setIsOpenEqlSettings] = useState(false); - const [localSize, setLocalSize] = useState<string | number>(optionsSelected?.size ?? 100); + const [localSize, setLocalSize] = useState<number>(eqlOptions?.size ?? 100); const debounceSize = useRef<DebouncedFunc<SizeVoidFunc>>(); + const { keywordFields, nonDateFields, dateFields } = useMemo( + () => + isEmpty(dataView?.fields) + ? { + keywordFields: [], + dateFields: [], + nonDateFields: [], + } + : { + keywordFields: dataView.fields + .filter((f) => f.esTypes?.includes('keyword')) + .map((f) => ({ label: f.name })), + dateFields: dataView.fields + .filter((f) => f.type === 'date') + .map((f) => ({ label: f.name })), + nonDateFields: dataView.fields + .filter((f) => f.type !== 'date') + .map((f) => ({ label: f.name })), + }, + [dataView] + ); + const openEqlSettingsHandler = useCallback(() => { setIsOpenEqlSettings(true); }, []); @@ -90,74 +111,70 @@ export const EqlQueryBarFooter: FC<Props> = ({ const handleEventCategoryField = useCallback( (opt: EuiComboBoxOptionOption[]) => { - if (onOptionsChange) { + if (onEqlOptionsChange) { if (opt.length > 0) { - onOptionsChange('eventCategoryField', opt[0].label); + onEqlOptionsChange('eventCategoryField', opt[0].label); } else { - onOptionsChange('eventCategoryField', undefined); + onEqlOptionsChange('eventCategoryField', undefined); } } }, - [onOptionsChange] + [onEqlOptionsChange] ); const handleTiebreakerField = useCallback( (opt: EuiComboBoxOptionOption[]) => { - if (onOptionsChange) { + if (onEqlOptionsChange) { if (opt.length > 0) { - onOptionsChange('tiebreakerField', opt[0].label); + onEqlOptionsChange('tiebreakerField', opt[0].label); } else { - onOptionsChange('tiebreakerField', undefined); + onEqlOptionsChange('tiebreakerField', undefined); } } }, - [onOptionsChange] + [onEqlOptionsChange] ); const handleTimestampField = useCallback( (opt: EuiComboBoxOptionOption[]) => { - if (onOptionsChange) { + if (onEqlOptionsChange) { if (opt.length > 0) { - onOptionsChange('timestampField', opt[0].label); + onEqlOptionsChange('timestampField', opt[0].label); } else { - onOptionsChange('timestampField', undefined); + onEqlOptionsChange('timestampField', undefined); } } }, - [onOptionsChange] + [onEqlOptionsChange] ); const handleSizeField = useCallback<NonNullable<EuiFieldNumberProps['onChange']>>( (evt) => { - if (onOptionsChange) { + if (onEqlOptionsChange) { setLocalSize(evt?.target?.valueAsNumber); if (debounceSize.current?.cancel) { debounceSize.current?.cancel(); } - debounceSize.current = debounce((newSize) => onOptionsChange('size', newSize), 800); - debounceSize.current(evt?.target?.value); + debounceSize.current = debounce((newSize) => onEqlOptionsChange('size', newSize), 800); + debounceSize.current(evt?.target?.valueAsNumber); } }, - [onOptionsChange] + [onEqlOptionsChange] ); const eventCategoryField = useMemo( () => - optionsSelected?.eventCategoryField != null - ? [{ label: optionsSelected?.eventCategoryField }] + eqlOptions?.eventCategoryField != null + ? [{ label: eqlOptions?.eventCategoryField }] : undefined, - [optionsSelected?.eventCategoryField] + [eqlOptions?.eventCategoryField] ); const tiebreakerField = useMemo( () => - optionsSelected?.tiebreakerField != null - ? [{ label: optionsSelected?.tiebreakerField }] - : undefined, - [optionsSelected?.tiebreakerField] + eqlOptions?.tiebreakerField != null ? [{ label: eqlOptions?.tiebreakerField }] : undefined, + [eqlOptions?.tiebreakerField] ); const timestampField = useMemo( () => - optionsSelected?.timestampField != null - ? [{ label: optionsSelected?.timestampField }] - : undefined, - [optionsSelected?.timestampField] + eqlOptions?.timestampField != null ? [{ label: eqlOptions?.timestampField }] : undefined, + [eqlOptions?.timestampField] ); return ( @@ -183,13 +200,13 @@ export const EqlQueryBarFooter: FC<Props> = ({ </EuiFlexItem> <EuiFlexItem grow={false}> <EuiFlexGroup gutterSize={'none'} alignItems="center" responsive={false}> - {!onOptionsChange && ( + {!onEqlOptionsChange && ( <EuiFlexItem grow={false}> <EqlOverviewLink /> </EuiFlexItem> )} - {onOptionsChange && ( + {onEqlOptionsChange && ( <> <FlexItemWithMarginRight grow={false}> <EqlOverviewLink /> @@ -232,7 +249,7 @@ export const EqlQueryBarFooter: FC<Props> = ({ helpText={i18n.EQL_OPTIONS_EVENT_CATEGORY_FIELD_HELPER} > <EuiComboBox - options={optionsData?.keywordFields} + options={keywordFields} selectedOptions={eventCategoryField} singleSelection={singleSelection} onChange={handleEventCategoryField} @@ -244,7 +261,7 @@ export const EqlQueryBarFooter: FC<Props> = ({ helpText={i18n.EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_HELPER} > <EuiComboBox - options={optionsData?.nonDateFields} + options={nonDateFields} selectedOptions={tiebreakerField} singleSelection={singleSelection} onChange={handleTiebreakerField} @@ -256,7 +273,7 @@ export const EqlQueryBarFooter: FC<Props> = ({ helpText={i18n.EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_HELPER} > <EuiComboBox - options={optionsData?.dateFields} + options={dateFields} selectedOptions={timestampField} singleSelection={singleSelection} onChange={handleTimestampField} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/index.ts similarity index 84% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/index.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/index.ts index 0ad62099e2bc..6044ff5a12e0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/index.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { EqlQueryBar } from './eql_query_bar'; +export * from './eql_query_edit'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/translations.ts similarity index 94% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/translations.ts index 092c9b4bc1a1..1a2d6cb7c09a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/translations.ts @@ -7,6 +7,13 @@ import { i18n } from '@kbn/i18n'; +export const EQL_QUERY_BAR_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel', + { + defaultMessage: 'EQL query', + } +); + export const EQL_VALIDATION_REQUEST_ERROR = i18n.translate( 'xpack.securitySolution.detectionEngine.eqlValidation.requestError', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.mock.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.mock.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts new file mode 100644 index 000000000000..676a780d9daf --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/eql_query_edit/validators.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FieldHook } from '../../../../shared_imports'; +import { EQL_ERROR_CODES } from '../../../../common/hooks/eql/api'; + +export const getValidationResults = <T = unknown>( + field: FieldHook<T> +): { isValid: boolean; message: string; messages?: string[]; error?: Error } => { + const hasErrors = field.errors.length > 0; + const isValid = !field.isChangingValue && !hasErrors; + + if (hasErrors) { + const [error] = field.errors; + const message = error.message; + + if (error.code === EQL_ERROR_CODES.FAILED_REQUEST) { + return { isValid, message, error: error.error }; + } else { + return { isValid, message, messages: error.messages }; + } + } else { + return { isValid, message: '' }; + } +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/helpers.tsx index 6740e9fc8d01..57292d91953d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/helpers.tsx @@ -32,7 +32,7 @@ import type { } from '../../../../../common/api/detection_engine/model/rule_schema'; import { AlertSuppressionMissingFieldsStrategyEnum } from '../../../../../common/api/detection_engine/model/rule_schema'; import { MATCHES, AND, OR } from '../../../../common/components/threat_match/translations'; -import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import { assertUnreachable } from '../../../../../common/utility_types'; import * as i18nSeverity from '../severity_mapping/translations'; import * as i18nRiskScore from '../risk_score_mapping/translations'; @@ -147,7 +147,7 @@ export const buildQueryBarDescription = ({ return items; }; -export const buildEqlOptionsDescription = (eqlOptions: EqlOptionsSelected): ListItems[] => { +export const buildEqlOptionsDescription = (eqlOptions: EqlOptions): ListItems[] => { let items: ListItems[] = []; if (!isEmpty(eqlOptions.eventCategoryField)) { items = [ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx index 814bb94b8e70..1e6420df7ced 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx @@ -19,7 +19,7 @@ import type { } from '../../../../../common/api/detection_engine/model/rule_schema'; import { buildRelatedIntegrationsDescription } from '../../../../detections/components/rules/related_integrations/integrations_description'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; -import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import { useKibana } from '../../../../common/lib/kibana'; import type { AboutStepRiskScore, @@ -277,7 +277,7 @@ export const getDescriptionItem = ( return []; } } else if (field === 'eqlOptions') { - const eqlOptions: EqlOptionsSelected = get(field, data); + const eqlOptions: EqlOptions = get(field, data); return buildEqlOptionsDescription(eqlOptions); } else if (field === 'threat') { const threats: Threats = get(field, data); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts deleted file mode 100644 index 8cd9a4d60745..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isEmpty } from 'lodash'; - -import type { FieldHook, ValidationError, ValidationFunc } from '../../../../shared_imports'; -import { isEqlRule } from '../../../../../common/detection_engine/utils'; -import { KibanaServices } from '../../../../common/lib/kibana'; -import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; -import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; -import type { EqlResponseError } from '../../../../common/hooks/eql/api'; -import { validateEql, EQL_ERROR_CODES } from '../../../../common/hooks/eql/api'; -import type { FieldValueQueryBar } from '../query_bar'; -import * as i18n from './translations'; - -/** - * Unlike lodash's debounce, which resolves intermediate calls with the most - * recent value, this implementation waits to resolve intermediate calls until - * the next invocation resolves. - * - * @param fn an async function - * - * @returns A debounced async function that resolves on the next invocation - */ -export const debounceAsync = <Args extends unknown[], Result extends Promise<unknown>>( - fn: (...args: Args) => Result, - interval: number -): ((...args: Args) => Result) => { - let handle: ReturnType<typeof setTimeout> | undefined; - let resolves: Array<(value?: Result) => void> = []; - - return (...args: Args): Result => { - if (handle) { - clearTimeout(handle); - } - - handle = setTimeout(() => { - const result = fn(...args); - resolves.forEach((resolve) => resolve(result)); - resolves = []; - }, interval); - - return new Promise((resolve) => resolves.push(resolve)) as Result; - }; -}; - -export const transformEqlResponseErrorToValidationError = ( - responseError: EqlResponseError -): ValidationError<EQL_ERROR_CODES> => { - if (responseError.error) { - return { - code: EQL_ERROR_CODES.FAILED_REQUEST, - message: i18n.EQL_VALIDATION_REQUEST_ERROR, - error: responseError.error, - }; - } - return { - code: responseError.code, - message: '', - messages: responseError.messages, - }; -}; - -export const eqlValidator = async ( - ...args: Parameters<ValidationFunc> -): Promise<ValidationError<EQL_ERROR_CODES> | void | undefined> => { - const [{ value, formData }] = args; - const { query: queryValue } = value as FieldValueQueryBar; - const query = queryValue.query as string; - const { dataViewId, index, ruleType, eqlOptions } = formData as DefineStepRule; - - const needsValidation = - (ruleType === undefined && !isEmpty(query)) || (isEqlRule(ruleType) && !isEmpty(query)); - if (!needsValidation) { - return; - } - - try { - const { data } = KibanaServices.get(); - let dataViewTitle = index?.join(); - let runtimeMappings = {}; - if ( - dataViewId != null && - dataViewId !== '' && - formData.dataSourceType === DataSourceType.DataView - ) { - const dataView = await data.dataViews.get(dataViewId); - - dataViewTitle = dataView.title; - runtimeMappings = dataView.getRuntimeMappings(); - } - - const signal = new AbortController().signal; - const response = await validateEql({ - data, - query, - signal, - dataViewTitle, - runtimeMappings, - options: eqlOptions, - }); - - if (response?.valid === false && response.error) { - return transformEqlResponseErrorToValidationError(response.error); - } - } catch (error) { - return { - code: EQL_ERROR_CODES.FAILED_REQUEST, - message: i18n.EQL_VALIDATION_REQUEST_ERROR, - error, - }; - } -}; - -export const getValidationResults = <T = unknown>( - field: FieldHook<T> -): { isValid: boolean; message: string; messages?: string[]; error?: Error } => { - const hasErrors = field.errors.length > 0; - const isValid = !field.isChangingValue && !hasErrors; - - if (hasErrors) { - const [error] = field.errors; - const message = error.message; - - if (error.code === EQL_ERROR_CODES.FAILED_REQUEST) { - return { isValid, message, error: error.error }; - } else { - return { isValid, message, messages: error.messages }; - } - } else { - return { isValid, message: '' }; - } -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/query_bar/index.test.tsx index 0ba6bea89e0f..7b757f8fc621 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/query_bar/index.test.tsx @@ -18,6 +18,7 @@ import { mockHistory, Router } from '../../../../common/mock/router'; import { render, act, fireEvent } from '@testing-library/react'; import { resolveTimeline } from '../../../../timelines/containers/api'; import { mockTimeline } from '../../../../../server/lib/timeline/__mocks__/create_timelines'; +import type { ResolveTimelineResponse } from '../../../../../common/api/timeline'; jest.mock('../../../../timelines/containers/api'); jest.mock('../../../../common/lib/kibana', () => { @@ -49,6 +50,11 @@ jest.mock('../../../../timelines/containers/all', () => { }; }); +const resolvedTimeline: ResolveTimelineResponse = { + timeline: { ...mockTimeline, savedObjectId: '1', version: 'abc' }, + outcome: 'exactMatch', +}; + describe('QueryBarDefineRule', () => { beforeEach(() => { jest.clearAllMocks(); @@ -59,11 +65,7 @@ describe('QueryBarDefineRule', () => { totalCount: mockOpenTimelineQueryResults.totalCount, refetch: jest.fn(), }); - (resolveTimeline as jest.Mock).mockResolvedValue({ - data: { - timeline: { mockTimeline }, - }, - }); + (resolveTimeline as jest.Mock).mockResolvedValue(resolvedTimeline); }); it('renders correctly', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx index 50264fffabfb..364f1b770573 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { screen, fireEvent, render, within, act, waitFor } from '@testing-library/react'; import type { Type as RuleType } from '@kbn/securitysolution-io-ts-alerting-types'; import type { DataViewBase } from '@kbn/es-query'; @@ -638,7 +638,6 @@ function TestForm({ onSubmit, formProps, }: TestFormProps): JSX.Element { - const [selectedEqlOptions, setSelectedEqlOptions] = useState(stepDefineDefaultValue.eqlOptions); const { form } = useForm({ options: { stripEmptyFields: false }, schema: defineRuleSchema, @@ -653,8 +652,6 @@ function TestForm({ form={form} indicesConfig={[]} threatIndicesConfig={[]} - optionsSelected={selectedEqlOptions} - setOptionsSelected={setSelectedEqlOptions} indexPattern={indexPattern} isIndexPatternLoading={false} isQueryBarValid={true} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index 37529f28e401..3eca09c7bdaa 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -20,7 +20,7 @@ import React, { memo, useCallback, useState, useEffect, useMemo, useRef } from ' import styled from 'styled-components'; import { i18n as i18nCore } from '@kbn/i18n'; -import { isEqual, isEmpty } from 'lodash'; +import { isEqual } from 'lodash'; import type { FieldSpec } from '@kbn/data-plugin/common'; import usePrevious from 'react-use/lib/usePrevious'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; @@ -33,7 +33,6 @@ import { useSetFieldValueWithCallback } from '../../../../common/utils/use_set_f import type { SetRuleQuery } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; import { useRuleFromTimeline } from '../../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; -import type { EqlOptionsSelected, FieldsEqlOptions } from '../../../../../common/search_strategy'; import { filterRuleFieldsForType, getStepDataDataSource } from '../../pages/rule_creation/helpers'; import type { DefineStepRule, @@ -73,7 +72,7 @@ import { isEqlSequenceQuery, isSuppressionRuleInGA, } from '../../../../../common/detection_engine/utils'; -import { EqlQueryBar } from '../eql_query_bar'; +import { EqlQueryEdit } from '../../../rule_creation/components/eql_query_edit'; import { DataViewSelectorField } from '../data_view_selector_field'; import { ThreatMatchInput } from '../threatmatch_input'; import { useFetchIndex } from '../../../../common/containers/source'; @@ -88,7 +87,10 @@ import { useAlertSuppression } from '../../../rule_management/logic/use_alert_su import { AiAssistant } from '../ai_assistant'; import { RelatedIntegrations } from '../../../rule_creation/components/related_integrations'; import { useMLRuleConfig } from '../../../../common/components/ml/hooks/use_ml_rule_config'; -import { AlertSuppressionEdit } from '../../../rule_creation/components/alert_suppression_edit'; +import { + ALERT_SUPPRESSION_FIELDS_FIELD_NAME, + AlertSuppressionEdit, +} from '../../../rule_creation/components/alert_suppression_edit'; import { ThresholdAlertSuppressionEdit } from '../../../rule_creation/components/threshold_alert_suppression_edit'; import { usePersistentAlertSuppressionState } from './use_persistent_alert_suppression_state'; import { useTermsAggregationFields } from '../../../../common/hooks/use_terms_aggregation_fields'; @@ -106,8 +108,6 @@ export interface StepDefineRuleProps extends RuleStepProps { threatIndicesConfig: string[]; defaultSavedQuery?: SavedQuery; form: FormHook<DefineStepRule>; - optionsSelected: EqlOptionsSelected; - setOptionsSelected: React.Dispatch<React.SetStateAction<EqlOptionsSelected>>; indexPattern: DataViewBase; isIndexPatternLoading: boolean; isQueryBarValid: boolean; @@ -164,13 +164,11 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ isLoading, isQueryBarValid, isUpdateView = false, - optionsSelected, queryBarSavedId, queryBarTitle, ruleType, setIsQueryBarValid, setIsThreatQueryBarValid, - setOptionsSelected, shouldLoadQueryDynamically, threatIndex, threatIndicesConfig, @@ -221,15 +219,12 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ }; if (timelineQueryBar.query.language === 'eql') { setRuleTypeCallback('eql', setQuery); - setOptionsSelected((prevOptions) => ({ - ...prevOptions, - ...(eqlOptions != null ? eqlOptions : {}), - })); + setFieldValue('eqlOptions', eqlOptions ?? {}); } else { setQuery(); } }, - [setFieldValue, setRuleTypeCallback, setOptionsSelected] + [setFieldValue, setRuleTypeCallback] ); const { onOpenTimeline, loading: timelineQueryLoading } = @@ -719,43 +714,6 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ ] ); - const onOptionsChange = useCallback( - (field: FieldsEqlOptions, value: string | undefined) => { - setOptionsSelected((prevOptions) => { - const newOptions = { - ...prevOptions, - [field]: value, - }; - - setFieldValue('eqlOptions', newOptions); - return newOptions; - }); - }, - [setFieldValue, setOptionsSelected] - ); - - const optionsData = useMemo( - () => - isEmpty(indexPattern.fields) - ? { - keywordFields: [], - dateFields: [], - nonDateFields: [], - } - : { - keywordFields: (indexPattern.fields as FieldSpec[]) - .filter((f) => f.esTypes?.includes('keyword')) - .map((f) => ({ label: f.name })), - dateFields: indexPattern.fields - .filter((f) => f.type === 'date') - .map((f) => ({ label: f.name })), - nonDateFields: indexPattern.fields - .filter((f) => f.type !== 'date') - .map((f) => ({ label: f.name })), - }, - [indexPattern] - ); - const selectRuleTypeProps = useMemo( () => ({ describedByIds: ['detectionEngineStepDefineRuleType'], @@ -794,29 +752,18 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ <EuiSpacer size="s" /> {isEqlRule(ruleType) ? ( <> - <UseField - key="EqlQueryBar" + <EqlQueryEdit + key="eqlQueryBar" path="queryBar" - component={EqlQueryBar} - componentProps={{ - optionsData, - optionsSelected, - isSizeOptionDisabled: true, - onOptionsChange, - onValidityChange: setIsQueryBarValid, - idAria: 'detectionEngineStepDefineRuleEqlQueryBar', - isDisabled: isLoading, - isLoading: isIndexPatternLoading, - indexPattern, - showFilterBar: true, - dataTestSubj: 'detectionEngineStepDefineRuleEqlQueryBar', - }} - config={{ - ...schema.queryBar, - label: i18n.EQL_QUERY_BAR_LABEL, - }} + eqlOptionsPath="eqlOptions" + fieldsToValidateOnChange={ALERT_SUPPRESSION_FIELDS_FIELD_NAME} + required + showFilterBar + dataView={indexPattern} + loading={isIndexPatternLoading} + disabled={isLoading} + onValidityChange={setIsQueryBarValid} /> - <UseField path="eqlOptions" component={HiddenField} /> </> ) : isEsqlRule(ruleType) ? ( EsqlQueryBarMemo diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx index 953b46eda0dc..bf70bb6104e6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx @@ -9,8 +9,7 @@ import { isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiText } from '@elastic/eui'; import React from 'react'; - -import { fromKueryExpression } from '@kbn/es-query'; +import { debounceAsync } from '@kbn/securitysolution-utils'; import { singleEntryThreat, containsInvalidItems, @@ -25,32 +24,29 @@ import { isSuppressionRuleConfiguredWithGroupBy, } from '../../../../../common/detection_engine/utils'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; -import type { FieldValueQueryBar } from '../query_bar'; import type { ERROR_CODE, FormSchema, ValidationFunc } from '../../../../shared_imports'; import { FIELD_TYPES, fieldValidators } from '../../../../shared_imports'; import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; -import { debounceAsync, eqlValidator } from '../eql_query_bar/validators'; import { esqlValidator } from '../../../rule_creation/logic/esql_validator'; import { dataViewIdValidatorFactory } from '../../validators/data_view_id_validator_factory'; import { indexPatternValidatorFactory } from '../../validators/index_pattern_validator_factory'; import { alertSuppressionFieldsValidatorFactory } from '../../validators/alert_suppression_fields_validator_factory'; import { - CUSTOM_QUERY_REQUIRED, - INVALID_CUSTOM_QUERY, INDEX_HELPER_TEXT, THREAT_MATCH_INDEX_HELPER_TEXT, THREAT_MATCH_REQUIRED, THREAT_MATCH_EMPTIES, EQL_SEQUENCE_SUPPRESSION_GROUPBY_VALIDATION_TEXT, } from './translations'; -import { getQueryRequiredMessage } from './utils'; import { ALERT_SUPPRESSION_DURATION_FIELD_NAME, ALERT_SUPPRESSION_FIELDS_FIELD_NAME, ALERT_SUPPRESSION_MISSING_FIELDS_FIELD_NAME, } from '../../../rule_creation/components/alert_suppression_edit'; import * as alertSuppressionEditI81n from '../../../rule_creation/components/alert_suppression_edit/components/translations'; +import { queryRequiredValidatorFactory } from '../../validators/query_required_validator_factory'; +import { kueryValidatorFactory } from '../../validators/kuery_validator_factory'; export const schema: FormSchema<DefineStepRule> = { index: { @@ -66,7 +62,7 @@ export const schema: FormSchema<DefineStepRule> = { helpText: <EuiText size="xs">{INDEX_HELPER_TEXT}</EuiText>, validations: [ { - validator: (...args: Parameters<ValidationFunc>) => { + validator: (...args) => { const [{ formData }] = args; if ( @@ -92,7 +88,7 @@ export const schema: FormSchema<DefineStepRule> = { fieldsToValidateOnChange: ['dataViewId'], validations: [ { - validator: (...args: Parameters<ValidationFunc>) => { + validator: (...args) => { const [{ formData }] = args; if (isMlRule(formData.ruleType) || formData.dataSourceType !== DataSourceType.DataView) { @@ -120,55 +116,21 @@ export const schema: FormSchema<DefineStepRule> = { fieldsToValidateOnChange: ['queryBar', ALERT_SUPPRESSION_FIELDS_FIELD_NAME], validations: [ { - validator: ( - ...args: Parameters<ValidationFunc> - ): ReturnType<ValidationFunc<{}, ERROR_CODE>> | undefined => { - const [{ value, path, formData }] = args; - const { query, filters, saved_id: savedId } = value as FieldValueQueryBar; - const needsValidation = !isMlRule(formData.ruleType); - if (!needsValidation) { - return undefined; - } - const isFieldEmpty = isEmpty(query.query as string) && isEmpty(filters); - if (!isFieldEmpty) { - return undefined; - } - if (savedId) { + validator: (...args) => { + const [{ value, formData }] = args; + + if (isMlRule(formData.ruleType) || value.saved_id) { // Ignore field validation error in this case. // Instead, we show the error toast when saved query object does not exist. // https://github.com/elastic/kibana/issues/159060 - return undefined; - } - const message = getQueryRequiredMessage(formData.ruleType); - return { code: 'ERR_FIELD_MISSING', path, message }; - }, - }, - { - validator: ( - ...args: Parameters<ValidationFunc> - ): ReturnType<ValidationFunc<{}, ERROR_CODE>> | undefined => { - const [{ value, path, formData }] = args; - const { query } = value as FieldValueQueryBar; - const needsValidation = !isMlRule(formData.ruleType); - if (!needsValidation) { return; } - if (!isEmpty(query.query as string) && query.language === 'kuery') { - try { - fromKueryExpression(query.query); - } catch (err) { - return { - code: 'ERR_FIELD_FORMAT', - path, - message: INVALID_CUSTOM_QUERY, - }; - } - } + return queryRequiredValidatorFactory(formData.ruleType)(...args); }, }, { - validator: debounceAsync(eqlValidator, 300), + validator: kueryValidatorFactory(), }, { validator: debounceAsync(esqlValidator, 300), @@ -507,49 +469,17 @@ export const schema: FormSchema<DefineStepRule> = { ), validations: [ { - validator: ( - ...args: Parameters<ValidationFunc> - ): ReturnType<ValidationFunc<{}, ERROR_CODE>> | undefined => { - const [{ value, path, formData }] = args; - const needsValidation = isThreatMatchRule(formData.ruleType); - if (!needsValidation) { + validator: (...args) => { + const [{ formData }] = args; + if (!isThreatMatchRule(formData.ruleType)) { return; } - const { query, filters } = value as FieldValueQueryBar; - - return isEmpty(query.query as string) && isEmpty(filters) - ? { - code: 'ERR_FIELD_MISSING', - path, - message: CUSTOM_QUERY_REQUIRED, - } - : undefined; + return queryRequiredValidatorFactory(formData.ruleType)(...args); }, }, { - validator: ( - ...args: Parameters<ValidationFunc> - ): ReturnType<ValidationFunc<{}, ERROR_CODE>> | undefined => { - const [{ value, path, formData }] = args; - const needsValidation = isThreatMatchRule(formData.ruleType); - if (!needsValidation) { - return; - } - const { query } = value as FieldValueQueryBar; - - if (!isEmpty(query.query as string) && query.language === 'kuery') { - try { - fromKueryExpression(query.query); - } catch (err) { - return { - code: 'ERR_FIELD_FORMAT', - path, - message: INVALID_CUSTOM_QUERY, - }; - } - } - }, + validator: kueryValidatorFactory(), }, ], }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx index d8b24f978afd..7b8063b23e30 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/translations.tsx @@ -9,34 +9,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -export const CUSTOM_QUERY_REQUIRED = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError', - { - defaultMessage: 'A custom query is required.', - } -); - -export const EQL_QUERY_REQUIRED = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError', - { - defaultMessage: 'An EQL query is required.', - } -); - -export const ESQL_QUERY_REQUIRED = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryFieldRequiredError', - { - defaultMessage: 'An ES|QL query is required.', - } -); - -export const INVALID_CUSTOM_QUERY = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError', - { - defaultMessage: 'The KQL is invalid', - } -); - export const INDEX_HELPER_TEXT = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.indicesHelperDescription', { @@ -66,13 +38,6 @@ export const QUERY_BAR_LABEL = i18n.translate( } ); -export const EQL_QUERY_BAR_LABEL = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.EqlQueryBarLabel', - { - defaultMessage: 'EQL query', - } -); - export const SAVED_QUERY_FORM_ROW_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.SavedQueryFormRowLabel', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts index 88592da7ecd8..baedda1ae162 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts @@ -5,13 +5,8 @@ * 2.0. */ -import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { FieldSpec } from '@kbn/data-plugin/common'; -import { CUSTOM_QUERY_REQUIRED, EQL_QUERY_REQUIRED, ESQL_QUERY_REQUIRED } from './translations'; - -import { isEqlRule, isEsqlRule } from '../../../../../common/detection_engine/utils'; - /** * Filters out fields, that are not supported in terms aggregation. * Terms aggregation supports limited number of types: @@ -25,18 +20,3 @@ export const getTermsAggregationFields = (fields: FieldSpec[]): FieldSpec[] => // binary types is excluded, as binary field has property aggregatable === false const ALLOWED_AGGREGATABLE_FIELD_TYPES_SET = new Set(['string', 'number', 'ip', 'boolean']); - -/** - * return query is required message depends on a rule type - */ -export const getQueryRequiredMessage = (ruleType: Type) => { - if (isEsqlRule(ruleType)) { - return ESQL_QUERY_REQUIRED; - } - - if (isEqlRule(ruleType)) { - return EQL_QUERY_REQUIRED; - } - - return CUSTOM_QUERY_REQUIRED; -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts index 84d10bae1c5d..d2bbe9edb116 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.test.ts @@ -17,7 +17,6 @@ import type { } from '../../../detections/pages/detection_engine/rules/types'; import { useRuleFormsErrors } from './form'; -import { transformEqlResponseErrorToValidationError } from '../components/eql_query_bar/validators'; import { ALERT_SUPPRESSION_FIELDS_FIELD_NAME } from '../../rule_creation/components/alert_suppression_edit'; const getFormWithErrorsMock = <T extends FormData = FormData>(fields: { @@ -33,13 +32,15 @@ describe('useRuleFormsErrors', () => { it('should return blocking error in case of syntax validation error', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.INVALID_SYNTAX, - messages: ["line 1:5: missing 'where' at 'demo'"], - }); const defineStepForm = getFormWithErrorsMock<DefineStepRule>({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.INVALID_SYNTAX, + message: '', + messages: ["line 1:5: missing 'where' at 'demo'"], + }, + ], }, }); @@ -53,13 +54,17 @@ describe('useRuleFormsErrors', () => { it('should return non-blocking error in case of missing data source validation error', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, - messages: ['index_not_found_exception Found 1 problem line -1:-1: Unknown index [*,-*]'], - }); const defineStepForm = getFormWithErrorsMock<DefineStepRule>({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, + message: '', + messages: [ + 'index_not_found_exception Found 1 problem line -1:-1: Unknown index [*,-*]', + ], + }, + ], }, }); @@ -75,15 +80,17 @@ describe('useRuleFormsErrors', () => { it('should return non-blocking error in case of missing data field validation error', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.INVALID_EQL, - messages: [ - 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]', - ], - }); const defineStepForm = getFormWithErrorsMock<DefineStepRule>({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.INVALID_EQL, + message: '', + messages: [ + 'Found 2 problems\nline 1:1: Unknown column [event.category]\nline 1:13: Unknown column [event.name]', + ], + }, + ], }, }); @@ -99,13 +106,15 @@ describe('useRuleFormsErrors', () => { it('should return non-blocking error in case of failed request error', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.FAILED_REQUEST, - error: new Error('Some internal error'), - }); const defineStepForm = getFormWithErrorsMock<DefineStepRule>({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.FAILED_REQUEST, + message: 'An error occurred while validating your EQL query', + error: new Error('Some internal error'), + }, + ], }, }); @@ -121,13 +130,15 @@ describe('useRuleFormsErrors', () => { it('should return blocking and non-blocking errors', async () => { const { result } = renderHook(() => useRuleFormsErrors()); - const validationError = transformEqlResponseErrorToValidationError({ - code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, - messages: ['Missing data source'], - }); const defineStepForm = getFormWithErrorsMock<DefineStepRule>({ queryBar: { - errors: [validationError], + errors: [ + { + code: EQL_ERROR_CODES.MISSING_DATA_SOURCE, + message: '', + messages: ['Missing data source'], + }, + ], }, }); const aboutStepForm = getFormWithErrorsMock<AboutStepRule>({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx index 9e232e4bff2b..64230fd3a8a2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/form.tsx @@ -19,7 +19,6 @@ import { useKibana } from '../../../common/lib/kibana'; import type { FormHook, ValidationError } from '../../../shared_imports'; import { useForm, useFormData } from '../../../shared_imports'; import { schema as defineRuleSchema } from '../components/step_define_rule/schema'; -import type { EqlOptionsSelected } from '../../../../common/search_strategy'; import { schema as aboutRuleSchema, threatMatchAboutSchema, @@ -53,20 +52,14 @@ export const useRuleForms = ({ options: { stripEmptyFields: false }, schema: defineRuleSchema, }); - const [eqlOptionsSelected, setEqlOptionsSelected] = useState<EqlOptionsSelected>( - defineStepDefault.eqlOptions - ); const [defineStepFormData] = useFormData<DefineStepRule | {}>({ form: defineStepForm, }); // FormData doesn't populate on the first render, so we use the defaultValue if the formData // doesn't have what we wanted const defineStepData = useMemo( - () => - 'index' in defineStepFormData - ? { ...defineStepFormData, eqlOptions: eqlOptionsSelected } - : defineStepDefault, - [defineStepDefault, defineStepFormData, eqlOptionsSelected] + () => ('index' in defineStepFormData ? defineStepFormData : defineStepDefault), + [defineStepDefault, defineStepFormData] ); // ABOUT STEP FORM @@ -118,8 +111,6 @@ export const useRuleForms = ({ scheduleStepData, actionsStepForm, actionsStepData, - eqlOptionsSelected, - setEqlOptionsSelected, }; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx index d19c9c7c89d0..01435a2f7c65 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx @@ -171,8 +171,6 @@ const CreateRulePageComponent: React.FC = () => { scheduleStepData, actionsStepForm, actionsStepData, - eqlOptionsSelected, - setEqlOptionsSelected, } = useRuleForms({ defineStepDefault, aboutStepDefault: stepAboutDefaultValue, @@ -392,10 +390,9 @@ const CreateRulePageComponent: React.FC = () => { const createRuleFromFormData = useCallback( async (enabled: boolean) => { - const localDefineStepData: DefineStepRule = defineFieldsTransform({ - ...defineStepForm.getFormData(), - eqlOptions: eqlOptionsSelected, - }); + const localDefineStepData: DefineStepRule = defineFieldsTransform( + defineStepForm.getFormData() + ); const localAboutStepData = aboutStepForm.getFormData(); const localScheduleStepData = scheduleStepForm.getFormData(); const localActionsStepData = actionsStepForm.getFormData(); @@ -435,7 +432,6 @@ const CreateRulePageComponent: React.FC = () => { createRule, defineFieldsTransform, defineStepForm, - eqlOptionsSelected, navigateToApp, ruleType, scheduleStepForm, @@ -556,8 +552,6 @@ const CreateRulePageComponent: React.FC = () => { indicesConfig={indicesConfig} threatIndicesConfig={threatIndicesConfig} form={defineStepForm} - optionsSelected={eqlOptionsSelected} - setOptionsSelected={setEqlOptionsSelected} indexPattern={indexPattern} isIndexPatternLoading={isIndexPatternLoading} isQueryBarValid={isQueryBarValid} @@ -588,7 +582,6 @@ const CreateRulePageComponent: React.FC = () => { defineStepData, memoizedIndex, defineStepForm, - eqlOptionsSelected, indexPattern, indicesConfig, isCreateRuleLoading, @@ -596,7 +589,6 @@ const CreateRulePageComponent: React.FC = () => { isQueryBarValid, loading, memoDefineStepReadOnly, - setEqlOptionsSelected, threatIndicesConfig, ] ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx index 0a1733bd831f..b09ee5f4e3f4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx @@ -131,8 +131,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { scheduleStepData, actionsStepForm, actionsStepData, - eqlOptionsSelected, - setEqlOptionsSelected, } = useRuleForms({ defineStepDefault: defineRuleData, aboutStepDefault: aboutRuleData, @@ -232,8 +230,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { threatIndicesConfig={threatIndicesConfig} defaultSavedQuery={savedQuery} form={defineStepForm} - optionsSelected={eqlOptionsSelected} - setOptionsSelected={setEqlOptionsSelected} key="defineStep" indexPattern={indexPattern} isIndexPatternLoading={isIndexPatternLoading} @@ -355,8 +351,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { threatIndicesConfig, savedQuery, defineStepForm, - eqlOptionsSelected, - setEqlOptionsSelected, indexPattern, isIndexPatternLoading, isQueryBarValid, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/kuery_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/kuery_validator_factory.ts new file mode 100644 index 000000000000..f362acffd3bc --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/kuery_validator_factory.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; +import { fromKueryExpression } from '@kbn/es-query'; +import type { FormData, ValidationFunc } from '../../../shared_imports'; +import type { FieldValueQueryBar } from '../components/query_bar'; + +export function kueryValidatorFactory(): ValidationFunc<FormData, string, FieldValueQueryBar> { + return (...args) => { + const [{ path, value }] = args; + + if (isEmpty(value.query.query) || value.query.language !== 'kuery') { + return; + } + + try { + fromKueryExpression(value.query.query); + } catch (err) { + return { + code: 'ERR_FIELD_FORMAT', + path, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldInvalidError', + { + defaultMessage: 'The KQL is invalid', + } + ), + }; + } + }; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_required_validator_factory.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_required_validator_factory.ts new file mode 100644 index 000000000000..f06aaa6b312f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/validators/query_required_validator_factory.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 { i18n } from '@kbn/i18n'; +import { isEmpty } from 'lodash'; +import type { RuleType } from '@kbn/securitysolution-rules'; +import type { FormData, ValidationFunc } from '../../../shared_imports'; +import { isEqlRule, isEsqlRule } from '../../../../common/detection_engine/utils'; +import type { FieldValueQueryBar } from '../components/query_bar'; + +export function queryRequiredValidatorFactory( + ruleType: RuleType +): ValidationFunc<FormData, string, FieldValueQueryBar> { + return (...args) => { + const [{ path, value }] = args; + + if (isEmpty(value.query.query as string) && isEmpty(value.filters)) { + return { + code: 'ERR_FIELD_MISSING', + path, + message: getErrorMessage(ruleType), + }; + } + }; +} + +function getErrorMessage(ruleType: RuleType): string { + if (isEsqlRule(ruleType)) { + return i18n.translate( + 'xpack.securitySolution.ruleManagement.ruleCreation.validation.query.esqlQueryFieldRequiredError', + { + defaultMessage: 'An ES|QL query is required.', + } + ); + } + + if (isEqlRule(ruleType)) { + return i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError', + { + defaultMessage: 'An EQL query is required.', + } + ); + } + + return i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError', + { + defaultMessage: 'A custom query is required.', + } + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts index 04660191c9cb..8e159c94aca0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/constants.ts @@ -34,9 +34,6 @@ export const DEFINITION_UPGRADE_FIELD_ORDER: Array<keyof DiffableAllFields> = [ 'type', 'kql_query', 'eql_query', - 'event_category_override', - 'timestamp_field', - 'tiebreaker_field', 'esql_query', 'anomaly_threshold', 'machine_learning_job_id', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx index 5e417b3d862e..701ab18e9a0a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx @@ -92,16 +92,16 @@ describe('PerFieldRuleDiffTab', () => { }); describe('Undefined values are displayed with empty diffs', () => { - test('Displays only an updated field value when changed from undefined', () => { + test('Displays only an updated field value when changed from an empty value', () => { const mockData: PartialRuleDiff = { ...ruleFieldsDiffMock, fields: { - timestamp_field: { + name: { ...ruleFieldsDiffBaseFieldsMock, base_version: undefined, - current_version: undefined, - merged_version: 'new timestamp field', - target_version: 'new timestamp field', + current_version: '', + merged_version: 'new name', + target_version: 'new name', }, }, }; @@ -109,19 +109,19 @@ describe('PerFieldRuleDiffTab', () => { const diffContent = wrapper.getByTestId('ruleUpgradePerFieldDiffContent').textContent; // Only the new timestamp field should be displayed - expect(diffContent).toEqual('+new timestamp field'); + expect(diffContent).toEqual('+new name'); }); - test('Displays only an outdated field value when incoming update is undefined', () => { + test('Displays only an outdated field value when incoming update is an empty value', () => { const mockData: PartialRuleDiff = { ...ruleFieldsDiffMock, fields: { - timestamp_field: { + name: { ...ruleFieldsDiffBaseFieldsMock, - base_version: 'old timestamp field', - current_version: 'old timestamp field', - merged_version: undefined, - target_version: undefined, + base_version: 'old name', + current_version: 'old name', + merged_version: '', + target_version: '', }, }, }; @@ -129,7 +129,7 @@ describe('PerFieldRuleDiffTab', () => { const diffContent = wrapper.getByTestId('ruleUpgradePerFieldDiffContent').textContent; // Only the old timestamp_field should be displayed - expect(diffContent).toEqual('-old timestamp field'); + expect(diffContent).toEqual('-old name'); }); }); @@ -144,13 +144,6 @@ describe('PerFieldRuleDiffTab', () => { merged_version: 'new setup', target_version: 'new setup', }, - timestamp_field: { - ...ruleFieldsDiffBaseFieldsMock, - base_version: undefined, - current_version: undefined, - merged_version: 'new timestamp', - target_version: 'new timestamp', - }, name: { ...ruleFieldsDiffBaseFieldsMock, base_version: 'old name', @@ -166,11 +159,11 @@ describe('PerFieldRuleDiffTab', () => { const sectionLabels = matchedSectionElements.map((element) => element.textContent); // Schedule doesn't have any fields in the diff and shouldn't be displayed - expect(sectionLabels).toEqual(['About', 'Definition', 'Setup guide']); + expect(sectionLabels).toEqual(['About', 'Setup guide']); const matchedFieldElements = wrapper.queryAllByTestId('ruleUpgradePerFieldDiffLabel'); const fieldLabels = matchedFieldElements.map((element) => element.textContent); - expect(fieldLabels).toEqual(['Name', 'Timestamp Field', 'Setup']); + expect(fieldLabels).toEqual(['Name', 'Setup']); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx index 2851a4d8a872..687234411a31 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx @@ -27,6 +27,7 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { FilterItems } from '@kbn/unified-search-plugin/public'; import type { AlertSuppressionMissingFieldsStrategy, + EqlOptionalFields, RequiredFieldArray, RuleResponse, Threshold as ThresholdType, @@ -60,6 +61,11 @@ import { getQueryLanguageLabel } from './helpers'; import { useDefaultIndexPattern } from '../../hooks/use_default_index_pattern'; import { convertDateMathToDuration } from '../../../../common/utils/date_math'; import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../common/constants'; +import { + EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL, + EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL, + EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL, +} from '../../../rule_creation/components/eql_query_edit/translations'; interface SavedQueryNameProps { savedQueryName: string; @@ -565,6 +571,51 @@ const prepareDefinitionSectionListItems = ( } } + if ((rule as EqlOptionalFields).event_category_override) { + definitionSectionListItems.push({ + title: ( + <span data-test-subj="eqlOptionsEventCategoryOverrideTitle"> + {EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL} + </span> + ), + description: ( + <span data-test-subj="eqlOptionsEventCategoryOverrideValue"> + {(rule as EqlOptionalFields).event_category_override} + </span> + ), + }); + } + + if ((rule as EqlOptionalFields).tiebreaker_field) { + definitionSectionListItems.push({ + title: ( + <span data-test-subj="eqlOptionsTiebreakerFieldTitle"> + {EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL} + </span> + ), + description: ( + <span data-test-subj="eqlOptionsEventTiebreakerFieldValue"> + {(rule as EqlOptionalFields).tiebreaker_field} + </span> + ), + }); + } + + if ((rule as EqlOptionalFields).timestamp_field) { + definitionSectionListItems.push({ + title: ( + <span data-test-subj="eqlOptionsTimestampFieldTitle"> + {EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL} + </span> + ), + description: ( + <span data-test-subj="eqlOptionsTimestampFieldValue"> + {(rule as EqlOptionalFields).timestamp_field} + </span> + ), + }); + } + if (rule.type) { definitionSectionListItems.push({ title: i18n.RULE_TYPE_FIELD_LABEL, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts index 25a4dff97dd2..b68eb44f7f86 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts @@ -15,26 +15,25 @@ export const getSubfieldChangesForEqlQuery = ( ): SubfieldChange[] => { const changes: SubfieldChange[] = []; - const oldQuery = stringifyToSortedJson(oldFieldValue?.query); - const newQuery = stringifyToSortedJson(newFieldValue?.query); + const subFieldNames: Array<keyof DiffableAllFields['eql_query']> = [ + 'query', + 'filters', + 'event_category_override', + 'tiebreaker_field', + 'timestamp_field', + ]; - if (oldQuery !== newQuery) { - changes.push({ - subfieldName: 'query', - oldSubfieldValue: oldQuery, - newSubfieldValue: newQuery, - }); - } - - const oldFilters = stringifyToSortedJson(oldFieldValue?.filters); - const newFilters = stringifyToSortedJson(newFieldValue?.filters); + for (const subFieldName of subFieldNames) { + const oldValue = stringifyToSortedJson(oldFieldValue?.[subFieldName]); + const newValue = stringifyToSortedJson(newFieldValue?.[subFieldName]); - if (oldFilters !== newFilters) { - changes.push({ - subfieldName: 'filters', - oldSubfieldValue: oldFilters, - newSubfieldValue: newFilters, - }); + if (newValue !== oldValue) { + changes.push({ + subfieldName: subFieldName, + oldSubfieldValue: oldValue, + newSubfieldValue: newValue, + }); + } } return changes; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx index 69e11c85e4d5..843f128dae8a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/custom_query_rule_field_edit.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; import type { UpgradeableCustomQueryFields } from '../../../../model/prebuilt_rule_upgrade/fields'; import { KqlQueryEditForm } from './fields/kql_query'; import { DataSourceEditForm } from './fields/data_source'; @@ -24,6 +25,6 @@ export function CustomQueryRuleFieldEdit({ fieldName }: CustomQueryRuleFieldEdit case 'alert_suppression': return <AlertSuppressionEditForm />; default: - return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + return assertUnreachable(fieldName); } } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx index dba33e57d56a..29fcfdf7d522 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/eql_rule_field_edit.tsx @@ -6,9 +6,11 @@ */ import React from 'react'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; import type { UpgradeableEqlFields } from '../../../../model/prebuilt_rule_upgrade/fields'; import { DataSourceEditForm } from './fields/data_source'; import { AlertSuppressionEditForm } from './fields/alert_suppression'; +import { EqlQueryEditForm } from './fields/eql_query'; interface EqlRuleFieldEditProps { fieldName: UpgradeableEqlFields; @@ -16,11 +18,13 @@ interface EqlRuleFieldEditProps { export function EqlRuleFieldEdit({ fieldName }: EqlRuleFieldEditProps) { switch (fieldName) { + case 'eql_query': + return <EqlQueryEditForm />; case 'data_source': return <DataSourceEditForm />; case 'alert_suppression': return <AlertSuppressionEditForm />; default: - return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + return assertUnreachable(fieldName); } } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx new file mode 100644 index 000000000000..787891452f1d --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_adapter.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { DataViewBase } from '@kbn/es-query'; +import { EqlQueryEdit } from '../../../../../../../rule_creation/components/eql_query_edit'; +import type { RuleFieldEditComponentProps } from '../rule_field_edit_component_props'; +import { useDiffableRuleDataView } from '../hooks/use_diffable_rule_data_view'; + +export function EqlQueryEditAdapter({ + finalDiffableRule, +}: RuleFieldEditComponentProps): JSX.Element | null { + const { dataView, isLoading } = useDiffableRuleDataView(finalDiffableRule); + + // Wait for dataView to be defined to trigger validation with the correct index patterns + if (!dataView) { + return null; + } + + return ( + <EqlQueryEdit + path="eqlQuery" + eqlOptionsPath="eqlOptions" + required + dataView={dataView ?? DEFAULT_DATA_VIEW_BASE} + loading={isLoading} + disabled={isLoading} + skipEqlValidation + /> + ); +} + +const DEFAULT_DATA_VIEW_BASE: DataViewBase = { + title: '', + fields: [], +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx new file mode 100644 index 000000000000..3a8312897671 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/eql_query_edit_form.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { Filter } from '@kbn/es-query'; +import type { EqlOptions } from '@kbn/timelines-plugin/common'; +import type { FormData, FormSchema } from '../../../../../../../../shared_imports'; +import { RuleFieldEditFormWrapper } from '../rule_field_edit_form_wrapper'; +import type { FieldValueQueryBar } from '../../../../../../../rule_creation_ui/components/query_bar'; +import { + type DiffableRule, + RuleEqlQuery, + QueryLanguageEnum, +} from '../../../../../../../../../common/api/detection_engine'; +import { EqlQueryEditAdapter } from './eql_query_edit_adapter'; + +export function EqlQueryEditForm(): JSX.Element { + return ( + <RuleFieldEditFormWrapper + component={EqlQueryEditAdapter} + ruleFieldFormSchema={kqlQuerySchema} + deserializer={deserializer} + serializer={serializer} + /> + ); +} + +const kqlQuerySchema = {} as FormSchema<{ + eqlQuery: RuleEqlQuery; +}>; + +function deserializer( + fieldValue: FormData, + finalDiffableRule: DiffableRule +): { + eqlQuery: FieldValueQueryBar; + eqlOptions: EqlOptions; +} { + const parsedEqlQuery = + 'eql_query' in finalDiffableRule + ? RuleEqlQuery.parse(fieldValue.eql_query) + : { + query: '', + language: QueryLanguageEnum.eql, + filters: [], + }; + + return { + eqlQuery: { + query: { + query: parsedEqlQuery.query, + language: parsedEqlQuery.language, + }, + // cast to Filter since RuleEqlQuery checks it's an array + // potentially it might be incompatible type + filters: parsedEqlQuery.filters as Filter[], + saved_id: null, + }, + eqlOptions: { + eventCategoryField: parsedEqlQuery.event_category_override, + timestampField: parsedEqlQuery.timestamp_field, + tiebreakerField: parsedEqlQuery.tiebreaker_field, + }, + }; +} + +function serializer(formData: FormData): { + eql_query: RuleEqlQuery; +} { + const formValue = formData as { eqlQuery: FieldValueQueryBar; eqlOptions: EqlOptions }; + + return { + eql_query: { + query: formValue.eqlQuery.query.query as string, + language: QueryLanguageEnum.eql, + filters: formValue.eqlQuery.filters, + event_category_override: formValue.eqlOptions.eventCategoryField, + timestamp_field: formValue.eqlOptions.timestampField, + tiebreaker_field: formValue.eqlOptions.tiebreakerField, + }, + }; +} diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/index.ts similarity index 82% rename from x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/index.ts index dcb2d12f6c98..c11bd722a717 100644 --- a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/eql_query/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { AddEmptyPrompt } from './add_empty_prompt'; +export * from './eql_query_edit_form'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx index b18b3203fdfb..0c1ba5b1b6f6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/saved_query_rule_field_edit.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; import type { UpgradeableSavedQueryFields } from '../../../../model/prebuilt_rule_upgrade/fields'; import { KqlQueryEditForm } from './fields/kql_query'; import { DataSourceEditForm } from './fields/data_source'; @@ -24,6 +25,6 @@ export function SavedQueryRuleFieldEdit({ fieldName }: SavedQueryRuleFieldEditPr case 'alert_suppression': return <AlertSuppressionEditForm />; default: - return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + return assertUnreachable(fieldName); } } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/eql_rule_field_readonly.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/eql_rule_field_readonly.tsx index b72fce91f198..5fbd9516e78b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/eql_rule_field_readonly.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/eql_rule_field_readonly.tsx @@ -12,9 +12,6 @@ import { EqlQueryReadOnly } from './fields/eql_query/eql_query'; import { TypeReadOnly } from './fields/type/type'; import { AlertSuppressionReadOnly } from './fields/alert_suppression/alert_suppression'; import { assertUnreachable } from '../../../../../../../common/utility_types'; -import { EventCategoryOverrideReadOnly } from './fields/event_category_override/event_category_override'; -import { TimestampFieldReadOnly } from './fields/timestamp_field/timestamp_field'; -import { TiebreakerFieldReadOnly } from './fields/tiebreaker_field/tiebreaker_field'; interface EqlRuleFieldReadOnlyProps { fieldName: keyof DiffableEqlFields; @@ -39,16 +36,6 @@ export function EqlRuleFieldReadOnly({ fieldName, finalDiffableRule }: EqlRuleFi dataSource={finalDiffableRule.data_source} /> ); - case 'event_category_override': - return ( - <EventCategoryOverrideReadOnly - eventCategoryOverride={finalDiffableRule.event_category_override} - /> - ); - case 'tiebreaker_field': - return <TiebreakerFieldReadOnly tiebreakerField={finalDiffableRule.tiebreaker_field} />; - case 'timestamp_field': - return <TimestampFieldReadOnly timestampField={finalDiffableRule.timestamp_field} />; case 'type': return <TypeReadOnly type={finalDiffableRule.type} />; default: diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/eql_query/eql_query.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/eql_query/eql_query.tsx index f94f0bbfbe6c..f2c49507b8ad 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/eql_query/eql_query.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/eql_query/eql_query.tsx @@ -6,11 +6,11 @@ */ import React from 'react'; -import { EuiDescriptionList } from '@elastic/eui'; +import { EuiDescriptionList, EuiText } from '@elastic/eui'; import type { EuiDescriptionListProps } from '@elastic/eui'; -import type { - RuleDataSource, - RuleEqlQuery, +import { + type RuleDataSource, + type RuleEqlQuery, } from '../../../../../../../../../common/api/detection_engine'; import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations'; import { Query, Filters } from '../../../../rule_definition_section'; @@ -38,5 +38,26 @@ export function EqlQueryReadOnly({ eqlQuery, dataSource }: EqlQueryReadOnlyProps }); } + if (eqlQuery.event_category_override) { + listItems.push({ + title: descriptionStepI18n.EQL_EVENT_CATEGORY_FIELD_LABEL, + description: <EuiText size="s">{eqlQuery.event_category_override}</EuiText>, + }); + } + + if (eqlQuery.tiebreaker_field) { + listItems.push({ + title: descriptionStepI18n.EQL_TIEBREAKER_FIELD_LABEL, + description: <EuiText size="s">{eqlQuery.tiebreaker_field}</EuiText>, + }); + } + + if (eqlQuery.timestamp_field) { + listItems.push({ + title: descriptionStepI18n.EQL_TIMESTAMP_FIELD_LABEL, + description: <EuiText size="s">{eqlQuery.timestamp_field}</EuiText>, + }); + } + return <EuiDescriptionList listItems={listItems} />; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.stories.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.stories.tsx deleted file mode 100644 index 1f5987287f66..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.stories.tsx +++ /dev/null @@ -1,40 +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 React from 'react'; -import type { Story } from '@storybook/react'; -import { EventCategoryOverrideReadOnly } from './event_category_override'; -import { FieldReadOnly } from '../../field_readonly'; -import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; -import { mockEqlRule } from '../../storybook/mocks'; -import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers'; - -export default { - component: EventCategoryOverrideReadOnly, - title: - 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/event_category_override', -}; - -interface TemplateProps { - finalDiffableRule: DiffableRule; -} - -const Template: Story<TemplateProps> = (args) => { - return ( - <ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}> - <FieldReadOnly fieldName="event_category_override" /> - </ThreeWayDiffStorybookProviders> - ); -}; - -export const Default = Template.bind({}); - -Default.args = { - finalDiffableRule: mockEqlRule({ - event_category_override: 'event.action', - }), -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.tsx deleted file mode 100644 index 910e639049f9..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/event_category_override/event_category_override.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiDescriptionList, EuiText } from '@elastic/eui'; -import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations'; -import type { EventCategoryOverride as EventCategoryOverrideType } from '../../../../../../../../../common/api/detection_engine'; - -interface EventCategoryOverrideReadOnlyProps { - eventCategoryOverride?: EventCategoryOverrideType; -} - -export function EventCategoryOverrideReadOnly({ - eventCategoryOverride, -}: EventCategoryOverrideReadOnlyProps) { - if (!eventCategoryOverride) { - return null; - } - - return ( - <EuiDescriptionList - listItems={[ - { - title: descriptionStepI18n.EQL_EVENT_CATEGORY_FIELD_LABEL, - description: <EventCategoryOverride eventCategoryOverride={eventCategoryOverride} />, - }, - ]} - /> - ); -} - -interface EventCategoryOverrideProps { - eventCategoryOverride: EventCategoryOverrideType; -} - -function EventCategoryOverride({ eventCategoryOverride }: EventCategoryOverrideProps) { - return <EuiText size="s">{eventCategoryOverride}</EuiText>; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.stories.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.stories.tsx deleted file mode 100644 index 3e73afda8f4e..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.stories.tsx +++ /dev/null @@ -1,40 +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 React from 'react'; -import type { Story } from '@storybook/react'; -import { TiebreakerFieldReadOnly } from './tiebreaker_field'; -import { FieldReadOnly } from '../../field_readonly'; -import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; -import { mockEqlRule } from '../../storybook/mocks'; -import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers'; - -export default { - component: TiebreakerFieldReadOnly, - title: - 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/tiebreaker_field', -}; - -interface TemplateProps { - finalDiffableRule: DiffableRule; -} - -const Template: Story<TemplateProps> = (args) => { - return ( - <ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}> - <FieldReadOnly fieldName="tiebreaker_field" /> - </ThreeWayDiffStorybookProviders> - ); -}; - -export const Default = Template.bind({}); - -Default.args = { - finalDiffableRule: mockEqlRule({ - tiebreaker_field: 'process.name', - }), -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.tsx deleted file mode 100644 index 10e52240748c..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/tiebreaker_field/tiebreaker_field.tsx +++ /dev/null @@ -1,40 +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 React from 'react'; -import { EuiDescriptionList, EuiText } from '@elastic/eui'; -import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations'; -import type { TiebreakerField as TiebreakerFieldType } from '../../../../../../../../../common/api/detection_engine'; - -interface TiebreakerFieldReadOnlyProps { - tiebreakerField?: TiebreakerFieldType; -} - -export function TiebreakerFieldReadOnly({ tiebreakerField }: TiebreakerFieldReadOnlyProps) { - if (!tiebreakerField) { - return null; - } - - return ( - <EuiDescriptionList - listItems={[ - { - title: descriptionStepI18n.EQL_TIEBREAKER_FIELD_LABEL, - description: <TiebreakerField tiebreakerField={tiebreakerField} />, - }, - ]} - /> - ); -} - -interface TiebreakerFieldProps { - tiebreakerField: TiebreakerFieldType; -} - -function TiebreakerField({ tiebreakerField }: TiebreakerFieldProps) { - return <EuiText size="s">{tiebreakerField}</EuiText>; -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.stories.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.stories.tsx deleted file mode 100644 index 9b3977c3deeb..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.stories.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { Story } from '@storybook/react'; -import { TimestampFieldReadOnly } from './timestamp_field'; -import { FieldReadOnly } from '../../field_readonly'; -import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; -import { mockEqlRule } from '../../storybook/mocks'; -import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers'; - -export default { - component: TimestampFieldReadOnly, - title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/timestamp_field', -}; - -interface TemplateProps { - finalDiffableRule: DiffableRule; -} - -const Template: Story<TemplateProps> = (args) => { - return ( - <ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}> - <FieldReadOnly fieldName="timestamp_field" /> - </ThreeWayDiffStorybookProviders> - ); -}; - -export const Default = Template.bind({}); - -Default.args = { - finalDiffableRule: mockEqlRule({ - timestamp_field: 'event.created', - }), -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.tsx deleted file mode 100644 index cd27bfde3db6..000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_readonly/fields/timestamp_field/timestamp_field.tsx +++ /dev/null @@ -1,40 +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 React from 'react'; -import { EuiDescriptionList, EuiText } from '@elastic/eui'; -import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations'; -import type { TimestampField as TimestampFieldType } from '../../../../../../../../../common/api/detection_engine'; - -interface TimestampFieldReadOnlyProps { - timestampField?: TimestampFieldType; -} - -export function TimestampFieldReadOnly({ timestampField }: TimestampFieldReadOnlyProps) { - if (!timestampField) { - return null; - } - - return ( - <EuiDescriptionList - listItems={[ - { - title: descriptionStepI18n.EQL_TIMESTAMP_FIELD_LABEL, - description: <TimestampField timestampField={timestampField} />, - }, - ]} - /> - ); -} - -interface TimestampFieldProps { - timestampField: TimestampFieldType; -} - -function TimestampField({ timestampField }: TimestampFieldProps) { - return <EuiText size="s">{timestampField}</EuiText>; -} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data.test.tsx index 74e04a61ae5b..e34d8be795ba 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data.test.tsx @@ -5,11 +5,10 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import type { PropsWithChildren } from 'react'; +import { renderHook } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; import { ALERTS_QUERY_NAMES } from '../../../containers/detection_engine/alerts/constants'; -import type { UseAlerts, UseAlertsQueryProps } from './use_summary_chart_data'; +import type { UseAlertsQueryProps } from './use_summary_chart_data'; import { useSummaryChartData, getAlertsQuery } from './use_summary_chart_data'; import * as aggregations from './aggregations'; import * as severityMock from '../severity_level_panel/mock_data'; @@ -76,7 +75,7 @@ describe('getAlertsQuery', () => { // helper function to render the hook const renderUseSummaryChartData = (props: Partial<UseAlertsQueryProps> = {}) => - renderHook<PropsWithChildren<UseAlertsQueryProps>, ReturnType<UseAlerts>>( + renderHook( () => useSummaryChartData({ aggregations: aggregations.severityAggregations, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.test.tsx index 303b85a40e6e..82e8456e2857 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import React from 'react'; import { useAlertsLocalStorage } from '.'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx index c89626b9edce..f7fd2eced3d3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { FieldSpec } from '@kbn/data-plugin/common'; import type { GetAggregatableFields, UseInspectButtonParams } from './hooks'; @@ -120,7 +120,7 @@ describe('hooks', () => { jest.clearAllMocks(); }); it('returns only aggregateable fields', () => { - const wrapper = ({ children }: { children: JSX.Element }) => ( + const wrapper = ({ children }: React.PropsWithChildren) => ( <TestProviders>{children}</TestProviders> ); const { result, unmount } = renderHook(() => useStackByFields(), { wrapper }); @@ -137,7 +137,7 @@ describe('hooks', () => { browserFields: { base: mockBrowserFields.base }, }); - const wrapper = ({ children }: { children: JSX.Element }) => ( + const wrapper = ({ children }: React.PropsWithChildren) => ( <TestProviders>{children}</TestProviders> ); const useLensCompatibleFields = true; @@ -155,7 +155,7 @@ describe('hooks', () => { browserFields: { nestedField: mockBrowserFields.nestedField }, }); - const wrapper = ({ children }: { children: JSX.Element }) => ( + const wrapper = ({ children }: React.PropsWithChildren) => ( <TestProviders>{children}</TestProviders> ); const useLensCompatibleFields = true; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index b16d59045d83..e44f3e8785be 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -25,7 +25,6 @@ import { getThresholdDetectionAlertAADMock, mockEcsDataWithAlert, mockTimelineDetails, - mockTimelineResult, mockAADEcsDataWithAlert, mockGetOneTimelineResult, mockTimelineData, @@ -283,7 +282,7 @@ describe('alert actions', () => { search: jest.fn().mockImplementation(() => of({ data: mockTimelineDetails })), }; - (getTimelineTemplate as jest.Mock).mockResolvedValue(mockTimelineResult); + (getTimelineTemplate as jest.Mock).mockResolvedValue(mockGetOneTimelineResult); clock = sinon.useFakeTimers(unix); }); @@ -355,7 +354,6 @@ describe('alert actions', () => { eventCategoryField: 'event.category', query: '', size: 100, - tiebreakerField: '', timestampField: '@timestamp', }, eventIdToNoteIds: {}, @@ -442,11 +440,13 @@ describe('alert actions', () => { test('it invokes createTimeline with kqlQuery.filterQuery.kuery.kind as "kuery" if not specified in returned timeline template', async () => { const mockTimelineResultModified = { - ...mockTimelineResult, - kqlQuery: { - filterQuery: { - kuery: { - expression: [''], + body: { + ...mockGetOneTimelineResult, + kqlQuery: { + filterQuery: { + kuery: { + expression: [''], + }, }, }, }, @@ -460,7 +460,6 @@ describe('alert actions', () => { getExceptionFilter: mockGetExceptionFilter, }); const createTimelineArg = (createTimeline as jest.Mock).mock.calls[0][0]; - expect(mockGetExceptionFilter).not.toHaveBeenCalled(); expect(createTimeline).toHaveBeenCalledTimes(1); expect(createTimelineArg.timeline.kqlQuery.filterQuery.kuery.kind).toEqual('kuery'); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 3a3e0d025553..822adecbe8c4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -7,7 +7,7 @@ /* eslint-disable complexity */ -import { getOr, isEmpty } from 'lodash/fp'; +import { isEmpty } from 'lodash/fp'; import moment from 'moment'; import dateMath from '@kbn/datemath'; @@ -51,7 +51,6 @@ import { isThresholdRule, } from '../../../../common/detection_engine/utils'; import { TimelineId } from '../../../../common/types/timeline'; -import type { TimelineResponse } from '../../../../common/api/timeline'; import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import type { SendAlertToTimelineActionProps, @@ -67,10 +66,7 @@ import type { } from '../../../../common/search_strategy/timeline'; import { TimelineEventsQueries } from '../../../../common/search_strategy/timeline'; import { timelineDefaults } from '../../../timelines/store/defaults'; -import { - omitTypenameInTimeline, - formatTimelineResponseToModel, -} from '../../../timelines/components/open_timeline/helpers'; +import { formatTimelineResponseToModel } from '../../../timelines/components/open_timeline/helpers'; import { convertKueryToElasticSearchQuery } from '../../../common/lib/kuery'; import { getField, getFieldKey } from '../../../helpers'; import { @@ -980,15 +976,9 @@ export const sendAlertToTimelineAction = async ({ ) ), ]); - - const resultingTimeline: TimelineResponse = getOr( - {}, - 'data.getOneTimeline', - responseTimeline - ); const eventData: TimelineEventsDetailsItem[] = eventDataResp.data ?? []; - if (!isEmpty(resultingTimeline)) { - const timelineTemplate = omitTypenameInTimeline(resultingTimeline); + if (!isEmpty(responseTimeline)) { + const timelineTemplate = responseTimeline; const { timeline, notes } = formatTimelineResponseToModel( timelineTemplate, true, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx index cb1fa0b4ef86..fe3648c426bc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_resolver.test.tsx @@ -7,7 +7,7 @@ import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { useIsInvestigateInResolverActionEnabled } from './investigate_in_resolver'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; describe('InvestigateInResolverAction', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.test.tsx index b60a7a5644b5..d22520f9f955 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.test.tsx @@ -7,8 +7,7 @@ import React from 'react'; import { EuiContextMenu, EuiPopover } from '@elastic/eui'; -import { act, renderHook } from '@testing-library/react-hooks'; -import { render, screen } from '@testing-library/react'; +import { render, screen, renderHook, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useAddToCaseActions } from './use_add_to_case_actions'; import { TestProviders } from '../../../../common/mock'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.test.tsx index a7bde416b7ca..b3173bcff377 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_assignees_actions.test.tsx @@ -5,12 +5,11 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; import type { UseAlertAssigneesActionsProps } from './use_alert_assignees_actions'; import { useAlertAssigneesActions } from './use_alert_assignees_actions'; import { useAlertsPrivileges } from '../../../containers/detection_engine/alerts/use_alerts_privileges'; import type { AlertTableContextMenuItem } from '../types'; -import { render } from '@testing-library/react'; +import { render, renderHook } from '@testing-library/react'; import React from 'react'; import type { EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { EuiPopover, EuiContextMenu } from '@elastic/eui'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_tags_actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_tags_actions.test.tsx index 4e037467597c..64461725b862 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_tags_actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_alert_tags_actions.test.tsx @@ -5,12 +5,11 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; import type { UseAlertTagsActionsProps } from './use_alert_tags_actions'; import { useAlertTagsActions } from './use_alert_tags_actions'; import { useAlertsPrivileges } from '../../../containers/detection_engine/alerts/use_alerts_privileges'; import type { AlertTableContextMenuItem } from '../types'; -import { render } from '@testing-library/react'; +import { render, renderHook } from '@testing-library/react'; import React from 'react'; import type { EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { EuiPopover, EuiContextMenu } from '@elastic/eui'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.test.tsx index 6d20ef397333..8de172d54a07 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.test.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; -import { fireEvent, render, waitFor } from '@testing-library/react'; + +import { fireEvent, render, waitFor, renderHook, act } from '@testing-library/react'; import { of } from 'rxjs'; import { TestProviders } from '../../../../common/mock'; import { useKibana } from '../../../../common/lib/kibana'; @@ -112,114 +112,110 @@ const mockSendAlertToTimeline = jest.spyOn(actions, 'sendAlertToTimelineAction') }); const mockTimelineTemplateResponse = { - data: { - getOneTimeline: { - savedObjectId: '15bc8185-06ef-4956-b7e7-be8e289b13c2', - version: 'WzIzMzUsMl0=', - columns: [ - { - columnHeaderType: 'not-filtered', - id: '@timestamp', - type: 'date', - }, - { - columnHeaderType: 'not-filtered', - id: 'host.name', - }, - { - columnHeaderType: 'not-filtered', - id: 'user.name', - }, - ], - dataProviders: [ - { - and: [], - enabled: true, - id: 'some-random-id', - name: 'host.name', - excluded: false, - kqlQuery: '', - queryMatch: { - field: 'host.name', - value: '{host.name}', - operator: ':', - }, - type: 'template', - }, - ], - dataViewId: 'security-solution-default', - description: '', - eqlOptions: { - eventCategoryField: 'event.category', - tiebreakerField: '', - timestampField: '@timestamp', - query: '', - size: 100, - }, - eventType: 'all', - excludedRowRendererIds: [ - 'alert', - 'alerts', - 'auditd', - 'auditd_file', - 'library', - 'netflow', - 'plain', - 'registry', - 'suricata', - 'system', - 'system_dns', - 'system_endgame_process', - 'system_file', - 'system_fim', - 'system_security_event', - 'system_socket', - 'threat_match', - 'zeek', - ], - favorite: [], - filters: [], - indexNames: ['.alerts-security.alerts-default', 'auditbeat-*', 'filebeat-*', 'packetbeat-*'], - kqlMode: 'filter', - kqlQuery: { - filterQuery: { - kuery: { - kind: 'kuery', - expression: '*', - }, - serializedQuery: '{"query_string":{"query":"*"}}', - }, + savedObjectId: '15bc8185-06ef-4956-b7e7-be8e289b13c2', + version: 'WzIzMzUsMl0=', + columns: [ + { + columnHeaderType: 'not-filtered', + id: '@timestamp', + type: 'date', + }, + { + columnHeaderType: 'not-filtered', + id: 'host.name', + }, + { + columnHeaderType: 'not-filtered', + id: 'user.name', + }, + ], + dataProviders: [ + { + and: [], + enabled: true, + id: 'some-random-id', + name: 'host.name', + excluded: false, + kqlQuery: '', + queryMatch: { + field: 'host.name', + value: '{host.name}', + operator: ':', }, - title: 'Named Template', - templateTimelineId: 'c755cda6-8a65-4ec2-b6ff-35a5356de8b9', - templateTimelineVersion: 1, - dateRange: { - start: '2024-08-13T22:00:00.000Z', - end: '2024-08-14T21:59:59.999Z', + type: 'template', + }, + ], + dataViewId: 'security-solution-default', + description: '', + eqlOptions: { + eventCategoryField: 'event.category', + tiebreakerField: '', + timestampField: '@timestamp', + query: '', + size: 100, + }, + eventType: 'all', + excludedRowRendererIds: [ + 'alert', + 'alerts', + 'auditd', + 'auditd_file', + 'library', + 'netflow', + 'plain', + 'registry', + 'suricata', + 'system', + 'system_dns', + 'system_endgame_process', + 'system_file', + 'system_fim', + 'system_security_event', + 'system_socket', + 'threat_match', + 'zeek', + ], + favorite: [], + filters: [], + indexNames: ['.alerts-security.alerts-default', 'auditbeat-*', 'filebeat-*', 'packetbeat-*'], + kqlMode: 'filter', + kqlQuery: { + filterQuery: { + kuery: { + kind: 'kuery', + expression: '*', }, - savedQueryId: null, - created: 1723625359467, - createdBy: 'elastic', - updated: 1723625359988, - updatedBy: 'elastic', - timelineType: 'template', - status: 'active', - sort: [ - { - columnId: '@timestamp', - columnType: 'date', - sortDirection: 'desc', - esTypes: ['date'], - }, - ], - savedSearchId: null, - eventIdToNoteIds: [], - noteIds: [], - notes: [], - pinnedEventIds: [], - pinnedEventsSaveObject: [], + serializedQuery: '{"query_string":{"query":"*"}}', }, }, + title: 'Named Template', + templateTimelineId: 'c755cda6-8a65-4ec2-b6ff-35a5356de8b9', + templateTimelineVersion: 1, + dateRange: { + start: '2024-08-13T22:00:00.000Z', + end: '2024-08-14T21:59:59.999Z', + }, + savedQueryId: null, + created: 1723625359467, + createdBy: 'elastic', + updated: 1723625359988, + updatedBy: 'elastic', + timelineType: 'template', + status: 'active', + sort: [ + { + columnId: '@timestamp', + columnType: 'date', + sortDirection: 'desc', + esTypes: ['date'], + }, + ], + savedSearchId: null, + eventIdToNoteIds: [], + noteIds: [], + notes: [], + pinnedEventIds: [], + pinnedEventsSaveObject: [], }; const props = { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.test.ts index 57a0aa43cdde..2a07746a6d67 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.test.ts @@ -16,6 +16,7 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; import { mockTimeline } from '../../../../../server/lib/timeline/__mocks__/create_timelines'; import type { TimelineModel } from '../../../..'; +import type { ResolveTimelineResponse } from '../../../../../common/api/timeline'; jest.mock('../../../../common/hooks/use_experimental_features'); jest.mock('../../../../common/utils/global_query_string/helpers'); @@ -45,53 +46,52 @@ jest.mock('react-redux', () => { const timelineId = 'eb2781c0-1df5-11eb-8589-2f13958b79f7'; -const selectedTimeline = { - data: { - timeline: { - ...mockTimeline, - id: timelineId, - savedObjectId: timelineId, - indexNames: ['awesome-*'], - dataViewId: 'custom-data-view-id', - kqlQuery: { - filterQuery: { - serializedQuery: - '{"bool":{"filter":[{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}},{"bool":{"should":[{"exists":{"field":"user.name"}}],"minimum_should_match":1}}]}}', - kuery: { - expression: 'host.name:* AND user.name:*', - kind: 'kuery', - }, +const selectedTimeline: ResolveTimelineResponse = { + outcome: 'exactMatch', + timeline: { + ...mockTimeline, + savedObjectId: timelineId, + version: 'wedwed', + indexNames: ['awesome-*'], + dataViewId: 'custom-data-view-id', + kqlQuery: { + filterQuery: { + serializedQuery: + '{"bool":{"filter":[{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}},{"bool":{"should":[{"exists":{"field":"user.name"}}],"minimum_should_match":1}}]}}', + kuery: { + expression: 'host.name:* AND user.name:*', + kind: 'kuery', }, }, - dataProviders: [ - { - excluded: false, - and: [], - kqlQuery: '', - name: 'Stephs-MBP.lan', - queryMatch: { - field: 'host.name', - value: 'Stephs-MBP.lan', - operator: ':', - }, - id: 'draggable-badge-default-draggable-process_stopped-timeline-1-NH9UwoMB2HTqQ3G4wUFM-host_name-Stephs-MBP_lan', - enabled: true, + }, + dataProviders: [ + { + excluded: false, + and: [], + kqlQuery: '', + name: 'Stephs-MBP.lan', + queryMatch: { + field: 'host.name', + value: 'Stephs-MBP.lan', + operator: ':', }, - { - excluded: false, - and: [], - kqlQuery: '', - name: '--lang=en-US', - queryMatch: { - field: 'process.args', - value: '--lang=en-US', - operator: ':', - }, - id: 'draggable-badge-default-draggable-process_started-timeline-1-args-5---lang=en-US-MH9TwoMB2HTqQ3G4_UH--process_args---lang=en-US', - enabled: true, + id: 'draggable-badge-default-draggable-process_stopped-timeline-1-NH9UwoMB2HTqQ3G4wUFM-host_name-Stephs-MBP_lan', + enabled: true, + }, + { + excluded: false, + and: [], + kqlQuery: '', + name: '--lang=en-US', + queryMatch: { + field: 'process.args', + value: '--lang=en-US', + operator: ':', }, - ], - }, + id: 'draggable-badge-default-draggable-process_started-timeline-1-args-5---lang=en-US-MH9TwoMB2HTqQ3G4_UH--process_args---lang=en-US', + enabled: true, + }, + ], }, }; @@ -157,8 +157,8 @@ describe('useRuleFromTimeline', () => { type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', payload: { id: 'timeline', - selectedDataViewId: selectedTimeline.data.timeline.dataViewId, - selectedPatterns: selectedTimeline.data.timeline.indexNames, + selectedDataViewId: selectedTimeline.timeline.dataViewId, + selectedPatterns: selectedTimeline.timeline.indexNames, }, }); }); @@ -220,16 +220,15 @@ describe('useRuleFromTimeline', () => { query: 'find it EQL', size: 100, }; - const eqlTimeline = { - data: { - timeline: { - ...mockTimeline, - id: timelineId, - savedObjectId: timelineId, - indexNames: ['awesome-*'], - dataViewId: 'custom-data-view-id', - eqlOptions, - }, + const eqlTimeline: ResolveTimelineResponse = { + outcome: 'exactMatch', + timeline: { + ...mockTimeline, + version: '123', + savedObjectId: timelineId, + indexNames: ['awesome-*'], + dataViewId: 'custom-data-view-id', + eqlOptions, }, }; (resolveTimeline as jest.Mock).mockResolvedValue(eqlTimeline); @@ -256,7 +255,7 @@ describe('useRuleFromTimeline', () => { const { result } = renderHook(() => useRuleFromTimeline(setRuleQuery)); expect(result.current.loading).toEqual(false); await act(async () => { - result.current.onOpenTimeline(selectedTimeline.data.timeline as unknown as TimelineModel); + result.current.onOpenTimeline(selectedTimeline.timeline as unknown as TimelineModel); }); // not loading anything as an external call to onOpenTimeline provides the timeline @@ -307,7 +306,7 @@ describe('useRuleFromTimeline', () => { const { result } = renderHook(() => useRuleFromTimeline(setRuleQuery)); expect(result.current.loading).toEqual(false); const tl = { - ...selectedTimeline.data.timeline, + ...selectedTimeline.timeline, dataProviders: [ { property: 'bad', diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx index 27c2699cdf19..b72c043ffa40 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash/fp'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { i18n } from '@kbn/i18n'; -import type { EqlOptionsSelected } from '@kbn/timelines-plugin/common'; +import type { EqlOptions } from '@kbn/timelines-plugin/common'; import { convertKueryToElasticSearchQuery } from '../../../../common/lib/kuery'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useSourcererDataView } from '../../../../sourcerer/containers'; @@ -37,7 +37,7 @@ export type SetRuleQuery = ({ }: { index: string[]; queryBar: FieldValueQueryBar; - eqlOptions?: EqlOptionsSelected; + eqlOptions?: EqlOptions; }) => void; export const useRuleFromTimeline = (setRuleQuery: SetRuleQuery): RuleFromTimeline => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index cf2c3264f315..2f0cf92737f4 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -50,7 +50,7 @@ import type { RequiredFieldInput, } from '../../../../../common/api/detection_engine/model/rule_schema'; import type { SortOrder } from '../../../../../common/api/detection_engine'; -import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../../common/search_strategy'; import type { RuleResponseAction, ResponseAction, @@ -164,7 +164,7 @@ export interface DefineStepRule { threatIndex: ThreatIndex; threatQueryBar: FieldValueQueryBar; threatMapping: ThreatMapping; - eqlOptions: EqlOptionsSelected; + eqlOptions: EqlOptions; dataSourceType: DataSourceType; newTermsFields: string[]; historyWindowSize: string; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/index.tsx index 6f728d765378..1073be375b09 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_details_flyout/index.tsx @@ -44,6 +44,6 @@ export const getInsightsInputTab = ({ defaultMessage="Insights" /> ), - content: <InsightsTabCsp name={name} fieldName={fieldName} />, + content: <InsightsTabCsp value={name} field={fieldName} />, }; }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx index 494cfd5c16b2..9cc773df320b 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_summary_flyout/risk_summary.test.tsx @@ -17,6 +17,7 @@ import type { LensAttributes, VisualizationEmbeddableProps, } from '../../../common/components/visualization_actions/types'; +import type { Query } from '@kbn/es-query'; const mockVisualizationEmbeddable = jest .fn() @@ -159,7 +160,7 @@ describe('FlyoutRiskSummary', () => { ); const firstColumn = Object.values(datasourceLayers[0].columns)[0]; - expect(lensAttributes.state.query.query).toEqual('host.name: test'); + expect((lensAttributes.state.query as Query).query).toEqual('host.name: test'); expect(firstColumn).toEqual( expect.objectContaining({ sourceField: 'host.risk.calculated_score_norm', @@ -230,7 +231,7 @@ describe('FlyoutRiskSummary', () => { ); const firstColumn = Object.values(datasourceLayers[0].columns)[0]; - expect(lensAttributes.state.query.query).toEqual('user.name: test'); + expect((lensAttributes.state.query as Query).query).toEqual('user.name: test'); expect(firstColumn).toEqual( expect.objectContaining({ sourceField: 'user.risk.calculated_score_norm', diff --git a/x-pack/plugins/security_solution/public/entity_analytics/lens_attributes/risk_score_summary.test.ts b/x-pack/plugins/security_solution/public/entity_analytics/lens_attributes/risk_score_summary.test.ts index 2a00cb169197..ac7028fa9028 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/lens_attributes/risk_score_summary.test.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/lens_attributes/risk_score_summary.test.ts @@ -12,6 +12,7 @@ import { RiskSeverity } from '../../../common/search_strategy'; import type { MetricVisualizationState } from '@kbn/lens-plugin/public'; import { wrapper } from '../../common/components/visualization_actions/mocks'; import { useLensAttributes } from '../../common/components/visualization_actions/use_lens_attributes'; +import type { Query } from '@kbn/es-query'; jest.mock('../../sourcerer/containers', () => ({ useSourcererDataView: jest.fn().mockReturnValue({ @@ -78,6 +79,6 @@ describe('getRiskScoreSummaryAttributes', () => { { wrapper } ); - expect(result?.current?.state.query.query).toBe(query); + expect((result?.current?.state.query as Query).query).toBe(query); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.ts index c4910f5daa42..9f9d031cc0bf 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_fetch_alerts.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { useKibana } from '../../../../common/lib/kibana'; import { createFindAlerts } from '../services/find_alerts'; import { useFetchAlerts, type UseAlertsQueryParams } from './use_fetch_alerts'; @@ -41,15 +41,14 @@ describe('useFetchAlerts', () => { sort: [{ '@timestamp': 'desc' }], }; - const { result, waitFor } = renderHook(() => useFetchAlerts(params), { + const { result } = renderHook(() => useFetchAlerts(params), { wrapper: createReactQueryWrapper(), }); expect(result.current.loading).toBe(true); - await waitFor(() => !result.current.loading); + await waitFor(() => expect(result.current.loading).toBe(false)); - expect(result.current.loading).toBe(false); expect(result.current.error).toBe(false); expect(result.current.totalItemCount).toBe(10); expect(result.current.data).toEqual(['alert1', 'alert2', 'alert3']); @@ -70,13 +69,13 @@ describe('useFetchAlerts', () => { sort: [{ '@timestamp': 'desc' }], }; - const { result, waitFor } = renderHook(() => useFetchAlerts(params), { + const { result } = renderHook(() => useFetchAlerts(params), { wrapper: createReactQueryWrapper(), }); expect(result.current.loading).toBe(true); - await waitFor(() => !result.current.loading); + await waitFor(() => expect(result.current.loading).toBe(false)); expect(result.current.loading).toBe(false); expect(result.current.error).toBe(true); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_response_actions_view.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_response_actions_view.test.ts index cafac9f3a0b9..5e6469e44a04 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_response_actions_view.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_response_actions_view.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useResponseActionsView } from './use_response_actions_view'; import { mockSearchHit } from '../../shared/mocks/mock_search_hit'; import { mockDataAsNestedObject } from '../../shared/mocks/mock_data_as_nested_object'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.test.ts index 17e564a1eb8a..75b76adae787 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.test.ts @@ -6,7 +6,7 @@ */ import { useThreatIntelligenceDetails } from './use_threat_intelligence_details'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { SecurityPageName } from '@kbn/deeplinks-security'; import { useTimelineEventsDetails } from '../../../../timelines/containers/details'; import { useSourcererDataView } from '../../../../sourcerer/containers'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_accordion_state.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_accordion_state.test.ts index ac3c9c8a6be0..80ac41b539fa 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_accordion_state.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_accordion_state.test.ts @@ -7,14 +7,14 @@ import type { ToggleReducerAction, UseAccordionStateValue } from './use_accordion_state'; import { useAccordionState, toggleReducer } from './use_accordion_state'; -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import { FLYOUT_STORAGE_KEYS } from '../../shared/constants/local_storage'; const mockSet = jest.fn(); describe('useAccordionState', () => { - let hookResult: RenderHookResult<boolean, UseAccordionStateValue>; + let hookResult: RenderHookResult<UseAccordionStateValue, boolean>; it('should return initial value', () => { hookResult = renderHook((props: boolean) => useAccordionState(props), { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx index 9ca0d9fd18e7..1871c4aad5b8 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_assistant.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseAssistantParams, UseAssistantResult } from './use_assistant'; import { useAssistant } from './use_assistant'; import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser'; @@ -25,7 +25,7 @@ const renderUseAssistant = () => }); describe('useAssistant', () => { - let hookResult: RenderHookResult<UseAssistantParams, UseAssistantResult>; + let hookResult: RenderHookResult<UseAssistantResult, UseAssistantParams>; it(`should return showAssistant true and a value for promptContextId`, () => { jest.mocked(useAssistantAvailability).mockReturnValue({ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_expand_section.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_expand_section.test.ts index 998f56312b0f..26c9b36a728a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_expand_section.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_expand_section.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseExpandSectionParams } from './use_expand_section'; import { useExpandSection } from './use_expand_section'; import { useKibana } from '../../../../common/lib/kibana'; @@ -14,7 +14,7 @@ import { useKibana } from '../../../../common/lib/kibana'; jest.mock('../../../../common/lib/kibana'); describe('useExpandSection', () => { - let hookResult: RenderHookResult<UseExpandSectionParams, boolean>; + let hookResult: RenderHookResult<boolean, UseExpandSectionParams>; it('should return default value if nothing in localStorage', () => { (useKibana as jest.Mock).mockReturnValue({ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.test.tsx index e778552dff61..74672356f076 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseThreatIntelligenceParams, UseThreatIntelligenceResult, @@ -41,7 +41,7 @@ const dataFormattedForFieldBrowser = [ ]; describe('useFetchThreatIntelligence', () => { - let hookResult: RenderHookResult<UseThreatIntelligenceParams, UseThreatIntelligenceResult>; + let hookResult: RenderHookResult<UseThreatIntelligenceResult, UseThreatIntelligenceParams>; it('return render 1 match detected and 1 field enriched', () => { (useInvestigationTimeEnrichment as jest.Mock).mockReturnValue({ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_flyout_is_expandable.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_flyout_is_expandable.test.tsx index a67bb675a373..3611ab628264 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_flyout_is_expandable.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_flyout_is_expandable.test.tsx @@ -6,7 +6,7 @@ */ import { useFlyoutIsExpandable } from './use_flyout_is_expandable'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_get_flyout_link.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_get_flyout_link.test.tsx index 2db21334e59f..71b24ab891e7 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_get_flyout_link.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_get_flyout_link.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useGetFlyoutLink } from './use_get_flyout_link'; import { useGetAppUrl } from '@kbn/security-solution-navigation'; import { ALERT_DETAILS_REDIRECT_PATH } from '../../../../../common/constants'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.test.tsx index d12154a390ab..7fa0741a8511 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_graph_preview.test.tsx @@ -5,15 +5,15 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseGraphPreviewParams, UseGraphPreviewResult } from './use_graph_preview'; import { useGraphPreview } from './use_graph_preview'; import type { GetFieldsData } from '../../shared/hooks/use_get_fields_data'; import { mockFieldData } from '../../shared/mocks/mock_get_fields_data'; describe('useGraphPreview', () => { - let hookResult: RenderHookResult<UseGraphPreviewParams, UseGraphPreviewResult>; + let hookResult: RenderHookResult<UseGraphPreviewResult, UseGraphPreviewParams>; it(`should return false when missing actor`, () => { const getFieldsData: GetFieldsData = (field: string) => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_process_data.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_process_data.test.tsx index 31eb78975d19..6f6085c13ee6 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_process_data.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_process_data.test.tsx @@ -6,7 +6,7 @@ */ import { getUserDisplayName, useProcessData } from './use_process_data'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { FC, PropsWithChildren } from 'react'; import { DocumentDetailsContext } from '../../shared/context'; import React from 'react'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.test.tsx index 0f6f23377262..4e2e19c6b54f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_session_preview.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseSessionPreviewParams } from './use_session_preview'; import { useSessionPreview } from './use_session_preview'; import type { SessionViewConfig } from '@kbn/securitysolution-data-table/common/types'; @@ -15,7 +15,7 @@ import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_f import { mockFieldData, mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data'; describe('useSessionPreview', () => { - let hookResult: RenderHookResult<UseSessionPreviewParams, SessionViewConfig | null>; + let hookResult: RenderHookResult<SessionViewConfig | null, UseSessionPreviewParams>; it(`should return a session view config object if alert ancestor index is available`, () => { const getFieldsData: GetFieldsData = (field: string) => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_tabs.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_tabs.test.tsx index 87a23a06b7ce..a8a7f61bac26 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_tabs.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseTabsParams, UseTabsResult } from './use_tabs'; import { allThreeTabs, twoTabs, useTabs } from './use_tabs'; @@ -26,7 +26,7 @@ jest.mock('../../../../common/lib/kibana', () => { }); describe('useTabs', () => { - let hookResult: RenderHookResult<UseTabsParams, UseTabsResult>; + let hookResult: RenderHookResult<UseTabsResult, UseTabsParams>; it('should return 3 tabs to render and the one from path as selected', () => { const initialProps: UseTabsParams = { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.test.tsx index 3c31720b53f9..8d9cbd958e43 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_document_analyzer_schema.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import { useQuery } from '@tanstack/react-query'; import type { UseAlertDocumentAnalyzerSchemaParams, @@ -20,8 +20,8 @@ jest.mock('@tanstack/react-query'); describe('useAlertPrevalenceFromProcessTree', () => { let hookResult: RenderHookResult< - UseAlertDocumentAnalyzerSchemaParams, - UseAlertDocumentAnalyzerSchemaResult + UseAlertDocumentAnalyzerSchemaResult, + UseAlertDocumentAnalyzerSchemaParams >; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.test.tsx index 231e0e541944..27b83255443b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import { ALERT_PREVALENCE_AGG, useAlertPrevalence } from './use_alert_prevalence'; import type { UseAlertPrevalenceParams, UserAlertPrevalenceResult } from './use_alert_prevalence'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; @@ -18,7 +18,7 @@ jest.mock('../../../../common/hooks/use_selector'); jest.mock('../../../../detections/containers/detection_engine/alerts/use_query'); describe('useAlertPrevalence', () => { - let hookResult: RenderHookResult<UseAlertPrevalenceParams, UserAlertPrevalenceResult>; + let hookResult: RenderHookResult<UserAlertPrevalenceResult, UseAlertPrevalenceParams>; beforeEach(() => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.test.tsx index 94b7cfa62350..668f233e6571 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_alert_prevalence_from_process_tree.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseAlertPrevalenceFromProcessTreeParams, UserAlertPrevalenceFromProcessTreeResult, @@ -25,8 +25,8 @@ jest.mock('@tanstack/react-query'); describe('useAlertPrevalenceFromProcessTree', () => { let hookResult: RenderHookResult< - UseAlertPrevalenceFromProcessTreeParams, - UserAlertPrevalenceFromProcessTreeResult + UserAlertPrevalenceFromProcessTreeResult, + UseAlertPrevalenceFromProcessTreeParams >; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_basic_data_from_details_data.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_basic_data_from_details_data.test.tsx index b4cd7c35824a..2894cb0d2127 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_basic_data_from_details_data.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_basic_data_from_details_data.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useBasicDataFromDetailsData } from './use_basic_data_from_details_data'; import { mockDataFormattedForFieldBrowser } from '../mocks/mock_data_formatted_for_field_browser'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_event_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_event_details.test.tsx index efa56c9e6572..1d9181b8514b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_event_details.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseEventDetailsParams, UseEventDetailsResult } from './use_event_details'; import { getAlertIndexAlias, useEventDetails } from './use_event_details'; import { useSpaceId } from '../../../../common/hooks/use_space_id'; @@ -45,7 +45,7 @@ describe('getAlertIndexAlias', () => { }); describe('useEventDetails', () => { - let hookResult: RenderHookResult<UseEventDetailsParams, UseEventDetailsResult>; + let hookResult: RenderHookResult<UseEventDetailsResult, UseEventDetailsParams>; it('should return all properties', () => { jest.mocked(useSpaceId).mockReturnValue('default'); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx index 4d65339c6b41..7630c260f4c2 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_ancestry.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseFetchRelatedAlertsByAncestryParams, UseFetchRelatedAlertsByAncestryResult, @@ -22,8 +22,8 @@ const scopeId = 'scopeId'; describe('useFetchRelatedAlertsByAncestry', () => { let hookResult: RenderHookResult< - UseFetchRelatedAlertsByAncestryParams, - UseFetchRelatedAlertsByAncestryResult + UseFetchRelatedAlertsByAncestryResult, + UseFetchRelatedAlertsByAncestryParams >; it('should return loading true while data is loading', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx index ff74774068ad..9da01a94d51d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_same_source_event.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseFetchRelatedAlertsBySameSourceEventParams, UseFetchRelatedAlertsBySameSourceEventResult, @@ -21,8 +21,8 @@ const scopeId = 'scopeId'; describe('useFetchRelatedAlertsBySameSourceEvent', () => { let hookResult: RenderHookResult< - UseFetchRelatedAlertsBySameSourceEventParams, - UseFetchRelatedAlertsBySameSourceEventResult + UseFetchRelatedAlertsBySameSourceEventResult, + UseFetchRelatedAlertsBySameSourceEventParams >; it('should return loading true while data is loading', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.test.tsx index b38ef44178f9..27f030cc11e2 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_alerts_by_session.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseFetchRelatedAlertsBySessionParams, @@ -22,8 +22,8 @@ const scopeId = 'scopeId'; describe('useFetchRelatedAlertsBySession', () => { let hookResult: RenderHookResult< - UseFetchRelatedAlertsBySessionParams, - UseFetchRelatedAlertsBySessionResult + UseFetchRelatedAlertsBySessionResult, + UseFetchRelatedAlertsBySessionParams >; it('should return loading true while data is loading', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.ts index 6ebdc2bc4b7c..9e494ef8e05d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_fetch_related_cases.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { createReactQueryWrapper } from '../../../../common/mock'; import { useFetchRelatedCases } from './use_fetch_related_cases'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_get_fields_data.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_get_fields_data.test.tsx index fcf370b4bca1..a91d14c7a84e 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_get_fields_data.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_get_fields_data.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import { mockSearchHit } from '../mocks/mock_search_hit'; import type { UseGetFieldsDataParams, UseGetFieldsDataResult } from './use_get_fields_data'; import { useGetFieldsData } from './use_get_fields_data'; @@ -17,7 +17,7 @@ const fieldsData = { }; describe('useGetFieldsData', () => { - let hookResult: RenderHookResult<UseGetFieldsDataParams, UseGetFieldsDataResult>; + let hookResult: RenderHookResult<UseGetFieldsDataResult, UseGetFieldsDataParams>; it('should return the value for a field', () => { hookResult = renderHook(() => useGetFieldsData({ fieldsData })); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.test.tsx index 6eb8c242c79f..6bffb7c58ae3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { mockDataFormattedForFieldBrowser, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.test.ts index 0e1cdbc845b3..c79e1ec07ce1 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useInvestigationTimeEnrichment } from './use_investigation_enrichment'; import { DEFAULT_EVENT_ENRICHMENT_FROM, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_guide.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_guide.test.ts index f7e3a40e60c4..39522df67548 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_guide.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_guide.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseInvestigationGuideParams, UseInvestigationGuideResult, @@ -22,7 +22,7 @@ jest.mock('../../../../detection_engine/rule_management/logic/use_rule_with_fall const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; describe('useInvestigationGuide', () => { - let hookResult: RenderHookResult<UseInvestigationGuideParams, UseInvestigationGuideResult>; + let hookResult: RenderHookResult<UseInvestigationGuideResult, UseInvestigationGuideParams>; it('should return loading true', () => { (useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ ruleId: 'ruleId' }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_analyzer.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_analyzer.test.tsx index 6f578c7cdb95..334bf5f08721 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_analyzer.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_analyzer.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { useNavigateToAnalyzer } from './use_navigate_to_analyzer'; import { mockFlyoutApi } from '../mocks/mock_flyout_context'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_session_view.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_session_view.test.tsx index c0f85e07377d..ac624ce11ce5 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_session_view.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_navigate_to_session_view.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { useNavigateToSessionView } from './use_navigate_to_session_view'; import { mockFlyoutApi } from '../mocks/mock_flyout_context'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_prevalence.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_prevalence.test.tsx index a9c12adfc84c..d4acc0eb0c7f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_prevalence.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_prevalence.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { usePrevalence } from './use_prevalence'; import { mockDataFormattedForFieldBrowser } from '../mocks/mock_data_formatted_for_field_browser'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_rule_details_link.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_rule_details_link.test.ts index f77b82af8006..ca748433c193 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_rule_details_link.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_rule_details_link.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseRuleDetailsLinkParams } from './use_rule_details_link'; import { useRuleDetailsLink } from './use_rule_details_link'; @@ -24,7 +24,7 @@ jest.mock('../../../../common/components/link_to', () => ({ })); describe('useRuleDetailsLink', () => { - let hookResult: RenderHookResult<UseRuleDetailsLinkParams, string | null>; + let hookResult: RenderHookResult<string | null, UseRuleDetailsLinkParams>; it('should return null if the ruleId prop is null', () => { const initialProps: UseRuleDetailsLinkParams = { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx index f4be536d5419..938930496d80 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_ancestry.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseShowRelatedAlertsByAncestryParams, UseShowRelatedAlertsByAncestryResult, @@ -36,8 +36,8 @@ const dataAsNestedObject = mockDataAsNestedObject; describe('useShowRelatedAlertsByAncestry', () => { let hookResult: RenderHookResult< - UseShowRelatedAlertsByAncestryParams, - UseShowRelatedAlertsByAncestryResult + UseShowRelatedAlertsByAncestryResult, + UseShowRelatedAlertsByAncestryParams >; it('should return false if Process Entity Info is not available', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_same_source_event.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_same_source_event.test.tsx index dfbfeeccc655..71f681c64e80 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_same_source_event.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_same_source_event.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { ShowRelatedAlertsBySameSourceEventParams, @@ -18,8 +18,8 @@ const eventId = 'eventId'; describe('useShowRelatedAlertsBySameSourceEvent', () => { let hookResult: RenderHookResult< - ShowRelatedAlertsBySameSourceEventParams, - ShowRelatedAlertsBySameSourceEventResult + ShowRelatedAlertsBySameSourceEventResult, + ShowRelatedAlertsBySameSourceEventParams >; it('should return eventId if getFieldsData returns null', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_session.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_session.test.tsx index 32595a4c27c6..92d9c076ab64 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_session.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_alerts_by_session.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { UseShowRelatedAlertsBySessionParams, @@ -16,8 +16,8 @@ import { useShowRelatedAlertsBySession } from './use_show_related_alerts_by_sess describe('useShowRelatedAlertsBySession', () => { let hookResult: RenderHookResult< - UseShowRelatedAlertsBySessionParams, - UseShowRelatedAlertsBySessionResult + UseShowRelatedAlertsBySessionResult, + UseShowRelatedAlertsBySessionParams >; it('should return false if getFieldsData returns null', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_cases.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_cases.test.tsx index cfa9b87d8fcf..6245e480a35d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_cases.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_related_cases.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useKibana as mockUseKibana } from '../../../../common/lib/kibana/__mocks__'; import { useShowRelatedCases } from './use_show_related_cases'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_suppressed_alerts.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_suppressed_alerts.test.tsx index 622f37a0997c..b10e154fed3d 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_suppressed_alerts.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_show_suppressed_alerts.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import type { ShowSuppressedAlertsParams, ShowSuppressedAlertsResult, @@ -14,7 +14,7 @@ import type { import { useShowSuppressedAlerts } from './use_show_suppressed_alerts'; describe('useShowSuppressedAlerts', () => { - let hookResult: RenderHookResult<ShowSuppressedAlertsParams, ShowSuppressedAlertsResult>; + let hookResult: RenderHookResult<ShowSuppressedAlertsResult, ShowSuppressedAlertsParams>; it('should return false if getFieldsData returns null', () => { const getFieldsData = () => null; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_which_flyout.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_which_flyout.test.tsx index 76277b8da889..033fb53a5c02 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_which_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_which_flyout.test.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import { useWhichFlyout } from './use_which_flyout'; import { Flyouts } from '../constants/flyouts'; describe('useWhichFlyout', () => { - let hookResult: RenderHookResult<{}, string | null>; + let hookResult: RenderHookResult<string | null, unknown>; beforeEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx index 2f5bd6510430..fefacf3e7fc1 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx @@ -7,11 +7,11 @@ import { groupBy, isObject } from 'lodash'; import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { getDataFromFieldsHits } from '@kbn/timelines-plugin/common'; import { i18n } from '@kbn/i18n'; import type { ThreatDetailsRow } from '../../left/components/threat_details_view_enrichment_accordion'; import type { CtiEnrichment, EventFields } from '../../../../../common/search_strategy'; import { isValidEventField } from '../../../../../common/search_strategy'; -import { getDataFromFieldsHits } from '../../../../../common/utils/field_formatters'; import { DEFAULT_INDICATOR_SOURCE_PATH, ENRICHMENT_DESTINATION_PATH, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.tsx index 2e7c14fc3802..9c2ce61dea7f 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/content.tsx @@ -64,7 +64,7 @@ export const HostPanelContent = ({ entity={{ name: hostName, type: 'host' }} onChange={onAssetCriticalityChange} /> - <EntityInsight name={hostName} fieldName={'host.name'} isPreviewMode={isPreviewMode} /> + <EntityInsight value={hostName} field={'host.name'} isPreviewMode={isPreviewMode} /> <ObservedEntity observedData={observedHost} contextID={contextID} diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx index 7bffaa010ded..64c8e74d6271 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx @@ -9,12 +9,10 @@ import React, { useCallback, useMemo } from 'react'; import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; -import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; -import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; -import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; -import { sum } from 'lodash'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; +import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities'; +import { useNonClosedAlerts } from '../../../cloud_security_posture/hooks/use_non_closed_alerts'; import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types'; -import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status'; import { useRefetchQueryById } from '../../../entity_analytics/api/hooks/use_refetch_query_by_id'; import { RISK_INPUTS_TAB_QUERY_ID } from '../../../entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab'; import type { Refetch } from '../../../common/types'; @@ -37,7 +35,6 @@ import { useObservedHost } from './hooks/use_observed_host'; import { HostDetailsPanelKey } from '../host_details_left'; import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header'; import { HostPreviewPanelFooter } from '../host_preview/footer'; -import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index'; import { EntityEventTypes } from '../../../common/lib/telemetry'; export interface HostPanelProps extends Record<string, unknown> { @@ -101,43 +98,18 @@ export const HostPanel = ({ { onSuccess: refetchRiskScore } ); - const { data } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', hostName), - sort: [], - enabled: true, - pageSize: 1, - ignore_unavailable: true, - }); - - const passedFindings = data?.count.passed || 0; - const failedFindings = data?.count.failed || 0; - - const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; - - const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({ - query: buildEntityFlyoutPreviewQuery('host.name', hostName), - sort: [], - enabled: true, - pageSize: 1, - }); + const { hasMisconfigurationFindings } = useHasMisconfigurations('host.name', hostName); - const hasVulnerabilitiesFindings = sum(Object.values(vulnerabilitiesData?.count || {})) > 0; + const { hasVulnerabilitiesFindings } = useHasVulnerabilities('host.name', hostName); - const { signalIndexName } = useSignalIndex(); - - const entityFilter = useMemo(() => ({ field: 'host.name', value: hostName }), [hostName]); - - const { items: alertsData } = useAlertsByStatus({ - entityFilter, - signalIndexName, - queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}HOST_NAME_RIGHT`, + const { hasNonClosedAlerts } = useNonClosedAlerts({ + field: 'host.name', + value: hostName, to, from, + queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}HOST_NAME_RIGHT`, }); - const hasNonClosedAlerts = - (alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0; - useQueryInspector({ deleteQuery, inspect: inspectRiskScore, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx index 0dbc1faa5cb4..08295038a1bd 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/content.tsx @@ -73,7 +73,7 @@ export const UserPanelContent = ({ entity={{ name: userName, type: 'user' }} onChange={onAssetCriticalityChange} /> - <EntityInsight name={userName} fieldName={'user.name'} isPreviewMode={isPreviewMode} /> + <EntityInsight value={userName} field={'user.name'} isPreviewMode={isPreviewMode} /> <ObservedEntity observedData={observedUser} contextID={contextID} diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx index 07762ed9aea0..1a97c691f373 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx @@ -8,8 +8,8 @@ import React, { useCallback, useMemo } from 'react'; import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; -import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; -import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; +import { useNonClosedAlerts } from '../../../cloud_security_posture/hooks/use_non_closed_alerts'; import { useRefetchQueryById } from '../../../entity_analytics/api/hooks/use_refetch_query_by_id'; import type { Refetch } from '../../../common/types'; import { RISK_INPUTS_TAB_QUERY_ID } from '../../../entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab'; @@ -33,8 +33,6 @@ import { UserDetailsPanelKey } from '../user_details_left'; import { useObservedUser } from './hooks/use_observed_user'; import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header'; import { UserPreviewPanelFooter } from '../user_preview/footer'; -import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index'; -import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status'; import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types'; import { EntityEventTypes } from '../../../common/lib/telemetry'; @@ -102,34 +100,16 @@ export const UserPanel = ({ { onSuccess: refetchRiskScore } ); - const { data } = useMisconfigurationPreview({ - query: buildEntityFlyoutPreviewQuery('user.name', userName), - sort: [], - enabled: true, - pageSize: 1, - ignore_unavailable: true, - }); - - const passedFindings = data?.count.passed || 0; - const failedFindings = data?.count.failed || 0; - - const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; - - const { signalIndexName } = useSignalIndex(); + const { hasMisconfigurationFindings } = useHasMisconfigurations('user.name', userName); - const entityFilter = useMemo(() => ({ field: 'user.name', value: userName }), [userName]); - - const { items: alertsData } = useAlertsByStatus({ - entityFilter, - signalIndexName, - queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}USER_NAME_RIGHT`, + const { hasNonClosedAlerts } = useNonClosedAlerts({ + field: 'user.name', + value: userName, to, from, + queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}USER_NAME_RIGHT`, }); - const hasNonClosedAlerts = - (alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0; - useQueryInspector({ deleteQuery, inspect, diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_on_expandable_flyout_close.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_on_expandable_flyout_close.test.tsx index 308c1bcfc6cf..2a4e5576e24b 100644 --- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_on_expandable_flyout_close.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_on_expandable_flyout_close.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useWhichFlyout } from '../../document_details/shared/hooks/use_which_flyout'; import { useOnExpandableFlyoutClose } from './use_on_expandable_flyout_close'; import { Flyouts } from '../../document_details/shared/constants/flyouts'; diff --git a/x-pack/plugins/security_solution/public/kubernetes/routes.tsx b/x-pack/plugins/security_solution/public/kubernetes/routes.tsx index 67bd4a9fcad8..7520de8cffa5 100644 --- a/x-pack/plugins/security_solution/public/kubernetes/routes.tsx +++ b/x-pack/plugins/security_solution/public/kubernetes/routes.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; +import { allowedExperimentalValues } from '../../common'; import { KubernetesContainer } from './pages'; import type { SecuritySubPluginRoutes } from '../app/types'; @@ -24,7 +25,7 @@ export const KubernetesRoutes = () => ( export const routes: SecuritySubPluginRoutes = [ { - path: KUBERNETES_PATH, + path: allowedExperimentalValues.kubernetesEnabled ? KUBERNETES_PATH : [], component: KubernetesRoutes, }, ]; diff --git a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx index 8bbba3885a2a..1be423c98839 100644 --- a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx +++ b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx @@ -29,6 +29,7 @@ import { EntityAnalytics } from './entity_analytics'; import { Assets } from './assets'; import { Investigations } from './investigations'; import { MachineLearning } from './machine_learning'; +import { SiemMigrations } from './siem_migrations'; /** * The classes used to instantiate the sub plugins. These are grouped into a single object for the sake of bundling them in a single dynamic import. @@ -53,5 +54,6 @@ const subPluginClasses = { Assets, Investigations, MachineLearning, + SiemMigrations, }; export { subPluginClasses }; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac.cy.ts index 64779bb2ba27..b108cb8985b8 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac.cy.ts @@ -9,7 +9,8 @@ import { closeAllToasts } from '../../tasks/toasts'; import { login, ROLE } from '../../tasks/login'; import { loadPage } from '../../tasks/common'; -describe('When defining a kibana role for Endpoint security access', { tags: '@ess' }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/200967 +describe.skip('When defining a kibana role for Endpoint security access', { tags: '@ess' }, () => { const getAllSubFeatureRows = (): Cypress.Chainable<JQuery<HTMLElement>> => { return cy .get('#featurePrivilegeControls_siem') diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac_with_space_awareness.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac_with_space_awareness.cy.ts index 424b3fc954c5..d2a86e7899ae 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac_with_space_awareness.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/rbac/endpoint_role_rbac_with_space_awareness.cy.ts @@ -23,7 +23,8 @@ import { setSecuritySolutionEndpointGroupPrivilege, } from '../../screens/stack_management/role_page'; -describe( +// Failing: See https://github.com/elastic/kibana/issues/200962 +describe.skip( 'When defining a kibana role for Endpoint security access with space awareness enabled', { // TODO:PR Remove `'@skipInServerlessMKI` once PR merges to `main` diff --git a/x-pack/plugins/security_solution/public/notes/api/api.test.ts b/x-pack/plugins/security_solution/public/notes/api/api.test.ts index bc53b7bd78ac..1b2250a11965 100644 --- a/x-pack/plugins/security_solution/public/notes/api/api.test.ts +++ b/x-pack/plugins/security_solution/public/notes/api/api.test.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { PersistNoteRouteResponse } from '../../../common/api/timeline'; import { KibanaServices } from '../../common/lib/kibana'; import * as api from './api'; @@ -22,22 +21,14 @@ describe('Notes API client', () => { }); describe('create note', () => { it('should throw an error when a response code other than 200 is returned', async () => { - const errorResponse: PersistNoteRouteResponse = { - data: { - persistNote: { - code: 500, - message: 'Internal server error', - note: { - timelineId: '1', - noteId: '2', - version: '3', - }, - }, - }, - }; (KibanaServices.get as jest.Mock).mockReturnValue({ http: { - patch: jest.fn().mockReturnValue(errorResponse), + patch: jest.fn().mockRejectedValue({ + body: { + status_code: 500, + message: 'Internal server error', + }, + }), }, }); diff --git a/x-pack/plugins/security_solution/public/notes/api/api.ts b/x-pack/plugins/security_solution/public/notes/api/api.ts index 892b01e3d17f..5f380d0ae304 100644 --- a/x-pack/plugins/security_solution/public/notes/api/api.ts +++ b/x-pack/plugins/security_solution/public/notes/api/api.ts @@ -7,7 +7,6 @@ import type { BareNote, - DeleteNoteResponse, GetNotesResponse, PersistNoteRouteResponse, } from '../../../common/api/timeline'; @@ -27,11 +26,7 @@ export const createNote = async ({ note }: { note: BareNote }) => { body: JSON.stringify({ note }), version: '2023-10-31', }); - const noteResponse = response.data.persistNote; - if (noteResponse.code !== 200) { - throw new Error(noteResponse.message); - } - return noteResponse.note; + return response.note; } catch (err) { throw new Error(('message' in err && err.message) || 'Request failed'); } @@ -98,7 +93,7 @@ export const fetchNotesBySaveObjectIds = async (savedObjectIds: string[]) => { * Deletes multiple notes */ export const deleteNotes = async (noteIds: string[]) => { - const response = await KibanaServices.get().http.delete<DeleteNoteResponse>(NOTE_URL, { + const response = await KibanaServices.get().http.delete(NOTE_URL, { body: JSON.stringify({ noteIds }), version: '2023-10-31', }); diff --git a/x-pack/plugins/security_solution/public/notes/hooks/use_fetch_notes.test.ts b/x-pack/plugins/security_solution/public/notes/hooks/use_fetch_notes.test.ts index 7498a6f40c6b..b7fa3ab3fc51 100644 --- a/x-pack/plugins/security_solution/public/notes/hooks/use_fetch_notes.test.ts +++ b/x-pack/plugins/security_solution/public/notes/hooks/use_fetch_notes.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useDispatch } from 'react-redux'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { fetchNotesByDocumentIds } from '../store/notes.slice'; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index b74d0cffdc88..f93383226424 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -245,6 +245,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S assets: new subPluginClasses.Assets(), investigations: new subPluginClasses.Investigations(), machineLearning: new subPluginClasses.MachineLearning(), + siemMigrations: new subPluginClasses.SiemMigrations(), }; } return this._subPlugins; @@ -279,6 +280,9 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S assets: subPlugins.assets.start(), investigations: subPlugins.investigations.start(), machineLearning: subPlugins.machineLearning.start(), + siemMigrations: subPlugins.siemMigrations.start( + this.experimentalFeatures.siemMigrationsEnabled + ), }; } diff --git a/x-pack/plugins/security_solution/public/plugin_services.ts b/x-pack/plugins/security_solution/public/plugin_services.ts index 97b9a163b9f8..92b4bc586a5b 100644 --- a/x-pack/plugins/security_solution/public/plugin_services.ts +++ b/x-pack/plugins/security_solution/public/plugin_services.ts @@ -19,6 +19,7 @@ import type { ConfigSettings } from '../common/config_settings'; import { parseConfigSettings } from '../common/config_settings'; import { APP_UI_ID } from '../common/constants'; import { TopValuesPopoverService } from './app/components/top_values_popover/top_values_popover_service'; +import { createSiemMigrationsService } from './siem_migrations/service'; import type { SecuritySolutionUiConfigType } from './common/types'; import type { PluginStart, @@ -152,6 +153,7 @@ export class PluginServices { customDataService, timelineDataService, topValuesPopover: new TopValuesPopoverService(), + siemMigrations: await createSiemMigrationsService(coreStart), ...(params && { onAppLeave: params.onAppLeave, setHeaderActionMenu: params.setHeaderActionMenu, diff --git a/x-pack/plugins/security_solution/public/rules/links.ts b/x-pack/plugins/security_solution/public/rules/links.ts index 5564a8b9b4e2..ba4280739bbe 100644 --- a/x-pack/plugins/security_solution/public/rules/links.ts +++ b/x-pack/plugins/security_solution/public/rules/links.ts @@ -29,6 +29,7 @@ import type { LinkItem } from '../common/links'; import { IconConsoleCloud } from '../common/icons/console_cloud'; import { IconRollup } from '../common/icons/rollup'; import { IconDashboards } from '../common/icons/dashboards'; +import { siemMigrationsLinks } from '../siem_migrations/links'; export const links: LinkItem = { id: SecurityPageName.rulesLanding, @@ -106,6 +107,7 @@ export const links: LinkItem = { }), ], }, + siemMigrationsLinks, ], categories: [ { @@ -116,6 +118,7 @@ export const links: LinkItem = { SecurityPageName.rules, SecurityPageName.cloudSecurityPostureBenchmarks, SecurityPageName.exceptions, + SecurityPageName.siemMigrationsRules, ], }, { diff --git a/x-pack/plugins/security_solution/public/siem_migrations/index.ts b/x-pack/plugins/security_solution/public/siem_migrations/index.ts new file mode 100644 index 000000000000..4b842e2a8a56 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecuritySubPlugin } from '../app/types'; +import { routes } from './routes'; + +export class SiemMigrations { + public setup() {} + + public start(isEnabled = false): SecuritySubPlugin { + return { + routes: isEnabled ? routes : [], + }; + } +} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/jest.config.js b/x-pack/plugins/security_solution/public/siem_migrations/jest.config.js new file mode 100644 index 000000000000..fd313059456a --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/jest.config.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../..', + roots: ['<rootDir>/x-pack/plugins/security_solution/public/siem_migrations'], + coverageDirectory: + '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/security_solution/public/siem_migrations', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '<rootDir>/x-pack/plugins/security_solution/public/siem_migrations/**/*.{ts,tsx}', + ], + moduleNameMapper: require('../../server/__mocks__/module_name_map'), +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/links.ts b/x-pack/plugins/security_solution/public/siem_migrations/links.ts new file mode 100644 index 000000000000..34db8a357785 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/links.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { + SecurityPageName, + SERVER_APP_ID, + SIEM_MIGRATIONS_RULES_PATH, +} from '../../common/constants'; +import { SIEM_MIGRATIONS_RULES } from '../app/translations'; +import type { LinkItem } from '../common/links/types'; +import { IconConsoleCloud } from '../common/icons/console_cloud'; + +export const siemMigrationsLinks: LinkItem = { + id: SecurityPageName.siemMigrationsRules, + title: SIEM_MIGRATIONS_RULES, + description: i18n.translate('xpack.securitySolution.appLinks.siemMigrationsRulesDescription', { + defaultMessage: 'SIEM Rules Migrations.', + }), + landingIcon: IconConsoleCloud, + path: SIEM_MIGRATIONS_RULES_PATH, + capabilities: [`${SERVER_APP_ID}.show`], + skipUrlState: true, + hideTimeline: true, + globalSearchKeywords: [ + i18n.translate('xpack.securitySolution.appLinks.siemMigrationsRules', { + defaultMessage: 'SIEM Rules Migrations', + }), + ], + experimentalKey: 'siemMigrationsEnabled', +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx b/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx new file mode 100644 index 000000000000..610eb7e2a72d --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import type { SecuritySubPluginRoutes } from '../app/types'; +import { SIEM_MIGRATIONS_RULES_PATH, SecurityPageName } from '../../common/constants'; +import { RulesPage } from './rules/pages'; +import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; +import { SecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper'; + +export const RulesRoutes = () => { + return ( + <PluginTemplateWrapper> + <SecurityRoutePageWrapper pageName={SecurityPageName.siemMigrationsRules}> + <RulesPage /> + </SecurityRoutePageWrapper> + </PluginTemplateWrapper> + ); +}; + +export const routes: SecuritySubPluginRoutes = [ + { + path: SIEM_MIGRATIONS_RULES_PATH, + component: RulesRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts new file mode 100644 index 000000000000..7232cb722bd1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.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 { replaceParams } from '@kbn/openapi-common/shared'; + +import { KibanaServices } from '../../../common/lib/kibana'; + +import { + SIEM_RULE_MIGRATIONS_ALL_STATS_PATH, + SIEM_RULE_MIGRATION_PATH, +} from '../../../../common/siem_migrations/constants'; +import type { + GetAllStatsRuleMigrationResponse, + GetRuleMigrationResponse, +} from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; + +/** + * Retrieves the stats for all the existing migrations, aggregated by `migration_id`. + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getRuleMigrationsStatsAll = async ({ + signal, +}: { + signal: AbortSignal | undefined; +}): Promise<GetAllStatsRuleMigrationResponse> => { + return KibanaServices.get().http.fetch<GetAllStatsRuleMigrationResponse>( + SIEM_RULE_MIGRATIONS_ALL_STATS_PATH, + { + method: 'GET', + version: '1', + signal, + } + ); +}; + +/** + * Retrieves all the migration rule documents of a specific migration. + * + * @param migrationId `id` of the migration to retrieve rule documents for + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const getRuleMigrations = async ({ + migrationId, + signal, +}: { + migrationId: string; + signal: AbortSignal | undefined; +}): Promise<GetRuleMigrationResponse> => { + return KibanaServices.get().http.fetch<GetRuleMigrationResponse>( + replaceParams(SIEM_RULE_MIGRATION_PATH, { migration_id: migrationId }), + { + method: 'GET', + version: '1', + signal, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/constants.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/constants.ts new file mode 100644 index 000000000000..61e0d1e05f7f --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/constants.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const ONE_MINUTE = 60000; + +export const DEFAULT_QUERY_OPTIONS = { + refetchIntervalInBackground: false, + staleTime: ONE_MINUTE * 5, +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_rule_migrations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_rule_migrations.ts new file mode 100644 index 000000000000..76cf01c6c35d --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_rule_migrations.ts @@ -0,0 +1,33 @@ +/* + * 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 { UseQueryOptions } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { replaceParams } from '@kbn/openapi-common/shared'; +import { DEFAULT_QUERY_OPTIONS } from './constants'; +import { getRuleMigrations } from '../api'; +import type { GetRuleMigrationResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import { SIEM_RULE_MIGRATION_PATH } from '../../../../../common/siem_migrations/constants'; + +export const useGetRuleMigrationsQuery = ( + migrationId: string, + options?: UseQueryOptions<GetRuleMigrationResponse> +) => { + const SPECIFIC_MIGRATION_PATH = replaceParams(SIEM_RULE_MIGRATION_PATH, { + migration_id: migrationId, + }); + return useQuery<GetRuleMigrationResponse>( + ['GET', SPECIFIC_MIGRATION_PATH], + async ({ signal }) => { + return getRuleMigrations({ migrationId, signal }); + }, + { + ...DEFAULT_QUERY_OPTIONS, + ...options, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/index.tsx new file mode 100644 index 000000000000..ba73bd9c8494 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/index.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; + +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { EuiComboBox, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import * as i18n from './translations'; + +export interface HeaderButtonsProps { + /** + * Available rule migrations ids + */ + migrationsIds: string[]; + + /** + * Selected rule migration id + */ + selectedMigrationId: string | undefined; + + /** + * Handles migration selection changes + * @param selectedId Selected migration id + * @returns + */ + onMigrationIdChange: (selectedId?: string) => void; +} + +const HeaderButtonsComponent: React.FC<HeaderButtonsProps> = ({ + migrationsIds, + selectedMigrationId, + onMigrationIdChange, +}) => { + const migrationOptions = useMemo(() => { + const options: Array<EuiComboBoxOptionOption<string>> = migrationsIds.map((id, index) => ({ + value: id, + 'data-test-subj': `migrationSelectionOption-${index}`, + label: i18n.SIEM_MIGRATIONS_OPTION_LABEL(index + 1), + })); + return options; + }, [migrationsIds]); + const selectedMigrationOption = useMemo<Array<EuiComboBoxOptionOption<string>>>(() => { + const index = migrationsIds.findIndex((id) => id === selectedMigrationId); + return index !== -1 + ? [ + { + value: selectedMigrationId, + 'data-test-subj': `migrationSelectionOption-${index}`, + label: i18n.SIEM_MIGRATIONS_OPTION_LABEL(index + 1), + }, + ] + : []; + }, [migrationsIds, selectedMigrationId]); + + const onChange = (selected: Array<EuiComboBoxOptionOption<string>>) => { + onMigrationIdChange(selected[0].value); + }; + + if (!migrationsIds.length) { + return null; + } + + return ( + <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}> + <EuiFlexItem grow={false}> + <EuiComboBox + aria-label={i18n.SIEM_MIGRATIONS_OPTION_AREAL_LABEL} + onChange={onChange} + options={migrationOptions} + selectedOptions={selectedMigrationOption} + singleSelection={{ asPlainText: true }} + isClearable={false} + /> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +export const HeaderButtons = React.memo(HeaderButtonsComponent); +HeaderButtons.displayName = 'HeaderButtons'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/translations.ts new file mode 100644 index 000000000000..e00721c70103 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/translations.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const SIEM_MIGRATIONS_OPTION_AREAL_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.selectionOption.arealLabel', + { + defaultMessage: 'Select a migration', + } +); + +export const SIEM_MIGRATIONS_OPTION_LABEL = (optionIndex: number) => + i18n.translate('xpack.securitySolution.siemMigrations.rules.selectionOption.title', { + defaultMessage: 'SIEM rule migration {optionIndex}', + values: { + optionIndex, + }, + }); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/index.tsx new file mode 100644 index 000000000000..e4b3701d94c7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/index.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { SecurityPageName } from '../../../../../common'; +import { useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; +import * as i18n from './translations'; + +const NoMigrationsComponent = () => { + const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); + const { onClick: onClickLink } = getSecuritySolutionLinkProps({ + deepLinkId: SecurityPageName.landing, + path: 'siem_migrations', + }); + + return ( + <EuiFlexGroup + alignItems="center" + gutterSize="s" + responsive={false} + direction="column" + wrap={true} + > + <EuiFlexItem grow={false}> + <EuiEmptyPrompt + title={<h2>{i18n.NO_MIGRATIONS_AVAILABLE}</h2>} + titleSize="s" + body={i18n.NO_MIGRATIONS_AVAILABLE_BODY} + data-test-subj="noMigrationsAvailable" + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButton + fill + iconType="arrowLeft" + color={'primary'} + onClick={onClickLink} + data-test-subj="goToSiemMigrationsButton" + > + {i18n.GO_BACK_TO_RULES_TABLE_BUTTON} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +export const NoMigrations = React.memo(NoMigrationsComponent); +NoMigrations.displayName = 'NoMigrations'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/translations.ts new file mode 100644 index 000000000000..77ec0454fb5a --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/translations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const NO_MIGRATIONS_AVAILABLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.noMigrationsTitle', + { + defaultMessage: 'No migrations', + } +); + +export const NO_MIGRATIONS_AVAILABLE_BODY = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.noMigrationsBodyTitle', + { + defaultMessage: 'There are no migrations available', + } +); + +export const GO_BACK_TO_RULES_TABLE_BUTTON = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.goToMigrationsPageButton', + { + defaultMessage: 'Go back to SIEM Migrations', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/filters.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/filters.tsx new file mode 100644 index 000000000000..5f4ae3098b6a --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/filters.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup } from '@elastic/eui'; +import type { Dispatch, SetStateAction } from 'react'; +import React, { useCallback } from 'react'; +import styled from 'styled-components'; +import * as i18n from './translations'; +import { RuleSearchField } from '../../../../detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rule_search_field'; +import type { TableFilterOptions } from '../../hooks/use_filter_rules_to_install'; + +const FilterWrapper = styled(EuiFlexGroup)` + margin-bottom: ${({ theme }) => theme.eui.euiSizeM}; +`; + +export interface FiltersComponentProps { + /** + * Currently selected table filter + */ + filterOptions: TableFilterOptions; + + /** + * Handles filter options changes + */ + setFilterOptions: Dispatch<SetStateAction<TableFilterOptions>>; +} + +/** + * Collection of filters for filtering data within the SIEM Rules Migrations table. + * Contains search bar and tag selection + */ +const FiltersComponent: React.FC<FiltersComponentProps> = ({ filterOptions, setFilterOptions }) => { + const handleOnSearch = useCallback( + (filterString: string) => { + setFilterOptions((filters) => ({ + ...filters, + filter: filterString.trim(), + })); + }, + [setFilterOptions] + ); + + return ( + <FilterWrapper gutterSize="m" justifyContent="flexEnd" wrap> + <RuleSearchField + initialValue={filterOptions.filter} + onSearch={handleOnSearch} + placeholder={i18n.SEARCH_PLACEHOLDER} + /> + </FilterWrapper> + ); +}; + +export const Filters = React.memo(FiltersComponent); +Filters.displayName = 'Filters'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx new file mode 100644 index 000000000000..0cd3e07ea11a --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx @@ -0,0 +1,125 @@ +/* + * 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 { + EuiInMemoryTable, + EuiSkeletonLoading, + EuiProgress, + EuiSkeletonTitle, + EuiSkeletonText, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import React, { useState } from 'react'; + +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import { + RULES_TABLE_INITIAL_PAGE_SIZE, + RULES_TABLE_PAGE_SIZE_OPTIONS, +} from '../../../../detection_engine/rule_management_ui/components/rules_table/constants'; +import { NoItemsMessage } from './no_items_message'; +import { Filters } from './filters'; +import { useRulesTableColumns } from '../../hooks/use_rules_table_columns'; +import { useGetRuleMigrationsQuery } from '../../api/hooks/use_get_rule_migrations'; +import type { TableFilterOptions } from '../../hooks/use_filter_rules_to_install'; +import { useFilterRulesToInstall } from '../../hooks/use_filter_rules_to_install'; + +export interface RulesTableComponentProps { + /** + * Selected rule migration id + */ + migrationId: string; + + /** + * Opens the flyout with the details of the rule migration + * @param rule Rule migration + * @returns + */ + openRulePreview: (rule: RuleMigration) => void; +} + +/** + * Table Component for displaying SIEM rules migrations + */ +const RulesTableComponent: React.FC<RulesTableComponentProps> = ({ + migrationId, + openRulePreview, +}) => { + const { data: ruleMigrations, isLoading } = useGetRuleMigrationsQuery(migrationId); + + const [selectedRuleMigrations, setSelectedRuleMigrations] = useState<RuleMigration[]>([]); + + const [filterOptions, setFilterOptions] = useState<TableFilterOptions>({ + filter: '', + }); + + const filteredRuleMigrations = useFilterRulesToInstall({ + filterOptions, + ruleMigrations: ruleMigrations ?? [], + }); + + const shouldShowProgress = isLoading; + + const rulesColumns = useRulesTableColumns({ + openRulePreview, + }); + + return ( + <> + {shouldShowProgress && ( + <EuiProgress + data-test-subj="loadingRulesInfoProgress" + size="xs" + position="absolute" + color="accent" + /> + )} + <EuiSkeletonLoading + isLoading={isLoading} + loadingContent={ + <> + <EuiSkeletonTitle /> + <EuiSkeletonText /> + </> + } + loadedContent={ + !filteredRuleMigrations.length ? ( + <NoItemsMessage /> + ) : ( + <> + <EuiFlexGroup direction="column"> + <EuiFlexItem grow={false}> + <Filters filterOptions={filterOptions} setFilterOptions={setFilterOptions} /> + </EuiFlexItem> + </EuiFlexGroup> + + <EuiInMemoryTable + items={filteredRuleMigrations} + sorting + pagination={{ + initialPageSize: RULES_TABLE_INITIAL_PAGE_SIZE, + pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS, + }} + selection={{ + selectable: () => true, + onSelectionChange: setSelectedRuleMigrations, + initialSelected: selectedRuleMigrations, + }} + itemId="rule_id" + data-test-subj="rules-translation-table" + columns={rulesColumns} + /> + </> + ) + } + /> + </> + ); +}; + +export const RulesTable = React.memo(RulesTableComponent); +RulesTable.displayName = 'RulesTable'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/no_items_message.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/no_items_message.tsx new file mode 100644 index 000000000000..7aeaac7ab2f6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/no_items_message.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { SecurityPageName } from '../../../../../common'; +import { useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; +import * as i18n from './translations'; + +const NoItemsMessageComponent = () => { + const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); + const { onClick: onClickLink } = getSecuritySolutionLinkProps({ + deepLinkId: SecurityPageName.landing, + path: 'siem_migrations', + }); + + return ( + <EuiFlexGroup + alignItems="center" + gutterSize="s" + responsive={false} + direction="column" + wrap={true} + > + <EuiFlexItem grow={false}> + <EuiEmptyPrompt + title={<h2>{i18n.NO_TRANSLATIONS_AVAILABLE_FOR_INSTALL}</h2>} + titleSize="s" + body={i18n.NO_TRANSLATIONS_AVAILABLE_FOR_INSTALL_BODY} + data-test-subj="noRulesTranslationAvailableForInstall" + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButton + fill + iconType="arrowLeft" + color={'primary'} + onClick={onClickLink} + data-test-subj="goToSiemMigrationsButton" + > + {i18n.GO_BACK_TO_RULES_TABLE_BUTTON} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +export const NoItemsMessage = React.memo(NoItemsMessageComponent); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts new file mode 100644 index 000000000000..812f26f628e4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const SEARCH_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.searchBarPlaceholder', + { + defaultMessage: 'Search by rule name', + } +); + +export const NO_TRANSLATIONS_AVAILABLE_FOR_INSTALL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.noRulesTitle', + { + defaultMessage: 'Empty migration', + } +); + +export const NO_TRANSLATIONS_AVAILABLE_FOR_INSTALL_BODY = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.noRulesBodyTitle', + { + defaultMessage: 'There are no translations available for installation', + } +); + +export const GO_BACK_TO_RULES_TABLE_BUTTON = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.goToMigrationsPageButton', + { + defaultMessage: 'Go back to SIEM Migrations', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.test.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx similarity index 55% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.test.tsx rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx index 2516382914d6..aaf256cfb60b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/setup_guide/setup_guide_cta.test.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx @@ -6,16 +6,14 @@ */ import React from 'react'; - import { shallow } from 'enzyme'; -import { SetupGuideCta } from '.'; +import { StatusBadge } from '.'; -describe('SetupGuideCta', () => { - it('renders', () => { - const wrapper = shallow(<SetupGuideCta />); +describe('StatusBadge', () => { + it('renders correctly', () => { + const wrapper = shallow(<StatusBadge value="full" />); - expect(wrapper.find('.enterpriseSearchSetupCta')).toHaveLength(1); - expect(wrapper.find('EuiImage')).toHaveLength(1); + expect(wrapper.find('HealthTruncateText')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx new file mode 100644 index 000000000000..40b3c5ceb571 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { euiLightVars } from '@kbn/ui-theme'; + +import type { RuleMigrationTranslationResult } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import { HealthTruncateText } from '../../../../common/components/health_truncate_text'; +import { convertTranslationResultIntoText } from '../../utils/helpers'; + +const { euiColorVis0, euiColorVis7, euiColorVis9 } = euiLightVars; +const statusToColorMap: Record<RuleMigrationTranslationResult, string> = { + full: euiColorVis0, + partial: euiColorVis7, + untranslatable: euiColorVis9, +}; + +interface Props { + value?: RuleMigrationTranslationResult; + installedRuleId?: string; + 'data-test-subj'?: string; +} + +const StatusBadgeComponent: React.FC<Props> = ({ + value, + installedRuleId, + 'data-test-subj': dataTestSubj = 'translation-result', +}) => { + const translationResult = installedRuleId || !value ? 'full' : value; + const displayValue = convertTranslationResultIntoText(translationResult); + const color = statusToColorMap[translationResult]; + + return ( + <HealthTruncateText + healthColor={color} + tooltipContent={displayValue} + dataTestSubj={dataTestSubj} + > + {displayValue} + </HealthTruncateText> + ); +}; + +export const StatusBadge = React.memo(StatusBadgeComponent); +StatusBadge.displayName = 'StatusBadge'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/constants.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/constants.ts new file mode 100644 index 000000000000..4d6bcd542b86 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/constants.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 const DEFAULT_DESCRIPTION_LIST_COLUMN_WIDTHS: [string, string] = ['50%', '50%']; +export const LARGE_DESCRIPTION_LIST_COLUMN_WIDTHS: [string, string] = ['30%', '70%']; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx new file mode 100644 index 000000000000..4aaff21281d6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx @@ -0,0 +1,246 @@ +/* + * 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 { FC, PropsWithChildren } from 'react'; +import React, { useMemo, useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { css } from '@emotion/css'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { + EuiButtonEmpty, + EuiTitle, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiTabbedContent, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + useGeneratedHtmlId, +} from '@elastic/eui'; +import type { EuiTabbedContentTab, EuiTabbedContentProps, EuiFlyoutProps } from '@elastic/eui'; + +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import { + RuleOverviewTab, + useOverviewTabSections, +} from '../../../../detection_engine/rule_management/components/rule_details/rule_overview_tab'; +import { + type RuleResponse, + type Severity, +} from '../../../../../common/api/detection_engine/model/rule_schema'; + +import * as i18n from './translations'; +import { + DEFAULT_DESCRIPTION_LIST_COLUMN_WIDTHS, + LARGE_DESCRIPTION_LIST_COLUMN_WIDTHS, +} from './constants'; +import { TranslationTab } from './translation_tab'; +import { + DEFAULT_TRANSLATION_RISK_SCORE, + DEFAULT_TRANSLATION_SEVERITY, +} from '../../utils/constants'; + +const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` + .euiFlyoutBody__overflow { + display: flex; + flex: 1; + overflow: hidden; + + .euiFlyoutBody__overflowContent { + flex: 1; + overflow: hidden; + padding: ${({ theme }) => `0 ${theme.eui.euiSizeL} 0`}; + } + } +`; + +const StyledFlexGroup = styled(EuiFlexGroup)` + height: 100%; +`; + +const StyledEuiFlexItem = styled(EuiFlexItem)` + &.euiFlexItem { + flex: 1 0 0; + overflow: hidden; + } +`; + +const StyledEuiTabbedContent = styled(EuiTabbedContent)` + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; + + > [role='tabpanel'] { + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; + overflow-y: auto; + + ::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; + } + + ::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + } + } +`; + +/* + * Fixes tabs to the top and allows the content to scroll. + */ +const ScrollableFlyoutTabbedContent = (props: EuiTabbedContentProps) => ( + <StyledFlexGroup direction="column" gutterSize="none"> + <StyledEuiFlexItem grow={true}> + <StyledEuiTabbedContent {...props} /> + </StyledEuiFlexItem> + </StyledFlexGroup> +); + +const tabPaddingClassName = css` + padding: 0 ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeXL} ${euiThemeVars.euiSizeM}; +`; + +export const TabContentPadding: FC<PropsWithChildren<unknown>> = ({ children }) => ( + <div className={tabPaddingClassName}>{children}</div> +); + +interface TranslationDetailsFlyoutProps { + ruleActions?: React.ReactNode; + ruleMigration: RuleMigration; + size?: EuiFlyoutProps['size']; + extraTabs?: EuiTabbedContentTab[]; + closeFlyout: () => void; +} + +export const TranslationDetailsFlyout = ({ + ruleActions, + ruleMigration, + size = 'm', + extraTabs = [], + closeFlyout, +}: TranslationDetailsFlyoutProps) => { + const { expandedOverviewSections, toggleOverviewSection } = useOverviewTabSections(); + + const rule: RuleResponse = useMemo(() => { + const esqlLanguage = ruleMigration.elastic_rule?.query_language ?? 'esql'; + return { + type: esqlLanguage, + language: esqlLanguage, + name: ruleMigration.elastic_rule?.title, + description: ruleMigration.elastic_rule?.description, + query: ruleMigration.elastic_rule?.query, + + // Default values + severity: (ruleMigration.elastic_rule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY, + risk_score: DEFAULT_TRANSLATION_RISK_SCORE, + from: 'now-360s', + to: 'now', + interval: '5m', + } as RuleResponse; // TODO: we need to adjust RuleOverviewTab to allow partial RuleResponse as a parameter + }, [ruleMigration]); + + const translationTab: EuiTabbedContentTab = useMemo( + () => ({ + id: 'translation', + name: i18n.TRANSLATION_TAB_LABEL, + content: ( + <TabContentPadding> + <TranslationTab ruleMigration={ruleMigration} /> + </TabContentPadding> + ), + }), + [ruleMigration] + ); + + const overviewTab: EuiTabbedContentTab = useMemo( + () => ({ + id: 'overview', + name: i18n.OVERVIEW_TAB_LABEL, + content: ( + <TabContentPadding> + <RuleOverviewTab + rule={rule} + columnWidths={ + size === 'l' + ? LARGE_DESCRIPTION_LIST_COLUMN_WIDTHS + : DEFAULT_DESCRIPTION_LIST_COLUMN_WIDTHS + } + expandedOverviewSections={expandedOverviewSections} + toggleOverviewSection={toggleOverviewSection} + /> + </TabContentPadding> + ), + }), + [rule, size, expandedOverviewSections, toggleOverviewSection] + ); + + const tabs = useMemo(() => { + return [...extraTabs, translationTab, overviewTab]; + }, [extraTabs, translationTab, overviewTab]); + + const [selectedTabId, setSelectedTabId] = useState<string>(tabs[0].id); + const selectedTab = tabs.find((tab) => tab.id === selectedTabId) ?? tabs[0]; + + useEffect(() => { + if (!tabs.find((tab) => tab.id === selectedTabId)) { + // Switch to first tab if currently selected tab is not available for this rule + setSelectedTabId(tabs[0].id); + } + }, [tabs, selectedTabId]); + + const onTabClick = (tab: EuiTabbedContentTab) => { + setSelectedTabId(tab.id); + }; + + const migrationsRulesFlyoutTitleId = useGeneratedHtmlId({ + prefix: 'migrationRulesFlyoutTitle', + }); + + return ( + <EuiFlyout + size={size} + onClose={closeFlyout} + key="migrations-rules-flyout" + paddingSize="l" + data-test-subj="ruleMigrationDetailsFlyout" + aria-labelledby={migrationsRulesFlyoutTitleId} + ownFocus + > + <EuiFlyoutHeader> + <EuiTitle size="m"> + <h2 id={migrationsRulesFlyoutTitleId}>{rule.name}</h2> + </EuiTitle> + <EuiSpacer size="l" /> + </EuiFlyoutHeader> + <StyledEuiFlyoutBody> + <ScrollableFlyoutTabbedContent + tabs={tabs} + selectedTab={selectedTab} + onTabClick={onTabClick} + /> + </StyledEuiFlyoutBody> + <EuiFlyoutFooter> + <EuiFlexGroup justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty onClick={closeFlyout} flush="left"> + {i18n.DISMISS_BUTTON_LABEL} + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem grow={false}>{ruleActions}</EuiFlexItem> + </EuiFlexGroup> + </EuiFlyoutFooter> + </EuiFlyout> + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/header.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/header.tsx new file mode 100644 index 000000000000..57e99440e60a --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/header.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiTitle } from '@elastic/eui'; +import * as i18n from './translations'; + +export function TranslationTabHeader(): JSX.Element { + return ( + <EuiFlexGroup direction="row" alignItems="center"> + <EuiTitle data-test-subj="ruleTranslationLabel" size="xs"> + <h5>{i18n.TAB_HEADER_TITLE}</h5> + </EuiTitle> + </EuiFlexGroup> + ); +} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/index.tsx new file mode 100644 index 000000000000..66836b8ea563 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/index.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiAccordion, + EuiBadge, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + EuiSplitPanel, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/css'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { RuleMigration } from '../../../../../../common/siem_migrations/model/rule_migration.gen'; +import { TranslationTabHeader } from './header'; +import { RuleQueryComponent } from './rule_query'; +import * as i18n from './translations'; +import { convertTranslationResultIntoText } from '../../../utils/helpers'; + +interface TranslationTabProps { + ruleMigration: RuleMigration; +} + +export const TranslationTab = ({ ruleMigration }: TranslationTabProps) => { + const { euiTheme } = useEuiTheme(); + + const name = ruleMigration.elastic_rule?.title ?? ruleMigration.original_rule.title; + const originalQuery = ruleMigration.original_rule.query; + const elasticQuery = ruleMigration.elastic_rule?.query ?? 'Prebuilt rule query'; + + return ( + <> + <EuiSpacer size="m" /> + <EuiFormRow label={i18n.NAME_LABEL} fullWidth> + <EuiFieldText value={name} fullWidth /> + </EuiFormRow> + <EuiSpacer size="m" /> + <EuiAccordion + id="translationQueryItem" + buttonContent={<TranslationTabHeader />} + initialIsOpen={true} + > + <EuiFlexItem> + <EuiSpacer size="s" /> + <EuiSplitPanel.Outer grow hasShadow={false} hasBorder={true}> + <EuiSplitPanel.Inner grow={false} color="subdued" paddingSize="s"> + <EuiFlexGroup justifyContent="flexEnd"> + <EuiFlexItem grow={false}> + <EuiTitle size="xxs"> + <h2> + <FormattedMessage + id="xpack.securitySolution.detectionEngine.translationDetails.translationTab.statusTitle" + defaultMessage="Translation status" + /> + </h2> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiBadge + iconSide="right" + iconType="arrowDown" + color="primary" + onClick={() => {}} + onClickAriaLabel={'Click to update translation status'} + > + {convertTranslationResultIntoText(ruleMigration.translation_result)} + </EuiBadge> + </EuiFlexItem> + </EuiFlexGroup> + </EuiSplitPanel.Inner> + <EuiSplitPanel.Inner grow> + <EuiFlexGroup gutterSize="s" alignItems="flexStart"> + <EuiFlexItem grow={1}> + <RuleQueryComponent + title={i18n.SPLUNK_QUERY_TITLE} + query={originalQuery} + canEdit={false} + /> + </EuiFlexItem> + <EuiFlexItem + grow={0} + css={css` + align-self: stretch; + border-right: ${euiTheme.border.thin}; + `} + /> + <EuiFlexItem grow={1}> + <RuleQueryComponent + title={i18n.ESQL_TRANSLATION_TITLE} + query={elasticQuery} + canEdit={false} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiSplitPanel.Inner> + </EuiSplitPanel.Outer> + </EuiFlexItem> + </EuiAccordion> + </> + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/rule_query.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/rule_query.tsx new file mode 100644 index 000000000000..50977cafb18d --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/rule_query.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { EuiMarkdownEditor, EuiMarkdownFormat, EuiTitle } from '@elastic/eui'; +import { SideHeader } from '../../../../../detection_engine/rule_management/components/rule_details/three_way_diff/components/side_header'; +import { FinalSideHelpInfo } from '../../../../../detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info'; +import * as i18n from './translations'; + +interface RuleQueryProps { + title: string; + query: string; + canEdit?: boolean; +} + +export const RuleQueryComponent = ({ title, query, canEdit }: RuleQueryProps) => { + const queryTextComponent = useMemo(() => { + if (canEdit) { + return ( + <EuiMarkdownEditor + aria-label={i18n.TRANSLATED_QUERY_AREAL_LABEL} + value={query} + onChange={() => {}} + height={400} + initialViewMode={'viewing'} + /> + ); + } else { + return <EuiMarkdownFormat>{query}</EuiMarkdownFormat>; + } + }, [canEdit, query]); + return ( + <> + <SideHeader> + <EuiTitle size="xxs"> + <h3> + {title} + <FinalSideHelpInfo /> + </h3> + </EuiTitle> + </SideHeader> + {queryTextComponent} + </> + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/translations.ts new file mode 100644 index 000000000000..e7532a5a8b2e --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/translations.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const TAB_HEADER_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.title', + { + defaultMessage: 'Translation', + } +); + +export const NAME_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.nameLabel', + { + defaultMessage: 'Name', + } +); + +export const SPLUNK_QUERY_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.splunkQueryTitle', + { + defaultMessage: 'Splunk query', + } +); + +export const ESQL_TRANSLATION_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.esqlTranslationTitle', + { + defaultMessage: 'ES|QL translation', + } +); + +export const TRANSLATED_QUERY_AREAL_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.queryArealLabel', + { + defaultMessage: 'Translated query', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translations.ts new file mode 100644 index 000000000000..8e6582b8c198 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const OVERVIEW_TAB_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationDetails.overviewTabLabel', + { + defaultMessage: 'Overview', + } +); + +export const TRANSLATION_TAB_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTabLabel', + { + defaultMessage: 'Translation', + } +); + +export const DISMISS_BUTTON_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationDetails.dismissButtonLabel', + { + defaultMessage: 'Dismiss', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/translations.ts new file mode 100644 index 000000000000..74845b5f257a --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const COLUMN_STATUS = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.columns.statusTitle', + { + defaultMessage: 'Status', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_filter_rules_to_install.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_filter_rules_to_install.ts new file mode 100644 index 000000000000..f6862d3d9038 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_filter_rules_to_install.ts @@ -0,0 +1,33 @@ +/* + * 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 { useMemo } from 'react'; +import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; +import type { FilterOptions } from '../../../detection_engine/rule_management/logic/types'; + +export type TableFilterOptions = Pick<FilterOptions, 'filter'>; + +export const useFilterRulesToInstall = ({ + ruleMigrations, + filterOptions, +}: { + ruleMigrations: RuleMigration[]; + filterOptions: TableFilterOptions; +}) => { + const filteredRules = useMemo(() => { + const { filter } = filterOptions; + return ruleMigrations.filter((migration) => { + const name = migration.elastic_rule?.title ?? migration.original_rule.title; + if (filter && !name.toLowerCase().includes(filter.toLowerCase())) { + return false; + } + return true; + }); + }, [filterOptions, ruleMigrations]); + + return filteredRules; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_latest_stats.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_latest_stats.ts new file mode 100644 index 000000000000..c681af0d2a21 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_latest_stats.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import useObservable from 'react-use/lib/useObservable'; +import { useEffect, useMemo } from 'react'; +import { useKibana } from '../../../common/lib/kibana'; + +export const useLatestStats = () => { + const { siemMigrations } = useKibana().services; + + useEffect(() => { + siemMigrations.rules.startPolling(); + }, [siemMigrations.rules]); + + const latestStats$ = useMemo(() => siemMigrations.rules.getLatestStats$(), [siemMigrations]); + const latestStats = useObservable(latestStats$, null); + + return { data: latestStats ?? [], isLoading: latestStats === null }; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rule_preview_flyout.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rule_preview_flyout.tsx new file mode 100644 index 000000000000..1721b4e280aa --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rule_preview_flyout.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ReactNode } from 'react'; +import React, { useCallback, useState, useMemo } from 'react'; +import type { EuiTabbedContentTab } from '@elastic/eui'; +import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; +import { TranslationDetailsFlyout } from '../components/translation_details_flyout'; + +interface UseRulePreviewFlyoutParams { + ruleActionsFactory: (ruleMigration: RuleMigration, closeRulePreview: () => void) => ReactNode; + extraTabsFactory?: (ruleMigration: RuleMigration) => EuiTabbedContentTab[]; +} + +interface UseRulePreviewFlyoutResult { + rulePreviewFlyout: ReactNode; + openRulePreview: (rule: RuleMigration) => void; + closeRulePreview: () => void; +} + +export function useRulePreviewFlyout({ + extraTabsFactory, + ruleActionsFactory, +}: UseRulePreviewFlyoutParams): UseRulePreviewFlyoutResult { + const [ruleMigration, setRuleMigrationForPreview] = useState<RuleMigration | undefined>(); + const closeRulePreview = useCallback(() => setRuleMigrationForPreview(undefined), []); + const ruleActions = useMemo( + () => ruleMigration && ruleActionsFactory(ruleMigration, closeRulePreview), + [ruleMigration, ruleActionsFactory, closeRulePreview] + ); + const extraTabs = useMemo( + () => (ruleMigration && extraTabsFactory ? extraTabsFactory(ruleMigration) : []), + [ruleMigration, extraTabsFactory] + ); + + return { + rulePreviewFlyout: ruleMigration && ( + <TranslationDetailsFlyout + ruleMigration={ruleMigration} + size="l" + closeFlyout={closeRulePreview} + ruleActions={ruleActions} + extraTabs={extraTabs} + /> + ), + openRulePreview: useCallback((rule: RuleMigration) => { + setRuleMigrationForPreview(rule); + }, []), + closeRulePreview, + }; +} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx new file mode 100644 index 000000000000..3b13b9e631cc --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiBasicTableColumn } from '@elastic/eui'; +import { EuiText, EuiLink } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; +import { SeverityBadge } from '../../../common/components/severity_badge'; +import * as rulesI18n from '../../../detections/pages/detection_engine/rules/translations'; +import * as i18n from './translations'; +import { getNormalizedSeverity } from '../../../detection_engine/rule_management_ui/components/rules_table/helpers'; +import { StatusBadge } from '../components/status_badge'; +import { DEFAULT_TRANSLATION_RISK_SCORE, DEFAULT_TRANSLATION_SEVERITY } from '../utils/constants'; + +export type TableColumn = EuiBasicTableColumn<RuleMigration>; + +interface RuleNameProps { + name: string; + rule: RuleMigration; + openRulePreview: (rule: RuleMigration) => void; +} + +const RuleName = ({ name, rule, openRulePreview }: RuleNameProps) => { + return ( + <EuiLink + onClick={() => { + openRulePreview(rule); + }} + data-test-subj="ruleName" + > + {name} + </EuiLink> + ); +}; + +const createRuleNameColumn = ({ + openRulePreview, +}: { + openRulePreview: (rule: RuleMigration) => void; +}): TableColumn => { + return { + field: 'original_rule.title', + name: rulesI18n.COLUMN_RULE, + render: (value: RuleMigration['original_rule']['title'], rule: RuleMigration) => ( + <RuleName name={value} rule={rule} openRulePreview={openRulePreview} /> + ), + sortable: true, + truncateText: true, + width: '40%', + align: 'left', + }; +}; + +const STATUS_COLUMN: TableColumn = { + field: 'translation_result', + name: i18n.COLUMN_STATUS, + render: (value: RuleMigration['translation_result'], rule: RuleMigration) => ( + <StatusBadge value={value} installedRuleId={rule.elastic_rule?.id} /> + ), + sortable: false, + truncateText: true, + width: '12%', +}; + +export const useRulesTableColumns = ({ + openRulePreview, +}: { + openRulePreview: (rule: RuleMigration) => void; +}): TableColumn[] => { + return useMemo( + () => [ + createRuleNameColumn({ openRulePreview }), + STATUS_COLUMN, + { + field: 'risk_score', + name: rulesI18n.COLUMN_RISK_SCORE, + render: () => ( + <EuiText data-test-subj="riskScore" size="s"> + {DEFAULT_TRANSLATION_RISK_SCORE} + </EuiText> + ), + sortable: true, + truncateText: true, + width: '75px', + }, + { + field: 'elastic_rule.severity', + name: rulesI18n.COLUMN_SEVERITY, + render: (value?: Severity) => ( + <SeverityBadge value={value ?? DEFAULT_TRANSLATION_SEVERITY} /> + ), + sortable: ({ elastic_rule: elasticRule }: RuleMigration) => + getNormalizedSeverity( + (elasticRule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY + ), + truncateText: true, + width: '12%', + }, + ], + [openRulePreview] + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx new file mode 100644 index 000000000000..83392f0a70d2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { EuiSkeletonLoading, EuiSkeletonText, EuiSkeletonTitle } from '@elastic/eui'; +import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; +import { HeaderPage } from '../../../common/components/header_page'; +import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; + +import * as i18n from './translations'; +import { RulesTable } from '../components/rules_table'; +import { NeedAdminForUpdateRulesCallOut } from '../../../detections/components/callouts/need_admin_for_update_callout'; +import { MissingPrivilegesCallOut } from '../../../detections/components/callouts/missing_privileges_callout'; +import { HeaderButtons } from '../components/header_buttons'; +import { useRulePreviewFlyout } from '../hooks/use_rule_preview_flyout'; +import { NoMigrations } from '../components/no_migrations'; +import { useLatestStats } from '../hooks/use_latest_stats'; + +export const RulesPage = React.memo(() => { + const { data: ruleMigrationsStatsAll, isLoading: isLoadingMigrationsStats } = useLatestStats(); + + const migrationsIds = useMemo(() => { + if (isLoadingMigrationsStats || !ruleMigrationsStatsAll?.length) { + return []; + } + return ruleMigrationsStatsAll + .filter((migration) => migration.status === 'finished') + .map((migration) => migration.id); + }, [isLoadingMigrationsStats, ruleMigrationsStatsAll]); + + const [selectedMigrationId, setSelectedMigrationId] = useState<string | undefined>(); + const onMigrationIdChange = (selectedId?: string) => { + setSelectedMigrationId(selectedId); + }; + + useEffect(() => { + if (!migrationsIds.length) { + return; + } + const index = migrationsIds.findIndex((id) => id === selectedMigrationId); + if (index === -1) { + setSelectedMigrationId(migrationsIds[0]); + } + }, [migrationsIds, selectedMigrationId]); + + const ruleActionsFactory = useCallback( + (ruleMigration: RuleMigration, closeRulePreview: () => void) => { + // TODO: Add flyout action buttons + return null; + }, + [] + ); + + const { rulePreviewFlyout, openRulePreview } = useRulePreviewFlyout({ + ruleActionsFactory, + }); + + return ( + <> + <NeedAdminForUpdateRulesCallOut /> + <MissingPrivilegesCallOut /> + + <SecuritySolutionPageWrapper> + <HeaderPage title={i18n.PAGE_TITLE}> + <HeaderButtons + migrationsIds={migrationsIds} + selectedMigrationId={selectedMigrationId} + onMigrationIdChange={onMigrationIdChange} + /> + </HeaderPage> + <EuiSkeletonLoading + isLoading={isLoadingMigrationsStats} + loadingContent={ + <> + <EuiSkeletonTitle /> + <EuiSkeletonText /> + </> + } + loadedContent={ + selectedMigrationId ? ( + <RulesTable migrationId={selectedMigrationId} openRulePreview={openRulePreview} /> + ) : ( + <NoMigrations /> + ) + } + /> + {rulePreviewFlyout} + </SecuritySolutionPageWrapper> + </> + ); +}); +RulesPage.displayName = 'RulesPage'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/translations.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/translations.tsx new file mode 100644 index 000000000000..3c95eaab8fe9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/translations.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const PAGE_TITLE = i18n.translate('xpack.securitySolution.siemMigrations.rules.pageTitle', { + defaultMessage: 'Translated rules', +}); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts new file mode 100644 index 000000000000..ba6543f5171d --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { BehaviorSubject, type Observable } from 'rxjs'; +import type { CoreStart } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { ExperimentalFeaturesService } from '../../../common/experimental_features_service'; +import { licenseService } from '../../../common/hooks/use_license'; +import { getRuleMigrationsStatsAll } from '../api/api'; +import type { RuleMigrationStats } from '../types'; +import { getSuccessToast } from './success_notification'; + +const POLLING_ERROR_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rulesService.polling.errorTitle', + { defaultMessage: 'Error fetching rule migrations' } +); + +export class SiemRulesMigrationsService { + private readonly pollingInterval = 5000; + private readonly latestStats$: BehaviorSubject<RuleMigrationStats[]>; + private isPolling = false; + + constructor(private readonly core: CoreStart) { + this.latestStats$ = new BehaviorSubject<RuleMigrationStats[]>([]); + this.startPolling(); + } + + public getLatestStats$(): Observable<RuleMigrationStats[]> { + return this.latestStats$.asObservable(); + } + + public isAvailable() { + return ExperimentalFeaturesService.get().siemMigrationsEnabled && licenseService.isEnterprise(); + } + + public startPolling() { + if (this.isPolling || !this.isAvailable()) { + return; + } + + this.isPolling = true; + this.startStatsPolling() + .catch((e) => { + this.core.notifications.toasts.addError(e, { title: POLLING_ERROR_TITLE }); + }) + .finally(() => { + this.isPolling = false; + }); + } + + private async startStatsPolling(): Promise<void> { + let pendingMigrationIds: string[] = []; + do { + const results = await this.fetchRuleMigrationsStats(); + this.latestStats$.next(results); + + if (pendingMigrationIds.length > 0) { + // send notifications for finished migrations + pendingMigrationIds.forEach((pendingMigrationId) => { + const migration = results.find((item) => item.id === pendingMigrationId); + if (migration && migration.status === 'finished') { + this.core.notifications.toasts.addSuccess(getSuccessToast(migration, this.core)); + } + }); + } + + // reassign pending migrations + pendingMigrationIds = results.reduce<string[]>((acc, item) => { + if (item.status === 'running') { + acc.push(item.id); + } + return acc; + }, []); + + await new Promise((resolve) => setTimeout(resolve, this.pollingInterval)); + } while (pendingMigrationIds.length > 0); + } + + private async fetchRuleMigrationsStats(): Promise<RuleMigrationStats[]> { + const stats = await getRuleMigrationsStatsAll({ signal: new AbortController().signal }); + return stats.map((stat, index) => ({ ...stat, number: index + 1 })); // the array order (by creation) is guaranteed by the API + } +} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/service/success_notification.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/service/success_notification.tsx new file mode 100644 index 000000000000..f87755943f83 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/service/success_notification.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import { i18n } from '@kbn/i18n'; +import { + SecurityPageName, + useNavigation, + NavigationProvider, +} from '@kbn/security-solution-navigation'; +import type { ToastInput } from '@kbn/core-notifications-browser'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { RuleMigrationStats } from '../types'; + +export const getSuccessToast = (migration: RuleMigrationStats, core: CoreStart): ToastInput => ({ + color: 'success', + iconType: 'check', + toastLifeTimeMs: 1000 * 60 * 30, // 30 minutes + title: i18n.translate('xpack.securitySolution.siemMigrations.rulesService.polling.successTitle', { + defaultMessage: 'Rules translation complete.', + }), + text: toMountPoint( + <NavigationProvider core={core}> + <SuccessToastContent migration={migration} /> + </NavigationProvider>, + core + ), +}); + +const SuccessToastContent: React.FC<{ migration: RuleMigrationStats }> = ({ migration }) => { + const navigation = { deepLinkId: SecurityPageName.siemMigrationsRules, path: migration.id }; + + const { navigateTo, getAppUrl } = useNavigation(); + const onClick: React.MouseEventHandler = (ev) => { + ev.preventDefault(); + navigateTo(navigation); + }; + const url = getAppUrl(navigation); + + return ( + <EuiFlexGroup direction="column" alignItems="flexEnd" gutterSize="s"> + <EuiFlexItem> + <FormattedMessage + id="xpack.securitySolution.siemMigrations.rulesService.polling.successText" + defaultMessage="SIEM rules migration #{number} has finished translating. Results have been added to a dedicated page." + values={{ number: migration.number }} + /> + </EuiFlexItem> + <EuiFlexItem> + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + <EuiButton onClick={onClick} href={url} color="success"> + {i18n.translate( + 'xpack.securitySolution.siemMigrations.rulesService.polling.successLinkText', + { defaultMessage: 'Go to translated rules' } + )} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts new file mode 100644 index 000000000000..db9ca9507702 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RuleMigrationTaskStats } from '../../../common/siem_migrations/model/rule_migration.gen'; + +export interface RuleMigrationStats extends RuleMigrationTaskStats { + /** The sequential number of the migration */ + number: number; +} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/constants.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/constants.ts new file mode 100644 index 000000000000..7400d4b0bcb6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; + +export const DEFAULT_TRANSLATION_RISK_SCORE = 21; +export const DEFAULT_TRANSLATION_SEVERITY: Severity = 'low'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/helpers.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/helpers.tsx new file mode 100644 index 000000000000..cd49311db21e --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/helpers.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + RuleMigrationTranslationResultEnum, + type RuleMigrationTranslationResult, +} from '../../../../common/siem_migrations/model/rule_migration.gen'; +import * as i18n from './translations'; + +export const convertTranslationResultIntoText = (status?: RuleMigrationTranslationResult) => { + switch (status) { + case RuleMigrationTranslationResultEnum.full: + return i18n.SIEM_TRANSLATION_RESULT_FULL_LABEL; + + case RuleMigrationTranslationResultEnum.partial: + return i18n.SIEM_TRANSLATION_RESULT_PARTIAL_LABEL; + + case RuleMigrationTranslationResultEnum.untranslatable: + return i18n.SIEM_TRANSLATION_RESULT_UNTRANSLATABLE_LABEL; + + default: + return i18n.SIEM_TRANSLATION_RESULT_UNKNOWN_LABEL; + } +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/translations.ts new file mode 100644 index 000000000000..bc098936c00f --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/utils/translations.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const SIEM_TRANSLATION_RESULT_FULL_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationResult.full', + { + defaultMessage: 'Fully translated', + } +); + +export const SIEM_TRANSLATION_RESULT_PARTIAL_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationResult.partially', + { + defaultMessage: 'Partially translated', + } +); + +export const SIEM_TRANSLATION_RESULT_UNTRANSLATABLE_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationResult.untranslatable', + { + defaultMessage: 'Not translated', + } +); + +export const SIEM_TRANSLATION_RESULT_UNKNOWN_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.translationResult.unknown', + { + defaultMessage: 'Unknown', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/service/index.ts b/x-pack/plugins/security_solution/public/siem_migrations/service/index.ts new file mode 100644 index 000000000000..08a50d018976 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/service/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart } from '@kbn/core-lifecycle-browser'; + +export type { SiemMigrationsService } from './siem_migrations_service'; + +export const createSiemMigrationsService = async (coreStart: CoreStart) => { + const { SiemMigrationsService } = await import( + /* webpackChunkName: "lazySiemMigrationsService" */ + './siem_migrations_service' + ); + return new SiemMigrationsService(coreStart); +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/service/siem_migrations_service.ts b/x-pack/plugins/security_solution/public/siem_migrations/service/siem_migrations_service.ts new file mode 100644 index 000000000000..1775296f6e23 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/service/siem_migrations_service.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart } from '@kbn/core/public'; +import { SiemRulesMigrationsService } from '../rules/service/rule_migrations_service'; + +export class SiemMigrationsService { + public rules: SiemRulesMigrationsService; + + constructor(coreStart: CoreStart) { + this.rules = new SiemRulesMigrationsService(coreStart); + } +} diff --git a/x-pack/plugins/security_solution/public/sourcerer/components/use_get_sourcerer_data_view.test.ts b/x-pack/plugins/security_solution/public/sourcerer/components/use_get_sourcerer_data_view.test.ts index 87345f80ef70..bf687fa361db 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/components/use_get_sourcerer_data_view.test.ts +++ b/x-pack/plugins/security_solution/public/sourcerer/components/use_get_sourcerer_data_view.test.ts @@ -6,7 +6,7 @@ */ import { DataView } from '@kbn/data-views-plugin/common'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useSourcererDataView } from '../containers'; import { mockSourcererScope } from '../containers/mocks'; import { SourcererScopeName } from '../store/model'; @@ -14,14 +14,11 @@ import type { UseGetScopedSourcererDataViewArgs } from './use_get_sourcerer_data import { useGetScopedSourcererDataView } from './use_get_sourcerer_data_view'; const renderHookCustom = (args: UseGetScopedSourcererDataViewArgs) => { - return renderHook<UseGetScopedSourcererDataViewArgs, DataView | undefined>( - ({ sourcererScope }) => useGetScopedSourcererDataView({ sourcererScope }), - { - initialProps: { - ...args, - }, - } - ); + return renderHook(({ sourcererScope }) => useGetScopedSourcererDataView({ sourcererScope }), { + initialProps: { + ...args, + }, + }); }; jest.mock('../containers'); diff --git a/x-pack/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx b/x-pack/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx index b37565f3eb91..bb4d935dbdc9 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/components/use_update_data_view.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useUpdateDataView } from './use_update_data_view'; import { useKibana as mockUseKibana } from '../../common/lib/kibana/__mocks__'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/sourcerer/containers/hooks.test.tsx b/x-pack/plugins/security_solution/public/sourcerer/containers/hooks.test.tsx index e712e780636e..83019347d309 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/containers/hooks.test.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/containers/hooks.test.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { act, renderHook } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/react'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { Provider } from 'react-redux'; import { useSourcererDataView } from '.'; @@ -137,8 +136,12 @@ jest.mock('../../common/lib/kibana', () => ({ type: 'keyword', }, }), + toSpec: () => ({ + id: dataViewId, + }), }) ), + getExistingIndices: jest.fn(() => [] as string[]), }, indexPatterns: { getTitles: jest.fn().mockImplementation(() => Promise.resolve(mockPatterns)), @@ -153,34 +156,35 @@ jest.mock('../../common/lib/kibana', () => ({ describe('Sourcerer Hooks', () => { let store = createMockStore(); + const StoreProvider: React.FC<React.PropsWithChildren> = ({ children }) => ( + <Provider store={store}>{children}</Provider> + ); + const Wrapper: React.FC<React.PropsWithChildren> = ({ children }) => ( + <TestProviders>{children}</TestProviders> + ); + beforeEach(() => { jest.clearAllMocks(); store = createMockStore(); mockUseUserInfo.mockImplementation(() => userInfoState); }); it('initializes loading default and timeline index patterns', async () => { - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - expect(mockDispatch).toBeCalledTimes(3); - expect(mockDispatch.mock.calls[0][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', - payload: { id: 'security-solution', loading: true }, - }); - expect(mockDispatch.mock.calls[1][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', - payload: { - id: 'timeline', - selectedDataViewId: 'security-solution', - selectedPatterns: ['.siem-signals-spacename', ...DEFAULT_INDEX_PATTERN], - }, - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + expect(mockDispatch.mock.calls[0][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', + payload: { id: 'security-solution', loading: true }, + }); + expect(mockDispatch.mock.calls[1][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', + payload: { + id: 'timeline', + selectedDataViewId: 'security-solution', + selectedPatterns: ['.siem-signals-spacename', ...DEFAULT_INDEX_PATTERN], + }, }); }); it('sets signal index name', async () => { @@ -202,47 +206,42 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - mockUseUserInfo.mockImplementation(() => ({ - ...userInfoState, - loading: false, - signalIndexName: mockSourcererState.signalIndexName, - })); - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - await waitFor(() => { - expect(mockDispatch.mock.calls[3][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { loading: true }, - }); - expect(mockDispatch.mock.calls[4][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SIGNAL_INDEX_NAME', - payload: { signalIndexName: mockSourcererState.signalIndexName }, - }); - expect(mockDispatch.mock.calls[5][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', - payload: { - id: mockSourcererState.defaultDataView.id, - loading: true, - }, - }); - expect(mockDispatch.mock.calls[6][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_DATA_VIEWS', - payload: mockNewDataViews, - }); - expect(mockDispatch.mock.calls[7][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', - payload: { loading: false }, - }); - expect(mockDispatch).toHaveBeenCalledTimes(8); - expect(mockSearch).toHaveBeenCalledTimes(2); + mockUseUserInfo.mockImplementation(() => ({ + ...userInfoState, + loading: false, + signalIndexName: mockSourcererState.signalIndexName, + })); + const { rerender } = renderHook(useInitSourcerer, { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => { + expect(mockDispatch.mock.calls[3][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { loading: true }, + }); + expect(mockDispatch.mock.calls[4][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SIGNAL_INDEX_NAME', + payload: { signalIndexName: mockSourcererState.signalIndexName }, + }); + expect(mockDispatch.mock.calls[5][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_DATA_VIEW_LOADING', + payload: { + id: mockSourcererState.defaultDataView.id, + loading: true, + }, + }); + expect(mockDispatch.mock.calls[6][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_DATA_VIEWS', + payload: mockNewDataViews, }); + expect(mockDispatch.mock.calls[7][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING', + payload: { loading: false }, + }); + + expect(mockSearch).toHaveBeenCalledTimes(2); }); }); @@ -258,8 +257,8 @@ describe('Sourcerer Hooks', () => { }) ); - renderHook<React.PropsWithChildren<{}>, void>(() => useInitSourcerer(), { - wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, + renderHook(() => useInitSourcerer(), { + wrapper: Wrapper, }); expect(mockDispatch).toHaveBeenCalledWith( @@ -278,8 +277,8 @@ describe('Sourcerer Hooks', () => { onInitialize(null) ); - renderHook<React.PropsWithChildren<{}>, void>(() => useInitSourcerer(), { - wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, + renderHook(() => useInitSourcerer(), { + wrapper: Wrapper, }); expect(updateUrlParam).toHaveBeenCalledWith({ @@ -302,16 +301,14 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - renderHook<React.PropsWithChildren<{}>, void>(() => useInitSourcerer(), { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - }); + renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); - await waitFor(() => { - expect(mockAddWarning).toHaveBeenNthCalledWith(1, { - text: 'Users with write permission need to access the Elastic Security app to initialize the app source data.', - title: 'Write role required to generate data', - }); + await waitFor(() => { + expect(mockAddWarning).toHaveBeenNthCalledWith(1, { + text: 'Users with write permission need to access the Elastic Security app to initialize the app source data.', + title: 'Write role required to generate data', }); }); }); @@ -333,27 +330,25 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - mockUseUserInfo.mockImplementation(() => ({ - ...userInfoState, - loading: false, - signalIndexName: mockSourcererState.signalIndexName, - })); - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); + mockUseUserInfo.mockImplementation(() => ({ + ...userInfoState, + loading: false, + signalIndexName: mockSourcererState.signalIndexName, + })); - await waitFor(() => { - expect(mockCreateSourcererDataView).toHaveBeenCalled(); - expect(mockAddError).not.toHaveBeenCalled(); - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, }); + + await waitFor(() => new Promise((resolve) => resolve(null))); + + rerender(); + + await waitFor(() => new Promise((resolve) => resolve(null))); + + expect(mockCreateSourcererDataView).toHaveBeenCalled(); + expect(mockAddError).not.toHaveBeenCalled(); }); it('does call addError if updateSourcererDataView receives a non-abort error', async () => { @@ -375,66 +370,51 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - mockUseUserInfo.mockImplementation(() => ({ - ...userInfoState, - loading: false, - signalIndexName: mockSourcererState.signalIndexName, - })); - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); + mockUseUserInfo.mockImplementation(() => ({ + ...userInfoState, + loading: false, + signalIndexName: mockSourcererState.signalIndexName, + })); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); - await waitForNextUpdate(); - rerender(); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); - await waitFor(() => { - expect(mockAddError).toHaveBeenCalled(); - }); + await waitFor(() => { + expect(mockAddError).toHaveBeenCalled(); }); }); it('handles detections page', async () => { - await act(async () => { - mockUseUserInfo.mockImplementation(() => ({ - ...userInfoState, - signalIndexName: mockSourcererState.signalIndexName, - isSignalIndexExists: true, - })); - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(SourcererScopeName.detections), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - expect(mockDispatch.mock.calls[3][0]).toEqual({ - type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', - payload: { - id: 'detections', - selectedDataViewId: mockSourcererState.defaultDataView.id, - selectedPatterns: [mockSourcererState.signalIndexName], - }, - }); + mockUseUserInfo.mockImplementation(() => ({ + ...userInfoState, + signalIndexName: mockSourcererState.signalIndexName, + isSignalIndexExists: true, + })); + const { rerender } = renderHook(() => useInitSourcerer(SourcererScopeName.detections), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + expect(mockDispatch.mock.calls[3][0]).toEqual({ + type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', + payload: { + id: 'detections', + selectedDataViewId: mockSourcererState.defaultDataView.id, + selectedPatterns: [mockSourcererState.signalIndexName], + }, }); }); it('index field search is not repeated when default and timeline have same dataViewId', async () => { - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - await waitFor(() => { - expect(mockSearch).toHaveBeenCalledTimes(1); - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => { + expect(mockSearch).toHaveBeenCalledTimes(1); }); }); it('index field search called twice when default and timeline have different dataViewId', async () => { @@ -451,18 +431,13 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - await waitFor(() => { - expect(mockSearch).toHaveBeenCalledTimes(2); - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => { + expect(mockSearch).toHaveBeenCalledTimes(2); }); }); describe('initialization settings', () => { @@ -476,21 +451,16 @@ describe('Sourcerer Hooks', () => { })); }); it('does not needToBeInit if scope is default and selectedPatterns/missingPatterns have values', async () => { - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - await waitFor(() => { - expect(mockIndexFieldsSearch).toHaveBeenCalledWith({ - dataViewId: mockSourcererState.defaultDataView.id, - needToBeInit: false, - scopeId: SourcererScopeName.default, - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => { + expect(mockIndexFieldsSearch).toHaveBeenCalledWith({ + dataViewId: mockSourcererState.defaultDataView.id, + needToBeInit: false, + scopeId: SourcererScopeName.default, }); }); }); @@ -510,21 +480,16 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - await waitFor(() => { - expect(mockIndexFieldsSearch).toHaveBeenCalledWith({ - dataViewId: mockSourcererState.defaultDataView.id, - needToBeInit: true, - scopeId: SourcererScopeName.default, - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => { + expect(mockIndexFieldsSearch).toHaveBeenCalledWith({ + dataViewId: mockSourcererState.defaultDataView.id, + needToBeInit: true, + scopeId: SourcererScopeName.default, }); }); }); @@ -549,22 +514,17 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - await waitFor(() => { - expect(mockIndexFieldsSearch).toHaveBeenNthCalledWith(2, { - dataViewId: 'something-weird', - needToBeInit: true, - scopeId: SourcererScopeName.timeline, - skipScopeUpdate: false, - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => { + expect(mockIndexFieldsSearch).toHaveBeenNthCalledWith(2, { + dataViewId: 'something-weird', + needToBeInit: true, + scopeId: SourcererScopeName.timeline, + skipScopeUpdate: false, }); }); }); @@ -589,22 +549,17 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - await waitFor(() => { - expect(mockIndexFieldsSearch).toHaveBeenNthCalledWith(2, { - dataViewId: 'something-weird', - needToBeInit: true, - scopeId: SourcererScopeName.timeline, - skipScopeUpdate: true, - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => { + expect(mockIndexFieldsSearch).toHaveBeenNthCalledWith(2, { + dataViewId: 'something-weird', + needToBeInit: true, + scopeId: SourcererScopeName.timeline, + skipScopeUpdate: true, }); }); }); @@ -633,21 +588,16 @@ describe('Sourcerer Hooks', () => { }, }, }); - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<React.PropsWithChildren<{}>, void>( - () => useInitSourcerer(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); - await waitForNextUpdate(); - rerender(); - await waitFor(() => { - expect(mockIndexFieldsSearch).toHaveBeenNthCalledWith(2, { - dataViewId: 'something-weird', - needToBeInit: false, - scopeId: SourcererScopeName.timeline, - }); + const { rerender } = renderHook(() => useInitSourcerer(), { + wrapper: StoreProvider, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => { + expect(mockIndexFieldsSearch).toHaveBeenNthCalledWith(2, { + dataViewId: 'something-weird', + needToBeInit: false, + scopeId: SourcererScopeName.timeline, }); }); }); @@ -655,38 +605,39 @@ describe('Sourcerer Hooks', () => { describe('useSourcererDataView', () => { it('Should put any excludes in the index pattern at the end of the pattern list, and sort both the includes and excludes', async () => { - await act(async () => { - store = createMockStore({ - ...mockGlobalState, - sourcerer: { - ...mockGlobalState.sourcerer, - sourcererScopes: { - ...mockGlobalState.sourcerer.sourcererScopes, - [SourcererScopeName.default]: { - ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], - selectedPatterns: [ - '-packetbeat-*', - 'endgame-*', - 'auditbeat-*', - 'filebeat-*', - 'winlogbeat-*', - '-filebeat-*', - 'packetbeat-*', - 'traces-apm*', - 'apm-*-transaction*', - ], - }, + store = createMockStore({ + ...mockGlobalState, + sourcerer: { + ...mockGlobalState.sourcerer, + sourcererScopes: { + ...mockGlobalState.sourcerer.sourcererScopes, + [SourcererScopeName.default]: { + ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default], + selectedPatterns: [ + '-packetbeat-*', + 'endgame-*', + 'auditbeat-*', + 'filebeat-*', + 'winlogbeat-*', + '-filebeat-*', + 'packetbeat-*', + 'traces-apm*', + 'apm-*-transaction*', + ], }, }, - }); - const { result, rerender, waitForNextUpdate } = renderHook< - React.PropsWithChildren<{}>, - SelectedDataView - >(() => useSourcererDataView(), { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - }); - await waitForNextUpdate(); - rerender(); + }, + }); + + const { result, rerender } = renderHook<SelectedDataView, SourcererScopeName>( + useSourcererDataView, + { + wrapper: StoreProvider, + } + ); + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender(); + await waitFor(() => expect(result.current.selectedPatterns).toEqual([ 'apm-*-transaction*', 'auditbeat-*', @@ -697,17 +648,14 @@ describe('Sourcerer Hooks', () => { 'winlogbeat-*', '-filebeat-*', '-packetbeat-*', - ]); - }); + ]) + ); }); it('should update the title and name of the data view according to the selected patterns', async () => { - const { result, rerender } = renderHook<React.PropsWithChildren<{}>, SelectedDataView>( - () => useSourcererDataView(), - { - wrapper: ({ children }) => <Provider store={store}>{children}</Provider>, - } - ); + const { result, rerender } = renderHook(() => useSourcererDataView(), { + wrapper: StoreProvider, + }); expect(result.current.sourcererDataView.title).toBe( 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,traces-apm*,winlogbeat-*,-*elastic-cloud-logs-*' @@ -724,7 +672,7 @@ describe('Sourcerer Hooks', () => { ); }); - await rerender(); + rerender(); expect(result.current.sourcererDataView.title).toBe(testPatterns.join(',')); expect(result.current.sourcererDataView.name).toBe(testPatterns.join(',')); diff --git a/x-pack/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.test.tsx b/x-pack/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.test.tsx index 5bb0cc11ebff..43f6f5d8a0a1 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/containers/use_signal_helpers.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { createMockStore, mockGlobalState, TestProviders } from '../../common/mock'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useSignalHelpers } from './use_signal_helpers'; import type { State } from '../../common/store'; import { createSourcererDataView } from './create_sourcerer_data_view'; @@ -41,14 +41,12 @@ describe('useSignalHelpers', () => { ); test('Default state, does not need init and does not need poll', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { - wrapper: wrapperContainer, - }); - await waitForNextUpdate(); - expect(result.current.signalIndexNeedsInit).toEqual(false); - expect(result.current.pollForSignalIndex).toEqual(undefined); + const { result } = renderHook(() => useSignalHelpers(), { + wrapper: wrapperContainer, }); + await waitFor(() => new Promise((resolve) => resolve(null))); + expect(result.current.signalIndexNeedsInit).toEqual(false); + expect(result.current.pollForSignalIndex).toEqual(undefined); }); test('Needs init and does not need poll when signal index is not yet in default data view', async () => { const state: State = { @@ -70,16 +68,14 @@ describe('useSignalHelpers', () => { }, }; const store = createMockStore(state); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { - wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - <TestProviders store={store}>{children}</TestProviders> - ), - }); - await waitForNextUpdate(); - expect(result.current.signalIndexNeedsInit).toEqual(true); - expect(result.current.pollForSignalIndex).toEqual(undefined); + const { result } = renderHook(() => useSignalHelpers(), { + wrapper: ({ children }: React.PropsWithChildren<{}>) => ( + <TestProviders store={store}>{children}</TestProviders> + ), }); + await waitFor(() => new Promise((resolve) => resolve(null))); + expect(result.current.signalIndexNeedsInit).toEqual(true); + expect(result.current.pollForSignalIndex).toEqual(undefined); }); test('Init happened and signal index does not have data yet, poll function becomes available', async () => { const state: State = { @@ -101,16 +97,14 @@ describe('useSignalHelpers', () => { }, }; const store = createMockStore(state); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { - wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - <TestProviders store={store}>{children}</TestProviders> - ), - }); - await waitForNextUpdate(); - expect(result.current.signalIndexNeedsInit).toEqual(false); - expect(result.current.pollForSignalIndex).not.toEqual(undefined); + const { result } = renderHook(() => useSignalHelpers(), { + wrapper: ({ children }: React.PropsWithChildren<{}>) => ( + <TestProviders store={store}>{children}</TestProviders> + ), }); + await waitFor(() => new Promise((resolve) => resolve(null))); + expect(result.current.signalIndexNeedsInit).toEqual(false); + expect(result.current.pollForSignalIndex).not.toEqual(undefined); }); test('Init happened and signal index does not have data yet, poll function becomes available but createSourcererDataView throws an abort error', async () => { @@ -134,17 +128,15 @@ describe('useSignalHelpers', () => { }, }; const store = createMockStore(state); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { - wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - <TestProviders store={store}>{children}</TestProviders> - ), - }); - await waitForNextUpdate(); - expect(result.current.signalIndexNeedsInit).toEqual(false); - expect(result.current.pollForSignalIndex).not.toEqual(undefined); - expect(mockAddError).not.toHaveBeenCalled(); + const { result } = renderHook(() => useSignalHelpers(), { + wrapper: ({ children }: React.PropsWithChildren<{}>) => ( + <TestProviders store={store}>{children}</TestProviders> + ), }); + await waitFor(() => new Promise((resolve) => resolve(null))); + expect(result.current.signalIndexNeedsInit).toEqual(false); + expect(result.current.pollForSignalIndex).not.toEqual(undefined); + expect(mockAddError).not.toHaveBeenCalled(); }); test('Init happened and signal index does not have data yet, poll function becomes available but createSourcererDataView throws a non-abort error', async () => { @@ -170,17 +162,15 @@ describe('useSignalHelpers', () => { }, }; const store = createMockStore(state); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), { - wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - <TestProviders store={store}>{children}</TestProviders> - ), - }); - await waitForNextUpdate(); - expect(result.current.signalIndexNeedsInit).toEqual(false); - expect(result.current.pollForSignalIndex).not.toEqual(undefined); - result.current.pollForSignalIndex?.(); - expect(mockAddError).toHaveBeenCalled(); + const { result } = renderHook(() => useSignalHelpers(), { + wrapper: ({ children }: React.PropsWithChildren<{}>) => ( + <TestProviders store={store}>{children}</TestProviders> + ), }); + await waitFor(() => new Promise((resolve) => resolve(null))); + expect(result.current.signalIndexNeedsInit).toEqual(false); + expect(result.current.pollForSignalIndex).not.toEqual(undefined); + result.current.pollForSignalIndex?.(); + expect(mockAddError).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/create_field_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/create_field_button/index.test.tsx index 4c1c72500ae8..01c9dd701292 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/create_field_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/create_field_button/index.test.tsx @@ -5,19 +5,18 @@ * 2.0. */ -import { render } from '@testing-library/react'; +import { render, renderHook } from '@testing-library/react'; import React from 'react'; -import type { UseCreateFieldButton, UseCreateFieldButtonProps } from '.'; +import type { UseCreateFieldButtonProps } from '.'; import { useCreateFieldButton } from '.'; import { TestProviders } from '../../../../common/mock'; -import { renderHook } from '@testing-library/react-hooks'; const mockOpenFieldEditor = jest.fn(); const mockOnHide = jest.fn(); const renderUseCreateFieldButton = (props: Partial<UseCreateFieldButtonProps> = {}) => - renderHook<React.PropsWithChildren<UseCreateFieldButtonProps>, ReturnType<UseCreateFieldButton>>( + renderHook( () => useCreateFieldButton({ isAllowed: true, diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_table_columns/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_table_columns/index.test.tsx index 8d82c3402af7..a565a31ef67a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_table_columns/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_table_columns/index.test.tsx @@ -6,12 +6,11 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; -import type { UseFieldTableColumnsProps, UseFieldTableColumns } from '.'; +import { render, renderHook } from '@testing-library/react'; +import type { UseFieldTableColumnsProps } from '.'; import { useFieldTableColumns } from '.'; import { TestProviders } from '../../../../common/mock'; -import { renderHook } from '@testing-library/react-hooks'; import { EuiInMemoryTable } from '@elastic/eui'; import type { BrowserFieldItem } from '@kbn/triggers-actions-ui-plugin/public/types'; @@ -21,7 +20,7 @@ const mockOpenDeleteFieldModal = jest.fn(); // helper function to render the hook const renderUseFieldTableColumns = (props: Partial<UseFieldTableColumnsProps> = {}) => - renderHook<React.PropsWithChildren<UseFieldTableColumnsProps>, ReturnType<UseFieldTableColumns>>( + renderHook( () => useFieldTableColumns({ hasFieldEditPermission: true, diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx index 67094a18cf32..7b67fb6614ad 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { render, act } from '@testing-library/react'; +import type { RenderHookResult } from '@testing-library/react'; +import { render, act, waitFor, renderHook } from '@testing-library/react'; import type { Store } from 'redux'; import type { UseFieldBrowserOptionsProps, UseFieldBrowserOptions, FieldEditorActionsRef } from '.'; import { useFieldBrowserOptions } from '.'; @@ -16,8 +17,6 @@ import { indexPatternFieldEditorPluginMock } from '@kbn/data-view-field-editor-p import { TestProviders } from '../../../common/mock'; import { useKibana } from '../../../common/lib/kibana'; import type { DataView, DataViewField } from '@kbn/data-plugin/common'; -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook } from '@testing-library/react-hooks'; import { SourcererScopeName } from '../../../sourcerer/store/model'; import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers'; import { DEFAULT_COLUMN_MIN_WIDTH } from '../timeline/body/constants'; @@ -42,12 +41,13 @@ const mockOnHide = jest.fn(); const runAllPromises = () => new Promise(setImmediate); // helper function to render the hook -const renderUseFieldBrowserOptions = ( - props: Partial<UseFieldBrowserOptionsProps & { store?: Store }> = {} -) => +const renderUseFieldBrowserOptions = ({ + store, + ...props +}: Partial<UseFieldBrowserOptionsProps & { store?: Store }> = {}) => renderHook< - React.PropsWithChildren<UseFieldBrowserOptionsProps & { store?: Store }>, - ReturnType<UseFieldBrowserOptions> + ReturnType<UseFieldBrowserOptions>, + React.PropsWithChildren<UseFieldBrowserOptionsProps & { store?: Store }> >( () => useFieldBrowserOptions({ @@ -57,7 +57,7 @@ const renderUseFieldBrowserOptions = ( ...props, }), { - wrapper: ({ children, store }) => { + wrapper: ({ children }) => { if (store) { return <TestProviders store={store}>{children}</TestProviders>; } @@ -71,12 +71,12 @@ const renderUpdatedUseFieldBrowserOptions = async ( props: Partial<UseFieldBrowserOptionsProps> = {} ) => { let renderHookResult: RenderHookResult< - UseFieldBrowserOptionsProps, - ReturnType<UseFieldBrowserOptions> + ReturnType<UseFieldBrowserOptions>, + UseFieldBrowserOptionsProps > | null = null; await act(async () => { renderHookResult = renderUseFieldBrowserOptions(props); - await renderHookResult.waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); }); return renderHookResult!; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts index df761c8854f4..ecd028c3d4ca 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts @@ -5,421 +5,384 @@ * 2.0. */ -import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; +import { + DataProviderTypeEnum, + type ResolveTimelineResponse, + TimelineStatusEnum, + TimelineTypeEnum, +} from '../../../../../common/api/timeline'; -export const mockTimeline = { - data: { - timeline: { - savedObjectId: 'eb2781c0-1df5-11eb-8589-2f13958b79f7', - columns: [ - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: '@timestamp', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'message', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'event.category', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'event.action', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'host.name', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'source.ip', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'destination.ip', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'user.name', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - ], - dataProviders: [], - dateRange: { - start: '2020-11-01T14:30:59.935Z', - end: '2020-11-03T14:31:11.417Z', - __typename: 'DateRangePickerResult', +export const mockTimeline: ResolveTimelineResponse = { + timeline: { + savedObjectId: 'eb2781c0-1df5-11eb-8589-2f13958b79f7', + columns: [ + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: '@timestamp', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'message', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'event.category', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'event.action', + name: null, + searchable: null, + type: null, }, - description: '', - eventType: 'all', - eventIdToNoteIds: [], - excludedRowRendererIds: [], - favorite: [], - filters: [], - kqlMode: 'filter', - kqlQuery: { filterQuery: null, __typename: 'SerializedFilterQueryResult' }, - indexNames: [ - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - '.siem-signals-angelachuang-default', - ], - notes: [], - noteIds: [], - pinnedEventIds: [], - pinnedEventsSaveObject: [], - status: TimelineStatusEnum.active, - title: 'my timeline', - timelineType: TimelineTypeEnum.default, - templateTimelineId: null, - templateTimelineVersion: null, - savedQueryId: null, - sort: { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - __typename: 'SortTimelineResult', + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'host.name', + name: null, + searchable: null, + type: null, }, - created: 1604497127973, - createdBy: 'elastic', - updated: 1604500278364, - updatedBy: 'elastic', - version: 'WzQ4NSwxXQ==', - __typename: 'TimelineResult', + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'source.ip', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'destination.ip', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'user.name', + name: null, + searchable: null, + type: null, + }, + ], + dataProviders: [], + dateRange: { + start: '2020-11-01T14:30:59.935Z', + end: '2020-11-03T14:31:11.417Z', }, - outcome: 'exactMatch', + description: '', + eventType: 'all', + eventIdToNoteIds: [], + excludedRowRendererIds: [], + favorite: [], + filters: [], + kqlMode: 'filter', + kqlQuery: { filterQuery: null }, + indexNames: [ + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + '.siem-signals-angelachuang-default', + ], + notes: [], + noteIds: [], + pinnedEventIds: [], + pinnedEventsSaveObject: [], + status: TimelineStatusEnum.active, + title: 'my timeline', + timelineType: TimelineTypeEnum.default, + templateTimelineId: null, + templateTimelineVersion: null, + savedQueryId: null, + sort: { + columnId: '@timestamp', + columnType: 'number', + sortDirection: 'desc', + }, + created: 1604497127973, + createdBy: 'elastic', + updated: 1604500278364, + updatedBy: 'elastic', + version: 'WzQ4NSwxXQ==', }, - loading: false, - networkStatus: 7, - stale: false, + outcome: 'exactMatch', }; -export const mockTemplate = { - data: { - timeline: { - savedObjectId: '0c70a200-1de0-11eb-885c-6fc13fca1850', - columns: [ - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: '@timestamp', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'signal.rule.description', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'event.action', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'process.name', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: true, - category: 'process', - columnHeaderType: 'not-filtered', - description: 'The working directory of the process.', - example: '/home/alice', - indexes: null, - id: 'process.working_directory', - name: null, - searchable: null, - type: 'string', - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: true, - category: 'process', - columnHeaderType: 'not-filtered', - description: - 'Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.', - example: '["/usr/bin/ssh","-l","user","10.0.0.16"]', - indexes: null, - id: 'process.args', - name: null, - searchable: null, - type: 'string', - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: null, - category: null, - columnHeaderType: 'not-filtered', - description: null, - example: null, - indexes: null, - id: 'process.pid', - name: null, - searchable: null, - type: null, - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: true, - category: 'process', - columnHeaderType: 'not-filtered', - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - indexes: null, - id: 'process.parent.executable', - name: null, - searchable: null, - type: 'string', - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: true, - category: 'process', - columnHeaderType: 'not-filtered', - description: - 'Array of process arguments.\n\nMay be filtered to protect sensitive information.', - example: '["ssh","-l","user","10.0.0.16"]', - indexes: null, - id: 'process.parent.args', - name: null, - searchable: null, - type: 'string', - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: true, - category: 'process', - columnHeaderType: 'not-filtered', - description: 'Process id.', - example: '4242', - indexes: null, - id: 'process.parent.pid', - name: null, - searchable: null, - type: 'number', - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: true, - category: 'user', - columnHeaderType: 'not-filtered', - description: 'Short name or login of the user.', - example: 'albert', - indexes: null, - id: 'user.name', - name: null, - searchable: null, - type: 'string', - __typename: 'ColumnHeaderResult', - }, - { - aggregatable: true, - category: 'host', - columnHeaderType: 'not-filtered', - description: - 'Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.', - example: null, - indexes: null, - id: 'host.name', - name: null, - searchable: null, - type: 'string', - __typename: 'ColumnHeaderResult', - }, - ], - dataProviders: [ - { - id: 'timeline-1-8622010a-61fb-490d-b162-beac9c36a853', - name: '{process.name}', - enabled: true, - excluded: false, - kqlQuery: '', - type: 'template', - queryMatch: { - field: 'process.name', - displayField: null, - value: '{process.name}', - displayValue: null, - operator: ':', - __typename: 'QueryMatchResult', - }, - and: [], - __typename: 'DataProviderResult', - }, - { - id: 'timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568', - name: '{event.type}', - enabled: true, - excluded: false, - kqlQuery: '', - type: 'template', - queryMatch: { - field: 'event.type', - displayField: null, - value: '{event.type}', - displayValue: null, - operator: ':*', - __typename: 'QueryMatchResult', - }, - and: [], - __typename: 'DataProviderResult', +export const mockTemplate: ResolveTimelineResponse = { + timeline: { + savedObjectId: '0c70a200-1de0-11eb-885c-6fc13fca1850', + columns: [ + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: '@timestamp', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'signal.rule.description', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'event.action', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'process.name', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: true, + category: 'process', + columnHeaderType: 'not-filtered', + description: 'The working directory of the process.', + example: '/home/alice', + indexes: null, + id: 'process.working_directory', + name: null, + searchable: null, + type: 'string', + }, + { + aggregatable: true, + category: 'process', + columnHeaderType: 'not-filtered', + description: + 'Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.', + example: '["/usr/bin/ssh","-l","user","10.0.0.16"]', + indexes: null, + id: 'process.args', + name: null, + searchable: null, + type: 'string', + }, + { + aggregatable: null, + category: null, + columnHeaderType: 'not-filtered', + description: null, + example: null, + indexes: null, + id: 'process.pid', + name: null, + searchable: null, + type: null, + }, + { + aggregatable: true, + category: 'process', + columnHeaderType: 'not-filtered', + description: 'Absolute path to the process executable.', + example: '/usr/bin/ssh', + indexes: null, + id: 'process.parent.executable', + name: null, + searchable: null, + type: 'string', + }, + { + aggregatable: true, + category: 'process', + columnHeaderType: 'not-filtered', + description: + 'Array of process arguments.\n\nMay be filtered to protect sensitive information.', + example: '["ssh","-l","user","10.0.0.16"]', + indexes: null, + id: 'process.parent.args', + name: null, + searchable: null, + type: 'string', + }, + { + aggregatable: true, + category: 'process', + columnHeaderType: 'not-filtered', + description: 'Process id.', + example: '4242', + indexes: null, + id: 'process.parent.pid', + name: null, + searchable: null, + type: 'number', + }, + { + aggregatable: true, + category: 'user', + columnHeaderType: 'not-filtered', + description: 'Short name or login of the user.', + example: 'albert', + indexes: null, + id: 'user.name', + name: null, + searchable: null, + type: 'string', + }, + { + aggregatable: true, + category: 'host', + columnHeaderType: 'not-filtered', + description: + 'Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.', + example: null, + indexes: null, + id: 'host.name', + name: null, + searchable: null, + type: 'string', + }, + ], + dataProviders: [ + { + id: 'timeline-1-8622010a-61fb-490d-b162-beac9c36a853', + name: '{process.name}', + enabled: true, + excluded: false, + kqlQuery: '', + type: DataProviderTypeEnum.template, + queryMatch: { + field: 'process.name', + displayField: null, + value: '{process.name}', + displayValue: null, + operator: ':', }, - ], - dateRange: { - start: '2020-10-27T14:22:11.809Z', - end: '2020-11-03T14:22:11.809Z', - __typename: 'DateRangePickerResult', + and: [], }, - description: '', - eventType: 'all', - eventIdToNoteIds: [], - excludedRowRendererIds: [], - favorite: [], - filters: [], - kqlMode: 'filter', - kqlQuery: { - filterQuery: { - kuery: { kind: 'kuery', expression: '', __typename: 'KueryFilterQueryResult' }, - serializedQuery: '', - __typename: 'SerializedKueryQueryResult', + { + id: 'timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568', + name: '{event.type}', + enabled: true, + excluded: false, + kqlQuery: '', + type: DataProviderTypeEnum.template, + queryMatch: { + field: 'event.type', + displayField: null, + value: '{event.type}', + displayValue: null, + operator: ':*', }, - __typename: 'SerializedFilterQueryResult', + and: [], }, - indexNames: [], - notes: [], - noteIds: [], - pinnedEventIds: [], - pinnedEventsSaveObject: [], - status: TimelineStatusEnum.immutable, - title: 'Generic Process Timeline', - timelineType: 'template', - templateTimelineId: 'cd55e52b-7bce-4887-88e2-f1ece4c75447', - templateTimelineVersion: 1, - savedQueryId: null, - sort: { - columnId: '@timestamp', - columnType: 'number', - sortDirection: 'desc', - __typename: 'SortTimelineResult', + ], + dateRange: { + start: '2020-10-27T14:22:11.809Z', + end: '2020-11-03T14:22:11.809Z', + }, + description: '', + eventType: 'all', + eventIdToNoteIds: [], + excludedRowRendererIds: [], + favorite: [], + filters: [], + kqlMode: 'filter', + kqlQuery: { + filterQuery: { + kuery: { kind: 'kuery', expression: '' }, + serializedQuery: '', }, - created: 1604413368243, - createdBy: 'angela', - updated: 1604413368243, - updatedBy: 'angela', - version: 'WzQwMywxXQ==', - __typename: 'TimelineResult', }, - outcome: 'exactMatch', + indexNames: [], + notes: [], + noteIds: [], + pinnedEventIds: [], + pinnedEventsSaveObject: [], + status: TimelineStatusEnum.immutable, + title: 'Generic Process Timeline', + timelineType: TimelineTypeEnum.template, + templateTimelineId: 'cd55e52b-7bce-4887-88e2-f1ece4c75447', + templateTimelineVersion: 1, + savedQueryId: null, + sort: { + columnId: '@timestamp', + columnType: 'number', + sortDirection: 'desc', + }, + created: 1604413368243, + createdBy: 'angela', + updated: 1604413368243, + updatedBy: 'angela', + version: 'WzQwMywxXQ==', }, - loading: false, - networkStatus: 7, - stale: false, + outcome: 'exactMatch', }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 525d8bba3d90..0d5e013e3038 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -5,11 +5,10 @@ * 2.0. */ -import { cloneDeep, getOr, omit } from 'lodash/fp'; -import { renderHook } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/react'; +import { cloneDeep, omit } from 'lodash/fp'; +import { waitFor, renderHook } from '@testing-library/react'; -import { mockTimelineResults, mockGetOneTimelineResult } from '../../../common/mock'; +import { mockTimelineResults } from '../../../common/mock'; import { timelineDefaults } from '../../store/defaults'; import type { QueryTimelineById } from './helpers'; import { @@ -17,7 +16,6 @@ import { getNotesCount, getPinnedEventCount, isUntitled, - omitTypenameInTimeline, useQueryTimelineById, formatTimelineResponseToModel, } from './helpers'; @@ -655,7 +653,7 @@ describe('helpers', () => { test('Do not override daterange if TimelineStatus is active', () => { const { timeline } = formatTimelineResponseToModel( - omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), + selectedTimeline.timeline, args.duplicate, args.timelineType ); @@ -667,7 +665,7 @@ describe('helpers', () => { describe('update a timeline', () => { const selectedTimeline = { ...mockSelectedTimeline }; - const untitledTimeline = { ...mockSelectedTimeline, title: '' }; + const untitledTimeline = { timeline: { ...mockSelectedTimeline.timeline, title: '' } }; const onOpenTimeline = jest.fn(); const args: QueryTimelineById = { duplicate: false, @@ -684,7 +682,6 @@ describe('helpers', () => { afterEach(() => { jest.clearAllMocks(); }); - test('should get timeline by Id with correct statuses', async () => { renderHook(async () => { const queryTimelineById = useQueryTimelineById(); @@ -693,7 +690,7 @@ describe('helpers', () => { // expect(resolveTimeline).toHaveBeenCalled(); const { timeline } = formatTimelineResponseToModel( - omitTypenameInTimeline(getOr({}, 'data.timeline', selectedTimeline)), + selectedTimeline.timeline, args.duplicate, args.timelineType ); @@ -751,7 +748,7 @@ describe('helpers', () => { }); test('should update timeline correctly when timeline is already saved and onOpenTimeline is not provided', async () => { - (resolveTimeline as jest.Mock).mockResolvedValue(mockSelectedTimeline); + (resolveTimeline as jest.Mock).mockResolvedValue(selectedTimeline); renderHook(async () => { const queryTimelineById = useQueryTimelineById(); queryTimelineById(args); @@ -762,7 +759,7 @@ describe('helpers', () => { 1, expect.objectContaining({ timeline: expect.objectContaining({ - columns: mockSelectedTimeline.data.timeline.columns.map((col) => ({ + columns: selectedTimeline.timeline.columns!.map((col) => ({ columnHeaderType: col.columnHeaderType, id: col.id, initialWidth: defaultUdtHeaders.find((defaultCol) => col.id === defaultCol.id) @@ -784,7 +781,7 @@ describe('helpers', () => { waitFor(() => { expect(onOpenTimeline).toHaveBeenCalledWith( expect.objectContaining({ - columns: mockSelectedTimeline.data.timeline.columns.map((col) => ({ + columns: mockSelectedTimeline.timeline.columns!.map((col) => ({ columnHeaderType: col.columnHeaderType, id: col.id, initialWidth: defaultUdtHeaders.find((defaultCol) => col.id === defaultCol.id) @@ -827,7 +824,7 @@ describe('helpers', () => { test('override daterange if TimelineStatus is immutable', () => { const { timeline } = formatTimelineResponseToModel( - omitTypenameInTimeline(getOr({}, 'data.timeline', template)), + template.timeline, args.duplicate, args.timelineType ); @@ -841,26 +838,4 @@ describe('helpers', () => { }); }); }); - - describe('omitTypenameInTimeline', () => { - test('should not modify the passed in timeline if no __typename exists', () => { - const result = omitTypenameInTimeline(mockGetOneTimelineResult); - - expect(result).toEqual(mockGetOneTimelineResult); - }); - - test('should return timeline with __typename removed when it exists', () => { - const mockTimeline = { - ...mockGetOneTimelineResult, - __typename: 'something, something', - }; - const result = omitTypenameInTimeline(mockTimeline); - const expectedTimeline = { - ...mockTimeline, - __typename: undefined, - }; - - expect(result).toEqual(expectedTimeline); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index d3ca6c4654ff..46ab60d9324b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -13,7 +13,6 @@ import { useDiscoverInTimelineContext } from '../../../common/components/discove import type { ColumnHeaderOptions } from '../../../../common/types/timeline'; import type { TimelineResponse, - ResolvedTimeline, ColumnHeaderResult, FilterTimelineResult, DataProviderResult, @@ -73,12 +72,6 @@ export const getNotesCount = ({ eventIdToNoteIds, noteIds }: OpenTimelineResult) export const isUntitled = ({ title }: OpenTimelineResult): boolean => title == null || title.trim().length === 0; -const omitTypename = (key: string, value: keyof TimelineModel) => - key === '__typename' ? undefined : value; - -export const omitTypenameInTimeline = (timeline: TimelineResponse): TimelineResponse => - JSON.parse(JSON.stringify(timeline), omitTypename); - const parseString = (params: string) => { try { return JSON.parse(params); @@ -348,13 +341,10 @@ export const useQueryTimelineById = () => { } else { return Promise.resolve(resolveTimeline(timelineId)) .then((result) => { - const data: ResolvedTimeline | null = getOr(null, 'data', result); - if (!data) return; - - const timelineToOpen = omitTypenameInTimeline(data.timeline); + if (!result) return; const { timeline, notes } = formatTimelineResponseToModel( - timelineToOpen, + result.timeline, duplicate, timelineType ); @@ -372,9 +362,9 @@ export const useQueryTimelineById = () => { id: TimelineId.active, notes, resolveTimelineConfig: { - outcome: data.outcome, - alias_target_id: data.alias_target_id, - alias_purpose: data.alias_purpose, + outcome: result.outcome, + alias_target_id: result.alias_target_id, + alias_purpose: result.alias_purpose, }, timeline: { ...timeline, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx index da56cb12b4a0..5a3ba28a8276 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx @@ -6,9 +6,8 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; import { mount } from 'enzyme'; -import { fireEvent, render, waitFor } from '@testing-library/react'; +import { fireEvent, render, waitFor, renderHook } from '@testing-library/react'; import { useHistory, useParams } from 'react-router-dom'; import '../../../common/mock/formatted_relative'; @@ -30,7 +29,6 @@ import { NotePreviews } from './note_previews'; import { OPEN_TIMELINE_CLASS_NAME } from './helpers'; import { StatefulOpenTimeline } from '.'; import { TimelineTabsStyle } from './types'; -import type { UseTimelineTypesArgs, UseTimelineTypesResult } from './use_timeline_types'; import { useTimelineTypes } from './use_timeline_types'; import { deleteTimelinesByIds } from '../../containers/api'; import { useUserPrivileges } from '../../../common/components/user_privileges'; @@ -165,12 +163,12 @@ describe('StatefulOpenTimeline', () => { describe("Template timelines' tab", () => { test("should land on correct timelines' tab with url timelines/default", () => { - const { result } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 0 }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, - }); + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 0 }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); expect(result.current.timelineType).toBe(TimelineTypeEnum.default); }); @@ -181,12 +179,12 @@ describe('StatefulOpenTimeline', () => { pageName: SecurityPageName.timelines, }); - const { result } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 0 }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, - }); + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 0 }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); expect(result.current.timelineType).toBe(TimelineTypeEnum.template); }); @@ -223,12 +221,12 @@ describe('StatefulOpenTimeline', () => { pageName: SecurityPageName.case, }); - const { result } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 0 }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, - }); + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 0 }), + { + wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + } + ); expect(result.current.timelineType).toBe(TimelineTypeEnum.default); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/hooks/use_delete_note.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/hooks/use_delete_note.test.ts index b2c88364a820..d9dd243357d5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/hooks/use_delete_note.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/hooks/use_delete_note.test.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; import { useKibana } from '../../../../../common/lib/kibana'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import { appActions } from '../../../../../common/store/app'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx index 66d3a41bc0d4..22e03955afbc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx @@ -6,9 +6,7 @@ */ import React from 'react'; -import { fireEvent, render } from '@testing-library/react'; -import { renderHook, act } from '@testing-library/react-hooks'; -import type { UseTimelineTypesArgs, UseTimelineTypesResult } from './use_timeline_types'; +import { fireEvent, render, waitFor, screen, renderHook } from '@testing-library/react'; import { useTimelineTypes } from './use_timeline_types'; import { TestProviders } from '../../../common/mock'; @@ -31,12 +29,14 @@ jest.mock('../../../common/components/link_to', () => { }; }); +const mockNavigateToUrl = jest.fn(); + jest.mock('@kbn/kibana-react-plugin/public', () => { const originalModule = jest.requireActual('@kbn/kibana-react-plugin/public'); const useKibana = jest.fn().mockImplementation(() => ({ services: { application: { - navigateToUrl: jest.fn(), + navigateToUrl: mockNavigateToUrl, }, }, })); @@ -48,92 +48,83 @@ jest.mock('@kbn/kibana-react-plugin/public', () => { }); describe('useTimelineTypes', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), + { wrapper: TestProviders, - }); - await waitForNextUpdate(); - expect(result.current).toEqual({ - timelineType: 'default', - timelineTabs: result.current.timelineTabs, - timelineFilters: result.current.timelineFilters, - }); + } + ); + + expect(result.current).toEqual({ + timelineType: 'default', + timelineTabs: result.current.timelineTabs, + timelineFilters: result.current.timelineFilters, }); }); describe('timelineTabs', () => { it('render timelineTabs', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), + { wrapper: TestProviders, - }); - await waitForNextUpdate(); - - const { container } = render(result.current.timelineTabs); - expect( - container.querySelector('[data-test-subj="timeline-tab-default"]') - ).toHaveTextContent('Timelines'); - expect( - container.querySelector('[data-test-subj="timeline-tab-template"]') - ).toHaveTextContent('Templates'); - }); + } + ); + await waitFor(() => new Promise((resolve) => resolve(null))); + + render(result.current.timelineTabs); + expect(screen.getByTestId('timeline-tab-default')).toHaveTextContent('Timelines'); + expect(screen.getByTestId('timeline-tab-template')).toHaveTextContent('Templates'); }); it('set timelineTypes correctly', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), + { wrapper: TestProviders, - }); - await waitForNextUpdate(); + } + ); - const { container } = render(result.current.timelineTabs); + await waitFor(() => expect(result.current.timelineTabs).toBeDefined()); - fireEvent( - container.querySelector('[data-test-subj="timeline-tab-template"]')!, - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }) - ); + const { container } = render(result.current.timelineTabs); - expect(result.current).toEqual({ - timelineType: 'template', - timelineTabs: result.current.timelineTabs, - timelineFilters: result.current.timelineFilters, - }); - }); + fireEvent( + container.querySelector('[data-test-subj="timeline-tab-template"]')!, + new MouseEvent('click', { + bubbles: true, + cancelable: true, + }) + ); + + expect(mockNavigateToUrl).toHaveBeenCalled(); }); it('stays in the same tab if clicking again on current tab', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), + { wrapper: TestProviders, - }); - await waitForNextUpdate(); + } + ); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { container } = render(result.current.timelineTabs); + render(result.current.timelineTabs); - fireEvent( - container.querySelector('[data-test-subj="timeline-tab-default"]')!, - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }) - ); + fireEvent( + screen.getByTestId('timeline-tab-default'), + new MouseEvent('click', { + bubbles: true, + cancelable: true, + }) + ); + await waitFor(() => { expect(result.current).toEqual({ timelineType: 'default', timelineTabs: result.current.timelineTabs, @@ -145,79 +136,77 @@ describe('useTimelineTypes', () => { describe('timelineFilters', () => { it('render timelineFilters', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), + { wrapper: TestProviders, - }); - await waitForNextUpdate(); - - const { container } = render(<>{result.current.timelineFilters}</>); - expect( - container.querySelector('[data-test-subj="open-timeline-modal-body-filter-default"]') - ).toHaveTextContent('Timelines'); - expect( - container.querySelector('[data-test-subj="open-timeline-modal-body-filter-template"]') - ).toHaveTextContent('Templates'); - }); + } + ); + await waitFor(() => new Promise((resolve) => resolve(null))); + + const { container } = render(<>{result.current.timelineFilters}</>); + + expect( + container.querySelector('[data-test-subj="open-timeline-modal-body-filter-default"]') + ).toHaveTextContent('Timelines'); + expect( + container.querySelector('[data-test-subj="open-timeline-modal-body-filter-template"]') + ).toHaveTextContent('Templates'); }); it('set timelineTypes correctly', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), + { wrapper: TestProviders, - }); - await waitForNextUpdate(); + } + ); - const { container } = render(<>{result.current.timelineFilters}</>); + await waitFor(() => expect(result.current.timelineFilters).toBeDefined()); - fireEvent( - container.querySelector('[data-test-subj="open-timeline-modal-body-filter-template"]')!, - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }) - ); + render(<>{result.current.timelineFilters}</>); - expect(result.current).toEqual({ - timelineType: 'template', + await waitFor(() => new Promise((resolve) => resolve(null))); + + fireEvent.click(screen.getByTestId('open-timeline-modal-body-filter-template')); + + await waitFor(() => expect(result.current.timelineType).toEqual('template')); + + expect(result.current).toEqual( + expect.objectContaining({ timelineTabs: result.current.timelineTabs, timelineFilters: result.current.timelineFilters, - }); - }); + }) + ); }); it('stays in the same tab if clicking again on current tab', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - React.PropsWithChildren<UseTimelineTypesArgs>, - UseTimelineTypesResult - >(() => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), { + const { result } = renderHook( + () => useTimelineTypes({ defaultTimelineCount: 0, templateTimelineCount: 3 }), + { wrapper: TestProviders, - }); - await waitForNextUpdate(); + } + ); - const { container } = render(<>{result.current.timelineFilters}</>); + await waitFor(() => new Promise((resolve) => resolve(null))); - fireEvent( - container.querySelector('[data-test-subj="open-timeline-modal-body-filter-default"]')!, - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }) - ); + const { container } = render(<>{result.current.timelineFilters}</>); + fireEvent( + container.querySelector('[data-test-subj="open-timeline-modal-body-filter-default"]')!, + new MouseEvent('click', { + bubbles: true, + cancelable: true, + }) + ); + + await waitFor(() => expect(result.current).toEqual({ timelineType: 'default', timelineTabs: result.current.timelineTabs, timelineFilters: result.current.timelineFilters, - }); - }); + }) + ); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx index b2c990b12ece..6d052a3f8b2d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { mockTimelineModel, TestProviders } from '../../../common/mock'; import { setTimelineRangeDatePicker as dispatchSetTimelineRangeDatePicker } from '../../../common/store/inputs/actions'; import { @@ -79,7 +79,10 @@ describe('dispatchUpdateTimeline', () => { beforeEach(() => { jest.clearAllMocks(); - clock = sinon.useFakeTimers(unix); + clock = sinon.useFakeTimers({ + now: unix, + toFake: ['Date'], + }); }); afterEach(function () { @@ -87,128 +90,134 @@ describe('dispatchUpdateTimeline', () => { }); it('it invokes date range picker dispatch', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), { - wrapper: TestProviders, - }); - await waitForNextUpdate(); + const { result } = renderHook(() => useUpdateTimeline(), { + wrapper: TestProviders, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + + act(() => { result.current(defaultArgs); + }); - expect(dispatchSetTimelineRangeDatePicker).toHaveBeenCalledWith({ - from: '2020-03-26T14:35:56.356Z', - to: '2020-03-26T14:41:56.356Z', - }); + expect(dispatchSetTimelineRangeDatePicker).toHaveBeenCalledWith({ + from: '2020-03-26T14:35:56.356Z', + to: '2020-03-26T14:41:56.356Z', }); }); it('it invokes add timeline dispatch', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), { - wrapper: TestProviders, - }); - await waitForNextUpdate(); + const { result } = renderHook(() => useUpdateTimeline(), { + wrapper: TestProviders, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + + act(() => { result.current(defaultArgs); + }); - expect(dispatchAddTimeline).toHaveBeenCalledWith({ - id: TimelineId.active, - savedTimeline: true, - timeline: { - ...mockTimelineModel, - version: null, - updated: undefined, - changed: true, - }, - }); + expect(dispatchAddTimeline).toHaveBeenCalledWith({ + id: TimelineId.active, + savedTimeline: true, + timeline: { + ...mockTimelineModel, + version: null, + updated: undefined, + changed: true, + }, }); }); it('it does not invoke kql filter query dispatches if timeline.kqlQuery.filterQuery is null', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), { - wrapper: TestProviders, - }); - await waitForNextUpdate(); - result.current(defaultArgs); + const { result } = renderHook(() => useUpdateTimeline(), { + wrapper: TestProviders, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); - expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled(); + act(() => { + result.current(defaultArgs); }); + + expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled(); }); it('it does not invoke notes dispatch if duplicate is true', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), { - wrapper: TestProviders, - }); - await waitForNextUpdate(); + const { result } = renderHook(() => useUpdateTimeline(), { + wrapper: TestProviders, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + act(() => { result.current(defaultArgs); - - expect(dispatchAddNotes).not.toHaveBeenCalled(); }); + + expect(dispatchAddNotes).not.toHaveBeenCalled(); }); it('it does not invoke kql filter query dispatches if timeline.kqlQuery.kuery is null', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), { - wrapper: TestProviders, - }); - await waitForNextUpdate(); - const mockTimeline = { - ...mockTimelineModel, - kqlQuery: { - filterQuery: { - kuery: null, - serializedQuery: 'some-serialized-query', - }, + const { result } = renderHook(() => useUpdateTimeline(), { + wrapper: TestProviders, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + const mockTimeline = { + ...mockTimelineModel, + kqlQuery: { + filterQuery: { + kuery: null, + serializedQuery: 'some-serialized-query', }, - }; + }, + }; + + act(() => { result.current({ ...defaultArgs, timeline: mockTimeline, }); - - expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled(); }); + + expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled(); }); it('it invokes kql filter query dispatches if timeline.kqlQuery.filterQuery.kuery is not null', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), { - wrapper: TestProviders, - }); - await waitForNextUpdate(); - const mockTimeline = { - ...mockTimelineModel, - kqlQuery: { - filterQuery: { - kuery: { expression: 'expression', kind: 'kuery' as KueryFilterQueryKind }, - serializedQuery: 'some-serialized-query', - }, + const { result } = renderHook(() => useUpdateTimeline(), { + wrapper: TestProviders, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + const mockTimeline = { + ...mockTimelineModel, + kqlQuery: { + filterQuery: { + kuery: { expression: 'expression', kind: 'kuery' as KueryFilterQueryKind }, + serializedQuery: 'some-serialized-query', }, - }; + }, + }; + + act(() => { result.current({ ...defaultArgs, timeline: mockTimeline, }); + }); - expect(dispatchApplyKqlFilterQuery).toHaveBeenCalledWith({ - id: TimelineId.active, - filterQuery: { - kuery: { - kind: 'kuery', - expression: 'expression', - }, - serializedQuery: 'some-serialized-query', + expect(dispatchApplyKqlFilterQuery).toHaveBeenCalledWith({ + id: TimelineId.active, + filterQuery: { + kuery: { + kind: 'kuery', + expression: 'expression', }, - }); + serializedQuery: 'some-serialized-query', + }, }); }); it('it invokes dispatchAddNotes if duplicate is false', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), { - wrapper: TestProviders, - }); - await waitForNextUpdate(); + const { result } = renderHook(() => useUpdateTimeline(), { + wrapper: TestProviders, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + + act(() => { result.current({ ...defaultArgs, duplicate: false, @@ -223,53 +232,55 @@ describe('dispatchUpdateTimeline', () => { }, ], }); + }); - expect(dispatchAddGlobalTimelineNote).not.toHaveBeenCalled(); - expect(dispatchUpdateNote).not.toHaveBeenCalled(); - expect(dispatchAddNotes).toHaveBeenCalledWith({ - notes: [ - { - created: new Date('2020-03-26T14:35:56.356Z'), - eventId: null, - id: 'note-id', - lastEdit: new Date('2020-03-26T14:35:56.356Z'), - note: 'I am a note', - user: 'unknown', - saveObjectId: 'note-id', - timelineId: 'abc', - version: 'testVersion', - }, - ], - }); + expect(dispatchAddGlobalTimelineNote).not.toHaveBeenCalled(); + expect(dispatchUpdateNote).not.toHaveBeenCalled(); + expect(dispatchAddNotes).toHaveBeenCalledWith({ + notes: [ + { + created: new Date('2020-03-26T14:35:56.356Z'), + eventId: null, + id: 'note-id', + lastEdit: new Date('2020-03-26T14:35:56.356Z'), + note: 'I am a note', + user: 'unknown', + saveObjectId: 'note-id', + timelineId: 'abc', + version: 'testVersion', + }, + ], }); }); it('it invokes dispatch to create a timeline note if duplicate is true and ruleNote exists', async () => { + const { result } = renderHook(() => useUpdateTimeline(), { + wrapper: TestProviders, + }); + await waitFor(() => new Promise((resolve) => resolve(null))); + await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useUpdateTimeline(), { - wrapper: TestProviders, - }); - await waitForNextUpdate(); result.current({ ...defaultArgs, ruleNote: '# this would be some markdown', }); - const expectedNote: Note = { - created: new Date(anchor), - id: 'uuidv4()', - lastEdit: null, - note: '# this would be some markdown', - saveObjectId: null, - user: 'elastic', - version: null, - }; + }); - expect(dispatchAddNotes).not.toHaveBeenCalled(); - expect(dispatchUpdateNote).toHaveBeenCalledWith({ note: expectedNote }); - expect(dispatchAddGlobalTimelineNote).toHaveBeenLastCalledWith({ - id: TimelineId.active, - noteId: 'uuidv4()', - }); + const expectedNote: Note = { + created: new Date(anchor), + id: 'uuidv4()', + lastEdit: null, + note: '# this would be some markdown', + saveObjectId: null, + user: 'elastic', + version: null, + }; + + expect(dispatchAddNotes).not.toHaveBeenCalled(); + expect(dispatchUpdateNote).toHaveBeenCalledWith({ note: expectedNote }); + expect(dispatchAddGlobalTimelineNote).toHaveBeenLastCalledWith({ + id: TimelineId.active, + noteId: 'uuidv4()', }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.test.tsx index 32b5b8bc3c12..fd50be53c6a8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_notes_in_flyout.test.tsx @@ -7,11 +7,10 @@ import React from 'react'; import { TimelineId, TimelineTabs } from '../../../../../common/types'; -import { renderHook, act } from '@testing-library/react-hooks/dom'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { createMockStore, mockGlobalState, TestProviders } from '../../../../common/mock'; import type { UseNotesInFlyoutArgs } from './use_notes_in_flyout'; import { useNotesInFlyout } from './use_notes_in_flyout'; -import { waitFor } from '@testing-library/react'; import { useDispatch } from 'react-redux'; jest.mock('react-redux', () => ({ @@ -202,8 +201,8 @@ describe('useNotesInFlyout', () => { expect(result.current.isNotesFlyoutVisible).toBe(false); }); - it('should close the flyout when activeTab is changed', () => { - const { result, rerender, waitForNextUpdate } = renderTestHook(); + it('should close the flyout when activeTab is changed', async () => { + const { result, rerender } = renderTestHook(); act(() => { result.current.setNotesEventId('event-1'); @@ -226,8 +225,6 @@ describe('useNotesInFlyout', () => { rerender({ activeTab: TimelineTabs.eql }); }); - waitForNextUpdate(); - - expect(result.current.isNotesFlyoutVisible).toBe(false); + await waitFor(() => expect(result.current.isNotesFlyoutVisible).toBe(false)); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx index 54b5a4a9aae2..f925a1d83d13 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx @@ -5,36 +5,28 @@ * 2.0. */ -import { isEmpty, isEqual } from 'lodash'; -import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { isEqual } from 'lodash'; +import React, { memo, useCallback, useEffect, useMemo } from 'react'; +import { EuiOutsideClickDetector } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; -import type { - EqlOptionsSelected, - FieldsEqlOptions, -} from '../../../../../../common/search_strategy'; +import type { EqlOptions } from '../../../../../../common/search_strategy'; import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; -import { EqlQueryBar } from '../../../../../detection_engine/rule_creation_ui/components/eql_query_bar'; - -import { - debounceAsync, - eqlValidator, -} from '../../../../../detection_engine/rule_creation_ui/components/eql_query_bar/validators'; +import { EqlQueryEdit } from '../../../../../detection_engine/rule_creation/components/eql_query_edit'; import type { FieldValueQueryBar } from '../../../../../detection_engine/rule_creation_ui/components/query_bar'; -import type { FormSchema } from '../../../../../shared_imports'; -import { Form, UseField, useForm, useFormData } from '../../../../../shared_imports'; +import type { FormSchema, FormSubmitHandler } from '../../../../../shared_imports'; +import { Form, UseField, useForm } from '../../../../../shared_imports'; import { timelineActions } from '../../../../store'; -import * as i18n from '../translations'; import { getEqlOptions } from './selectors'; interface TimelineEqlQueryBar { index: string[]; eqlQueryBar: FieldValueQueryBar; - eqlOptions: EqlOptionsSelected; + eqlOptions: EqlOptions; } const defaultValues = { @@ -55,13 +47,6 @@ const schema: FormSchema<TimelineEqlQueryBar> = { eqlOptions: { fieldsToValidateOnChange: ['eqlOptions', 'eqlQueryBar'], }, - eqlQueryBar: { - validations: [ - { - validator: debounceAsync(eqlValidator, 300), - }, - ], - }, }; const hiddenUseFieldClassName = css` @@ -71,11 +56,8 @@ const hiddenUseFieldClassName = css` // eslint-disable-next-line react/display-name export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) => { const dispatch = useDispatch(); - const isInit = useRef(true); - const [isQueryBarValid, setIsQueryBarValid] = useState(false); - const [isQueryBarValidating, setIsQueryBarValidating] = useState(false); const getOptionsSelected = useMemo(() => getEqlOptions(), []); - const optionsSelected = useDeepEqualSelector((state) => getOptionsSelected(state, timelineId)); + const eqlOptions = useDeepEqualSelector((state) => getOptionsSelected(state, timelineId)); const { loading: indexPatternsLoading, @@ -89,127 +71,117 @@ export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) index: [...selectedPatterns].sort(), eqlQueryBar: { ...defaultValues.eqlQueryBar, - query: { query: optionsSelected.query ?? '', language: 'eql' }, + query: { query: eqlOptions.query ?? '', language: 'eql' }, }, + eqlOptions, }), - [optionsSelected.query, selectedPatterns] + [eqlOptions, selectedPatterns] + ); + + const handleSubmit = useCallback<FormSubmitHandler<TimelineEqlQueryBar>>( + async (formData, isValid) => { + if (!isValid) { + return; + } + + if (eqlOptions.query !== `${formData.eqlQueryBar.query.query}`) { + dispatch( + timelineActions.updateEqlOptions({ + id: timelineId, + field: 'query', + value: `${formData.eqlQueryBar.query.query}`, + }) + ); + } + + for (const fieldName of Object.keys(formData.eqlOptions) as Array< + keyof typeof formData.eqlOptions + >) { + if (formData.eqlOptions[fieldName] !== eqlOptions[fieldName]) { + dispatch( + timelineActions.updateEqlOptions({ + id: timelineId, + field: fieldName, + value: formData.eqlOptions[fieldName], + }) + ); + } + } + }, + [dispatch, timelineId, eqlOptions] ); const { form } = useForm<TimelineEqlQueryBar>({ defaultValue: initialState, options: { stripEmptyFields: false }, schema, + onSubmit: handleSubmit, }); - const { getFields, setFieldValue } = form; - - const onOptionsChange = useCallback( - (field: FieldsEqlOptions, value: string | undefined) => { - dispatch( - timelineActions.updateEqlOptions({ - id: timelineId, - field, - value, - }) - ); - setFieldValue('eqlOptions', { ...optionsSelected, [field]: value }); - }, - [dispatch, optionsSelected, setFieldValue, timelineId] - ); - - const [{ eqlQueryBar: formEqlQueryBar }] = useFormData<TimelineEqlQueryBar>({ - form, - watch: ['eqlQueryBar'], - }); - - const prevEqlQuery = useRef<TimelineEqlQueryBar['eqlQueryBar']['query']['query']>(''); + const { getFields } = form; + const handleOutsideEqlQueryEditClick = useCallback(() => form.submit(), [form]); - const optionsData = useMemo(() => { - const fields = Object.values(sourcererDataView.fields || {}); + // Reset the form when new EQL Query came from the state + useEffect(() => { + getFields().eqlQueryBar.setValue({ + ...defaultValues.eqlQueryBar, + query: { query: eqlOptions.query ?? '', language: 'eql' }, + }); + }, [getFields, eqlOptions.query]); - return isEmpty(fields) - ? { - keywordFields: [], - dateFields: [], - nonDateFields: [], - } - : { - keywordFields: fields - .filter((f) => f.esTypes?.includes('keyword')) - .map((f) => ({ label: f.name })), - dateFields: fields.filter((f) => f.type === 'date').map((f) => ({ label: f.name })), - nonDateFields: fields.filter((f) => f.type !== 'date').map((f) => ({ label: f.name })), - }; - }, [sourcererDataView]); + // Reset the form when new EQL Options came from the state + useEffect(() => { + getFields().eqlOptions.setValue({ + eventCategoryField: eqlOptions.eventCategoryField, + tiebreakerField: eqlOptions.tiebreakerField, + timestampField: eqlOptions.timestampField, + size: eqlOptions.size, + }); + }, [ + getFields, + eqlOptions.eventCategoryField, + eqlOptions.tiebreakerField, + eqlOptions.timestampField, + eqlOptions.size, + ]); useEffect(() => { const { index: indexField } = getFields(); const newIndexValue = [...selectedPatterns].sort(); const indexFieldValue = (indexField.value as string[]).sort(); + if (!isEqual(indexFieldValue, newIndexValue)) { indexField.setValue(newIndexValue); } }, [getFields, selectedPatterns]); - useEffect(() => { - const { eqlQueryBar } = getFields(); - if (isInit.current) { - isInit.current = false; - setIsQueryBarValidating(true); - eqlQueryBar.setValue({ - ...defaultValues.eqlQueryBar, - query: { query: optionsSelected.query ?? '', language: 'eql' }, - }); - } - return () => { - isInit.current = true; - }; - }, [getFields, optionsSelected.query]); - - useEffect(() => { - if ( - formEqlQueryBar != null && - prevEqlQuery.current !== formEqlQueryBar.query.query && - isQueryBarValid && - !isQueryBarValidating - ) { - prevEqlQuery.current = formEqlQueryBar.query.query; - dispatch( - timelineActions.updateEqlOptions({ - id: timelineId, - field: 'query', - value: `${formEqlQueryBar.query.query}`, - }) - ); - setIsQueryBarValid(false); - setIsQueryBarValidating(false); - } - }, [dispatch, formEqlQueryBar, isQueryBarValid, isQueryBarValidating, timelineId]); + const dataView = useMemo( + () => ({ + ...sourcererDataView, + title: sourcererDataView.title ?? '', + fields: Object.values(sourcererDataView.fields || {}), + }), + [sourcererDataView] + ); + /* Force casting `sourcererDataView` to `DataViewBase` is required since EqlQueryEdit + accepts DataViewBase but `useSourcererDataView()` returns `DataViewSpec`. + + When using `UseField` with `EqlQueryBar` such casting isn't required by TS since + `UseField` component props are types as `Record<string, any>`. */ return ( <Form form={form} data-test-subj="EqlQueryBarTimeline"> <UseField key="Index" path="index" className={hiddenUseFieldClassName} /> - <UseField key="EqlOptions" path="eqlOptions" className={hiddenUseFieldClassName} /> - <UseField - key="EqlQueryBar" - path="eqlQueryBar" - component={EqlQueryBar} - componentProps={{ - optionsData, - optionsSelected, - onOptionsChange, - onValidityChange: setIsQueryBarValid, - onValiditingChange: setIsQueryBarValidating, - idAria: 'timelineEqlQueryBar', - isDisabled: indexPatternsLoading, - isLoading: indexPatternsLoading, - indexPattern: sourcererDataView, - dataTestSubj: 'timelineEqlQueryBar', - }} - config={{ - ...schema.eqlQueryBar, - label: i18n.EQL_QUERY_BAR_LABEL, - }} - /> + <EuiOutsideClickDetector onOutsideClick={handleOutsideEqlQueryEditClick}> + <EqlQueryEdit + key="EqlQueryBar" + path="eqlQueryBar" + eqlOptionsPath="eqlOptions" + showEqlSizeOption + dataView={dataView} + loading={indexPatternsLoading} + disabled={indexPatternsLoading} + /> + </EuiOutsideClickDetector> </Form> ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx index e533ef917517..eccaf8859198 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx @@ -13,12 +13,7 @@ export const getEqlOptions = () => selectTimeline, (timeline) => timeline?.eqlOptions ?? { - eventCategoryField: [{ label: 'event.category' }], - tiebreakerField: [ - { - label: '', - }, - ], + eventCategoryField: 'event.category', timestampField: [ { label: '@timestamp', diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/esql/customizations/use_histogram_customizations.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/esql/customizations/use_histogram_customizations.test.ts index d40b417b9521..10dfa97f35bd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/esql/customizations/use_histogram_customizations.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/esql/customizations/use_histogram_customizations.test.ts @@ -11,7 +11,7 @@ import type { ClickTriggerEvent, MultiClickTriggerEvent, } from '@kbn/charts-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { DiscoverStateContainer, UnifiedHistogramCustomization, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/esql/use_get_stateful_query_bar.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/esql/use_get_stateful_query_bar.test.tsx index 17b018ffea99..fe26d8780a90 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/esql/use_get_stateful_query_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/esql/use_get_stateful_query_bar.test.tsx @@ -6,7 +6,7 @@ */ import { TestProviders } from '../../../../../common/mock'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useGetStatefulQueryBar } from './use_get_stateful_query_bar'; describe('useGetStatefulQueryBar', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/session/use_session_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/session/use_session_view.test.tsx index 9c4b135b5e77..54155a493da6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/session/use_session_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/session/use_session_view.test.tsx @@ -8,8 +8,7 @@ import type { PropsWithChildren } from 'react'; import React, { memo } from 'react'; -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, renderHook } from '@testing-library/react'; import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { mockTimelineModel, TestProviders } from '../../../../../common/mock'; import { useKibana } from '../../../../../common/lib/kibana'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_columns.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_columns.test.ts index 926082ff9ed4..66162fe82e45 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_columns.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_columns.test.ts @@ -6,7 +6,7 @@ */ import { TestProviders } from '../../../../../common/mock'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useTimelineColumns } from './use_timeline_columns'; import { defaultUdtHeaders } from '../../body/column_headers/default_headers'; import type { ColumnHeaderOptions } from '../../../../../../common/types/timeline/columns'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx index efbe95425003..8168742d4e08 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx @@ -6,7 +6,7 @@ */ import type { EuiDataGridControlColumn } from '@elastic/eui'; import { TestProviders } from '../../../../../common/mock'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useLicense } from '../../../../../common/hooks/use_license'; import { useTimelineControlColumn } from './use_timeline_control_columns'; import type { ColumnHeaderOptions } from '../../../../../../common/types/timeline/columns'; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts index ba76f857dc34..7640b74d1d25 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts @@ -88,17 +88,9 @@ const timelineData = { savedSearchId: null, }; const mockPatchTimelineResponse = { - data: { - persistTimeline: { - code: 200, - message: 'success', - timeline: { - ...timelineData, - savedObjectId: '9d5693e0-a42a-11ea-b8f4-c5434162742a', - version: 'WzM0NSwxXQ==', - }, - }, - }, + ...timelineData, + savedObjectId: '9d5693e0-a42a-11ea-b8f4-c5434162742a', + version: 'WzM0NSwxXQ==', }; describe('persistTimeline', () => { describe('create draft timeline', () => { @@ -108,15 +100,9 @@ describe('persistTimeline', () => { status: TimelineStatusEnum.draft, }; const mockDraftResponse = { - data: { - persistTimeline: { - timeline: { - ...initialDraftTimeline, - savedObjectId: '9d5693e0-a42a-11ea-b8f4-c5434162742a', - version: 'WzMzMiwxXQ==', - }, - }, - }, + ...initialDraftTimeline, + savedObjectId: '9d5693e0-a42a-11ea-b8f4-c5434162742a', + version: 'WzMzMiwxXQ==', }; const version = null; @@ -161,14 +147,13 @@ describe('persistTimeline', () => { test("it should update timeline from clean draft timeline's response", () => { expect(JSON.parse(patchMock.mock.calls[0][1].body)).toEqual({ - timelineId: mockDraftResponse.data.persistTimeline.timeline.savedObjectId, + timelineId: mockDraftResponse.savedObjectId, timeline: { ...initialDraftTimeline, - templateTimelineId: mockDraftResponse.data.persistTimeline.timeline.templateTimelineId, - templateTimelineVersion: - mockDraftResponse.data.persistTimeline.timeline.templateTimelineVersion, + templateTimelineId: mockDraftResponse.templateTimelineId, + templateTimelineVersion: mockDraftResponse.templateTimelineVersion, }, - version: mockDraftResponse.data.persistTimeline.timeline.version ?? '', + version: mockDraftResponse.version ?? '', }); }); }); @@ -211,13 +196,8 @@ describe('persistTimeline', () => { version, }); expect(persist).toEqual({ - data: { - persistTimeline: { - code: 403, - message: 'you do not have the permission', - timeline: { ...initialDraftTimeline, savedObjectId: '', version: '' }, - }, - }, + statusCode: 403, + message: 'you do not have the permission', }); }); }); @@ -226,15 +206,9 @@ describe('persistTimeline', () => { const timelineId = null; const importTimeline = timelineData; const mockPostTimelineResponse = { - data: { - persistTimeline: { - timeline: { - ...timelineData, - savedObjectId: '9d5693e0-a42a-11ea-b8f4-c5434162742a', - version: 'WzMzMiwxXQ==', - }, - }, - }, + ...timelineData, + savedObjectId: '9d5693e0-a42a-11ea-b8f4-c5434162742a', + version: 'WzMzMiwxXQ==', }; const version = null; @@ -273,17 +247,11 @@ describe('persistTimeline', () => { const timelineId = '9d5693e0-a42a-11ea-b8f4-c5434162742a'; const inputTimeline = timelineData; const mockPatchTimelineResponseNew = { - data: { - persistTimeline: { - timeline: { - ...mockPatchTimelineResponse.data.persistTimeline.timeline, - version: 'WzMzMiwxXQ==', - description: 'x', - created: 1591092702804, - updated: 1591092705206, - }, - }, - }, + ...mockPatchTimelineResponse, + version: 'WzMzMiwxXQ==', + description: 'x', + created: 1591092702804, + updated: 1591092705206, }; const version = 'initial version'; @@ -454,15 +422,9 @@ describe('cleanDraftTimeline', () => { describe('copyTimeline', () => { const mockPostTimelineResponse = { - data: { - persistTimeline: { - timeline: { - ...timelineData, - savedObjectId: '9d5693e0-a42a-11ea-b8f4-c5434162742a', - version: 'WzMzMiwxXQ==', - }, - }, - }, + ...timelineData, + savedObjectId: '9d5693e0-a42a-11ea-b8f4-c5434162742a', + version: 'WzMzMiwxXQ==', }; const saveSavedSearchMock = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index fa006293719d..03f3d3ff2ff2 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -67,26 +67,30 @@ const createToasterPlainError = (message: string) => new ToasterError([message]) const parseOrThrow = parseOrThrowErrorFactory(createToasterPlainError); -const decodeTimelineResponse = (respTimeline?: PersistTimelineResponse | TimelineErrorResponse) => - parseOrThrow(PersistTimelineResponse)(respTimeline); +const decodeTimelineResponse = ( + respTimeline?: PersistTimelineResponse | TimelineErrorResponse +): PersistTimelineResponse => parseOrThrow(PersistTimelineResponse)(respTimeline); -const decodeSingleTimelineResponse = (respTimeline?: GetTimelineResponse) => +const decodeSingleTimelineResponse = (respTimeline?: GetTimelineResponse): GetTimelineResponse => parseOrThrow(GetTimelineResponse)(respTimeline); -const decodeResolvedSingleTimelineResponse = (respTimeline?: ResolveTimelineResponse) => - parseOrThrow(ResolveTimelineResponse)(respTimeline); +const decodeResolvedSingleTimelineResponse = ( + respTimeline?: ResolveTimelineResponse +): ResolveTimelineResponse => parseOrThrow(ResolveTimelineResponse)(respTimeline); -const decodeGetTimelinesResponse = (respTimeline: GetTimelinesResponse) => +const decodeGetTimelinesResponse = (respTimeline: GetTimelinesResponse): GetTimelinesResponse => parseOrThrow(GetTimelinesResponse)(respTimeline); -const decodeTimelineErrorResponse = (respTimeline?: TimelineErrorResponse) => +const decodeTimelineErrorResponse = (respTimeline?: TimelineErrorResponse): TimelineErrorResponse => parseOrThrow(TimelineErrorResponse)(respTimeline); -const decodePrepackedTimelineResponse = (respTimeline?: ImportTimelineResult) => - parseOrThrow(ImportTimelineResult)(respTimeline); +const decodePrepackedTimelineResponse = ( + respTimeline?: ImportTimelineResult +): ImportTimelineResult => parseOrThrow(ImportTimelineResult)(respTimeline); -const decodeResponseFavoriteTimeline = (respTimeline?: PersistFavoriteRouteResponse) => - parseOrThrow(PersistFavoriteRouteResponse)(respTimeline); +const decodeResponseFavoriteTimeline = ( + respTimeline?: PersistFavoriteRouteResponse +): PersistFavoriteRouteResponse => parseOrThrow(PersistFavoriteRouteResponse)(respTimeline); const postTimeline = async ({ timeline, @@ -219,22 +223,19 @@ export const persistTimeline = async ({ const templateTimelineInfo = timeline.timelineType === TimelineTypeEnum.template ? { - templateTimelineId: - draftTimeline.data.persistTimeline.timeline.templateTimelineId ?? - timeline.templateTimelineId, + templateTimelineId: draftTimeline.templateTimelineId ?? timeline.templateTimelineId, templateTimelineVersion: - draftTimeline.data.persistTimeline.timeline.templateTimelineVersion ?? - timeline.templateTimelineVersion, + draftTimeline.templateTimelineVersion ?? timeline.templateTimelineVersion, } : {}; return patchTimeline({ - timelineId: draftTimeline.data.persistTimeline.timeline.savedObjectId, + timelineId: draftTimeline.savedObjectId, timeline: { ...timeline, ...templateTimelineInfo, }, - version: draftTimeline.data.persistTimeline.timeline.version ?? '', + version: draftTimeline.version ?? '', savedSearch, }); } @@ -250,19 +251,10 @@ export const persistTimeline = async ({ savedSearch, }); } catch (err) { - if (err.status_code === 403 || err.body.status_code === 403) { + if (err.status_code === 403 || err.body?.status_code === 403) { return Promise.resolve({ - data: { - persistTimeline: { - code: 403, - message: err.message || err.body.message, - timeline: { - ...timeline, - savedObjectId: '', - version: '', - }, - }, - }, + statusCode: 403, + message: err.message || err.body.message, }); } return Promise.resolve(err); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx index 68846de0fed3..f00ca0551a9a 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx @@ -6,7 +6,7 @@ */ import { DataLoadingState } from '@kbn/unified-data-table'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import type { TimelineArgs, UseTimelineEventsProps } from '.'; import { initSortDefault, useTimelineEvents } from '.'; import { SecurityPageName } from '../../../common/constants'; @@ -60,21 +60,23 @@ jest.mock('../../common/lib/kibana', () => ({ mockSearch(); return { subscribe: jest.fn().mockImplementation(({ next }) => { - next({ - isRunning: false, - isPartial: false, - inspect: { - dsl: [], - response: [], - }, - edges: mockEvents.map((item) => ({ node: item })), - pageInfo: { - activePage: 0, - totalPages: 10, - }, - rawResponse: {}, - totalCount: mockTimelineData.length, - }); + setTimeout(() => { + next({ + isRunning: false, + isPartial: false, + inspect: { + dsl: [], + response: [], + }, + edges: mockEvents.map((item) => ({ node: item })), + pageInfo: { + activePage: 0, + totalPages: 10, + }, + rawResponse: {}, + totalCount: mockTimelineData.length, + }); + }, 0); return { unsubscribe: jest.fn() }; }), }; @@ -135,47 +137,41 @@ describe('useTimelineEvents', () => { }; test('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseTimelineEventsProps, - [DataLoadingState, TimelineArgs] - >((args) => useTimelineEvents(args), { - initialProps: { ...props }, - }); - - // useEffect on params request - await waitForNextUpdate(); - expect(result.current).toEqual([ - DataLoadingState.loaded, - { - events: [], - id: TimelineId.active, - inspect: result.current[1].inspect, - loadPage: result.current[1].loadPage, - pageInfo: result.current[1].pageInfo, - refetch: result.current[1].refetch, - totalCount: -1, - refreshedAt: 0, - }, - ]); + const { result } = renderHook((args) => useTimelineEvents(args), { + initialProps: props, }); + + expect(result.current).toEqual([ + DataLoadingState.loading, + { + events: [], + id: TimelineId.active, + inspect: expect.objectContaining({ dsl: [], response: [] }), + loadPage: expect.any(Function), + pageInfo: expect.objectContaining({ + activePage: 0, + querySize: 0, + }), + refetch: expect.any(Function), + totalCount: -1, + refreshedAt: 0, + }, + ]); }); test('happy path query', async () => { - await act(async () => { - const { result, waitForNextUpdate, rerender } = renderHook< - UseTimelineEventsProps, - [DataLoadingState, TimelineArgs] - >((args) => useTimelineEvents(args), { - initialProps: { ...props }, - }); - - // useEffect on params request - await waitForNextUpdate(); - rerender({ ...props, startDate, endDate }); - // useEffect on params request - await waitForNextUpdate(); + const { result, rerender } = renderHook< + [DataLoadingState, TimelineArgs], + UseTimelineEventsProps + >((args) => useTimelineEvents(args), { + initialProps: props, + }); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender({ ...props, startDate, endDate }); + // useEffect on params request + await waitFor(() => { expect(mockSearch).toHaveBeenCalledTimes(2); expect(result.current).toEqual([ DataLoadingState.loaded, @@ -194,73 +190,53 @@ describe('useTimelineEvents', () => { }); test('Mock cache for active timeline when switching page', async () => { - await act(async () => { - const { result, waitForNextUpdate, rerender } = renderHook< - UseTimelineEventsProps, - [DataLoadingState, TimelineArgs] - >((args) => useTimelineEvents(args), { - initialProps: { ...props }, - }); - - // useEffect on params request - await waitForNextUpdate(); - rerender({ ...props, startDate, endDate }); - // useEffect on params request - await waitForNextUpdate(); - - mockUseRouteSpy.mockReturnValue([ - { - pageName: SecurityPageName.timelines, - detailName: undefined, - tabName: undefined, - search: '', - pathName: '/timelines', - }, - ]); - - expect(mockSearch).toHaveBeenCalledTimes(2); - - expect(result.current).toEqual([ - DataLoadingState.loaded, - { - events: mockEvents, - id: TimelineId.active, - inspect: result.current[1].inspect, - loadPage: result.current[1].loadPage, - pageInfo: result.current[1].pageInfo, - refetch: result.current[1].refetch, - totalCount: 32, - refreshedAt: result.current[1].refreshedAt, - }, - ]); + const { result, rerender } = renderHook< + [DataLoadingState, TimelineArgs], + UseTimelineEventsProps + >((args) => useTimelineEvents(args), { + initialProps: props, }); + + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender({ ...props, startDate, endDate }); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); + + mockUseRouteSpy.mockReturnValue([ + { + pageName: SecurityPageName.timelines, + detailName: undefined, + tabName: undefined, + search: '', + pathName: '/timelines', + }, + ]); + + expect(mockSearch).toHaveBeenCalledTimes(2); + + expect(result.current).toEqual([ + DataLoadingState.loaded, + { + events: mockEvents, + id: TimelineId.active, + inspect: result.current[1].inspect, + loadPage: result.current[1].loadPage, + pageInfo: result.current[1].pageInfo, + refetch: result.current[1].refetch, + totalCount: 32, + refreshedAt: result.current[1].refreshedAt, + }, + ]); }); test('Correlation pagination is calling search strategy when switching page', async () => { - await act(async () => { - const { result, waitForNextUpdate, rerender } = renderHook< - UseTimelineEventsProps, - [DataLoadingState, TimelineArgs] - >((args) => useTimelineEvents(args), { - initialProps: { - ...props, - language: 'eql', - eqlOptions: { - eventCategoryField: 'category', - tiebreakerField: '', - timestampField: '@timestamp', - query: 'find it EQL', - size: 100, - }, - }, - }); - - // useEffect on params request - await waitForNextUpdate(); - rerender({ + const { result, rerender } = renderHook< + [DataLoadingState, TimelineArgs], + UseTimelineEventsProps + >((args) => useTimelineEvents(args), { + initialProps: { ...props, - startDate, - endDate, language: 'eql', eqlOptions: { eventCategoryField: 'category', @@ -269,123 +245,113 @@ describe('useTimelineEvents', () => { query: 'find it EQL', size: 100, }, - }); - // useEffect on params request - await waitForNextUpdate(); - mockSearch.mockReset(); + }, + }); + + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender({ + ...props, + startDate, + endDate, + language: 'eql', + eqlOptions: { + eventCategoryField: 'category', + tiebreakerField: '', + timestampField: '@timestamp', + query: 'find it EQL', + size: 100, + }, + }); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); + mockSearch.mockReset(); + act(() => { result.current[1].loadPage(4); - await waitForNextUpdate(); - expect(mockSearch).toHaveBeenCalledTimes(1); }); + await waitFor(() => expect(mockSearch).toHaveBeenCalledTimes(1)); }); test('should query again when a new field is added', async () => { - await act(async () => { - const { waitForNextUpdate, rerender } = renderHook< - UseTimelineEventsProps, - [DataLoadingState, TimelineArgs] - >((args) => useTimelineEvents(args), { - initialProps: { ...props }, - }); - - // useEffect on params request - await waitForNextUpdate(); - rerender({ ...props, startDate, endDate }); - // useEffect on params request - await waitForNextUpdate(); - - expect(mockSearch).toHaveBeenCalledTimes(2); - mockSearch.mockClear(); + const { rerender } = renderHook((args) => useTimelineEvents(args), { + initialProps: props, + }); - rerender({ - ...props, - startDate, - endDate, - fields: ['@timestamp', 'event.kind', 'event.category'], - }); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender({ ...props, startDate, endDate }); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); - await waitForNextUpdate(); + expect(mockSearch).toHaveBeenCalledTimes(2); + mockSearch.mockClear(); - expect(mockSearch).toHaveBeenCalledTimes(1); + rerender({ + ...props, + startDate, + endDate, + fields: ['@timestamp', 'event.kind', 'event.category'], }); + + await waitFor(() => expect(mockSearch).toHaveBeenCalledTimes(1)); }); test('should not query again when a field is removed', async () => { - await act(async () => { - const { waitForNextUpdate, rerender } = renderHook< - UseTimelineEventsProps, - [DataLoadingState, TimelineArgs] - >((args) => useTimelineEvents(args), { - initialProps: { ...props }, - }); - - // useEffect on params request - await waitForNextUpdate(); - rerender({ ...props, startDate, endDate }); - // useEffect on params request - await waitForNextUpdate(); + const { rerender } = renderHook((args) => useTimelineEvents(args), { + initialProps: props, + }); - expect(mockSearch).toHaveBeenCalledTimes(2); - mockSearch.mockClear(); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender({ ...props, startDate, endDate }); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); - rerender({ ...props, startDate, endDate, fields: ['@timestamp'] }); + expect(mockSearch).toHaveBeenCalledTimes(2); + mockSearch.mockClear(); - // since there is no new update in useEffect, it should throw an timeout error - await expect(waitForNextUpdate()).rejects.toThrowError(); + rerender({ ...props, startDate, endDate, fields: ['@timestamp'] }); - expect(mockSearch).toHaveBeenCalledTimes(0); - }); + await waitFor(() => expect(mockSearch).toHaveBeenCalledTimes(0)); }); test('should not query again when a removed field is added back', async () => { - await act(async () => { - const { waitForNextUpdate, rerender } = renderHook< - UseTimelineEventsProps, - [DataLoadingState, TimelineArgs] - >((args) => useTimelineEvents(args), { - initialProps: { ...props }, - }); + const { rerender } = renderHook((args) => useTimelineEvents(args), { + initialProps: props, + }); - // useEffect on params request - await waitForNextUpdate(); - rerender({ ...props, startDate, endDate }); - // useEffect on params request - await waitForNextUpdate(); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); + rerender({ ...props, startDate, endDate }); + // useEffect on params request + await waitFor(() => new Promise((resolve) => resolve(null))); - expect(mockSearch).toHaveBeenCalledTimes(2); - mockSearch.mockClear(); + expect(mockSearch).toHaveBeenCalledTimes(2); + mockSearch.mockClear(); - // remove `event.kind` from default fields - rerender({ ...props, startDate, endDate, fields: ['@timestamp'] }); + // remove `event.kind` from default fields + rerender({ ...props, startDate, endDate, fields: ['@timestamp'] }); - // since there is no new update in useEffect, it should throw an timeout error - await expect(waitForNextUpdate()).rejects.toThrowError(); + await waitFor(() => new Promise((resolve) => resolve(null))); - expect(mockSearch).toHaveBeenCalledTimes(0); + expect(mockSearch).toHaveBeenCalledTimes(0); - // request default Fields - rerender({ ...props, startDate, endDate }); + // request default Fields + rerender({ ...props, startDate, endDate }); - // since there is no new update in useEffect, it should throw an timeout error - await expect(waitForNextUpdate()).rejects.toThrowError(); - - expect(mockSearch).toHaveBeenCalledTimes(0); - }); + // since there is no new update in useEffect, it should throw an timeout error + // await expect(waitFor(() => null)).rejects.toThrowError(); + await waitFor(() => expect(mockSearch).toHaveBeenCalledTimes(0)); }); describe('Fetch Notes', () => { test('should call onLoad for notes when events are fetched', async () => { - await act(async () => { - const { waitFor } = renderHook<UseTimelineEventsProps, [DataLoadingState, TimelineArgs]>( - (args) => useTimelineEvents(args), - { - initialProps: { ...props }, - } - ); - - await waitFor(() => { - expect(mockSearch).toHaveBeenCalledTimes(1); - expect(onLoadMock).toHaveBeenNthCalledWith(1, expect.objectContaining(mockEvents)); - }); + renderHook((args) => useTimelineEvents(args), { + initialProps: props, + }); + + await waitFor(() => { + expect(mockSearch).toHaveBeenCalledTimes(1); + expect(onLoadMock).toHaveBeenNthCalledWith(1, expect.objectContaining(mockEvents)); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 6c40ca1b7dfd..0301f0123c30 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -41,7 +41,7 @@ import { TimelineId } from '../../../common/types/timeline'; import { useRouteSpy } from '../../common/utils/route/use_route_spy'; import { activeTimeline } from './active_timeline_context'; import type { - EqlOptionsSelected, + EqlOptions, TimelineEqlResponse, } from '../../../common/search_strategy/timeline/events/eql'; import { useTrackHttpRequest } from '../../common/lib/apm/use_track_http_request'; @@ -84,7 +84,7 @@ type TimelineResponse<T extends KueryFilterQueryKind> = T extends 'kuery' export interface UseTimelineEventsProps { dataViewId: string | null; endDate?: string; - eqlOptions?: EqlOptionsSelected; + eqlOptions?: EqlOptions; fields: string[]; filterQuery?: ESQuery | string; id: string; @@ -112,7 +112,7 @@ export const initSortDefault: TimelineRequestSortField[] = [ }, ]; -const deStructureEqlOptions = (eqlOptions?: EqlOptionsSelected) => ({ +const deStructureEqlOptions = (eqlOptions?: EqlOptions) => ({ ...(!isEmpty(eqlOptions?.eventCategoryField) ? { eventCategoryField: eqlOptions?.eventCategoryField, diff --git a/x-pack/plugins/security_solution/public/timelines/containers/use_timeline_data_filters.test.tsx b/x-pack/plugins/security_solution/public/timelines/containers/use_timeline_data_filters.test.tsx index 9142aca78424..db9413df3059 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/use_timeline_data_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/use_timeline_data_filters.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { mockGlobalState, TestProviders, createMockStore } from '../../common/mock'; import { useTimelineDataFilters } from './use_timeline_data_filters'; import React from 'react'; diff --git a/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.test.tsx index a4c054371a31..0864c2bb024b 100644 --- a/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useCreateTimeline } from './use_create_timeline'; import type { TimeRange } from '../../common/store/inputs/model'; import { RowRendererCount, TimelineTypeEnum } from '../../../common/api/timeline'; diff --git a/x-pack/plugins/security_solution/public/timelines/store/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/actions.ts index 976d35c03065..78f74ef4670e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/actions.ts @@ -188,7 +188,7 @@ export const toggleModalSaveTimeline = actionCreator<{ export const updateEqlOptions = actionCreator<{ id: string; field: FieldsEqlOptions; - value: string | undefined; + value: string | number | undefined; }>('UPDATE_EQL_OPTIONS_TIMELINE'); export const setEventsLoading = actionCreator<{ diff --git a/x-pack/plugins/security_solution/public/timelines/store/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/defaults.ts index 5dbe2d1b9c1e..69d306be9b85 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/defaults.ts @@ -32,7 +32,6 @@ export const timelineDefaults: SubsetTimelineModel & description: '', eqlOptions: { eventCategoryField: 'event.category', - tiebreakerField: '', timestampField: '@timestamp', query: '', size: 100, diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts index 85bf3652ab0b..8ae986e95c4a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts @@ -39,16 +39,8 @@ describe('Timeline middleware helpers', () => { it('should return a draft timeline with a savedObjectId when an unsaved timeline is passed', async () => { const mockSavedObjectId = 'mockSavedObjectId'; (persistTimeline as jest.Mock).mockResolvedValue({ - data: { - persistTimeline: { - code: 200, - message: 'success', - timeline: { - ...mockGlobalState.timeline.timelineById[TimelineId.test], - savedObjectId: mockSavedObjectId, - }, - }, - }, + ...mockGlobalState.timeline.timelineById[TimelineId.test], + savedObjectId: mockSavedObjectId, }); const returnedTimeline = await ensureTimelineIsSaved({ diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts index c5cb62b523bb..adeca9279517 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts @@ -6,6 +6,7 @@ */ import type { MiddlewareAPI, Dispatch, AnyAction } from 'redux'; +import type { IHttpFetchError } from '@kbn/core/public'; import type { State } from '../../../common/store/types'; import { ALL_TIMELINE_QUERY_ID } from '../../containers/all'; import type { inputsModel } from '../../../common/store/inputs'; @@ -62,3 +63,17 @@ export async function ensureTimelineIsSaved({ // Make sure we're returning the most updated version of the timeline return selectTimelineById(store.getState(), localTimelineId); } + +export function isHttpFetchError( + error: unknown +): error is IHttpFetchError<{ status_code: number }> { + return ( + error !== null && + typeof error === 'object' && + 'body' in error && + error.body !== null && + typeof error.body === 'object' && + `status_code` in error.body && + typeof error.body.status_code === 'number' + ); +} diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.test.ts index 50a0ed53c491..01be4f72c83f 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.test.ts @@ -35,7 +35,14 @@ jest.mock('../actions', () => { }; }); jest.mock('../../containers/api'); -jest.mock('./helpers'); +jest.mock('./helpers', () => { + const actual = jest.requireActual('./helpers'); + + return { + ...actual, + refreshTimelines: jest.fn(), + }; +}); const startTimelineSavingMock = startTimelineSaving as unknown as jest.Mock; const endTimelineSavingMock = endTimelineSaving as unknown as jest.Mock; @@ -53,14 +60,9 @@ describe('Timeline favorite middleware', () => { it('should persist a timeline favorite when a favorite action is dispatched', async () => { (persistFavorite as jest.Mock).mockResolvedValue({ - data: { - persistFavorite: { - code: 200, - favorite: [{}], - savedObjectId: newSavedObjectId, - version: newVersion, - }, - }, + favorite: [{}], + savedObjectId: newSavedObjectId, + version: newVersion, }); expect(selectTimelineById(store.getState(), TimelineId.test).isFavorite).toEqual(false); await store.dispatch(updateIsFavorite({ id: TimelineId.test, isFavorite: true })); @@ -88,14 +90,9 @@ describe('Timeline favorite middleware', () => { }) ); (persistFavorite as jest.Mock).mockResolvedValue({ - data: { - persistFavorite: { - code: 200, - favorite: [], - savedObjectId: newSavedObjectId, - version: newVersion, - }, - }, + favorite: [], + savedObjectId: newSavedObjectId, + version: newVersion, }); expect(selectTimelineById(store.getState(), TimelineId.test).isFavorite).toEqual(true); await store.dispatch(updateIsFavorite({ id: TimelineId.test, isFavorite: false })); @@ -113,12 +110,8 @@ describe('Timeline favorite middleware', () => { }); it('should show an error message when the call is unauthorized', async () => { - (persistFavorite as jest.Mock).mockResolvedValue({ - data: { - persistFavorite: { - code: 403, - }, - }, + (persistFavorite as jest.Mock).mockRejectedValue({ + body: { status_code: 403 }, }); await store.dispatch(updateIsFavorite({ id: TimelineId.test, isFavorite: true })); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.ts index bf4854e60666..4f195e5fa13f 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { get } from 'lodash/fp'; import type { Action, Middleware } from 'redux'; import type { CoreStart } from '@kbn/core/public'; @@ -17,12 +16,11 @@ import { startTimelineSaving, showCallOutUnauthorizedMsg, } from '../actions'; -import type { FavoriteTimelineResponse } from '../../../../common/api/timeline'; import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { persistFavorite } from '../../containers/api'; import { selectTimelineById } from '../selectors'; import * as i18n from '../../pages/translations'; -import { refreshTimelines } from './helpers'; +import { isHttpFetchError, refreshTimelines } from './helpers'; type FavoriteTimelineAction = ReturnType<typeof updateIsFavorite>; @@ -42,19 +40,13 @@ export const favoriteTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, S store.dispatch(startTimelineSaving({ id })); try { - const result = await persistFavorite({ + const response = await persistFavorite({ timelineId: timeline.id, templateTimelineId: timeline.templateTimelineId, templateTimelineVersion: timeline.templateTimelineVersion, timelineType: timeline.timelineType ?? TimelineTypeEnum.default, }); - const response: FavoriteTimelineResponse = get('data.persistFavorite', result); - - if (response.code === 403) { - store.dispatch(showCallOutUnauthorizedMsg()); - } - refreshTimelines(store.getState()); store.dispatch( @@ -69,10 +61,14 @@ export const favoriteTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, S }) ); } catch (error) { - kibana.notifications.toasts.addDanger({ - title: i18n.UPDATE_TIMELINE_ERROR_TITLE, - text: error?.message ?? i18n.UPDATE_TIMELINE_ERROR_TEXT, - }); + if (isHttpFetchError(error) && error.body?.status_code === 403) { + store.dispatch(showCallOutUnauthorizedMsg()); + } else { + kibana.notifications.toasts.addDanger({ + title: i18n.UPDATE_TIMELINE_ERROR_TITLE, + text: error?.message ?? i18n.UPDATE_TIMELINE_ERROR_TEXT, + }); + } } finally { store.dispatch( endTimelineSaving({ diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts index 9fb058560104..f6f3a0d0dc15 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.test.ts @@ -70,14 +70,8 @@ describe('Timeline note middleware', () => { it('should persist a timeline note', async () => { (persistNote as jest.Mock).mockResolvedValue({ - data: { - persistNote: { - code: 200, - message: 'success', - note: { - noteId: testNote.id, - }, - }, + note: { + noteId: testNote.id, }, }); expect(selectTimelineById(store.getState(), TimelineId.test).noteIds).toEqual([]); @@ -92,14 +86,8 @@ describe('Timeline note middleware', () => { it('should persist a note on an event of a timeline', async () => { (persistNote as jest.Mock).mockResolvedValue({ - data: { - persistNote: { - code: 200, - message: 'success', - note: { - noteId: testNote.id, - }, - }, + note: { + noteId: testNote.id, }, }); expect(selectTimelineById(store.getState(), TimelineId.test).eventIdToNoteIds).toEqual({ @@ -123,14 +111,8 @@ describe('Timeline note middleware', () => { it('should ensure the timeline is saved or in draft mode before creating a note', async () => { (persistNote as jest.Mock).mockResolvedValue({ - data: { - persistNote: { - code: 200, - message: 'success', - note: { - noteId: testNote.id, - }, - }, + note: { + noteId: testNote.id, }, }); @@ -159,15 +141,9 @@ describe('Timeline note middleware', () => { it('should pin the event when the event is not pinned yet', async () => { const testTimelineId = 'testTimelineId'; (persistNote as jest.Mock).mockResolvedValue({ - data: { - persistNote: { - code: 200, - message: 'success', - note: { - noteId: testNote.id, - timelineId: testTimelineId, - }, - }, + note: { + noteId: testNote.id, + timelineId: testTimelineId, }, }); @@ -207,15 +183,9 @@ describe('Timeline note middleware', () => { ); const testTimelineId = 'testTimelineId'; (persistNote as jest.Mock).mockResolvedValue({ - data: { - persistNote: { - code: 200, - message: 'success', - note: { - noteId: testNote.id, - timelineId: testTimelineId, - }, - }, + note: { + noteId: testNote.id, + timelineId: testTimelineId, }, }); @@ -232,12 +202,8 @@ describe('Timeline note middleware', () => { }); it('should show an error message when the call is unauthorized', async () => { - (persistNote as jest.Mock).mockResolvedValue({ - data: { - persistNote: { - code: 403, - }, - }, + (persistNote as jest.Mock).mockRejectedValue({ + body: { status_code: 403 }, }); await store.dispatch(updateNote({ note: testNote })); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.ts index 876fd613d079..44b6e6d87203 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_note.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { get } from 'lodash/fp'; import type { Action, Middleware } from 'redux'; import type { CoreStart } from '@kbn/core/public'; @@ -22,10 +21,9 @@ import { pinEvent, } from '../actions'; import { persistNote } from '../../containers/notes/api'; -import type { ResponseNote } from '../../../../common/api/timeline'; import { selectTimelineById } from '../selectors'; import * as i18n from '../../pages/translations'; -import { ensureTimelineIsSaved, refreshTimelines } from './helpers'; +import { ensureTimelineIsSaved, isHttpFetchError, refreshTimelines } from './helpers'; type NoteAction = ReturnType<typeof addNote | typeof addNoteToEvent>; @@ -64,7 +62,7 @@ export const addNoteToTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, throw new Error('Cannot create note without a timelineId'); } - const result = await persistNote({ + const response = await persistNote({ noteId: null, version: null, note: { @@ -74,11 +72,6 @@ export const addNoteToTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, }, }); - const response: ResponseNote = get('data.persistNote', result); - if (response.code === 403) { - store.dispatch(showCallOutUnauthorizedMsg()); - } - refreshTimelines(store.getState()); await store.dispatch( @@ -112,10 +105,14 @@ export const addNoteToTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, } } } catch (error) { - kibana.notifications.toasts.addDanger({ - title: i18n.UPDATE_TIMELINE_ERROR_TITLE, - text: error?.message ?? i18n.UPDATE_TIMELINE_ERROR_TEXT, - }); + if (isHttpFetchError(error) && error.body?.status_code === 403) { + store.dispatch(showCallOutUnauthorizedMsg()); + } else { + kibana.notifications.toasts.addDanger({ + title: i18n.UPDATE_TIMELINE_ERROR_TITLE, + text: error?.message ?? i18n.UPDATE_TIMELINE_ERROR_TEXT, + }); + } } finally { store.dispatch( endTimelineSaving({ diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts index 67374a66019d..7108ecfc4a61 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.test.ts @@ -63,12 +63,7 @@ describe('Timeline pinned event middleware', () => { it('should persist a timeline pin event action', async () => { (persistPinnedEvent as jest.Mock).mockResolvedValue({ - data: { - persistPinnedEventOnTimeline: { - code: 200, - eventId: testEventId, - }, - }, + eventId: testEventId, }); expect(selectTimelineById(store.getState(), TimelineId.test).pinnedEventIds).toEqual({}); await store.dispatch(pinEvent({ id: TimelineId.test, eventId: testEventId })); @@ -103,7 +98,7 @@ describe('Timeline pinned event middleware', () => { ); (persistPinnedEvent as jest.Mock).mockResolvedValue({ - data: {}, + unpinned: true, }); expect(selectTimelineById(store.getState(), TimelineId.test).pinnedEventIds).toEqual({ [testEventId]: true, @@ -117,13 +112,7 @@ describe('Timeline pinned event middleware', () => { }); it('should ensure the timeline is saved or in draft mode before pinning an event', async () => { - (persistPinnedEvent as jest.Mock).mockResolvedValue({ - data: { - persistPinnedEventOnTimeline: { - code: 200, - }, - }, - }); + (persistPinnedEvent as jest.Mock).mockResolvedValue({}); expect(selectTimelineById(store.getState(), TimelineId.test).pinnedEventIds).toEqual({}); await store.dispatch(pinEvent({ id: TimelineId.test, eventId: testEventId })); @@ -139,12 +128,8 @@ describe('Timeline pinned event middleware', () => { }); it('should show an error message when the call is unauthorized', async () => { - (persistPinnedEvent as jest.Mock).mockResolvedValue({ - data: { - persistPinnedEventOnTimeline: { - code: 403, - }, - }, + (persistPinnedEvent as jest.Mock).mockRejectedValue({ + body: { status_code: 403 }, }); await store.dispatch(unPinEvent({ id: TimelineId.test, eventId: testEventId })); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts index 38e00af3f5f8..8ef7a661cc6e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_pinned_event.ts @@ -21,7 +21,7 @@ import { showCallOutUnauthorizedMsg, } from '../actions'; import { persistPinnedEvent } from '../../containers/pinned_event/api'; -import { ensureTimelineIsSaved, refreshTimelines } from './helpers'; +import { ensureTimelineIsSaved, isHttpFetchError, refreshTimelines } from './helpers'; type PinnedEventAction = ReturnType<typeof pinEvent | typeof unPinEvent>; @@ -55,7 +55,7 @@ export const addPinnedEventToTimelineMiddleware: (kibana: CoreStart) => Middlewa throw new Error('Cannot create a pinned event without a timelineId'); } - const result = await persistPinnedEvent({ + const response = await persistPinnedEvent({ pinnedEventId: timeline.pinnedEventsSaveObject[eventId] != null ? timeline.pinnedEventsSaveObject[eventId].pinnedEventId @@ -64,17 +64,10 @@ export const addPinnedEventToTimelineMiddleware: (kibana: CoreStart) => Middlewa timelineId: timeline.savedObjectId, }); - const response = result.data.persistPinnedEventOnTimeline; - if (response && 'code' in response && response.code === 403) { - store.dispatch(showCallOutUnauthorizedMsg()); - } - refreshTimelines(store.getState()); const currentTimeline = selectTimelineById(store.getState(), action.payload.id); - // The response is null or empty in case we unpinned an event. - // In that case we want to remove the locally pinned event. - if (!response || !('eventId' in response)) { + if ('unpinned' in response) { return store.dispatch( updateTimeline({ id: action.payload.id, @@ -106,10 +99,14 @@ export const addPinnedEventToTimelineMiddleware: (kibana: CoreStart) => Middlewa ); } } catch (error) { - kibana.notifications.toasts.addDanger({ - title: i18n.UPDATE_TIMELINE_ERROR_TITLE, - text: error?.message ?? i18n.UPDATE_TIMELINE_ERROR_TEXT, - }); + if (isHttpFetchError(error) && error.body?.status_code === 403) { + store.dispatch(showCallOutUnauthorizedMsg()); + } else { + kibana.notifications.toasts.addDanger({ + title: i18n.UPDATE_TIMELINE_ERROR_TITLE, + text: error?.message ?? i18n.UPDATE_TIMELINE_ERROR_TEXT, + }); + } } finally { store.dispatch( endTimelineSaving({ diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts index c3d7a26d7b02..29c2ad49ba3a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts @@ -58,16 +58,8 @@ describe('Timeline save middleware', () => { it('should persist a timeline', async () => { (persistTimeline as jest.Mock).mockResolvedValue({ - data: { - persistTimeline: { - code: 200, - message: 'success', - timeline: { - savedObjectId: 'soid', - version: 'newVersion', - }, - }, - }, + savedObjectId: 'soid', + version: 'newVersion', }); await store.dispatch(setChanged({ id: TimelineId.test, changed: true })); expect(selectTimelineById(store.getState(), TimelineId.test)).toEqual( @@ -92,16 +84,8 @@ describe('Timeline save middleware', () => { it('should copy a timeline', async () => { (copyTimeline as jest.Mock).mockResolvedValue({ - data: { - persistTimeline: { - code: 200, - message: 'success', - timeline: { - savedObjectId: 'soid', - version: 'newVersion', - }, - }, - }, + savedObjectId: 'soid', + version: 'newVersion', }); await store.dispatch(setChanged({ id: TimelineId.test, changed: true })); expect(selectTimelineById(store.getState(), TimelineId.test)).toEqual( diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts index a0d0ab4dd106..08cbb6bfa3ea 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts @@ -63,7 +63,7 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State store.dispatch(startTimelineSaving({ id: localTimelineId })); try { - const result = await (action.payload.saveAsNew && timeline.id + const response = await (action.payload.saveAsNew && timeline.id ? copyTimeline({ timelineId, timeline: { @@ -84,8 +84,8 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State savedSearch: timeline.savedSearch, })); - if (isTimelineErrorResponse(result)) { - const error = getErrorFromResponse(result); + if (isTimelineErrorResponse(response)) { + const error = getErrorFromResponse(response); switch (error?.errorCode) { case 403: store.dispatch(showCallOutUnauthorizedMsg()); @@ -106,7 +106,6 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State return; } - const response = result?.data?.persistTimeline; if (response == null) { kibana.notifications.toasts.addDanger({ title: i18n.UPDATE_TIMELINE_ERROR_TITLE, @@ -122,15 +121,15 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State id: localTimelineId, timeline: { ...timeline, - id: response.timeline.savedObjectId, - updated: response.timeline.updated ?? undefined, - savedObjectId: response.timeline.savedObjectId, - version: response.timeline.version, - status: response.timeline.status ?? TimelineStatusEnum.active, - timelineType: response.timeline.timelineType ?? TimelineTypeEnum.default, - templateTimelineId: response.timeline.templateTimelineId ?? null, - templateTimelineVersion: response.timeline.templateTimelineVersion ?? null, - savedSearchId: response.timeline.savedSearchId ?? null, + id: response.savedObjectId, + updated: response.updated ?? undefined, + savedObjectId: response.savedObjectId, + version: response.version, + status: response.status ?? TimelineStatusEnum.active, + timelineType: response.timelineType ?? TimelineTypeEnum.default, + templateTimelineId: response.templateTimelineId ?? null, + templateTimelineVersion: response.templateTimelineVersion ?? null, + savedSearchId: response.savedSearchId ?? null, isSaving: false, }, }) diff --git a/x-pack/plugins/security_solution/public/timelines/store/model.ts b/x-pack/plugins/security_solution/public/timelines/store/model.ts index 92c435f93cb4..f93d6b3f6b64 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/model.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/model.ts @@ -8,10 +8,7 @@ import type { Filter } from '@kbn/es-query'; import type { SavedSearch } from '@kbn/saved-search-plugin/common'; import type { SessionViewConfig } from '../../../common/types'; -import type { - EqlOptionsSelected, - TimelineNonEcsData, -} from '../../../common/search_strategy/timeline'; +import type { EqlOptions, TimelineNonEcsData } from '../../../common/search_strategy/timeline'; import type { TimelineTabs, ScrollToTopEvent, @@ -42,7 +39,7 @@ export interface TimelineModel { createdBy?: string; /** A summary of the events and notes in this timeline */ description: string; - eqlOptions: EqlOptionsSelected; + eqlOptions: EqlOptions; /** Type of event you want to see in this timeline */ eventType?: TimelineEventsType; /** A map of events in this timeline to the chronologically ordered notes (in this timeline) associated with the event */ diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 6642e02d5ecd..d0387c5d3abe 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -83,6 +83,7 @@ import type { EntityAnalytics } from './entity_analytics'; import type { Assets } from './assets'; import type { Investigations } from './investigations'; import type { MachineLearning } from './machine_learning'; +import type { SiemMigrations } from './siem_migrations'; import type { Dashboards } from './dashboards'; import type { BreadcrumbsNav } from './common/breadcrumbs/types'; @@ -93,6 +94,7 @@ import type { ConfigSettings } from '../common/config_settings'; import type { OnboardingService } from './onboarding/service'; import type { SolutionNavigation } from './app/solution_navigation/solution_navigation'; import type { TelemetryServiceStart } from './common/lib/telemetry'; +import type { SiemMigrationsService } from './siem_migrations/service'; export interface SetupPlugins { cloud?: CloudSetup; @@ -192,6 +194,7 @@ export type StartServices = CoreStart & customDataService: DataPublicPluginStart; topValuesPopover: TopValuesPopoverService; timelineDataService: DataPublicPluginStart; + siemMigrations: SiemMigrationsService; }; export type StartRenderServices = Pick< @@ -243,6 +246,7 @@ export interface SubPlugins { assets: Assets; investigations: Investigations; machineLearning: MachineLearning; + siemMigrations: SiemMigrations; } // TODO: find a better way to defined these types @@ -266,4 +270,5 @@ export interface StartedSubPlugins { assets: ReturnType<Assets['start']>; investigations: ReturnType<Investigations['start']>; machineLearning: ReturnType<MachineLearning['start']>; + siemMigrations: ReturnType<SiemMigrations['start']>; } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/index.ts index 242639818566..53c49d315ffa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/index.ts @@ -9,4 +9,5 @@ export * from './artifacts'; export * from './actions'; export * from './agent'; export * from './artifacts_exception_list'; +export * from './workflow_insights'; export type { FeatureKeys } from './feature_usage'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.ts b/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.ts index c925c666d695..560873d0a69e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/saved_objects/saved_objects_client_factory.ts @@ -9,10 +9,10 @@ import type { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; import { SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; -import type { HttpServiceSetup } from '@kbn/core/server'; -import { type SavedObjectsClientContract } from '@kbn/core/server'; -import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; +import type { HttpServiceSetup, KibanaRequest } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; import { DEFAULT_SPACE_ID, addSpaceIdToPath } from '@kbn/spaces-plugin/common'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { EndpointError } from '../../../../common/endpoint/errors'; type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract; @@ -39,8 +39,8 @@ export class SavedObjectsClientFactory { private readonly httpServiceSetup: HttpServiceSetup ) {} - protected createFakeHttpRequest(spaceId: string = DEFAULT_SPACE_ID): CoreKibanaRequest { - const fakeRequest = CoreKibanaRequest.from({ + protected createFakeHttpRequest(spaceId: string = DEFAULT_SPACE_ID): KibanaRequest { + const fakeRequest = kibanaRequestFactory({ headers: {}, path: '/', route: { settings: {} }, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/constants.ts b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/constants.ts new file mode 100644 index 000000000000..f0884f2214cb --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/constants.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const DATA_STREAM_PREFIX = '.security-workflow-insights'; +export const COMPONENT_TEMPLATE_NAME = `${DATA_STREAM_PREFIX}-component-template`; +export const INDEX_TEMPLATE_NAME = `${DATA_STREAM_PREFIX}-index-template`; +export const INGEST_PIPELINE_NAME = `${DATA_STREAM_PREFIX}-ingest-pipeline`; +export const DATA_STREAM_NAME = `${DATA_STREAM_PREFIX}-default`; + +export const TOTAL_FIELDS_LIMIT = 2000; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/errors.ts b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/errors.ts new file mode 100644 index 000000000000..fa2cba3a6cd7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/errors.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EndpointError } from '../../../../common/endpoint/errors'; + +export class SecurityWorkflowInsightsFailedInitialized extends EndpointError { + constructor(msg: string) { + super(`security workflow insights service failed to initialize with error: ${msg}`); + } +} diff --git a/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/field_map_configurations.ts b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/field_map_configurations.ts new file mode 100644 index 000000000000..32cc845ab00d --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/field_map_configurations.ts @@ -0,0 +1,183 @@ +/* + * 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 { FieldMap } from '@kbn/data-stream-adapter'; + +export const securityWorkflowInsightsFieldMap: FieldMap = { + '@timestamp': { + type: 'date', + array: false, + required: true, + }, + message: { + type: 'text', + array: false, + required: true, + }, + // endpoint or other part of security + category: { + type: 'keyword', + array: false, + required: true, + }, + // incompatible_virus, noisy_process_tree, etc + type: { + type: 'keyword', + array: false, + required: true, + }, + // the creator of the insight + source: { + type: 'nested', + array: false, + required: true, + }, + // kibana-insight-task, llm-request-id, etc + 'source.id': { + type: 'keyword', + array: false, + required: true, + }, + // endpoint, kibana, llm, etc + 'source.type': { + type: 'keyword', + array: false, + required: true, + }, + // starting timestamp of the source data used to generate the insight + 'source.data_range_start': { + type: 'date', + array: false, + required: true, + }, + // ending timestamp of the source data used to generate the insight + 'source.data_range_end': { + type: 'date', + array: false, + required: true, + }, + // the target that this insight is created for + target: { + type: 'nested', + array: false, + required: true, + }, + // endpoint, policy, etc + 'target.id': { + type: 'keyword', + array: true, + required: true, + }, + // endpoint ids, policy ids, etc + 'target.type': { + type: 'keyword', + array: false, + required: true, + }, + // latest action taken on the insight + action: { + type: 'nested', + array: false, + required: true, + }, + // refreshed, remediated, suppressed, dismissed + 'action.type': { + type: 'keyword', + array: false, + required: true, + }, + 'action.timestamp': { + type: 'date', + array: false, + required: true, + }, + // unique key for this insight, used for deduplicating insights. + // ie. crowdstrike or windows_defender + value: { + type: 'keyword', + array: false, + required: true, + }, + // suggested remediation for insight + remediation: { + type: 'object', + array: false, + required: true, + }, + // if remediation includes exception list items + 'remediation.exception_list_items': { + type: 'object', + array: true, + required: false, + }, + 'remediation.exception_list_items.list_id': { + type: 'keyword', + array: false, + required: true, + }, + 'remediation.exception_list_items.name': { + type: 'text', + array: false, + required: true, + }, + 'remediation.exception_list_items.description': { + type: 'text', + array: false, + required: false, + }, + 'remediation.exception_list_items.entries': { + type: 'object', + array: true, + required: true, + }, + 'remediation.exception_list_items.entries.field': { + type: 'keyword', + array: false, + required: true, + }, + 'remediation.exception_list_items.entries.operator': { + type: 'keyword', + array: false, + required: true, + }, + 'remediation.exception_list_items.entries.type': { + type: 'keyword', + array: false, + required: true, + }, + 'remediation.exception_list_items.entries.value': { + type: 'text', + array: false, + required: true, + }, + 'remediation.exception_list_items.tags': { + type: 'keyword', + array: true, + required: true, + }, + 'remediation.exception_list_items.os_types': { + type: 'keyword', + array: true, + required: true, + }, + metadata: { + type: 'object', + array: false, + required: true, + }, + // optional KV for notes + 'metadata.notes': { + type: 'object', + array: false, + required: false, + }, + // optional i8n variables + 'metadata.message_variables': { + type: 'text', + array: true, + required: false, + }, +} as const; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/helpers.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/helpers.test.ts new file mode 100644 index 000000000000..33f185109116 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/helpers.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core/server'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { DataStreamSpacesAdapter } from '@kbn/data-stream-adapter'; +import { kibanaPackageJson } from '@kbn/repo-info'; + +import { createDatastream, createPipeline } from './helpers'; +import { + DATA_STREAM_PREFIX, + COMPONENT_TEMPLATE_NAME, + INDEX_TEMPLATE_NAME, + INGEST_PIPELINE_NAME, + TOTAL_FIELDS_LIMIT, +} from './constants'; +import { securityWorkflowInsightsFieldMap } from './field_map_configurations'; + +jest.mock('@kbn/data-stream-adapter', () => ({ + DataStreamSpacesAdapter: jest.fn().mockImplementation(() => ({ + setComponentTemplate: jest.fn(), + setIndexTemplate: jest.fn(), + })), +})); + +describe('helpers', () => { + describe('createDatastream', () => { + it('should create a DataStreamSpacesAdapter with the correct configuration', () => { + const kibanaVersion = kibanaPackageJson.version; + const ds = createDatastream(kibanaVersion); + + expect(DataStreamSpacesAdapter).toHaveBeenCalledTimes(1); + expect(DataStreamSpacesAdapter).toHaveBeenCalledWith(DATA_STREAM_PREFIX, { + kibanaVersion, + totalFieldsLimit: TOTAL_FIELDS_LIMIT, + }); + expect(ds.setComponentTemplate).toHaveBeenCalledTimes(1); + expect(ds.setComponentTemplate).toHaveBeenCalledWith({ + name: COMPONENT_TEMPLATE_NAME, + fieldMap: securityWorkflowInsightsFieldMap, + }); + expect(ds.setIndexTemplate).toHaveBeenCalledTimes(1); + expect(ds.setIndexTemplate).toHaveBeenCalledWith({ + name: INDEX_TEMPLATE_NAME, + componentTemplateRefs: [COMPONENT_TEMPLATE_NAME], + template: { + settings: { + default_pipeline: INGEST_PIPELINE_NAME, + }, + }, + hidden: true, + }); + }); + }); + + describe('createPipeline', () => { + let esClient: ElasticsearchClient; + + beforeEach(() => { + esClient = elasticsearchServiceMock.createElasticsearchClient(); + }); + + it('should create an ingest pipeline with the correct configuration', async () => { + await createPipeline(esClient); + + expect(esClient.ingest.putPipeline).toHaveBeenCalledTimes(1); + expect(esClient.ingest.putPipeline).toHaveBeenCalledWith({ + id: INGEST_PIPELINE_NAME, + processors: [], + _meta: { + managed: true, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/helpers.ts b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/helpers.ts new file mode 100644 index 000000000000..54b449edf86f --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/helpers.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core/server'; + +import { DataStreamSpacesAdapter } from '@kbn/data-stream-adapter'; + +import { + COMPONENT_TEMPLATE_NAME, + DATA_STREAM_PREFIX, + INDEX_TEMPLATE_NAME, + INGEST_PIPELINE_NAME, + TOTAL_FIELDS_LIMIT, +} from './constants'; +import { securityWorkflowInsightsFieldMap } from './field_map_configurations'; + +export function createDatastream(kibanaVersion: string): DataStreamSpacesAdapter { + const ds = new DataStreamSpacesAdapter(DATA_STREAM_PREFIX, { + kibanaVersion, + totalFieldsLimit: TOTAL_FIELDS_LIMIT, + }); + ds.setComponentTemplate({ + name: COMPONENT_TEMPLATE_NAME, + fieldMap: securityWorkflowInsightsFieldMap, + }); + ds.setIndexTemplate({ + name: INDEX_TEMPLATE_NAME, + componentTemplateRefs: [COMPONENT_TEMPLATE_NAME], + template: { + settings: { + default_pipeline: INGEST_PIPELINE_NAME, + }, + }, + hidden: true, + }); + return ds; +} + +export async function createPipeline(esClient: ElasticsearchClient): Promise<boolean> { + const response = await esClient.ingest.putPipeline({ + id: INGEST_PIPELINE_NAME, + processors: [ + // requires @elastic/elasticsearch 8.16.0 + // { + // fingerprint: { + // fields: ['type', 'category', 'value', 'target.type', 'target.id'], + // target_field: '_id', + // method: 'SHA-256', + // if: 'ctx._id == null', + // }, + // }, + ], + _meta: { + managed: true, + }, + }); + return response.acknowledged; +} diff --git a/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/index.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/index.test.ts new file mode 100644 index 000000000000..6271bd780ded --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/index.test.ts @@ -0,0 +1,163 @@ +/* + * 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 { ReplaySubject } from 'rxjs'; + +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { DataStreamSpacesAdapter } from '@kbn/data-stream-adapter'; +import { loggerMock } from '@kbn/logging-mocks'; +import { kibanaPackageJson } from '@kbn/repo-info'; + +import { createDatastream, createPipeline } from './helpers'; +import { securityWorkflowInsightsService } from '.'; +import { DATA_STREAM_NAME } from './constants'; + +jest.mock('./helpers', () => ({ + createDatastream: jest.fn(), + createPipeline: jest.fn(), +})); + +describe('SecurityWorkflowInsightsService', () => { + let logger: Logger; + let esClient: ElasticsearchClient; + + beforeEach(() => { + logger = loggerMock.create(); + esClient = elasticsearchServiceMock.createElasticsearchClient(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('setup', () => { + it('should set up the data stream', () => { + const createDatastreamMock = createDatastream as jest.Mock; + createDatastreamMock.mockReturnValueOnce( + new DataStreamSpacesAdapter(DATA_STREAM_NAME, { + kibanaVersion: kibanaPackageJson.version, + }) + ); + + securityWorkflowInsightsService.setup({ + kibanaVersion: kibanaPackageJson.version, + logger, + isFeatureEnabled: true, + }); + + expect(createDatastreamMock).toHaveBeenCalledTimes(1); + expect(createDatastreamMock).toHaveBeenCalledWith(kibanaPackageJson.version); + }); + + it('should log a warning if createDatastream throws an error', () => { + const createDatastreamMock = createDatastream as jest.Mock; + createDatastreamMock.mockImplementation(() => { + throw new Error('test error'); + }); + + securityWorkflowInsightsService.setup({ + kibanaVersion: kibanaPackageJson.version, + logger, + isFeatureEnabled: true, + }); + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('test error')); + }); + }); + + describe('start', () => { + it('should start the service', async () => { + const createDatastreamMock = createDatastream as jest.Mock; + const ds = new DataStreamSpacesAdapter(DATA_STREAM_NAME, { + kibanaVersion: kibanaPackageJson.version, + }); + const dsInstallSpy = jest.spyOn(ds, 'install'); + dsInstallSpy.mockResolvedValueOnce(); + createDatastreamMock.mockReturnValueOnce(ds); + const createPipelineMock = createPipeline as jest.Mock; + createPipelineMock.mockResolvedValueOnce(true); + const createDataStreamMock = esClient.indices.createDataStream as jest.Mock; + + securityWorkflowInsightsService.setup({ + kibanaVersion: kibanaPackageJson.version, + logger, + isFeatureEnabled: true, + }); + expect(createDatastreamMock).toHaveBeenCalledTimes(1); + expect(createDatastreamMock).toHaveBeenCalledWith(kibanaPackageJson.version); + + await securityWorkflowInsightsService.start({ esClient }); + + expect(createPipelineMock).toHaveBeenCalledTimes(1); + expect(createPipelineMock).toHaveBeenCalledWith(esClient); + expect(dsInstallSpy).toHaveBeenCalledTimes(1); + expect(dsInstallSpy).toHaveBeenCalledWith({ + logger, + esClient, + pluginStop$: expect.any(ReplaySubject), + }); + expect(createDataStreamMock).toHaveBeenCalledTimes(1); + expect(createDataStreamMock).toHaveBeenCalledWith({ name: DATA_STREAM_NAME }); + }); + + it('should log a warning if createPipeline or ds.install throws an error', async () => { + securityWorkflowInsightsService.setup({ + kibanaVersion: kibanaPackageJson.version, + logger, + isFeatureEnabled: true, + }); + + const createPipelineMock = createPipeline as jest.Mock; + createPipelineMock.mockImplementationOnce(() => { + throw new Error('test error'); + }); + + await securityWorkflowInsightsService.start({ esClient }); + + expect(logger.warn).toHaveBeenCalledTimes(2); + expect(logger.warn).toHaveBeenNthCalledWith(1, expect.stringContaining('test error')); + }); + }); + + describe('create', () => { + it('should wait for initialization', async () => { + const isInitializedSpy = jest + .spyOn(securityWorkflowInsightsService, 'isInitialized', 'get') + .mockResolvedValueOnce([undefined, undefined]); + + await securityWorkflowInsightsService.create(); + + expect(isInitializedSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('update', () => { + it('should wait for initialization', async () => { + const isInitializedSpy = jest + .spyOn(securityWorkflowInsightsService, 'isInitialized', 'get') + .mockResolvedValueOnce([undefined, undefined]); + + await securityWorkflowInsightsService.update(); + + expect(isInitializedSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe('fetch', () => { + it('should wait for initialization', async () => { + const isInitializedSpy = jest + .spyOn(securityWorkflowInsightsService, 'isInitialized', 'get') + .mockResolvedValueOnce([undefined, undefined]); + + await securityWorkflowInsightsService.fetch(); + + expect(isInitializedSpy).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/index.ts new file mode 100644 index 000000000000..005be1b0398e --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/workflow_insights/index.ts @@ -0,0 +1,130 @@ +/* + * 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 { ReplaySubject, firstValueFrom, combineLatest } from 'rxjs'; + +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; + +import type { DataStreamSpacesAdapter } from '@kbn/data-stream-adapter'; + +import { SecurityWorkflowInsightsFailedInitialized } from './errors'; +import { createDatastream, createPipeline } from './helpers'; +import { DATA_STREAM_NAME } from './constants'; + +interface SetupInterface { + kibanaVersion: string; + logger: Logger; + isFeatureEnabled: boolean; +} + +interface StartInterface { + esClient: ElasticsearchClient; +} + +class SecurityWorkflowInsightsService { + private setup$ = new ReplaySubject<void>(1); + private start$ = new ReplaySubject<void>(1); + private stop$ = new ReplaySubject<void>(1); + private ds: DataStreamSpacesAdapter | undefined; + // private _esClient: ElasticsearchClient | undefined; + private _logger: Logger | undefined; + private _isInitialized: Promise<[void, void]> = firstValueFrom( + combineLatest<[void, void]>([this.setup$, this.start$]) + ); + private isFeatureEnabled = false; + + public get isInitialized() { + return this._isInitialized; + } + + public setup({ kibanaVersion, logger, isFeatureEnabled }: SetupInterface) { + this.isFeatureEnabled = isFeatureEnabled; + if (!isFeatureEnabled) { + return; + } + + this._logger = logger; + + try { + this.ds = createDatastream(kibanaVersion); + } catch (err) { + this.logger.warn(new SecurityWorkflowInsightsFailedInitialized(err.message).message); + return; + } + + this.setup$.next(); + } + + public async start({ esClient }: StartInterface) { + if (!this.isFeatureEnabled) { + return; + } + + // this._esClient = esClient; + await firstValueFrom(this.setup$); + + try { + await createPipeline(esClient); + await this.ds?.install({ + logger: this.logger, + esClient, + pluginStop$: this.stop$, + }); + await esClient.indices.createDataStream({ name: DATA_STREAM_NAME }); + } catch (err) { + // ignore datastream already exists error + if (err?.body?.error?.type === 'resource_already_exists_exception') { + return; + } + + this.logger.warn(new SecurityWorkflowInsightsFailedInitialized(err.message).message); + return; + } + + this.start$.next(); + } + + public stop() { + this.setup$.next(); + this.setup$.complete(); + this.start$.next(); + this.start$.complete(); + this.stop$.next(); + this.stop$.complete(); + } + + public async create() { + await this.isInitialized; + } + + public async update() { + await this.isInitialized; + } + + public async fetch() { + await this.isInitialized; + } + + // to be used in create/update/fetch above + // private get esClient(): ElasticsearchClient { + // if (!this._esClient) { + // throw new SecurityWorkflowInsightsFailedInitialized('no elasticsearch client found'); + // } + + // return this._esClient; + // } + + private get logger(): Logger { + if (!this._logger) { + throw new SecurityWorkflowInsightsFailedInitialized('no logger found'); + } + + return this._logger; + } +} + +export const securityWorkflowInsightsService = new SecurityWorkflowInsightsService(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts index 8a796b5db1e2..7caa0469eebe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { get } from 'lodash'; +import { get, has } from 'lodash'; import type { RuleSchedule, DataSourceIndexPatterns, @@ -48,9 +48,13 @@ export const mapDiffableRuleFieldValueToRuleSchemaFormat = ( return transformedValue.value; } + if (!SUBFIELD_MAPPING[fieldName] && !has(diffableField, diffableRuleSubfieldName)) { + return diffableField; + } + // From the ThreeWayDiff, get the specific field that maps to the diffable rule field // Otherwise, the diffableField itself already matches the rule field, so retrieve that value. - const mappedField = get(diffableField, diffableRuleSubfieldName, diffableField); + const mappedField = get(diffableField, diffableRuleSubfieldName); return mappedField; }; @@ -81,9 +85,27 @@ export function mapRuleFieldToDiffableRuleField({ ruleType, fieldName, }: MapRuleFieldToDiffableRuleFieldParams): keyof AllFieldsDiff { + // Handle query, filters and language fields based on rule type + if (fieldName === 'query' || fieldName === 'language' || fieldName === 'filters') { + switch (ruleType) { + case 'query': + case 'saved_query': + return 'kql_query' as const; + case 'eql': + return 'eql_query'; + case 'esql': + return 'esql_query'; + default: + return 'kql_query'; + } + } + const diffableRuleFieldMap: Record<string, keyof AllFieldsDiff> = { building_block_type: 'building_block', saved_id: 'kql_query', + event_category_override: 'eql_query', + tiebreaker_field: 'eql_query', + timestamp_field: 'eql_query', threat_query: 'threat_query', threat_language: 'threat_query', threat_filters: 'threat_query', @@ -99,24 +121,27 @@ export function mapRuleFieldToDiffableRuleField({ timestamp_override_fallback_disabled: 'timestamp_override', }; - // Handle query, filters and language fields based on rule type - if (fieldName === 'query' || fieldName === 'language' || fieldName === 'filters') { - switch (ruleType) { - case 'query': - case 'saved_query': - return 'kql_query' as const; - case 'eql': - return 'eql_query'; - case 'esql': - return 'esql_query'; - default: - return 'kql_query'; - } - } - return diffableRuleFieldMap[fieldName] || fieldName; } +const SUBFIELD_MAPPING: Record<string, string> = { + index: 'index_patterns', + data_view_id: 'data_view_id', + saved_id: 'saved_query_id', + event_category_override: 'event_category_override', + tiebreaker_field: 'tiebreaker_field', + timestamp_field: 'timestamp_field', + building_block_type: 'type', + rule_name_override: 'field_name', + timestamp_override: 'field_name', + timestamp_override_fallback_disabled: 'fallback_disabled', + timeline_id: 'timeline_id', + timeline_title: 'timeline_title', + interval: 'interval', + from: 'lookback', + to: 'lookback', +}; + /** * Maps a PrebuiltRuleAsset schema field name to its corresponding property * name within a DiffableRule group. @@ -134,22 +159,7 @@ export function mapRuleFieldToDiffableRuleField({ * */ export function mapRuleFieldToDiffableRuleSubfield(fieldName: string): string { - const fieldMapping: Record<string, string> = { - index: 'index_patterns', - data_view_id: 'data_view_id', - saved_id: 'saved_query_id', - building_block_type: 'type', - rule_name_override: 'field_name', - timestamp_override: 'field_name', - timestamp_override_fallback_disabled: 'fallback_disabled', - timeline_id: 'timeline_id', - timeline_title: 'timeline_title', - interval: 'interval', - from: 'lookback', - to: 'lookback', - }; - - return fieldMapping[fieldName] || fieldName; + return SUBFIELD_MAPPING[fieldName] || fieldName; } type TransformValuesReturnType = diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.test.ts new file mode 100644 index 000000000000..f6035c9e87ca --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ThreeVersionsOf } from '../../../../../../../../common/api/detection_engine'; +import { + ThreeWayMergeOutcome, + MissingVersion, + ThreeWayDiffConflict, +} from '../../../../../../../../common/api/detection_engine'; +import { forceTargetVersionDiffAlgorithm } from './force_target_version_diff_algorithm'; + +describe('forceTargetVersionDiffAlgorithm', () => { + describe('when base version exists', () => { + it('returns a NON conflict diff', () => { + const mockVersions: ThreeVersionsOf<number> = { + base_version: 1, + current_version: 1, + target_version: 2, + }; + + const result = forceTargetVersionDiffAlgorithm(mockVersions); + + expect(result).toMatchObject({ + conflict: ThreeWayDiffConflict.NONE, + }); + }); + + it('return merge outcome TARGET', () => { + const mockVersions: ThreeVersionsOf<number> = { + base_version: 1, + current_version: 1, + target_version: 2, + }; + + const result = forceTargetVersionDiffAlgorithm(mockVersions); + + expect(result).toMatchObject({ + has_base_version: true, + merge_outcome: ThreeWayMergeOutcome.Target, + }); + }); + }); + + describe('when base version missing', () => { + it('returns a NON conflict diff', () => { + const mockVersions: ThreeVersionsOf<number> = { + base_version: MissingVersion, + current_version: 1, + target_version: 2, + }; + + const result = forceTargetVersionDiffAlgorithm(mockVersions); + + expect(result).toMatchObject({ + conflict: ThreeWayDiffConflict.NONE, + }); + }); + + it('return merge outcome TARGET', () => { + const mockVersions: ThreeVersionsOf<number> = { + base_version: MissingVersion, + current_version: 1, + target_version: 2, + }; + + const result = forceTargetVersionDiffAlgorithm(mockVersions); + + expect(result).toMatchObject({ + has_base_version: false, + merge_outcome: ThreeWayMergeOutcome.Target, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.ts new file mode 100644 index 000000000000..ed19be0275ae --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + ThreeVersionsOf, + ThreeWayDiff, +} from '../../../../../../../../common/api/detection_engine/prebuilt_rules'; +import { + MissingVersion, + ThreeWayDiffConflict, + ThreeWayMergeOutcome, + determineDiffOutcome, +} from '../../../../../../../../common/api/detection_engine/prebuilt_rules'; + +/** + * Diff algorithm forcing target version. Useful for special fields like `version`. + */ +export const forceTargetVersionDiffAlgorithm = <TValue>( + versions: ThreeVersionsOf<TValue> +): ThreeWayDiff<TValue> => { + const { + base_version: baseVersion, + current_version: currentVersion, + target_version: targetVersion, + } = versions; + const hasBaseVersion = baseVersion !== MissingVersion; + const hasUpdate = targetVersion !== currentVersion; + + return { + has_base_version: hasBaseVersion, + base_version: hasBaseVersion ? baseVersion : undefined, + current_version: currentVersion, + target_version: targetVersion, + merged_version: targetVersion, + merge_outcome: ThreeWayMergeOutcome.Target, + + diff_outcome: determineDiffOutcome(baseVersion, currentVersion, targetVersion), + has_update: hasUpdate, + conflict: ThreeWayDiffConflict.NONE, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts index c8b55a49edc0..b0f192cf70e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts @@ -15,3 +15,4 @@ export { kqlQueryDiffAlgorithm } from './kql_query_diff_algorithm'; export { eqlQueryDiffAlgorithm } from './eql_query_diff_algorithm'; export { esqlQueryDiffAlgorithm } from './esql_query_diff_algorithm'; export { ruleTypeDiffAlgorithm } from './rule_type_diff_algorithm'; +export { forceTargetVersionDiffAlgorithm } from './force_target_version_diff_algorithm'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts index 72e87fde6ca2..8f0b3586066f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts @@ -174,9 +174,11 @@ describe('multiLineStringDiffAlgorithm', () => { const result = multiLineStringDiffAlgorithm(mockVersions); const endTime = performance.now(); - // If the regex merge in this function takes over 500ms, this test fails + // If the regex merge in this function takes over 1 sec, this test fails // Performance measurements: https://github.com/elastic/kibana/pull/199388 - expect(endTime - startTime).toBeLessThan(500); + // NOTE: despite the fact that this test runs in ~50ms locally, on CI it + // runs slower and can be flaky even with a 500ms threshold. + expect(endTime - startTime).toBeLessThan(1000); expect(result).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index bde52596667d..78ea28137bbf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -48,6 +48,7 @@ import { eqlQueryDiffAlgorithm, esqlQueryDiffAlgorithm, ruleTypeDiffAlgorithm, + forceTargetVersionDiffAlgorithm, } from './algorithms'; const BASE_TYPE_ERROR = `Base version can't be of different rule type`; @@ -179,7 +180,11 @@ const calculateCommonFieldsDiff = ( const commonFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableCommonFields> = { rule_id: simpleDiffAlgorithm, - version: numberDiffAlgorithm, + /** + * `version` shouldn't have a conflict. It always get target value automatically. + * Diff has informational purpose. + */ + version: forceTargetVersionDiffAlgorithm, name: singleLineStringDiffAlgorithm, tags: scalarArrayDiffAlgorithm, description: multiLineStringDiffAlgorithm, @@ -239,9 +244,6 @@ const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableEqlFields> = { type: ruleTypeDiffAlgorithm, eql_query: eqlQueryDiffAlgorithm, data_source: dataSourceDiffAlgorithm, - event_category_override: singleLineStringDiffAlgorithm, - timestamp_field: singleLineStringDiffAlgorithm, - tiebreaker_field: singleLineStringDiffAlgorithm, alert_suppression: simpleDiffAlgorithm, }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts index e7ae9b96afad..760fec7f58f3 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts @@ -97,7 +97,7 @@ export class AssetCriticalityDataClient { query, size = DEFAULT_CRITICALITY_RESPONSE_SIZE, from, - sort, + sort = ['@timestamp'], // without a default sort order the results are not deterministic which makes testing hard }: { query: ESFilter; size?: number; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index b99fa8935b69..a89469acf569 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -241,19 +241,19 @@ export class EntityStoreDataClient { ) { const setupStartTime = moment().utc().toISOString(); const { logger, namespace, appClient, dataViewsService } = this.options; - const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); + try { + const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); - const unitedDefinition = getUnitedEntityDefinition({ - indexPatterns, - entityType, - namespace, - fieldHistoryLength, - syncDelay: `${config.syncDelay.asSeconds()}s`, - frequency: `${config.frequency.asSeconds()}s`, - }); - const { entityManagerDefinition } = unitedDefinition; + const unitedDefinition = getUnitedEntityDefinition({ + indexPatterns, + entityType, + namespace, + fieldHistoryLength, + syncDelay: `${config.syncDelay.asSeconds()}s`, + frequency: `${config.frequency.asSeconds()}s`, + }); + const { entityManagerDefinition } = unitedDefinition; - try { // clean up any existing entity store await this.delete(entityType, taskManager, { deleteData: false, deleteEngine: false }); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/helpers.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/helpers.ts index 0fbb0ef2c865..9070f1b4070b 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/helpers.ts @@ -6,14 +6,12 @@ */ import datemath from '@kbn/datemath'; -import { - CoreKibanaRequest, - type KibanaRequest, - SECURITY_EXTENSION_ID, - type CoreStart, -} from '@kbn/core/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; +import type { CoreStart } from '@kbn/core-lifecycle-server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { Range } from '../../../../../common/entity_analytics/risk_engine'; export const convertDateToISOString = (dateString: string): string => { @@ -43,7 +41,7 @@ const buildFakeScopedRequest = ({ path: '/', }; - const request = CoreKibanaRequest.from(rawRequest); + const request = kibanaRequestFactory(rawRequest); const scopedPath = addSpaceIdToPath('/', namespace); coreStart.http.basePath.set(request, scopedPath); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts index a937560842f7..21a17bb7834a 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts @@ -8,11 +8,11 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { v4 as uuidV4 } from 'uuid'; +import { SIEM_RULE_MIGRATIONS_PATH } from '../../../../../common/siem_migrations/constants'; import { CreateRuleMigrationRequestBody, type CreateRuleMigrationResponse, } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; -import { SIEM_RULE_MIGRATIONS_PATH } from '../../../../../common/siem_migrations/constants'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import type { CreateRuleMigrationInput } from '../data/rule_migrations_data_rules_client'; import { withLicense } from './util/with_license'; @@ -47,7 +47,7 @@ export const registerSiemRuleMigrationsCreateRoute = ( migration_id: migrationId, original_rule: originalRule, })); - + await ruleMigrationsClient.data.integrations.create(); await ruleMigrationsClient.data.rules.create(ruleMigrations); return res.ok({ body: { migration_id: migrationId } }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/__mocks__/mocks.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/__mocks__/mocks.ts index 34e68d8a4736..d8dc1bb168a7 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/__mocks__/mocks.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/__mocks__/mocks.ts @@ -32,10 +32,15 @@ export const MockRuleMigrationsDataResourcesClient = jest .fn() .mockImplementation(() => mockRuleMigrationsDataResourcesClient); +export const mockRuleMigrationsDataIntegrationsClient = { + retrieveIntegrations: jest.fn().mockResolvedValue([]), +}; + // Rule migrations data client export const mockRuleMigrationsDataClient = { rules: mockRuleMigrationsDataRulesClient, resources: mockRuleMigrationsDataResourcesClient, + integrations: mockRuleMigrationsDataIntegrationsClient, }; export const MockRuleMigrationsDataClient = jest diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/integrations_temp.json b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/integrations_temp.json new file mode 100644 index 000000000000..9c312bb38e3d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/integrations_temp.json @@ -0,0 +1,6881 @@ +[ + { + "title": "Containerd", + "id": "containerd", + "description": "Collect metrics from containerd containers.", + "data_streams": [ + { + "dataset": "memory", + "index_pattern": "logs-containerd.memory-*", + "title": "Containerd memory metrics" + }, + { + "dataset": "blkio", + "index_pattern": "logs-containerd.blkio-*", + "title": "Containerd blkio metrics" + }, + { + "dataset": "cpu", + "index_pattern": "logs-containerd.cpu-*", + "title": "Containerd cpu metrics" + } + ], + "elser_embedding": "Containerd - Collect metrics from containerd containers. - Containerd memory metrics Containerd blkio metrics Containerd cpu metrics" + }, + { + "title": "Google Santa", + "id": "santa", + "description": "Collect logs from Google Santa with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-santa.log-*", + "title": "Google Santa log logs" + } + ], + "elser_embedding": "Google Santa - Collect logs from Google Santa with Elastic Agent. - Google Santa log logs" + }, + { + "title": "Keycloak", + "id": "keycloak", + "description": "Collect logs from Keycloak with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-keycloak.log-*", + "title": "Keycloak" + } + ], + "elser_embedding": "Keycloak - Collect logs from Keycloak with Elastic Agent. - Keycloak" + }, + { + "title": "Infoblox NIOS", + "id": "infoblox_nios", + "description": "Collect logs from Infoblox NIOS with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-infoblox_nios.log-*", + "title": "Infoblox NIOS logs" + } + ], + "elser_embedding": "Infoblox NIOS - Collect logs from Infoblox NIOS with Elastic Agent. - Infoblox NIOS logs" + }, + { + "title": "LastPass", + "id": "lastpass", + "description": "Collect logs from LastPass with Elastic Agent.", + "data_streams": [ + { + "dataset": "detailed_shared_folder", + "index_pattern": "logs-lastpass.detailed_shared_folder-*", + "title": "Collect Detailed Shared Folder logs from LastPass" + }, + { + "dataset": "user", + "index_pattern": "logs-lastpass.user-*", + "title": "Collect User logs from LastPass" + }, + { + "dataset": "event_report", + "index_pattern": "logs-lastpass.event_report-*", + "title": "Collect Event Report logs from LastPass" + } + ], + "elser_embedding": "LastPass - Collect logs from LastPass with Elastic Agent. - Collect Detailed Shared Folder logs from LastPass Collect User logs from LastPass Collect Event Report logs from LastPass" + }, + { + "title": "IBM MQ", + "id": "ibmmq", + "description": "Collect logs and metrics from IBM MQ with Elastic Agent.", + "data_streams": [ + { + "dataset": "qmgr", + "index_pattern": "logs-ibmmq.qmgr-*", + "title": "IBM MQ Queue Manager performance metrics" + }, + { + "dataset": "errorlog", + "index_pattern": "logs-ibmmq.errorlog-*", + "title": "IBM MQ Error logs" + } + ], + "elser_embedding": "IBM MQ - Collect logs and metrics from IBM MQ with Elastic Agent. - IBM MQ Queue Manager performance metrics IBM MQ Error logs" + }, + { + "title": "Jamf Protect", + "id": "jamf_protect", + "description": "Receives events from Jamf Protect with Elastic Agent.", + "data_streams": [ + { + "dataset": "web_traffic_events", + "index_pattern": "logs-jamf_protect.web_traffic_events-*", + "title": "Receives Web Traffic Events from Jamf Protect with Elastic Agent." + }, + { + "dataset": "telemetry_legacy", + "index_pattern": "logs-jamf_protect.telemetry_legacy-*", + "title": "Jamf Protect Telemetry (Legacy)." + }, + { + "dataset": "web_threat_events", + "index_pattern": "logs-jamf_protect.web_threat_events-*", + "title": "Receives Web Threat Events from Jamf Protect with Elastic Agent." + }, + { + "dataset": "telemetry", + "index_pattern": "logs-jamf_protect.telemetry-*", + "title": "Receives Telemetry from Jamf Protect with Elastic Agent." + }, + { + "dataset": "alerts", + "index_pattern": "logs-jamf_protect.alerts-*", + "title": "Receives Alerts from Jamf Protect with Elastic Agent." + } + ], + "elser_embedding": "Jamf Protect - Receives events from Jamf Protect with Elastic Agent. - Receives Web Traffic Events from Jamf Protect with Elastic Agent. Jamf Protect Telemetry (Legacy). Receives Web Threat Events from Jamf Protect with Elastic Agent. Receives Telemetry from Jamf Protect with Elastic Agent. Receives Alerts from Jamf Protect with Elastic Agent." + }, + { + "title": "Sysmon for Linux", + "id": "sysmon_linux", + "description": "Collect Sysmon Linux logs with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-sysmon_linux.log-*", + "title": "Sysmon for Linux logs" + } + ], + "elser_embedding": "Sysmon for Linux - Collect Sysmon Linux logs with Elastic Agent. - Sysmon for Linux logs" + }, + { + "title": "Trend Micro Deep Security", + "id": "trendmicro", + "description": "Collect logs from Trend Micro Deep Security with Elastic Agent.", + "data_streams": [ + { + "dataset": "deep_security", + "index_pattern": "logs-trendmicro.deep_security-*", + "title": "Collect logs from Trend Micro Deep Security" + } + ], + "elser_embedding": "Trend Micro Deep Security - Collect logs from Trend Micro Deep Security with Elastic Agent. - Collect logs from Trend Micro Deep Security" + }, + { + "title": "HAProxy", + "id": "haproxy", + "description": "Collect logs and metrics from HAProxy servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "info", + "index_pattern": "logs-haproxy.info-*", + "title": "HAProxy info metrics" + }, + { + "dataset": "stat", + "index_pattern": "logs-haproxy.stat-*", + "title": "HAProxy stat metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-haproxy.log-*", + "title": "HAProxy logs" + } + ], + "elser_embedding": "HAProxy - Collect logs and metrics from HAProxy servers with Elastic Agent. - HAProxy info metrics HAProxy stat metrics HAProxy logs" + }, + { + "title": "ESET Threat Intelligence", + "id": "ti_eset", + "description": "Ingest threat intelligence indicators from ESET Threat Intelligence with Elastic Agent.", + "data_streams": [ + { + "dataset": "cc", + "index_pattern": "logs-ti_eset.cc-*", + "title": "Botnet C&C" + }, + { + "dataset": "url", + "index_pattern": "logs-ti_eset.url-*", + "title": "URL" + }, + { + "dataset": "domains", + "index_pattern": "logs-ti_eset.domains-*", + "title": "Domain" + }, + { + "dataset": "files", + "index_pattern": "logs-ti_eset.files-*", + "title": "Malicious files" + }, + { + "dataset": "apt", + "index_pattern": "logs-ti_eset.apt-*", + "title": "APT" + }, + { + "dataset": "ip", + "index_pattern": "logs-ti_eset.ip-*", + "title": "IP" + }, + { + "dataset": "botnet", + "index_pattern": "logs-ti_eset.botnet-*", + "title": "Botnet" + } + ], + "elser_embedding": "ESET Threat Intelligence - Ingest threat intelligence indicators from ESET Threat Intelligence with Elastic Agent. - Botnet C&C URL Domain Malicious files APT IP Botnet" + }, + { + "title": "Lyve Cloud", + "id": "lyve_cloud", + "description": "Collect S3 API audit log from Lyve Cloud with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-lyve_cloud.audit-*", + "title": "Lyve Cloud logs" + } + ], + "elser_embedding": "Lyve Cloud - Collect S3 API audit log from Lyve Cloud with Elastic Agent. - Lyve Cloud logs" + }, + { + "title": "Tanium", + "id": "tanium", + "description": "This Elastic integration collects logs from Tanium with Elastic Agent.", + "data_streams": [ + { + "dataset": "discover", + "index_pattern": "logs-tanium.discover-*", + "title": "Collect Tanium Discover logs from Tanium" + }, + { + "dataset": "threat_response", + "index_pattern": "logs-tanium.threat_response-*", + "title": "Collect Threat Response logs from Tanium." + }, + { + "dataset": "client_status", + "index_pattern": "logs-tanium.client_status-*", + "title": "Client Status" + }, + { + "dataset": "reporting", + "index_pattern": "logs-tanium.reporting-*", + "title": "Reporting" + }, + { + "dataset": "action_history", + "index_pattern": "logs-tanium.action_history-*", + "title": "Collect Action History logs from Tanium." + }, + { + "dataset": "endpoint_config", + "index_pattern": "logs-tanium.endpoint_config-*", + "title": "Collect Endpoint Config logs from Tanium" + } + ], + "elser_embedding": "Tanium - This Elastic integration collects logs from Tanium with Elastic Agent. - Collect Tanium Discover logs from Tanium Collect Threat Response logs from Tanium. Client Status Reporting Collect Action History logs from Tanium. Collect Endpoint Config logs from Tanium" + }, + { + "title": "SonicWall Firewall", + "id": "sonicwall_firewall", + "description": "Integration for SonicWall firewall logs", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-sonicwall_firewall.log-*", + "title": "SonicWall Firewall logs" + } + ], + "elser_embedding": "SonicWall Firewall - Integration for SonicWall firewall logs - SonicWall Firewall logs" + }, + { + "title": "STAN", + "id": "stan", + "description": "Collect logs and metrics from STAN servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "stats", + "index_pattern": "logs-stan.stats-*", + "title": "Stan stats metrics" + }, + { + "dataset": "subscriptions", + "index_pattern": "logs-stan.subscriptions-*", + "title": "Stan subscriptions metrics" + }, + { + "dataset": "channels", + "index_pattern": "logs-stan.channels-*", + "title": "Stan channels metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-stan.log-*", + "title": "STAN logs" + } + ], + "elser_embedding": "STAN - Collect logs and metrics from STAN servers with Elastic Agent. - Stan stats metrics Stan subscriptions metrics Stan channels metrics STAN logs" + }, + { + "title": "Amazon Bedrock", + "id": "aws_bedrock", + "description": "Collect Amazon Bedrock model invocation logs and runtime metrics with Elastic Agent.", + "data_streams": [ + { + "dataset": "runtime", + "index_pattern": "logs-aws_bedrock.runtime-*", + "title": "Amazon Bedrock Runtime Metrics" + }, + { + "dataset": "invocation", + "index_pattern": "logs-aws_bedrock.invocation-*", + "title": "Amazon Bedrock model invocation logs" + } + ], + "elser_embedding": "Amazon Bedrock - Collect Amazon Bedrock model invocation logs and runtime metrics with Elastic Agent. - Amazon Bedrock Runtime Metrics Amazon Bedrock model invocation logs" + }, + { + "title": "Microsoft M365 Defender", + "id": "m365_defender", + "description": "Collect logs from Microsoft M365 Defender with Elastic Agent.", + "data_streams": [ + { + "dataset": "alert", + "index_pattern": "logs-m365_defender.alert-*", + "title": "Collect Alert logs from Microsoft 365 Defender" + }, + { + "dataset": "log", + "index_pattern": "logs-m365_defender.log-*", + "title": "M365 Defender Logs" + }, + { + "dataset": "incident", + "index_pattern": "logs-m365_defender.incident-*", + "title": "Collect Incident logs from Microsoft 365 Defender" + }, + { + "dataset": "event", + "index_pattern": "logs-m365_defender.event-*", + "title": "Collect Event logs from Microsoft 365 Defender." + } + ], + "elser_embedding": "Microsoft M365 Defender - Collect logs from Microsoft M365 Defender with Elastic Agent. - Collect Alert logs from Microsoft 365 Defender M365 Defender Logs Collect Incident logs from Microsoft 365 Defender Collect Event logs from Microsoft 365 Defender." + }, + { + "title": "NATS", + "id": "nats", + "description": "Collect logs and metrics from NATS servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "stats", + "index_pattern": "logs-nats.stats-*", + "title": "NATS stats metrics" + }, + { + "dataset": "route", + "index_pattern": "logs-nats.route-*", + "title": "NATS route metrics" + }, + { + "dataset": "connection", + "index_pattern": "logs-nats.connection-*", + "title": "NATS connection metrics" + }, + { + "dataset": "subscriptions", + "index_pattern": "logs-nats.subscriptions-*", + "title": "NATS subscriptions metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-nats.log-*", + "title": "NATS logs" + }, + { + "dataset": "routes", + "index_pattern": "logs-nats.routes-*", + "title": "NATS routes metrics" + }, + { + "dataset": "connections", + "index_pattern": "logs-nats.connections-*", + "title": "NATS connections metrics" + } + ], + "elser_embedding": "NATS - Collect logs and metrics from NATS servers with Elastic Agent. - NATS stats metrics NATS route metrics NATS connection metrics NATS subscriptions metrics NATS logs NATS routes metrics NATS connections metrics" + }, + { + "title": "GoFlow2 logs", + "id": "goflow2", + "description": "Collect logs from goflow2 with Elastic Agent.", + "data_streams": [ + { + "dataset": "sflow", + "index_pattern": "logs-goflow2.sflow-*", + "title": "Goflow2 sFlow" + } + ], + "elser_embedding": "GoFlow2 logs - Collect logs from goflow2 with Elastic Agent. - Goflow2 sFlow" + }, + { + "title": "Microsoft Defender for Cloud", + "id": "microsoft_defender_cloud", + "description": "Collect logs from Microsoft Defender for Cloud with Elastic Agent.", + "data_streams": [ + { + "dataset": "event", + "index_pattern": "logs-microsoft_defender_cloud.event-*", + "title": "Collect Event(Alert and Recommendation) logs from Microsoft Defender for Cloud." + } + ], + "elser_embedding": "Microsoft Defender for Cloud - Collect logs from Microsoft Defender for Cloud with Elastic Agent. - Collect Event(Alert and Recommendation) logs from Microsoft Defender for Cloud." + }, + { + "title": "RabbitMQ Logs and Metrics", + "id": "rabbitmq", + "description": "Collect and parse logs from RabbitMQ servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "queue", + "index_pattern": "logs-rabbitmq.queue-*", + "title": "RabbitMQ queue metrics" + }, + { + "dataset": "exchange", + "index_pattern": "logs-rabbitmq.exchange-*", + "title": "RabbitMQ exchange metrics" + }, + { + "dataset": "connection", + "index_pattern": "logs-rabbitmq.connection-*", + "title": "RabbitMQ connection metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-rabbitmq.log-*", + "title": "RabbitMQ application logs" + }, + { + "dataset": "node", + "index_pattern": "logs-rabbitmq.node-*", + "title": "RabbitMQ node metrics" + } + ], + "elser_embedding": "RabbitMQ Logs and Metrics - Collect and parse logs from RabbitMQ servers with Elastic Agent. - RabbitMQ queue metrics RabbitMQ exchange metrics RabbitMQ connection metrics RabbitMQ application logs RabbitMQ node metrics" + }, + { + "title": "Apache Tomcat", + "id": "apache_tomcat", + "description": "Collect and parse logs and metrics from Apache Tomcat servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "catalina", + "index_pattern": "logs-apache_tomcat.catalina-*", + "title": "Apache Tomcat Catalina logs" + }, + { + "dataset": "memory", + "index_pattern": "logs-apache_tomcat.memory-*", + "title": "Apache Tomcat Memory metrics" + }, + { + "dataset": "access", + "index_pattern": "logs-apache_tomcat.access-*", + "title": "Apache Tomcat Access logs" + }, + { + "dataset": "cache", + "index_pattern": "logs-apache_tomcat.cache-*", + "title": "Apache Tomcat Cache metrics" + }, + { + "dataset": "request", + "index_pattern": "logs-apache_tomcat.request-*", + "title": "Apache Tomcat Request metrics" + }, + { + "dataset": "session", + "index_pattern": "logs-apache_tomcat.session-*", + "title": "Apache Tomcat Session metrics" + }, + { + "dataset": "localhost", + "index_pattern": "logs-apache_tomcat.localhost-*", + "title": "Apache Tomcat Localhost logs" + }, + { + "dataset": "connection_pool", + "index_pattern": "logs-apache_tomcat.connection_pool-*", + "title": "Apache Tomcat Connection Pool metrics" + }, + { + "dataset": "thread_pool", + "index_pattern": "logs-apache_tomcat.thread_pool-*", + "title": "Apache Tomcat Thread Pool metrics" + } + ], + "elser_embedding": "Apache Tomcat - Collect and parse logs and metrics from Apache Tomcat servers with Elastic Agent. - Apache Tomcat Catalina logs Apache Tomcat Memory metrics Apache Tomcat Access logs Apache Tomcat Cache metrics Apache Tomcat Request metrics Apache Tomcat Session metrics Apache Tomcat Localhost logs Apache Tomcat Connection Pool metrics Apache Tomcat Thread Pool metrics" + }, + { + "title": "CylanceProtect Logs", + "id": "cylance", + "description": "Collect logs from CylanceProtect devices with Elastic Agent.", + "data_streams": [ + { + "dataset": "protect", + "index_pattern": "logs-cylance.protect-*", + "title": "CylanceProtect logs" + } + ], + "elser_embedding": "CylanceProtect Logs - Collect logs from CylanceProtect devices with Elastic Agent. - CylanceProtect logs" + }, + { + "title": "Rapid7 InsightVM", + "id": "rapid7_insightvm", + "description": "Collect logs from Rapid7 InsightVM with Elastic Agent.", + "data_streams": [ + { + "dataset": "vulnerability", + "index_pattern": "logs-rapid7_insightvm.vulnerability-*", + "title": "Collect Vulnerability logs from Rapid7 InsightVM" + }, + { + "dataset": "asset", + "index_pattern": "logs-rapid7_insightvm.asset-*", + "title": "Collect Asset logs from Rapid7 InsightVM" + } + ], + "elser_embedding": "Rapid7 InsightVM - Collect logs from Rapid7 InsightVM with Elastic Agent. - Collect Vulnerability logs from Rapid7 InsightVM Collect Asset logs from Rapid7 InsightVM" + }, + { + "title": "Symantec EDR Cloud", + "id": "symantec_edr_cloud", + "description": "Collect logs from Symantec EDR Cloud with Elastic Agent.", + "data_streams": [ + { + "dataset": "incident", + "index_pattern": "logs-symantec_edr_cloud.incident-*", + "title": "Collect Incident logs from Symantec EDR Cloud" + } + ], + "elser_embedding": "Symantec EDR Cloud - Collect logs from Symantec EDR Cloud with Elastic Agent. - Collect Incident logs from Symantec EDR Cloud" + }, + { + "title": "Nginx Ingress Controller OpenTelemetry Logs", + "id": "nginx_ingress_controller_otel", + "description": "Collect Nginx Ingress Controller logs using the OpenTelemetry collector.", + "data_streams": [], + "elser_embedding": "Nginx Ingress Controller OpenTelemetry Logs - Collect Nginx Ingress Controller logs using the OpenTelemetry collector. - " + }, + { + "title": "OpenCTI", + "id": "ti_opencti", + "description": "Ingest threat intelligence indicators from OpenCTI with Elastic Agent.", + "data_streams": [ + { + "dataset": "indicator", + "index_pattern": "logs-ti_opencti.indicator-*", + "title": "Indicator" + } + ], + "elser_embedding": "OpenCTI - Ingest threat intelligence indicators from OpenCTI with Elastic Agent. - Indicator" + }, + { + "title": "Windows", + "id": "windows", + "description": "Collect logs and metrics from Windows OS and services with Elastic Agent.", + "data_streams": [ + { + "dataset": "applocker_packaged_app_deployment", + "index_pattern": "logs-windows.applocker_packaged_app_deployment-*", + "title": "Windows AppLocker/Packaged app-Deployment logs" + }, + { + "dataset": "applocker_msi_and_script", + "index_pattern": "logs-windows.applocker_msi_and_script-*", + "title": "Windows AppLocker/MSI and Script logs" + }, + { + "dataset": "powershell_operational", + "index_pattern": "logs-windows.powershell_operational-*", + "title": "Windows Powershell/Operational logs" + }, + { + "dataset": "perfmon", + "index_pattern": "logs-windows.perfmon-*", + "title": "Windows perfmon metrics" + }, + { + "dataset": "windows_defender", + "index_pattern": "logs-windows.windows_defender-*", + "title": "Windows Defender logs" + }, + { + "dataset": "sysmon_operational", + "index_pattern": "logs-windows.sysmon_operational-*", + "title": "Windows Sysmon/Operational events" + }, + { + "dataset": "service", + "index_pattern": "logs-windows.service-*", + "title": "Windows service metrics" + }, + { + "dataset": "forwarded", + "index_pattern": "logs-windows.forwarded-*", + "title": "Windows forwarded events" + }, + { + "dataset": "powershell", + "index_pattern": "logs-windows.powershell-*", + "title": "Windows Powershell logs" + }, + { + "dataset": "applocker_exe_and_dll", + "index_pattern": "logs-windows.applocker_exe_and_dll-*", + "title": "Windows AppLocker/EXE and DLL logs" + }, + { + "dataset": "applocker_packaged_app_execution", + "index_pattern": "logs-windows.applocker_packaged_app_execution-*", + "title": "Windows AppLocker/Packaged app-Execution logs" + } + ], + "elser_embedding": "Windows - Collect logs and metrics from Windows OS and services with Elastic Agent. - Windows AppLocker/Packaged app-Deployment logs Windows AppLocker/MSI and Script logs Windows Powershell/Operational logs Windows perfmon metrics Windows Defender logs Windows Sysmon/Operational events Windows service metrics Windows forwarded events Windows Powershell logs Windows AppLocker/EXE and DLL logs Windows AppLocker/Packaged app-Execution logs" + }, + { + "title": "CouchDB", + "id": "couchdb", + "description": "Collect metrics from CouchDB with Elastic Agent.", + "data_streams": [ + { + "dataset": "server", + "index_pattern": "logs-couchdb.server-*", + "title": "Server" + } + ], + "elser_embedding": "CouchDB - Collect metrics from CouchDB with Elastic Agent. - Server" + }, + { + "title": "Custom UDP Logs", + "id": "udp", + "description": "Collect raw UDP data from listening UDP port with Elastic Agent.", + "data_streams": [ + { + "dataset": "generic", + "index_pattern": "logs-udp.generic-*", + "title": "Custom UDP Logs" + } + ], + "elser_embedding": "Custom UDP Logs - Collect raw UDP data from listening UDP port with Elastic Agent. - Custom UDP Logs" + }, + { + "title": "Cassandra", + "id": "cassandra", + "description": "This Elastic integration collects logs and metrics from cassandra.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cassandra.log-*", + "title": "Cassandra System Logs" + }, + { + "dataset": "metrics", + "index_pattern": "logs-cassandra.metrics-*", + "title": "Metrics" + } + ], + "elser_embedding": "Cassandra - This Elastic integration collects logs and metrics from cassandra. - Cassandra System Logs Metrics" + }, + { + "title": "Gigamon", + "id": "gigamon", + "description": "Collect logs from Gigamon with Elastic Agent.", + "data_streams": [ + { + "dataset": "ami", + "index_pattern": "logs-gigamon.ami-*", + "title": "Gigamon Application Metadata Intelligence (AMI) Logs" + } + ], + "elser_embedding": "Gigamon - Collect logs from Gigamon with Elastic Agent. - Gigamon Application Metadata Intelligence (AMI) Logs" + }, + { + "title": "Hashicorp Vault", + "id": "hashicorp_vault", + "description": "Collect logs and metrics from Hashicorp Vault with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-hashicorp_vault.audit-*", + "title": "Hashicorp Vault Audit Logs" + }, + { + "dataset": "log", + "index_pattern": "logs-hashicorp_vault.log-*", + "title": "Hashicorp Vault Operational Logs" + }, + { + "dataset": "metrics", + "index_pattern": "logs-hashicorp_vault.metrics-*", + "title": "Hashicorp Vault Metrics" + } + ], + "elser_embedding": "Hashicorp Vault - Collect logs and metrics from Hashicorp Vault with Elastic Agent. - Hashicorp Vault Audit Logs Hashicorp Vault Operational Logs Hashicorp Vault Metrics" + }, + { + "title": "Okta", + "id": "okta", + "description": "Collect and parse event logs from Okta API with Elastic Agent.", + "data_streams": [ + { + "dataset": "system", + "index_pattern": "logs-okta.system-*", + "title": "Okta system logs" + } + ], + "elser_embedding": "Okta - Collect and parse event logs from Okta API with Elastic Agent. - Okta system logs" + }, + { + "title": "Recorded Future", + "id": "ti_recordedfuture", + "description": "Ingest threat intelligence indicators from Recorded Future risk lists with Elastic Agent.", + "data_streams": [ + { + "dataset": "threat", + "index_pattern": "logs-ti_recordedfuture.threat-*", + "title": "Recorded Future" + } + ], + "elser_embedding": "Recorded Future - Ingest threat intelligence indicators from Recorded Future risk lists with Elastic Agent. - Recorded Future" + }, + { + "title": "IIS", + "id": "iis", + "description": "Collect logs and metrics from Internet Information Services (IIS) servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "access", + "index_pattern": "logs-iis.access-*", + "title": "IIS access logs" + }, + { + "dataset": "webserver", + "index_pattern": "logs-iis.webserver-*", + "title": "IIS web server metrics" + }, + { + "dataset": "error", + "index_pattern": "logs-iis.error-*", + "title": "IIS error logs" + }, + { + "dataset": "website", + "index_pattern": "logs-iis.website-*", + "title": "IIS website metrics" + }, + { + "dataset": "application_pool", + "index_pattern": "logs-iis.application_pool-*", + "title": "IIS application_pool metrics" + } + ], + "elser_embedding": "IIS - Collect logs and metrics from Internet Information Services (IIS) servers with Elastic Agent. - IIS access logs IIS web server metrics IIS error logs IIS website metrics IIS application_pool metrics" + }, + { + "title": "Golang", + "id": "golang", + "description": "This Elastic integration collects metrics from Golang applications.", + "data_streams": [ + { + "dataset": "heap", + "index_pattern": "logs-golang.heap-*", + "title": "Golang Heap metrics" + }, + { + "dataset": "expvar", + "index_pattern": "logs-golang.expvar-*", + "title": "Golang expvar metrics" + } + ], + "elser_embedding": "Golang - This Elastic integration collects metrics from Golang applications. - Golang Heap metrics Golang expvar metrics" + }, + { + "title": "MongoDB", + "id": "mongodb", + "description": "Collect logs and metrics from MongoDB instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "replstatus", + "index_pattern": "logs-mongodb.replstatus-*", + "title": "MongoDB replstatus metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-mongodb.log-*", + "title": "mongodb log logs" + }, + { + "dataset": "metrics", + "index_pattern": "logs-mongodb.metrics-*", + "title": "MongoDB metrics" + }, + { + "dataset": "status", + "index_pattern": "logs-mongodb.status-*", + "title": "MongoDB status metrics" + }, + { + "dataset": "dbstats", + "index_pattern": "logs-mongodb.dbstats-*", + "title": "MongoDB dbstats metrics" + }, + { + "dataset": "collstats", + "index_pattern": "logs-mongodb.collstats-*", + "title": "MongoDB collstats metrics" + } + ], + "elser_embedding": "MongoDB - Collect logs and metrics from MongoDB instances with Elastic Agent. - MongoDB replstatus metrics mongodb log logs MongoDB metrics MongoDB status metrics MongoDB dbstats metrics MongoDB collstats metrics" + }, + { + "title": "Sublime Security", + "id": "sublime_security", + "description": "Collect logs from Sublime Security with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-sublime_security.audit-*", + "title": "Sublime Security Audit logs" + }, + { + "dataset": "email_message", + "index_pattern": "logs-sublime_security.email_message-*", + "title": "Sublime Security Email Message logs" + }, + { + "dataset": "message_event", + "index_pattern": "logs-sublime_security.message_event-*", + "title": "Sublime Security Message Event logs" + } + ], + "elser_embedding": "Sublime Security - Collect logs from Sublime Security with Elastic Agent. - Sublime Security Audit logs Sublime Security Email Message logs Sublime Security Message Event logs" + }, + { + "title": "Nginx", + "id": "nginx", + "description": "Collect logs and metrics from Nginx HTTP servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "access", + "index_pattern": "logs-nginx.access-*", + "title": "Nginx access logs" + }, + { + "dataset": "error", + "index_pattern": "logs-nginx.error-*", + "title": "Nginx error logs" + }, + { + "dataset": "stubstatus", + "index_pattern": "logs-nginx.stubstatus-*", + "title": "Nginx stubstatus metrics" + } + ], + "elser_embedding": "Nginx - Collect logs and metrics from Nginx HTTP servers with Elastic Agent. - Nginx access logs Nginx error logs Nginx stubstatus metrics" + }, + { + "title": "Apache Spark", + "id": "apache_spark", + "description": "Collect metrics from Apache Spark with Elastic Agent.", + "data_streams": [ + { + "dataset": "driver", + "index_pattern": "logs-apache_spark.driver-*", + "title": "Apache Spark driver metrics" + }, + { + "dataset": "application", + "index_pattern": "logs-apache_spark.application-*", + "title": "Apache Spark application metrics" + }, + { + "dataset": "node", + "index_pattern": "logs-apache_spark.node-*", + "title": "Apache Spark node metrics" + }, + { + "dataset": "executor", + "index_pattern": "logs-apache_spark.executor-*", + "title": "Apache Spark executor metrics" + } + ], + "elser_embedding": "Apache Spark - Collect metrics from Apache Spark with Elastic Agent. - Apache Spark driver metrics Apache Spark application metrics Apache Spark node metrics Apache Spark executor metrics" + }, + { + "title": "Rapid7 Threat Command", + "id": "ti_rapid7_threat_command", + "description": "Collect threat intelligence from Threat Command API with Elastic Agent.", + "data_streams": [ + { + "dataset": "ioc", + "index_pattern": "logs-ti_rapid7_threat_command.ioc-*", + "title": "Rapid7 Threat Command IOCs" + }, + { + "dataset": "vulnerability", + "index_pattern": "logs-ti_rapid7_threat_command.vulnerability-*", + "title": "Rapid7 Threat Command Vulnerability" + }, + { + "dataset": "alert", + "index_pattern": "logs-ti_rapid7_threat_command.alert-*", + "title": "Rapid7 Threat Command Alerts" + } + ], + "elser_embedding": "Rapid7 Threat Command - Collect threat intelligence from Threat Command API with Elastic Agent. - Rapid7 Threat Command IOCs Rapid7 Threat Command Vulnerability Rapid7 Threat Command Alerts" + }, + { + "title": "Fortinet FortiEDR Logs", + "id": "fortinet_fortiedr", + "description": "Collect logs from Fortinet FortiEDR instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-fortinet_fortiedr.log-*", + "title": "Fortinet FortEDR Endpoint Detection and Response logs" + } + ], + "elser_embedding": "Fortinet FortiEDR Logs - Collect logs from Fortinet FortiEDR instances with Elastic Agent. - Fortinet FortEDR Endpoint Detection and Response logs" + }, + { + "title": "ThreatQuotient", + "id": "ti_threatq", + "description": "Ingest threat intelligence indicators from ThreatQuotient with Elastic Agent.", + "data_streams": [ + { + "dataset": "threat", + "index_pattern": "logs-ti_threatq.threat-*", + "title": "ThreatQ" + } + ], + "elser_embedding": "ThreatQuotient - Ingest threat intelligence indicators from ThreatQuotient with Elastic Agent. - ThreatQ" + }, + { + "title": "BBOT (Bighuge BLS OSINT Tool)", + "id": "bbot", + "description": "BBOT is a recursive internet scanner inspired by Spiderfoot, but designed to be faster, more reliable, and friendlier to pentesters, bug bounty hunters, and developers. ", + "data_streams": [ + { + "dataset": "asm_intel", + "index_pattern": "logs-bbot.asm_intel-*", + "title": "BBOT-Data-Ingest" + } + ], + "elser_embedding": "BBOT (Bighuge BLS OSINT Tool) - BBOT is a recursive internet scanner inspired by Spiderfoot, but designed to be faster, more reliable, and friendlier to pentesters, bug bounty hunters, and developers. - BBOT-Data-Ingest" + }, + { + "title": "Microsoft SQL Server", + "id": "microsoft_sqlserver", + "description": "Collect events from Microsoft SQL Server with Elastic Agent", + "data_streams": [ + { + "dataset": "performance", + "index_pattern": "logs-microsoft_sqlserver.performance-*", + "title": "Microsoft SQL Server performance metrics" + }, + { + "dataset": "audit", + "index_pattern": "logs-microsoft_sqlserver.audit-*", + "title": "SQL Server audit events" + }, + { + "dataset": "log", + "index_pattern": "logs-microsoft_sqlserver.log-*", + "title": "Microsoft SQL Server error logs" + }, + { + "dataset": "transaction_log", + "index_pattern": "logs-microsoft_sqlserver.transaction_log-*", + "title": "Microsoft SQL Server transaction_log metrics" + } + ], + "elser_embedding": "Microsoft SQL Server - Collect events from Microsoft SQL Server with Elastic Agent - Microsoft SQL Server performance metrics SQL Server audit events Microsoft SQL Server error logs Microsoft SQL Server transaction_log metrics" + }, + { + "title": "Claroty CTD", + "id": "claroty_ctd", + "description": "Collect logs from Claroty CTD using Elastic Agent.", + "data_streams": [ + { + "dataset": "baseline", + "index_pattern": "logs-claroty_ctd.baseline-*", + "title": "Baseline logs" + }, + { + "dataset": "event", + "index_pattern": "logs-claroty_ctd.event-*", + "title": "Event logs" + }, + { + "dataset": "asset", + "index_pattern": "logs-claroty_ctd.asset-*", + "title": "Asset logs" + } + ], + "elser_embedding": "Claroty CTD - Collect logs from Claroty CTD using Elastic Agent. - Baseline logs Event logs Asset logs" + }, + { + "title": "ZeroFox", + "id": "zerofox", + "description": "Collect logs from ZeroFox with Elastic Agent.", + "data_streams": [ + { + "dataset": "alerts", + "index_pattern": "logs-zerofox.alerts-*", + "title": "Alerts" + } + ], + "elser_embedding": "ZeroFox - Collect logs from ZeroFox with Elastic Agent. - Alerts" + }, + { + "title": "Darktrace", + "id": "darktrace", + "description": "Collect logs from Darktrace with Elastic Agent.", + "data_streams": [ + { + "dataset": "system_status_alert", + "index_pattern": "logs-darktrace.system_status_alert-*", + "title": "Collect System Status Alert logs from Darktrace" + }, + { + "dataset": "model_breach_alert", + "index_pattern": "logs-darktrace.model_breach_alert-*", + "title": "Collect Model Breach Alert logs from Darktrace" + }, + { + "dataset": "ai_analyst_alert", + "index_pattern": "logs-darktrace.ai_analyst_alert-*", + "title": "Collect AI Analyst Alert logs from Darktrace" + } + ], + "elser_embedding": "Darktrace - Collect logs from Darktrace with Elastic Agent. - Collect System Status Alert logs from Darktrace Collect Model Breach Alert logs from Darktrace Collect AI Analyst Alert logs from Darktrace" + }, + { + "title": "Cybersixgill", + "id": "ti_cybersixgill", + "description": "Ingest threat intelligence indicators from Cybersixgill with Elastic Agent.", + "data_streams": [ + { + "dataset": "threat", + "index_pattern": "logs-ti_cybersixgill.threat-*", + "title": "Cybersixgill Darkfeed Logs" + } + ], + "elser_embedding": "Cybersixgill - Ingest threat intelligence indicators from Cybersixgill with Elastic Agent. - Cybersixgill Darkfeed Logs" + }, + { + "title": "Trend Micro Vision One", + "id": "trend_micro_vision_one", + "description": "Collect logs from Trend Micro Vision One with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-trend_micro_vision_one.audit-*", + "title": "Collect Audit logs from Trend Micro Vision One." + }, + { + "dataset": "alert", + "index_pattern": "logs-trend_micro_vision_one.alert-*", + "title": "Collect Alert logs from Trend Micro Vision One." + }, + { + "dataset": "detection", + "index_pattern": "logs-trend_micro_vision_one.detection-*", + "title": "Collect Detection logs from Trend Micro Vision One." + } + ], + "elser_embedding": "Trend Micro Vision One - Collect logs from Trend Micro Vision One with Elastic Agent. - Collect Audit logs from Trend Micro Vision One. Collect Alert logs from Trend Micro Vision One. Collect Detection logs from Trend Micro Vision One." + }, + { + "title": "Traefik", + "id": "traefik", + "description": "Collect logs from Traefik servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "access", + "index_pattern": "logs-traefik.access-*", + "title": "Traefik access logs" + }, + { + "dataset": "health", + "index_pattern": "logs-traefik.health-*", + "title": "Traefik health metrics" + } + ], + "elser_embedding": "Traefik - Collect logs from Traefik servers with Elastic Agent. - Traefik access logs Traefik health metrics" + }, + { + "title": "F5 BIG-IP", + "id": "f5_bigip", + "description": "Collect logs from F5 BIG-IP with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-f5_bigip.log-*", + "title": "Collect logs from F5 BIG-IP" + } + ], + "elser_embedding": "F5 BIG-IP - Collect logs from F5 BIG-IP with Elastic Agent. - Collect logs from F5 BIG-IP" + }, + { + "title": "Custom Kafka Logs", + "id": "kafka_log", + "description": "Collect data from kafka topic with Elastic Agent.", + "data_streams": [ + { + "dataset": "generic", + "index_pattern": "logs-kafka_log.generic-*", + "title": "Custom Kafka Logs" + } + ], + "elser_embedding": "Custom Kafka Logs - Collect data from kafka topic with Elastic Agent. - Custom Kafka Logs" + }, + { + "title": "CyberArk Privileged Access Security", + "id": "cyberarkpas", + "description": "Collect logs from CyberArk Privileged Access Security with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-cyberarkpas.audit-*", + "title": "CyberArk PAS audit logs" + }, + { + "dataset": "monitor", + "index_pattern": "logs-cyberarkpas.monitor-*", + "title": "CyberArk PAS monitor Events" + } + ], + "elser_embedding": "CyberArk Privileged Access Security - Collect logs from CyberArk Privileged Access Security with Elastic Agent. - CyberArk PAS audit logs CyberArk PAS monitor Events" + }, + { + "title": "Palo Alto Prisma Cloud", + "id": "prisma_cloud", + "description": "Collect logs from Prisma Cloud with Elastic Agent.", + "data_streams": [ + { + "dataset": "host_profile", + "index_pattern": "logs-prisma_cloud.host_profile-*", + "title": "Collect Host Profile logs from Prisma Cloud Workload Protection." + }, + { + "dataset": "host", + "index_pattern": "logs-prisma_cloud.host-*", + "title": "Collect Host logs from Prisma Cloud Workload Protection." + }, + { + "dataset": "audit", + "index_pattern": "logs-prisma_cloud.audit-*", + "title": "Collect Audit logs from Prisma Cloud Security Posture Management." + }, + { + "dataset": "alert", + "index_pattern": "logs-prisma_cloud.alert-*", + "title": "Collect Alert logs from Prisma Cloud Security Posture Management." + }, + { + "dataset": "incident_audit", + "index_pattern": "logs-prisma_cloud.incident_audit-*", + "title": "Collect Incident Audit logs from Prisma Cloud Workload Protection." + } + ], + "elser_embedding": "Palo Alto Prisma Cloud - Collect logs from Prisma Cloud with Elastic Agent. - Collect Host Profile logs from Prisma Cloud Workload Protection. Collect Host logs from Prisma Cloud Workload Protection. Collect Audit logs from Prisma Cloud Security Posture Management. Collect Alert logs from Prisma Cloud Security Posture Management. Collect Incident Audit logs from Prisma Cloud Workload Protection." + }, + { + "title": "Cilium Tetragon", + "id": "cilium_tetragon", + "description": "Collect Cilium Tetragon logs from Kubernetes environments.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cilium_tetragon.log-*", + "title": "log\n" + } + ], + "elser_embedding": "Cilium Tetragon - Collect Cilium Tetragon logs from Kubernetes environments. - log\n" + }, + { + "title": "Qualys VMDR", + "id": "qualys_vmdr", + "description": "Collect data from Qualys VMDR platform with Elastic Agent.", + "data_streams": [ + { + "dataset": "knowledge_base", + "index_pattern": "logs-qualys_vmdr.knowledge_base-*", + "title": "Collect Knowledge Base data from Qualys VMDR platform." + }, + { + "dataset": "user_activity", + "index_pattern": "logs-qualys_vmdr.user_activity-*", + "title": "Collect User Activity Log data from Qualys VMDR platform." + }, + { + "dataset": "asset_host_detection", + "index_pattern": "logs-qualys_vmdr.asset_host_detection-*", + "title": "Collect Asset Host Detection data from Qualys VMDR platform." + } + ], + "elser_embedding": "Qualys VMDR - Collect data from Qualys VMDR platform with Elastic Agent. - Collect Knowledge Base data from Qualys VMDR platform. Collect User Activity Log data from Qualys VMDR platform. Collect Asset Host Detection data from Qualys VMDR platform." + }, + { + "title": "Elastic Agent", + "id": "elastic_agent", + "description": "Collect logs and metrics from Elastic Agents.", + "data_streams": [ + { + "dataset": "fleet_server_logs", + "index_pattern": "logs-elastic_agent.fleet_server_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "endpoint_security_metrics", + "index_pattern": "logs-elastic_agent.endpoint_security_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "apm_server_logs", + "index_pattern": "logs-elastic_agent.apm_server_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "osquerybeat_logs", + "index_pattern": "logs-elastic_agent.osquerybeat_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "heartbeat_logs", + "index_pattern": "logs-elastic_agent.heartbeat_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "metricbeat_logs", + "index_pattern": "logs-elastic_agent.metricbeat_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "elastic_agent_metrics", + "index_pattern": "logs-elastic_agent.elastic_agent_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "auditbeat_metrics", + "index_pattern": "logs-elastic_agent.auditbeat_metrics-*", + "title": "Elastic Agent Auditbeat Metrics" + }, + { + "dataset": "pf_elastic_symbolizer", + "index_pattern": "logs-elastic_agent.pf_elastic_symbolizer-*", + "title": "Elastic Agent" + }, + { + "dataset": "cloud_defend_logs", + "index_pattern": "logs-elastic_agent.cloud_defend_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "endpoint_sercurity_logs", + "index_pattern": "logs-elastic_agent.endpoint_sercurity_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "filebeat_input_metrics", + "index_pattern": "logs-elastic_agent.filebeat_input_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "metricbeat_metrics", + "index_pattern": "logs-elastic_agent.metricbeat_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "packetbeat_metrics", + "index_pattern": "logs-elastic_agent.packetbeat_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "apm_server_metrics", + "index_pattern": "logs-elastic_agent.apm_server_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "filebeat_input_logs", + "index_pattern": "logs-elastic_agent.filebeat_input_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "elastic_agent_logs", + "index_pattern": "logs-elastic_agent.elastic_agent_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "auditbeat_logs", + "index_pattern": "logs-elastic_agent.auditbeat_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "filebeat_logs", + "index_pattern": "logs-elastic_agent.filebeat_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "pf_host_agent_logs", + "index_pattern": "logs-elastic_agent.pf_host_agent_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "cloudbeat_logs", + "index_pattern": "logs-elastic_agent.cloudbeat_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "heartbeat_metrics", + "index_pattern": "logs-elastic_agent.heartbeat_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "cloudbeat_metrics", + "index_pattern": "logs-elastic_agent.cloudbeat_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "fleet_server_metrics", + "index_pattern": "logs-elastic_agent.fleet_server_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "packetbeat_logs", + "index_pattern": "logs-elastic_agent.packetbeat_logs-*", + "title": "Elastic Agent" + }, + { + "dataset": "osquerybeat_metrics", + "index_pattern": "logs-elastic_agent.osquerybeat_metrics-*", + "title": "Elastic Agent" + }, + { + "dataset": "pf_elastic_collector", + "index_pattern": "logs-elastic_agent.pf_elastic_collector-*", + "title": "Elastic Agent" + }, + { + "dataset": "filebeat_metrics", + "index_pattern": "logs-elastic_agent.filebeat_metrics-*", + "title": "Elastic Agent" + } + ], + "elser_embedding": "Elastic Agent - Collect logs and metrics from Elastic Agents. - Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Auditbeat Metrics Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent Elastic Agent" + }, + { + "title": "Custom Filestream Logs", + "id": "filestream", + "description": "Collect log data using filestream with Elastic Agent.", + "data_streams": [ + { + "dataset": "generic", + "index_pattern": "logs-filestream.generic-*", + "title": "Custom Filestream Logs" + } + ], + "elser_embedding": "Custom Filestream Logs - Collect log data using filestream with Elastic Agent. - Custom Filestream Logs" + }, + { + "title": "OpenCanary", + "id": "opencanary", + "description": "This integration collects and parses logs from OpenCanary honeypots.", + "data_streams": [ + { + "dataset": "events", + "index_pattern": "logs-opencanary.events-*", + "title": "OpenCanary HoneyPot Events" + } + ], + "elser_embedding": "OpenCanary - This integration collects and parses logs from OpenCanary honeypots. - OpenCanary HoneyPot Events" + }, + { + "title": "Palo Alto Cortex XDR", + "id": "panw_cortex_xdr", + "description": "Collect logs from Palo Alto Cortex XDR with Elastic Agent.", + "data_streams": [ + { + "dataset": "incidents", + "index_pattern": "logs-panw_cortex_xdr.incidents-*", + "title": "Palo Alto Cortex XDR Incidents API" + }, + { + "dataset": "alerts", + "index_pattern": "logs-panw_cortex_xdr.alerts-*", + "title": "Palo Alto Cortex XDR Alerts API" + } + ], + "elser_embedding": "Palo Alto Cortex XDR - Collect logs from Palo Alto Cortex XDR with Elastic Agent. - Palo Alto Cortex XDR Incidents API Palo Alto Cortex XDR Alerts API" + }, + { + "title": "Cisco Nexus", + "id": "cisco_nexus", + "description": "Collect logs from Cisco Nexus with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_nexus.log-*", + "title": "Collect logs from Cisco Nexus" + } + ], + "elser_embedding": "Cisco Nexus - Collect logs from Cisco Nexus with Elastic Agent. - Collect logs from Cisco Nexus" + }, + { + "title": "JumpCloud", + "id": "jumpcloud", + "description": "Collect logs from JumpCloud Directory as a Service", + "data_streams": [ + { + "dataset": "events", + "index_pattern": "logs-jumpcloud.events-*", + "title": "JumpCloud Directory as a Service Events" + } + ], + "elser_embedding": "JumpCloud - Collect logs from JumpCloud Directory as a Service - JumpCloud Directory as a Service Events" + }, + { + "title": "Microsoft Defender for Endpoint", + "id": "microsoft_defender_endpoint", + "description": "Collect logs from Microsoft Defender for Endpoint with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-microsoft_defender_endpoint.log-*", + "title": "Microsoft Defender for Endpoint logs" + } + ], + "elser_embedding": "Microsoft Defender for Endpoint - Collect logs from Microsoft Defender for Endpoint with Elastic Agent. - Microsoft Defender for Endpoint logs" + }, + { + "title": "ActiveMQ", + "id": "activemq", + "description": "Collect logs and metrics from ActiveMQ instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "broker", + "index_pattern": "logs-activemq.broker-*", + "title": "ActiveMQ broker metrics" + }, + { + "dataset": "queue", + "index_pattern": "logs-activemq.queue-*", + "title": "ActiveMQ queue metrics" + }, + { + "dataset": "audit", + "index_pattern": "logs-activemq.audit-*", + "title": "ActiveMQ audit logs" + }, + { + "dataset": "log", + "index_pattern": "logs-activemq.log-*", + "title": "ActiveMQ log logs" + }, + { + "dataset": "topic", + "index_pattern": "logs-activemq.topic-*", + "title": "ActiveMQ topic metrics" + } + ], + "elser_embedding": "ActiveMQ - Collect logs and metrics from ActiveMQ instances with Elastic Agent. - ActiveMQ broker metrics ActiveMQ queue metrics ActiveMQ audit logs ActiveMQ log logs ActiveMQ topic metrics" + }, + { + "title": "AbuseCH", + "id": "ti_abusech", + "description": "Ingest threat intelligence indicators from URL Haus, Malware Bazaar, and Threat Fox feeds with Elastic Agent.", + "data_streams": [ + { + "dataset": "threatfox", + "index_pattern": "logs-ti_abusech.threatfox-*", + "title": "AbuseCH Threat Fox indicators" + }, + { + "dataset": "url", + "index_pattern": "logs-ti_abusech.url-*", + "title": "AbuseCH URL logs" + }, + { + "dataset": "malware", + "index_pattern": "logs-ti_abusech.malware-*", + "title": "AbuseCH Malware payloads" + }, + { + "dataset": "malwarebazaar", + "index_pattern": "logs-ti_abusech.malwarebazaar-*", + "title": "AbuseCH MalwareBazaar payloads" + } + ], + "elser_embedding": "AbuseCH - Ingest threat intelligence indicators from URL Haus, Malware Bazaar, and Threat Fox feeds with Elastic Agent. - AbuseCH Threat Fox indicators AbuseCH URL logs AbuseCH Malware payloads AbuseCH MalwareBazaar payloads" + }, + { + "title": "Infoblox BloxOne DDI", + "id": "infoblox_bloxone_ddi", + "description": "Collect logs from Infoblox BloxOne DDI with Elastic Agent.", + "data_streams": [ + { + "dataset": "dns_data", + "index_pattern": "logs-infoblox_bloxone_ddi.dns_data-*", + "title": "Collect DNS Data logs from Infoblox BloxOne DDI" + }, + { + "dataset": "dns_config", + "index_pattern": "logs-infoblox_bloxone_ddi.dns_config-*", + "title": "Collect DNS Config logs from Infoblox BloxOne DDI" + }, + { + "dataset": "dhcp_lease", + "index_pattern": "logs-infoblox_bloxone_ddi.dhcp_lease-*", + "title": "Collect DHCP Lease logs from Infoblox BloxOne DDI" + } + ], + "elser_embedding": "Infoblox BloxOne DDI - Collect logs from Infoblox BloxOne DDI with Elastic Agent. - Collect DNS Data logs from Infoblox BloxOne DDI Collect DNS Config logs from Infoblox BloxOne DDI Collect DHCP Lease logs from Infoblox BloxOne DDI" + }, + { + "title": "Google Security Command Center", + "id": "google_scc", + "description": "Collect logs from Google Security Command Center with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-google_scc.audit-*", + "title": "Collect Audit logs from Google Security Command Center." + }, + { + "dataset": "finding", + "index_pattern": "logs-google_scc.finding-*", + "title": "Collect Finding logs from Google Security Command Center." + }, + { + "dataset": "asset", + "index_pattern": "logs-google_scc.asset-*", + "title": "Collect Asset logs from Google Security Command Center." + }, + { + "dataset": "source", + "index_pattern": "logs-google_scc.source-*", + "title": "Collect Source logs from Google Security Command Center." + } + ], + "elser_embedding": "Google Security Command Center - Collect logs from Google Security Command Center with Elastic Agent. - Collect Audit logs from Google Security Command Center. Collect Finding logs from Google Security Command Center. Collect Asset logs from Google Security Command Center. Collect Source logs from Google Security Command Center." + }, + { + "title": "CoreDNS", + "id": "coredns", + "description": "Collect logs from CoreDNS instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-coredns.log-*", + "title": "CoreDNS logs" + } + ], + "elser_embedding": "CoreDNS - Collect logs from CoreDNS instances with Elastic Agent. - CoreDNS logs" + }, + { + "title": "NetFlow Records", + "id": "netflow", + "description": "Collect flow records from NetFlow and IPFIX exporters with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-netflow.log-*", + "title": "NetFlow logs" + } + ], + "elser_embedding": "NetFlow Records - Collect flow records from NetFlow and IPFIX exporters with Elastic Agent. - NetFlow logs" + }, + { + "title": "Forcepoint Web Security", + "id": "forcepoint_web", + "description": "Forcepoint Web Security", + "data_streams": [ + { + "dataset": "logs", + "index_pattern": "logs-forcepoint_web.logs-*", + "title": "Forcepoint Web Security Logs" + } + ], + "elser_embedding": "Forcepoint Web Security - Forcepoint Web Security - Forcepoint Web Security Logs" + }, + { + "title": "Trellix EDR Cloud", + "id": "trellix_edr_cloud", + "description": "Collect logs from Trellix EDR Cloud with Elastic Agent.", + "data_streams": [ + { + "dataset": "event", + "index_pattern": "logs-trellix_edr_cloud.event-*", + "title": "Collect Event logs from Trellix EDR Cloud." + } + ], + "elser_embedding": "Trellix EDR Cloud - Collect logs from Trellix EDR Cloud with Elastic Agent. - Collect Event logs from Trellix EDR Cloud." + }, + { + "title": "Slack Logs", + "id": "slack", + "description": "Slack Logs Integration", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-slack.audit-*", + "title": "Slack Audit Logs" + } + ], + "elser_embedding": "Slack Logs - Slack Logs Integration - Slack Audit Logs" + }, + { + "title": "Cisco FTD", + "id": "cisco_ftd", + "description": "Collect logs from Cisco FTD with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_ftd.log-*", + "title": "Cisco FTD logs" + } + ], + "elser_embedding": "Cisco FTD - Collect logs from Cisco FTD with Elastic Agent. - Cisco FTD logs" + }, + { + "title": "Microsoft DNS Server", + "id": "microsoft_dnsserver", + "description": "Collect logs from Microsoft DNS Server with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-microsoft_dnsserver.audit-*", + "title": "Collect DNS Server Audit logs" + }, + { + "dataset": "analytical", + "index_pattern": "logs-microsoft_dnsserver.analytical-*", + "title": "Collect DNS Server Analytical logs" + } + ], + "elser_embedding": "Microsoft DNS Server - Collect logs from Microsoft DNS Server with Elastic Agent. - Collect DNS Server Audit logs Collect DNS Server Analytical logs" + }, + { + "title": "Mandiant Advantage", + "id": "ti_mandiant_advantage", + "description": "Collect Threat Intelligence from products within the Mandiant Advantage platform.", + "data_streams": [ + { + "dataset": "threat_intelligence", + "index_pattern": "logs-ti_mandiant_advantage.threat_intelligence-*", + "title": "Mandiant Threat Intelligence" + } + ], + "elser_embedding": "Mandiant Advantage - Collect Threat Intelligence from products within the Mandiant Advantage platform. - Mandiant Threat Intelligence" + }, + { + "title": "Fortinet FortiClient Logs", + "id": "fortinet_forticlient", + "description": "Collect logs from Fortinet FortiClient instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-fortinet_forticlient.log-*", + "title": "Fortinet FortiClient Endpoint Security logs" + } + ], + "elser_embedding": "Fortinet FortiClient Logs - Collect logs from Fortinet FortiClient instances with Elastic Agent. - Fortinet FortiClient Endpoint Security logs" + }, + { + "title": "AWS Fargate (for ECS clusters)", + "id": "awsfargate", + "description": "Collects metrics from containers and tasks running on Amazon ECS clusters with Elastic Agent.", + "data_streams": [ + { + "dataset": "task_stats", + "index_pattern": "logs-awsfargate.task_stats-*", + "title": "AWS Fargate task_stats metrics" + } + ], + "elser_embedding": "AWS Fargate (for ECS clusters) - Collects metrics from containers and tasks running on Amazon ECS clusters with Elastic Agent. - AWS Fargate task_stats metrics" + }, + { + "title": "Azure Network Watcher VNet", + "id": "azure_network_watcher_vnet", + "description": "Collect logs from Azure Network Watcher VNet with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-azure_network_watcher_vnet.log-*", + "title": "Collect VNet logs from Azure Network Watcher" + } + ], + "elser_embedding": "Azure Network Watcher VNet - Collect logs from Azure Network Watcher VNet with Elastic Agent. - Collect VNet logs from Azure Network Watcher" + }, + { + "title": "Osquery Logs", + "id": "osquery", + "description": "Collect logs from Osquery with Elastic Agent.", + "data_streams": [ + { + "dataset": "result", + "index_pattern": "logs-osquery.result-*", + "title": "Osquery result logs" + } + ], + "elser_embedding": "Osquery Logs - Collect logs from Osquery with Elastic Agent. - Osquery result logs" + }, + { + "title": "Pleasant Password Server", + "id": "pps", + "description": "Integration for Pleasant Password Server Syslog Messages", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-pps.log-*", + "title": "Pleasant Password Server logs" + } + ], + "elser_embedding": "Pleasant Password Server - Integration for Pleasant Password Server Syslog Messages - Pleasant Password Server logs" + }, + { + "title": "Bravura Monitor", + "id": "hid_bravura_monitor", + "description": "Collect logs from Bravura Security Fabric with Elastic Agent.", + "data_streams": [ + { + "dataset": "winlog", + "index_pattern": "logs-hid_bravura_monitor.winlog-*", + "title": "Bravura Security Fabric logs" + }, + { + "dataset": "log", + "index_pattern": "logs-hid_bravura_monitor.log-*", + "title": "Bravura Monitor" + } + ], + "elser_embedding": "Bravura Monitor - Collect logs from Bravura Security Fabric with Elastic Agent. - Bravura Security Fabric logs Bravura Monitor" + }, + { + "title": "MISP", + "id": "ti_misp", + "description": "Ingest threat intelligence indicators from MISP platform with Elastic Agent.", + "data_streams": [ + { + "dataset": "threat", + "index_pattern": "logs-ti_misp.threat-*", + "title": "MISP" + }, + { + "dataset": "threat_attributes", + "index_pattern": "logs-ti_misp.threat_attributes-*", + "title": "MISP" + } + ], + "elser_embedding": "MISP - Ingest threat intelligence indicators from MISP platform with Elastic Agent. - MISP MISP" + }, + { + "title": "Redis Enterprise", + "id": "redisenterprise", + "description": "Collect metrics from Redis Enterprise Cluster", + "data_streams": [ + { + "dataset": "node", + "index_pattern": "logs-redisenterprise.node-*", + "title": "node" + }, + { + "dataset": "proxy", + "index_pattern": "logs-redisenterprise.proxy-*", + "title": "proxy" + } + ], + "elser_embedding": "Redis Enterprise - Collect metrics from Redis Enterprise Cluster - node proxy" + }, + { + "title": "Network Packet Capture", + "id": "network_traffic", + "description": "Capture and analyze network traffic from a host with Elastic Agent.", + "data_streams": [ + { + "dataset": "nfs", + "index_pattern": "logs-network_traffic.nfs-*", + "title": "NFS" + }, + { + "dataset": "tls", + "index_pattern": "logs-network_traffic.tls-*", + "title": "TLS" + }, + { + "dataset": "icmp", + "index_pattern": "logs-network_traffic.icmp-*", + "title": "ICMP" + }, + { + "dataset": "cassandra", + "index_pattern": "logs-network_traffic.cassandra-*", + "title": "Cassandra" + }, + { + "dataset": "mongodb", + "index_pattern": "logs-network_traffic.mongodb-*", + "title": "MongoDB" + }, + { + "dataset": "thrift", + "index_pattern": "logs-network_traffic.thrift-*", + "title": "Thrift" + }, + { + "dataset": "flow", + "index_pattern": "logs-network_traffic.flow-*", + "title": "Flows" + }, + { + "dataset": "dhcpv4", + "index_pattern": "logs-network_traffic.dhcpv4-*", + "title": "DHCP" + }, + { + "dataset": "pgsql", + "index_pattern": "logs-network_traffic.pgsql-*", + "title": "PostgreSQL" + }, + { + "dataset": "redis", + "index_pattern": "logs-network_traffic.redis-*", + "title": "Redis" + }, + { + "dataset": "dns", + "index_pattern": "logs-network_traffic.dns-*", + "title": "DNS" + }, + { + "dataset": "sip", + "index_pattern": "logs-network_traffic.sip-*", + "title": "SIP" + }, + { + "dataset": "mysql", + "index_pattern": "logs-network_traffic.mysql-*", + "title": "MySQL" + }, + { + "dataset": "amqp", + "index_pattern": "logs-network_traffic.amqp-*", + "title": "AMQP" + }, + { + "dataset": "http", + "index_pattern": "logs-network_traffic.http-*", + "title": "HTTP" + }, + { + "dataset": "memcached", + "index_pattern": "logs-network_traffic.memcached-*", + "title": "Memcached" + } + ], + "elser_embedding": "Network Packet Capture - Capture and analyze network traffic from a host with Elastic Agent. - NFS TLS ICMP Cassandra MongoDB Thrift Flows DHCP PostgreSQL Redis DNS SIP MySQL AMQP HTTP Memcached" + }, + { + "title": "MySQL Enterprise", + "id": "mysql_enterprise", + "description": "Collect audit logs from MySQL Enterprise with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-mysql_enterprise.audit-*", + "title": "MySQL Enterprise Audit Log" + } + ], + "elser_embedding": "MySQL Enterprise - Collect audit logs from MySQL Enterprise with Elastic Agent. - MySQL Enterprise Audit Log" + }, + { + "title": "GitHub", + "id": "github", + "description": "Collect logs from GitHub with Elastic Agent.", + "data_streams": [ + { + "dataset": "dependabot", + "index_pattern": "logs-github.dependabot-*", + "title": "GHAS Dependabot" + }, + { + "dataset": "issues", + "index_pattern": "logs-github.issues-*", + "title": "Github Issue" + }, + { + "dataset": "secret_scanning", + "index_pattern": "logs-github.secret_scanning-*", + "title": "GHAS Secret Scanning" + }, + { + "dataset": "audit", + "index_pattern": "logs-github.audit-*", + "title": "GitHub Audit Logs" + }, + { + "dataset": "code_scanning", + "index_pattern": "logs-github.code_scanning-*", + "title": "GHAS Code Scanning" + } + ], + "elser_embedding": "GitHub - Collect logs from GitHub with Elastic Agent. - GHAS Dependabot Github Issue GHAS Secret Scanning GitHub Audit Logs GHAS Code Scanning" + }, + { + "title": "Microsoft Entra ID Entity Analytics", + "id": "entityanalytics_entra_id", + "description": "Collect identities from Microsoft Entra ID (formerly Azure Active Directory) with Elastic Agent.", + "data_streams": [ + { + "dataset": "device", + "index_pattern": "logs-entityanalytics_entra_id.device-*", + "title": "Microsoft Entra ID Entity Analytics Device Events" + }, + { + "dataset": "user", + "index_pattern": "logs-entityanalytics_entra_id.user-*", + "title": "Microsoft Entra ID Entity Analytics User Events" + }, + { + "dataset": "entity", + "index_pattern": "logs-entityanalytics_entra_id.entity-*", + "title": "Identities" + } + ], + "elser_embedding": "Microsoft Entra ID Entity Analytics - Collect identities from Microsoft Entra ID (formerly Azure Active Directory) with Elastic Agent. - Microsoft Entra ID Entity Analytics Device Events Microsoft Entra ID Entity Analytics User Events Identities" + }, + { + "title": "ThreatConnect", + "id": "ti_threatconnect", + "description": "Collects Indicators from ThreatConnect using the Elastic Agent and saves them as logs inside Elastic", + "data_streams": [ + { + "dataset": "indicator", + "index_pattern": "logs-ti_threatconnect.indicator-*", + "title": "Collect Indicators from ThreatConnect." + } + ], + "elser_embedding": "ThreatConnect - Collects Indicators from ThreatConnect using the Elastic Agent and saves them as logs inside Elastic - Collect Indicators from ThreatConnect." + }, + { + "title": "Microsoft Sentinel", + "id": "microsoft_sentinel", + "description": "Collect logs from Microsoft Sentinel with Elastic Agent.", + "data_streams": [ + { + "dataset": "alert", + "index_pattern": "logs-microsoft_sentinel.alert-*", + "title": "Microsoft Sentinel Alert logs" + }, + { + "dataset": "incident", + "index_pattern": "logs-microsoft_sentinel.incident-*", + "title": "Microsoft Sentinel Incident logs" + }, + { + "dataset": "event", + "index_pattern": "logs-microsoft_sentinel.event-*", + "title": "Collect Events from Microsoft Sentinel." + } + ], + "elser_embedding": "Microsoft Sentinel - Collect logs from Microsoft Sentinel with Elastic Agent. - Microsoft Sentinel Alert logs Microsoft Sentinel Incident logs Collect Events from Microsoft Sentinel." + }, + { + "title": "Check Point", + "id": "checkpoint", + "description": "Collect logs from Check Point with Elastic Agent.", + "data_streams": [ + { + "dataset": "firewall", + "index_pattern": "logs-checkpoint.firewall-*", + "title": "Check Point firewall logs" + } + ], + "elser_embedding": "Check Point - Collect logs from Check Point with Elastic Agent. - Check Point firewall logs" + }, + { + "title": "WatchGuard Firebox", + "id": "watchguard_firebox", + "description": "Collect logs from WatchGuard Firebox with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-watchguard_firebox.log-*", + "title": "WatchGuard Firebox logs" + } + ], + "elser_embedding": "WatchGuard Firebox - Collect logs from WatchGuard Firebox with Elastic Agent. - WatchGuard Firebox logs" + }, + { + "title": "Nagios XI", + "id": "nagios_xi", + "description": "Collect Logs and Metrics from Nagios XI with Elastic Agent.", + "data_streams": [ + { + "dataset": "host", + "index_pattern": "logs-nagios_xi.host-*", + "title": "Host" + }, + { + "dataset": "events", + "index_pattern": "logs-nagios_xi.events-*", + "title": "Events" + }, + { + "dataset": "service", + "index_pattern": "logs-nagios_xi.service-*", + "title": "Service" + } + ], + "elser_embedding": "Nagios XI - Collect Logs and Metrics from Nagios XI with Elastic Agent. - Host Events Service" + }, + { + "title": "Atlassian Jira", + "id": "atlassian_jira", + "description": "Collect logs from Atlassian Jira with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-atlassian_jira.audit-*", + "title": "Jira Audit Logs" + } + ], + "elser_embedding": "Atlassian Jira - Collect logs from Atlassian Jira with Elastic Agent. - Jira Audit Logs" + }, + { + "title": "Snyk", + "id": "snyk", + "description": "Collect logs from Snyk with Elastic Agent.", + "data_streams": [ + { + "dataset": "issues", + "index_pattern": "logs-snyk.issues-*", + "title": "Collect Snyk Issues Data" + }, + { + "dataset": "audit", + "index_pattern": "logs-snyk.audit-*", + "title": "Collect Snyk Audit Logs" + }, + { + "dataset": "audit_logs", + "index_pattern": "logs-snyk.audit_logs-*", + "title": "Collect Snyk Audit Logs" + }, + { + "dataset": "vulnerabilities", + "index_pattern": "logs-snyk.vulnerabilities-*", + "title": "Collect Snyk Vulnerability Data" + } + ], + "elser_embedding": "Snyk - Collect logs from Snyk with Elastic Agent. - Collect Snyk Issues Data Collect Snyk Audit Logs Collect Snyk Audit Logs Collect Snyk Vulnerability Data" + }, + { + "title": "Google Cloud Platform", + "id": "gcp", + "description": "Collect logs and metrics from Google Cloud Platform with Elastic Agent.", + "data_streams": [ + { + "dataset": "compute", + "index_pattern": "logs-gcp.compute-*", + "title": "GCP Compute Metrics" + }, + { + "dataset": "pubsub", + "index_pattern": "logs-gcp.pubsub-*", + "title": "GCP PubSub Metrics" + }, + { + "dataset": "cloudsql_postgresql", + "index_pattern": "logs-gcp.cloudsql_postgresql-*", + "title": "GCP CloudSQL PostgreSQL Metrics" + }, + { + "dataset": "billing", + "index_pattern": "logs-gcp.billing-*", + "title": "GCP Billing Metrics" + }, + { + "dataset": "loadbalancing_metrics", + "index_pattern": "logs-gcp.loadbalancing_metrics-*", + "title": "GCP Load Balancing Metrics" + }, + { + "dataset": "cloudrun_metrics", + "index_pattern": "logs-gcp.cloudrun_metrics-*", + "title": "GCP Cloud Run Metrics" + }, + { + "dataset": "audit", + "index_pattern": "logs-gcp.audit-*", + "title": "Google Cloud Platform (GCP) audit logs" + }, + { + "dataset": "dataproc", + "index_pattern": "logs-gcp.dataproc-*", + "title": "GCP Dataproc Metrics" + }, + { + "dataset": "redis", + "index_pattern": "logs-gcp.redis-*", + "title": "GCP Redis Metrics" + }, + { + "dataset": "cloudsql_mysql", + "index_pattern": "logs-gcp.cloudsql_mysql-*", + "title": "GCP CloudSQL MySQL Metrics" + }, + { + "dataset": "dns", + "index_pattern": "logs-gcp.dns-*", + "title": "Google Cloud Platform (GCP) DNS logs" + }, + { + "dataset": "cloudsql_sqlserver", + "index_pattern": "logs-gcp.cloudsql_sqlserver-*", + "title": "GCP CloudSQL SQL Server Metrics" + }, + { + "dataset": "storage", + "index_pattern": "logs-gcp.storage-*", + "title": "GCP Storage Metrics" + }, + { + "dataset": "gke", + "index_pattern": "logs-gcp.gke-*", + "title": "GCP GKE Metrics" + }, + { + "dataset": "vpcflow", + "index_pattern": "logs-gcp.vpcflow-*", + "title": "Google Cloud Platform (GCP) vpcflow logs" + }, + { + "dataset": "loadbalancing_logs", + "index_pattern": "logs-gcp.loadbalancing_logs-*", + "title": "Google Cloud Platform (GCP) Load Balancing logs" + }, + { + "dataset": "firestore", + "index_pattern": "logs-gcp.firestore-*", + "title": "GCP Firestore Metrics" + }, + { + "dataset": "firewall", + "index_pattern": "logs-gcp.firewall-*", + "title": "Google Cloud Platform (GCP) firewall logs" + } + ], + "elser_embedding": "Google Cloud Platform - Collect logs and metrics from Google Cloud Platform with Elastic Agent. - GCP Compute Metrics GCP PubSub Metrics GCP CloudSQL PostgreSQL Metrics GCP Billing Metrics GCP Load Balancing Metrics GCP Cloud Run Metrics Google Cloud Platform (GCP) audit logs GCP Dataproc Metrics GCP Redis Metrics GCP CloudSQL MySQL Metrics Google Cloud Platform (GCP) DNS logs GCP CloudSQL SQL Server Metrics GCP Storage Metrics GCP GKE Metrics Google Cloud Platform (GCP) vpcflow logs Google Cloud Platform (GCP) Load Balancing logs GCP Firestore Metrics Google Cloud Platform (GCP) firewall logs" + }, + { + "title": "Logstash", + "id": "logstash", + "description": "Collect logs and metrics from Logstash with Elastic Agent.", + "data_streams": [ + { + "dataset": "node_cel", + "index_pattern": "logs-logstash.node_cel-*", + "title": "Logstash Node Stats" + }, + { + "dataset": "pipeline", + "index_pattern": "logs-logstash.pipeline-*", + "title": "Logstash pipeline" + }, + { + "dataset": "plugins", + "index_pattern": "logs-logstash.plugins-*", + "title": "Logstash plugins" + }, + { + "dataset": "node_stats", + "index_pattern": "logs-logstash.node_stats-*", + "title": "Logstash node_stats metrics" + }, + { + "dataset": "slowlog", + "index_pattern": "logs-logstash.slowlog-*", + "title": "logstash slowlog logs" + }, + { + "dataset": "log", + "index_pattern": "logs-logstash.log-*", + "title": "Logstash logs" + }, + { + "dataset": "node", + "index_pattern": "logs-logstash.node-*", + "title": "Logstash node metrics" + } + ], + "elser_embedding": "Logstash - Collect logs and metrics from Logstash with Elastic Agent. - Logstash Node Stats Logstash pipeline Logstash plugins Logstash node_stats metrics logstash slowlog logs Logstash logs Logstash node metrics" + }, + { + "title": "Palo Alto Prisma Access", + "id": "prisma_access", + "description": "Collect logs from Palo Alto Prisma Access with Elastic Agent.", + "data_streams": [ + { + "dataset": "event", + "index_pattern": "logs-prisma_access.event-*", + "title": "Collect Events from Palo Alto Prisma Access" + } + ], + "elser_embedding": "Palo Alto Prisma Access - Collect logs from Palo Alto Prisma Access with Elastic Agent. - Collect Events from Palo Alto Prisma Access" + }, + { + "title": "Barracuda CloudGen Firewall Logs", + "id": "barracuda_cloudgen_firewall", + "description": "Collect logs from Barracuda CloudGen Firewall devices with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-barracuda_cloudgen_firewall.log-*", + "title": "Barracuda CloudGen Firewall Logs" + } + ], + "elser_embedding": "Barracuda CloudGen Firewall Logs - Collect logs from Barracuda CloudGen Firewall devices with Elastic Agent. - Barracuda CloudGen Firewall Logs" + }, + { + "title": "Jamf Pro", + "id": "jamf_pro", + "description": "Collect logs and inventory data from Jamf Pro with Elastic Agent", + "data_streams": [ + { + "dataset": "inventory", + "index_pattern": "logs-jamf_pro.inventory-*", + "title": "Inventory data" + }, + { + "dataset": "events", + "index_pattern": "logs-jamf_pro.events-*", + "title": "Jamf Pro Events" + } + ], + "elser_embedding": "Jamf Pro - Collect logs and inventory data from Jamf Pro with Elastic Agent - Inventory data Jamf Pro Events" + }, + { + "title": "Fortinet FortiManager Logs", + "id": "fortinet_fortimanager", + "description": "Collect logs from Fortinet FortiManager instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-fortinet_fortimanager.log-*", + "title": "Collect logs from Fortinet FortiManager" + } + ], + "elser_embedding": "Fortinet FortiManager Logs - Collect logs from Fortinet FortiManager instances with Elastic Agent. - Collect logs from Fortinet FortiManager" + }, + { + "title": "Elastic APM", + "id": "apm", + "description": "Monitor, detect, and diagnose complex application performance issues.", + "data_streams": [], + "elser_embedding": "Elastic APM - Monitor, detect, and diagnose complex application performance issues. - " + }, + { + "title": "AlienVault OTX", + "id": "ti_otx", + "description": "Ingest threat intelligence indicators from AlienVault Open Threat Exchange (OTX) with Elastic Agent.", + "data_streams": [ + { + "dataset": "pulses_subscribed", + "index_pattern": "logs-ti_otx.pulses_subscribed-*", + "title": "Alienvault OTX Subcribed Pulses" + }, + { + "dataset": "threat", + "index_pattern": "logs-ti_otx.threat-*", + "title": "Alienvault OTX logs" + } + ], + "elser_embedding": "AlienVault OTX - Ingest threat intelligence indicators from AlienVault Open Threat Exchange (OTX) with Elastic Agent. - Alienvault OTX Subcribed Pulses Alienvault OTX logs" + }, + { + "title": "Check Point", + "id": "checkpoint", + "description": "Collect logs from Check Point with Elastic Agent.", + "data_streams": [ + { + "dataset": "firewall", + "index_pattern": "logs-checkpoint.firewall-*", + "title": "Check Point firewall logs" + } + ], + "elser_embedding": "Check Point - Collect logs from Check Point with Elastic Agent. - Check Point firewall logs" + }, + { + "title": "Kubernetes OpenTelemetry Assets", + "id": "kubernetes_otel", + "description": "Utilise the pre-built dashboard for OTel-native metrics and events collected from a Kubernetes cluster", + "data_streams": [], + "elser_embedding": "Kubernetes OpenTelemetry Assets - Utilise the pre-built dashboard for OTel-native metrics and events collected from a Kubernetes cluster - " + }, + { + "title": "EclecticIQ", + "id": "ti_eclecticiq", + "description": "Ingest threat intelligence from EclecticIQ with Elastic Agent", + "data_streams": [ + { + "dataset": "threat", + "index_pattern": "logs-ti_eclecticiq.threat-*", + "title": "Poll Outgoing feed" + } + ], + "elser_embedding": "EclecticIQ - Ingest threat intelligence from EclecticIQ with Elastic Agent - Poll Outgoing feed" + }, + { + "title": "Lumos", + "id": "lumos", + "description": "An integration with Lumos to ship your Activity logs to your Elastic instance.", + "data_streams": [ + { + "dataset": "activity_logs", + "index_pattern": "logs-lumos.activity_logs-*", + "title": "Lumos Activity Logs" + } + ], + "elser_embedding": "Lumos - An integration with Lumos to ship your Activity logs to your Elastic instance. - Lumos Activity Logs" + }, + { + "title": "Anomali", + "id": "ti_anomali", + "description": "Ingest threat intelligence indicators from Anomali with Elastic Agent.", + "data_streams": [ + { + "dataset": "threatstream", + "index_pattern": "logs-ti_anomali.threatstream-*", + "title": "Anomali ThreatStream" + }, + { + "dataset": "intelligence", + "index_pattern": "logs-ti_anomali.intelligence-*", + "title": "Anomali ThreatStream" + } + ], + "elser_embedding": "Anomali - Ingest threat intelligence indicators from Anomali with Elastic Agent. - Anomali ThreatStream Anomali ThreatStream" + }, + { + "title": "Jolokia Input", + "id": "jolokia", + "description": "Collects Metrics from Jolokia Agents", + "data_streams": [], + "elser_embedding": "Jolokia Input - Collects Metrics from Jolokia Agents - " + }, + { + "title": "Sysdig", + "id": "sysdig", + "description": "Collect alerts from Sysdig using Elastic Agent.", + "data_streams": [ + { + "dataset": "alerts", + "index_pattern": "logs-sysdig.alerts-*", + "title": "Sysdig" + } + ], + "elser_embedding": "Sysdig - Collect alerts from Sysdig using Elastic Agent. - Sysdig" + }, + { + "title": "Pulse Connect Secure", + "id": "pulse_connect_secure", + "description": "Collect logs from Pulse Connect Secure with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-pulse_connect_secure.log-*", + "title": "Pulse Connect Secure" + } + ], + "elser_embedding": "Pulse Connect Secure - Collect logs from Pulse Connect Secure with Elastic Agent. - Pulse Connect Secure" + }, + { + "title": "Zeek", + "id": "zeek", + "description": "Collect logs from Zeek with Elastic Agent.", + "data_streams": [ + { + "dataset": "x509", + "index_pattern": "logs-zeek.x509-*", + "title": "Zeek x509 logs" + }, + { + "dataset": "software", + "index_pattern": "logs-zeek.software-*", + "title": "Zeek software logs" + }, + { + "dataset": "traceroute", + "index_pattern": "logs-zeek.traceroute-*", + "title": "Zeek traceroute logs" + }, + { + "dataset": "capture_loss", + "index_pattern": "logs-zeek.capture_loss-*", + "title": "Zeek capture_loss logs" + }, + { + "dataset": "smb_cmd", + "index_pattern": "logs-zeek.smb_cmd-*", + "title": "Zeek smb_cmd logs" + }, + { + "dataset": "snmp", + "index_pattern": "logs-zeek.snmp-*", + "title": "Zeek snmp logs" + }, + { + "dataset": "irc", + "index_pattern": "logs-zeek.irc-*", + "title": "Zeek irc logs" + }, + { + "dataset": "intel", + "index_pattern": "logs-zeek.intel-*", + "title": "Zeek intel logs" + }, + { + "dataset": "pe", + "index_pattern": "logs-zeek.pe-*", + "title": "Zeek pe logs" + }, + { + "dataset": "known_services", + "index_pattern": "logs-zeek.known_services-*", + "title": "Zeek Known Services logs" + }, + { + "dataset": "radius", + "index_pattern": "logs-zeek.radius-*", + "title": "Zeek radius logs" + }, + { + "dataset": "modbus", + "index_pattern": "logs-zeek.modbus-*", + "title": "Zeek modbus logs" + }, + { + "dataset": "tunnel", + "index_pattern": "logs-zeek.tunnel-*", + "title": "Zeek tunnel logs" + }, + { + "dataset": "stats", + "index_pattern": "logs-zeek.stats-*", + "title": "Zeek stats logs" + }, + { + "dataset": "smb_files", + "index_pattern": "logs-zeek.smb_files-*", + "title": "Zeek smb_files logs" + }, + { + "dataset": "ocsp", + "index_pattern": "logs-zeek.ocsp-*", + "title": "Zeek ocsp logs" + }, + { + "dataset": "connection", + "index_pattern": "logs-zeek.connection-*", + "title": "Zeek connection logs" + }, + { + "dataset": "kerberos", + "index_pattern": "logs-zeek.kerberos-*", + "title": "Zeek kerberos logs" + }, + { + "dataset": "weird", + "index_pattern": "logs-zeek.weird-*", + "title": "Zeek weird logs" + }, + { + "dataset": "smb_mapping", + "index_pattern": "logs-zeek.smb_mapping-*", + "title": "Zeek smb_mapping logs" + }, + { + "dataset": "signature", + "index_pattern": "logs-zeek.signature-*", + "title": "Zeek signature logs" + }, + { + "dataset": "ntp", + "index_pattern": "logs-zeek.ntp-*", + "title": "Zeek ntp logs" + }, + { + "dataset": "dns", + "index_pattern": "logs-zeek.dns-*", + "title": "Zeek dns logs" + }, + { + "dataset": "dpd", + "index_pattern": "logs-zeek.dpd-*", + "title": "Zeek dpd logs" + }, + { + "dataset": "dhcp", + "index_pattern": "logs-zeek.dhcp-*", + "title": "Zeek dhcp logs" + }, + { + "dataset": "notice", + "index_pattern": "logs-zeek.notice-*", + "title": "Zeek notice logs" + }, + { + "dataset": "files", + "index_pattern": "logs-zeek.files-*", + "title": "Zeek files logs" + }, + { + "dataset": "ntlm", + "index_pattern": "logs-zeek.ntlm-*", + "title": "Zeek ntlm logs" + }, + { + "dataset": "known_certs", + "index_pattern": "logs-zeek.known_certs-*", + "title": "Zeek Known Certs logs" + }, + { + "dataset": "sip", + "index_pattern": "logs-zeek.sip-*", + "title": "Zeek sip logs" + }, + { + "dataset": "rdp", + "index_pattern": "logs-zeek.rdp-*", + "title": "Zeek rdp logs" + }, + { + "dataset": "mysql", + "index_pattern": "logs-zeek.mysql-*", + "title": "Zeek mysql logs" + }, + { + "dataset": "rfb", + "index_pattern": "logs-zeek.rfb-*", + "title": "Zeek rfb logs" + }, + { + "dataset": "ssh", + "index_pattern": "logs-zeek.ssh-*", + "title": "Zeek ssh logs" + }, + { + "dataset": "syslog", + "index_pattern": "logs-zeek.syslog-*", + "title": "Zeek syslog logs" + }, + { + "dataset": "http", + "index_pattern": "logs-zeek.http-*", + "title": "Zeek http logs" + }, + { + "dataset": "ssl", + "index_pattern": "logs-zeek.ssl-*", + "title": "Zeek ssl logs" + }, + { + "dataset": "socks", + "index_pattern": "logs-zeek.socks-*", + "title": "Zeek socks logs" + }, + { + "dataset": "smtp", + "index_pattern": "logs-zeek.smtp-*", + "title": "Zeek smtp logs" + }, + { + "dataset": "ftp", + "index_pattern": "logs-zeek.ftp-*", + "title": "Zeek ftp logs" + }, + { + "dataset": "known_hosts", + "index_pattern": "logs-zeek.known_hosts-*", + "title": "Zeek Known Hosts logs" + }, + { + "dataset": "dnp3", + "index_pattern": "logs-zeek.dnp3-*", + "title": "Zeek dnp3 logs" + }, + { + "dataset": "dce_rpc", + "index_pattern": "logs-zeek.dce_rpc-*", + "title": "Zeek dce_rpc logs" + } + ], + "elser_embedding": "Zeek - Collect logs from Zeek with Elastic Agent. - Zeek x509 logs Zeek software logs Zeek traceroute logs Zeek capture_loss logs Zeek smb_cmd logs Zeek snmp logs Zeek irc logs Zeek intel logs Zeek pe logs Zeek Known Services logs Zeek radius logs Zeek modbus logs Zeek tunnel logs Zeek stats logs Zeek smb_files logs Zeek ocsp logs Zeek connection logs Zeek kerberos logs Zeek weird logs Zeek smb_mapping logs Zeek signature logs Zeek ntp logs Zeek dns logs Zeek dpd logs Zeek dhcp logs Zeek notice logs Zeek files logs Zeek ntlm logs Zeek Known Certs logs Zeek sip logs Zeek rdp logs Zeek mysql logs Zeek rfb logs Zeek ssh logs Zeek syslog logs Zeek http logs Zeek ssl logs Zeek socks logs Zeek smtp logs Zeek ftp logs Zeek Known Hosts logs Zeek dnp3 logs Zeek dce_rpc logs" + }, + { + "title": "CrowdStrike", + "id": "crowdstrike", + "description": "Collect logs from Crowdstrike with Elastic Agent.", + "data_streams": [ + { + "dataset": "fdr", + "index_pattern": "logs-crowdstrike.fdr-*", + "title": "Falcon Data Replicator" + }, + { + "dataset": "host", + "index_pattern": "logs-crowdstrike.host-*", + "title": "Collect Host logs from CrowdStrike." + }, + { + "dataset": "alert", + "index_pattern": "logs-crowdstrike.alert-*", + "title": "Collect Alert logs from CrowdStrike." + }, + { + "dataset": "falcon", + "index_pattern": "logs-crowdstrike.falcon-*", + "title": "Crowdstrike falcon logs" + } + ], + "elser_embedding": "CrowdStrike - Collect logs from Crowdstrike with Elastic Agent. - Falcon Data Replicator Collect Host logs from CrowdStrike. Collect Alert logs from CrowdStrike. Crowdstrike falcon logs" + }, + { + "title": "Fortinet FortiGate Firewall Logs", + "id": "fortinet_fortigate", + "description": "Collect logs from Fortinet FortiGate firewalls with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-fortinet_fortigate.log-*", + "title": "Fortinet FortiGate logs" + } + ], + "elser_embedding": "Fortinet FortiGate Firewall Logs - Collect logs from Fortinet FortiGate firewalls with Elastic Agent. - Fortinet FortiGate logs" + }, + { + "title": "Active Directory Entity Analytics", + "id": "entityanalytics_ad", + "description": "Collect User Identities from Active Directory Entity with Elastic Agent.", + "data_streams": [ + { + "dataset": "user", + "index_pattern": "logs-entityanalytics_ad.user-*", + "title": "Collect User Identities logs from Active Directory" + } + ], + "elser_embedding": "Active Directory Entity Analytics - Collect User Identities from Active Directory Entity with Elastic Agent. - Collect User Identities logs from Active Directory" + }, + { + "title": "Arista NG Firewall", + "id": "arista_ngfw", + "description": "Collect logs and metrics from Arista NG Firewall.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-arista_ngfw.log-*", + "title": "Firewall Event" + } + ], + "elser_embedding": "Arista NG Firewall - Collect logs and metrics from Arista NG Firewall. - Firewall Event" + }, + { + "title": "Proofpoint TAP", + "id": "proofpoint_tap", + "description": "Collect logs from Proofpoint TAP with Elastic Agent.", + "data_streams": [ + { + "dataset": "message_blocked", + "index_pattern": "logs-proofpoint_tap.message_blocked-*", + "title": "Message Blocked" + }, + { + "dataset": "clicks_blocked", + "index_pattern": "logs-proofpoint_tap.clicks_blocked-*", + "title": "Clicks Blocked" + }, + { + "dataset": "clicks_permitted", + "index_pattern": "logs-proofpoint_tap.clicks_permitted-*", + "title": "Clicks Permitted" + }, + { + "dataset": "message_delivered", + "index_pattern": "logs-proofpoint_tap.message_delivered-*", + "title": "Message Delivered" + } + ], + "elser_embedding": "Proofpoint TAP - Collect logs from Proofpoint TAP with Elastic Agent. - Message Blocked Clicks Blocked Clicks Permitted Message Delivered" + }, + { + "title": "BitDefender", + "id": "bitdefender", + "description": "Ingest BitDefender GravityZone logs and data", + "data_streams": [ + { + "dataset": "push_statistics", + "index_pattern": "logs-bitdefender.push_statistics-*", + "title": "BitDefender GravityZone Push Notification Statistics" + }, + { + "dataset": "push_configuration", + "index_pattern": "logs-bitdefender.push_configuration-*", + "title": "BitDefender GravityZone Push Notification Configuration" + }, + { + "dataset": "push_notifications", + "index_pattern": "logs-bitdefender.push_notifications-*", + "title": "BitDefender GravityZone Push Notifications" + } + ], + "elser_embedding": "BitDefender - Ingest BitDefender GravityZone logs and data - BitDefender GravityZone Push Notification Statistics BitDefender GravityZone Push Notification Configuration BitDefender GravityZone Push Notifications" + }, + { + "title": "Redis", + "id": "redis", + "description": "Collect logs and metrics from Redis servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "keyspace", + "index_pattern": "logs-redis.keyspace-*", + "title": "Redis keyspace metrics" + }, + { + "dataset": "key", + "index_pattern": "logs-redis.key-*", + "title": "Redis key metrics" + }, + { + "dataset": "info", + "index_pattern": "logs-redis.info-*", + "title": "Redis info metrics" + }, + { + "dataset": "slowlog", + "index_pattern": "logs-redis.slowlog-*", + "title": "Redis slow logs" + }, + { + "dataset": "log", + "index_pattern": "logs-redis.log-*", + "title": "Redis application logs" + } + ], + "elser_embedding": "Redis - Collect logs and metrics from Redis servers with Elastic Agent. - Redis keyspace metrics Redis key metrics Redis info metrics Redis slow logs Redis application logs" + }, + { + "title": "Cisco Duo", + "id": "cisco_duo", + "description": "Collect logs from Cisco Duo with Elastic Agent.", + "data_streams": [ + { + "dataset": "summary", + "index_pattern": "logs-cisco_duo.summary-*", + "title": "Cisco Duo summary logs" + }, + { + "dataset": "admin", + "index_pattern": "logs-cisco_duo.admin-*", + "title": "Cisco Duo administrator logs" + }, + { + "dataset": "telephony", + "index_pattern": "logs-cisco_duo.telephony-*", + "title": "Cisco Duo telephony logs (legacy)" + }, + { + "dataset": "telephony_v2", + "index_pattern": "logs-cisco_duo.telephony_v2-*", + "title": "Cisco Duo telephony logs" + }, + { + "dataset": "auth", + "index_pattern": "logs-cisco_duo.auth-*", + "title": "Cisco Duo authentication logs" + }, + { + "dataset": "trust_monitor", + "index_pattern": "logs-cisco_duo.trust_monitor-*", + "title": "Cisco Duo trust monitor logs" + }, + { + "dataset": "activity", + "index_pattern": "logs-cisco_duo.activity-*", + "title": "Cisco Duo activity logs" + }, + { + "dataset": "offline_enrollment", + "index_pattern": "logs-cisco_duo.offline_enrollment-*", + "title": "Cisco Duo offline enrollment logs" + } + ], + "elser_embedding": "Cisco Duo - Collect logs from Cisco Duo with Elastic Agent. - Cisco Duo summary logs Cisco Duo administrator logs Cisco Duo telephony logs (legacy) Cisco Duo telephony logs Cisco Duo authentication logs Cisco Duo trust monitor logs Cisco Duo activity logs Cisco Duo offline enrollment logs" + }, + { + "title": "Elasticsearch", + "id": "elasticsearch", + "description": "Elasticsearch Integration", + "data_streams": [ + { + "dataset": "index_recovery", + "index_pattern": "logs-elasticsearch.index_recovery-*", + "title": "Elasticsearch index_recovery metrics" + }, + { + "dataset": "shard", + "index_pattern": "logs-elasticsearch.shard-*", + "title": "Elasticsearch shard metrics" + }, + { + "dataset": "ingest_pipeline", + "index_pattern": "logs-elasticsearch.ingest_pipeline-*", + "title": "Elasticsearch ingest metrics" + }, + { + "dataset": "enrich", + "index_pattern": "logs-elasticsearch.enrich-*", + "title": "Elasticsearch enrich metrics" + }, + { + "dataset": "audit", + "index_pattern": "logs-elasticsearch.audit-*", + "title": "Elasticsearch audit logs" + }, + { + "dataset": "server", + "index_pattern": "logs-elasticsearch.server-*", + "title": "Elasticsearch server logs" + }, + { + "dataset": "node_stats", + "index_pattern": "logs-elasticsearch.node_stats-*", + "title": "Elasticsearch node_stats metrics" + }, + { + "dataset": "index_summary", + "index_pattern": "logs-elasticsearch.index_summary-*", + "title": "Elasticsearch index_summary metrics" + }, + { + "dataset": "deprecation", + "index_pattern": "logs-elasticsearch.deprecation-*", + "title": "Elasticsearch deprecation logs" + }, + { + "dataset": "index", + "index_pattern": "logs-elasticsearch.index-*", + "title": "Elasticsearch index metrics" + }, + { + "dataset": "slowlog", + "index_pattern": "logs-elasticsearch.slowlog-*", + "title": "Elasticsearch slowlog logs" + }, + { + "dataset": "pending_tasks", + "index_pattern": "logs-elasticsearch.pending_tasks-*", + "title": "Elasticsearch pending_tasks metrics" + }, + { + "dataset": "ccr", + "index_pattern": "logs-elasticsearch.ccr-*", + "title": "Elasticsearch ccr metrics" + }, + { + "dataset": "node", + "index_pattern": "logs-elasticsearch.node-*", + "title": "Elasticsearch node metrics" + }, + { + "dataset": "cluster_stats", + "index_pattern": "logs-elasticsearch.cluster_stats-*", + "title": "Elasticsearch cluster_stats metrics" + }, + { + "dataset": "gc", + "index_pattern": "logs-elasticsearch.gc-*", + "title": "Elasticsearch gc logs" + }, + { + "dataset": "ml_job", + "index_pattern": "logs-elasticsearch.ml_job-*", + "title": "Elasticsearch ml_job metrics" + } + ], + "elser_embedding": "Elasticsearch - Elasticsearch Integration - Elasticsearch index_recovery metrics Elasticsearch shard metrics Elasticsearch ingest metrics Elasticsearch enrich metrics Elasticsearch audit logs Elasticsearch server logs Elasticsearch node_stats metrics Elasticsearch index_summary metrics Elasticsearch deprecation logs Elasticsearch index metrics Elasticsearch slowlog logs Elasticsearch pending_tasks metrics Elasticsearch ccr metrics Elasticsearch node metrics Elasticsearch cluster_stats metrics Elasticsearch gc logs Elasticsearch ml_job metrics" + }, + { + "title": "Universal Profiling Agent", + "id": "profiler_agent", + "description": "Fleet-wide, whole-system, continuous profiling with zero instrumentation.", + "data_streams": [], + "elser_embedding": "Universal Profiling Agent - Fleet-wide, whole-system, continuous profiling with zero instrumentation. - " + }, + { + "title": "Check Point Harmony Email & Collaboration", + "id": "checkpoint_email", + "description": "Collect logs from Check Point Harmony Email & Collaboration with Elastic Agent.", + "data_streams": [ + { + "dataset": "event", + "index_pattern": "logs-checkpoint_email.event-*", + "title": "Check Point Harmony Email & Collaboration Event logs" + } + ], + "elser_embedding": "Check Point Harmony Email & Collaboration - Collect logs from Check Point Harmony Email & Collaboration with Elastic Agent. - Check Point Harmony Email & Collaboration Event logs" + }, + { + "title": "Apache HTTP Server", + "id": "apache", + "description": "Collect logs and metrics from Apache servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "access", + "index_pattern": "logs-apache.access-*", + "title": "Apache access logs" + }, + { + "dataset": "error", + "index_pattern": "logs-apache.error-*", + "title": "Apache error logs" + }, + { + "dataset": "status", + "index_pattern": "logs-apache.status-*", + "title": "Apache status metrics" + } + ], + "elser_embedding": "Apache HTTP Server - Collect logs and metrics from Apache servers with Elastic Agent. - Apache access logs Apache error logs Apache status metrics" + }, + { + "title": "Istio", + "id": "istio", + "description": "Collect logs and metrics from the service mesh Istio with Elastic Agent.", + "data_streams": [ + { + "dataset": "access_logs", + "index_pattern": "logs-istio.access_logs-*", + "title": "Istio access logs" + }, + { + "dataset": "proxy_metrics", + "index_pattern": "logs-istio.proxy_metrics-*", + "title": "Istio Proxy Metrics" + }, + { + "dataset": "istiod_metrics", + "index_pattern": "logs-istio.istiod_metrics-*", + "title": "Istiod Metrics" + } + ], + "elser_embedding": "Istio - Collect logs and metrics from the service mesh Istio with Elastic Agent. - Istio access logs Istio Proxy Metrics Istiod Metrics" + }, + { + "title": "GCP Metrics Input", + "id": "gcp_metrics", + "description": "GCP Metrics Input", + "data_streams": [], + "elser_embedding": "GCP Metrics Input - GCP Metrics Input - " + }, + { + "title": "Fortinet FortiMail", + "id": "fortinet_fortimail", + "description": "Collect logs from Fortinet FortiMail instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-fortinet_fortimail.log-*", + "title": "Collect logs from Fortinet FortiMail" + } + ], + "elser_embedding": "Fortinet FortiMail - Collect logs from Fortinet FortiMail instances with Elastic Agent. - Collect logs from Fortinet FortiMail" + }, + { + "title": "Spring Boot", + "id": "spring_boot", + "description": "This Elastic integration collects logs and metrics from Spring Boot integration.", + "data_streams": [ + { + "dataset": "memory", + "index_pattern": "logs-spring_boot.memory-*", + "title": "Memory Metrics" + }, + { + "dataset": "http_trace", + "index_pattern": "logs-spring_boot.http_trace-*", + "title": "HTTP Trace Metrics" + }, + { + "dataset": "gc", + "index_pattern": "logs-spring_boot.gc-*", + "title": "Garbage Collector (GC) Metrics" + }, + { + "dataset": "threading", + "index_pattern": "logs-spring_boot.threading-*", + "title": "Threading Metrics" + }, + { + "dataset": "audit_events", + "index_pattern": "logs-spring_boot.audit_events-*", + "title": "Audit Events" + } + ], + "elser_embedding": "Spring Boot - This Elastic integration collects logs and metrics from Spring Boot integration. - Memory Metrics HTTP Trace Metrics Garbage Collector (GC) Metrics Threading Metrics Audit Events" + }, + { + "title": "Jamf Compliance Reporter", + "id": "jamf_compliance_reporter", + "description": "Collect logs from Jamf Compliance Reporter with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-jamf_compliance_reporter.log-*", + "title": "Jamf Compliance Reporter logs" + } + ], + "elser_embedding": "Jamf Compliance Reporter - Collect logs from Jamf Compliance Reporter with Elastic Agent. - Jamf Compliance Reporter logs" + }, + { + "title": "SentinelOne", + "id": "sentinel_one", + "description": "Collect logs from SentinelOne with Elastic Agent.", + "data_streams": [ + { + "dataset": "group", + "index_pattern": "logs-sentinel_one.group-*", + "title": "Collect Group logs from SentinelOne" + }, + { + "dataset": "threat", + "index_pattern": "logs-sentinel_one.threat-*", + "title": "Collect Threat logs from SentinelOne" + }, + { + "dataset": "alert", + "index_pattern": "logs-sentinel_one.alert-*", + "title": "Collect Alert logs from SentinelOne" + }, + { + "dataset": "agent", + "index_pattern": "logs-sentinel_one.agent-*", + "title": "Collect Agent logs from SentinelOne" + }, + { + "dataset": "activity", + "index_pattern": "logs-sentinel_one.activity-*", + "title": "Collect Activity logs from SentinelOne" + } + ], + "elser_embedding": "SentinelOne - Collect logs from SentinelOne with Elastic Agent. - Collect Group logs from SentinelOne Collect Threat logs from SentinelOne Collect Alert logs from SentinelOne Collect Agent logs from SentinelOne Collect Activity logs from SentinelOne" + }, + { + "title": "Enterprise Search", + "id": "enterprisesearch", + "description": "Enterprise Search Integration", + "data_streams": [ + { + "dataset": "stats", + "index_pattern": "logs-enterprisesearch.stats-*", + "title": "Enterprise Search stats metrics" + }, + { + "dataset": "health", + "index_pattern": "logs-enterprisesearch.health-*", + "title": "Enterprise Search health metrics" + } + ], + "elser_embedding": "Enterprise Search - Enterprise Search Integration - Enterprise Search stats metrics Enterprise Search health metrics" + }, + { + "title": "Microsoft Exchange Online Message Trace", + "id": "microsoft_exchange_online_message_trace", + "description": "Microsoft Exchange Online Message Trace Integration", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-microsoft_exchange_online_message_trace.log-*", + "title": "Microsoft Exchange Online Message Trace logs" + } + ], + "elser_embedding": "Microsoft Exchange Online Message Trace - Microsoft Exchange Online Message Trace Integration - Microsoft Exchange Online Message Trace logs" + }, + { + "title": "CrowdStrike Falcon Intelligence", + "id": "ti_crowdstrike", + "description": "Collect logs from CrowdStrike Falcon Intelligence with Elastic Agent.", + "data_streams": [ + { + "dataset": "intel", + "index_pattern": "logs-ti_crowdstrike.intel-*", + "title": "Collect Intel logs from CrowdStrike Falcon Intelligence." + }, + { + "dataset": "ioc", + "index_pattern": "logs-ti_crowdstrike.ioc-*", + "title": "Collect IOC logs from CrowdStrike Falcon Intelligence." + } + ], + "elser_embedding": "CrowdStrike Falcon Intelligence - Collect logs from CrowdStrike Falcon Intelligence with Elastic Agent. - Collect Intel logs from CrowdStrike Falcon Intelligence. Collect IOC logs from CrowdStrike Falcon Intelligence." + }, + { + "title": "Auditd Manager", + "id": "auditd_manager", + "description": "The Auditd Manager Integration receives audit events from the Linux Audit Framework that is a part of the Linux kernel.", + "data_streams": [ + { + "dataset": "auditd", + "index_pattern": "logs-auditd_manager.auditd-*", + "title": "Auditd Manager" + } + ], + "elser_embedding": "Auditd Manager - The Auditd Manager Integration receives audit events from the Linux Audit Framework that is a part of the Linux kernel. - Auditd Manager" + }, + { + "title": "Oracle", + "id": "oracle", + "description": "Collect Oracle Audit Log, Performance metrics, Tablespace metrics, Sysmetrics metrics, System statistics metrics, memory metrics from Oracle database.", + "data_streams": [ + { + "dataset": "memory", + "index_pattern": "logs-oracle.memory-*", + "title": "Memory metrics" + }, + { + "dataset": "performance", + "index_pattern": "logs-oracle.performance-*", + "title": "Oracle performance metrics" + }, + { + "dataset": "database_audit", + "index_pattern": "logs-oracle.database_audit-*", + "title": "Oracle Audit Log" + }, + { + "dataset": "sysmetric", + "index_pattern": "logs-oracle.sysmetric-*", + "title": "Sysmetric related metrics." + }, + { + "dataset": "system_statistics", + "index_pattern": "logs-oracle.system_statistics-*", + "title": "System Statistics" + }, + { + "dataset": "tablespace", + "index_pattern": "logs-oracle.tablespace-*", + "title": "Oracle tablespace metrics" + } + ], + "elser_embedding": "Oracle - Collect Oracle Audit Log, Performance metrics, Tablespace metrics, Sysmetrics metrics, System statistics metrics, memory metrics from Oracle database. - Memory metrics Oracle performance metrics Oracle Audit Log Sysmetric related metrics. System Statistics Oracle tablespace metrics" + }, + { + "title": "Akamai", + "id": "akamai", + "description": "Collect logs from Akamai with Elastic Agent.", + "data_streams": [ + { + "dataset": "siem", + "index_pattern": "logs-akamai.siem-*", + "title": "Akamai SIEM Logs" + } + ], + "elser_embedding": "Akamai - Collect logs from Akamai with Elastic Agent. - Akamai SIEM Logs" + }, + { + "title": "Custom Journald logs", + "id": "journald", + "description": "Collect logs from journald with Elastic Agent.", + "data_streams": [], + "elser_embedding": "Custom Journald logs - Collect logs from journald with Elastic Agent. - " + }, + { + "title": "Universal Profiling Collector", + "id": "profiler_collector", + "description": "Fleet-wide, whole-system, continuous profiling with zero instrumentation.", + "data_streams": [], + "elser_embedding": "Universal Profiling Collector - Fleet-wide, whole-system, continuous profiling with zero instrumentation. - " + }, + { + "title": "Custom API using Common Expression Language", + "id": "cel", + "description": "Collect custom events from an API with Elastic agent", + "data_streams": [], + "elser_embedding": "Custom API using Common Expression Language - Collect custom events from an API with Elastic agent - " + }, + { + "title": "etcd", + "id": "etcd", + "description": "Collect metrics from etcd instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "self", + "index_pattern": "logs-etcd.self-*", + "title": "etcd self metrics" + }, + { + "dataset": "leader", + "index_pattern": "logs-etcd.leader-*", + "title": "etcd leader metrics" + }, + { + "dataset": "store", + "index_pattern": "logs-etcd.store-*", + "title": "etcd store metrics" + }, + { + "dataset": "metrics", + "index_pattern": "logs-etcd.metrics-*", + "title": "etcd v3 metrics" + } + ], + "elser_embedding": "etcd - Collect metrics from etcd instances with Elastic Agent. - etcd self metrics etcd leader metrics etcd store metrics etcd v3 metrics" + }, + { + "title": "Citrix Web App Firewall", + "id": "citrix_waf", + "description": "Ingest events from Citrix Systems Web App Firewall.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-citrix_waf.log-*", + "title": "Cisco ASA logs" + } + ], + "elser_embedding": "Citrix Web App Firewall - Ingest events from Citrix Systems Web App Firewall. - Cisco ASA logs" + }, + { + "title": "Azure OpenAI", + "id": "azure_openai", + "description": "Collects Azure OpenAI Logs and Metrics", + "data_streams": [ + { + "dataset": "logs", + "index_pattern": "logs-azure_openai.logs-*", + "title": "Collect Azure OpenAI logs" + }, + { + "dataset": "metrics", + "index_pattern": "logs-azure_openai.metrics-*", + "title": "Collect OpenAI metrics" + } + ], + "elser_embedding": "Azure OpenAI - Collects Azure OpenAI Logs and Metrics - Collect Azure OpenAI logs Collect OpenAI metrics" + }, + { + "title": "Cisco ISE", + "id": "cisco_ise", + "description": "Collect logs from Cisco ISE with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_ise.log-*", + "title": "Cisco ISE logs" + } + ], + "elser_embedding": "Cisco ISE - Collect logs from Cisco ISE with Elastic Agent. - Cisco ISE logs" + }, + { + "title": "Citrix ADC", + "id": "citrix_adc", + "description": "This Elastic integration collects logs and metrics from Citrix ADC product.", + "data_streams": [ + { + "dataset": "vpn", + "index_pattern": "logs-citrix_adc.vpn-*", + "title": "VPN metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-citrix_adc.log-*", + "title": "Citrix ADC logs" + }, + { + "dataset": "service", + "index_pattern": "logs-citrix_adc.service-*", + "title": "Citrix ADC Service metrics" + }, + { + "dataset": "system", + "index_pattern": "logs-citrix_adc.system-*", + "title": "System metrics" + }, + { + "dataset": "interface", + "index_pattern": "logs-citrix_adc.interface-*", + "title": "Interface metrics" + }, + { + "dataset": "lbvserver", + "index_pattern": "logs-citrix_adc.lbvserver-*", + "title": "Load Balancing Virtual Server metrics" + } + ], + "elser_embedding": "Citrix ADC - This Elastic integration collects logs and metrics from Citrix ADC product. - VPN metrics Citrix ADC logs Citrix ADC Service metrics System metrics Interface metrics Load Balancing Virtual Server metrics" + }, + { + "title": "Box Events", + "id": "box_events", + "description": "Collect logs from Box with Elastic Agent", + "data_streams": [ + { + "dataset": "events", + "index_pattern": "logs-box_events.events-*", + "title": "List user and enterprise events" + } + ], + "elser_embedding": "Box Events - Collect logs from Box with Elastic Agent - List user and enterprise events" + }, + { + "title": "Prometheus", + "id": "prometheus", + "description": "Collect metrics from Prometheus servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "query", + "index_pattern": "logs-prometheus.query-*", + "title": "Prometheus query metrics" + }, + { + "dataset": "remote_write", + "index_pattern": "logs-prometheus.remote_write-*", + "title": "Prometheus remote_write metrics" + }, + { + "dataset": "collector", + "index_pattern": "logs-prometheus.collector-*", + "title": "Prometheus collector metrics" + } + ], + "elser_embedding": "Prometheus - Collect metrics from Prometheus servers with Elastic Agent. - Prometheus query metrics Prometheus remote_write metrics Prometheus collector metrics" + }, + { + "title": "Kubernetes", + "id": "kubernetes", + "description": "Collect logs and metrics from Kubernetes clusters with Elastic Agent.", + "data_streams": [ + { + "dataset": "state_resourcequota", + "index_pattern": "logs-kubernetes.state_resourcequota-*", + "title": "Kubernetes ResourceQuota metrics" + }, + { + "dataset": "state_storageclass", + "index_pattern": "logs-kubernetes.state_storageclass-*", + "title": "Kubernetes StorageClass metrics" + }, + { + "dataset": "state_persistentvolume", + "index_pattern": "logs-kubernetes.state_persistentvolume-*", + "title": "Kubernetes PersistentVolume metrics" + }, + { + "dataset": "pod", + "index_pattern": "logs-kubernetes.pod-*", + "title": "Kubernetes Pod metrics" + }, + { + "dataset": "state_container", + "index_pattern": "logs-kubernetes.state_container-*", + "title": "Kubernetes Container metrics" + }, + { + "dataset": "state_service", + "index_pattern": "logs-kubernetes.state_service-*", + "title": "Kubernetes Service metrics" + }, + { + "dataset": "state_replicaset", + "index_pattern": "logs-kubernetes.state_replicaset-*", + "title": "Kubernetes state_replicaset metrics" + }, + { + "dataset": "state_deployment", + "index_pattern": "logs-kubernetes.state_deployment-*", + "title": "Kubernetes Deployment metrics" + }, + { + "dataset": "container", + "index_pattern": "logs-kubernetes.container-*", + "title": "Kubernetes Container metrics" + }, + { + "dataset": "state_cronjob", + "index_pattern": "logs-kubernetes.state_cronjob-*", + "title": "Kubernetes Cronjob metrics" + }, + { + "dataset": "state_persistentvolumeclaim", + "index_pattern": "logs-kubernetes.state_persistentvolumeclaim-*", + "title": "Kubernetes PersistentVolumeClaim metrics" + }, + { + "dataset": "apiserver", + "index_pattern": "logs-kubernetes.apiserver-*", + "title": "Kubernetes API Server metrics" + }, + { + "dataset": "audit_logs", + "index_pattern": "logs-kubernetes.audit_logs-*", + "title": "Kubernetes audit logs" + }, + { + "dataset": "container_logs", + "index_pattern": "logs-kubernetes.container_logs-*", + "title": "Kubernetes container logs" + }, + { + "dataset": "state_namespace", + "index_pattern": "logs-kubernetes.state_namespace-*", + "title": "Kubernetes Namespace metrics" + }, + { + "dataset": "controllermanager", + "index_pattern": "logs-kubernetes.controllermanager-*", + "title": "Kubernetes Controller Manager metrics" + }, + { + "dataset": "state_statefulset", + "index_pattern": "logs-kubernetes.state_statefulset-*", + "title": "Kubernetes StatefulSet metrics" + }, + { + "dataset": "state_pod", + "index_pattern": "logs-kubernetes.state_pod-*", + "title": "Kubernetes Pod metrics" + }, + { + "dataset": "event", + "index_pattern": "logs-kubernetes.event-*", + "title": "Kubernetes Event metrics" + }, + { + "dataset": "node", + "index_pattern": "logs-kubernetes.node-*", + "title": "Kubernetes Node metrics" + }, + { + "dataset": "scheduler", + "index_pattern": "logs-kubernetes.scheduler-*", + "title": "Kubernetes Scheduler metrics" + }, + { + "dataset": "system", + "index_pattern": "logs-kubernetes.system-*", + "title": "Kubernetes System metrics" + }, + { + "dataset": "proxy", + "index_pattern": "logs-kubernetes.proxy-*", + "title": "Kubernetes Proxy metrics" + }, + { + "dataset": "state_node", + "index_pattern": "logs-kubernetes.state_node-*", + "title": "Kubernetes Node metrics" + }, + { + "dataset": "volume", + "index_pattern": "logs-kubernetes.volume-*", + "title": "Kubernetes Volume metrics" + }, + { + "dataset": "state_job", + "index_pattern": "logs-kubernetes.state_job-*", + "title": "Kubernetes Job metrics" + }, + { + "dataset": "state_daemonset", + "index_pattern": "logs-kubernetes.state_daemonset-*", + "title": "Kubernetes Deamonset metrics" + } + ], + "elser_embedding": "Kubernetes - Collect logs and metrics from Kubernetes clusters with Elastic Agent. - Kubernetes ResourceQuota metrics Kubernetes StorageClass metrics Kubernetes PersistentVolume metrics Kubernetes Pod metrics Kubernetes Container metrics Kubernetes Service metrics Kubernetes state_replicaset metrics Kubernetes Deployment metrics Kubernetes Container metrics Kubernetes Cronjob metrics Kubernetes PersistentVolumeClaim metrics Kubernetes API Server metrics Kubernetes audit logs Kubernetes container logs Kubernetes Namespace metrics Kubernetes Controller Manager metrics Kubernetes StatefulSet metrics Kubernetes Pod metrics Kubernetes Event metrics Kubernetes Node metrics Kubernetes Scheduler metrics Kubernetes System metrics Kubernetes Proxy metrics Kubernetes Node metrics Kubernetes Volume metrics Kubernetes Job metrics Kubernetes Deamonset metrics" + }, + { + "title": "Okta Entity Analytics", + "id": "entityanalytics_okta", + "description": "Collect User Identities from Okta with Elastic Agent.", + "data_streams": [ + { + "dataset": "user", + "index_pattern": "logs-entityanalytics_okta.user-*", + "title": "Collect User Identities logs from Okta" + } + ], + "elser_embedding": "Okta Entity Analytics - Collect User Identities from Okta with Elastic Agent. - Collect User Identities logs from Okta" + }, + { + "title": "GCP Vertex AI", + "id": "gcp_vertexai", + "description": "Collect GCP Vertex AI metrics with Elastic Agent", + "data_streams": [ + { + "dataset": "metrics", + "index_pattern": "logs-gcp_vertexai.metrics-*", + "title": "GCP Vertex AI Metrics" + } + ], + "elser_embedding": "GCP Vertex AI - Collect GCP Vertex AI metrics with Elastic Agent - GCP Vertex AI Metrics" + }, + { + "title": "First EPSS", + "id": "first_epss", + "description": "Collect exploit prediction score data from the First EPSS API with Elastic Agent.", + "data_streams": [ + { + "dataset": "vulnerability", + "index_pattern": "logs-first_epss.vulnerability-*", + "title": "Collect EPSS data from First API." + } + ], + "elser_embedding": "First EPSS - Collect exploit prediction score data from the First EPSS API with Elastic Agent. - Collect EPSS data from First API." + }, + { + "title": "Snort", + "id": "snort", + "description": "Collect logs from Snort with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-snort.log-*", + "title": "Snort" + } + ], + "elser_embedding": "Snort - Collect logs from Snort with Elastic Agent. - Snort" + }, + { + "title": "Azure Functions", + "id": "azure_functions", + "description": "Get metrics and logs from Azure Functions", + "data_streams": [ + { + "dataset": "functionapplogs", + "index_pattern": "logs-azure_functions.functionapplogs-*", + "title": "Collect Azure Functions logs" + }, + { + "dataset": "metrics", + "index_pattern": "logs-azure_functions.metrics-*", + "title": "Azure Functions App Metrics" + } + ], + "elser_embedding": "Azure Functions - Get metrics and logs from Azure Functions - Collect Azure Functions logs Azure Functions App Metrics" + }, + { + "title": "SentinelOne Cloud Funnel", + "id": "sentinel_one_cloud_funnel", + "description": "Collect logs from SentinelOne Cloud Funnel with Elastic Agent.", + "data_streams": [ + { + "dataset": "threat_intelligence_indicators", + "index_pattern": "logs-sentinel_one_cloud_funnel.threat_intelligence_indicators-*", + "title": "SentinelOne Cloud Funnel Threat Intelligence Indicator Events" + }, + { + "dataset": "scheduled_task", + "index_pattern": "logs-sentinel_one_cloud_funnel.scheduled_task-*", + "title": "SentinelOne Cloud Funnel Scheduled Task Events" + }, + { + "dataset": "cross_process", + "index_pattern": "logs-sentinel_one_cloud_funnel.cross_process-*", + "title": "SentinelOne Cloud Funnel cross_process Events" + }, + { + "dataset": "url", + "index_pattern": "logs-sentinel_one_cloud_funnel.url-*", + "title": "SentinelOne Cloud Funnel URL Events" + }, + { + "dataset": "file", + "index_pattern": "logs-sentinel_one_cloud_funnel.file-*", + "title": "SentinelOne Cloud Funnel File Events" + }, + { + "dataset": "module", + "index_pattern": "logs-sentinel_one_cloud_funnel.module-*", + "title": "SentinelOne Cloud Funnel Module Events" + }, + { + "dataset": "process", + "index_pattern": "logs-sentinel_one_cloud_funnel.process-*", + "title": "SentinelOne Cloud Funnel Process Events" + }, + { + "dataset": "dns", + "index_pattern": "logs-sentinel_one_cloud_funnel.dns-*", + "title": "SentinelOne Cloud Funnel dns Events" + }, + { + "dataset": "logins", + "index_pattern": "logs-sentinel_one_cloud_funnel.logins-*", + "title": "SentinelOne Cloud Funnel Logins Events" + }, + { + "dataset": "command_script", + "index_pattern": "logs-sentinel_one_cloud_funnel.command_script-*", + "title": "SentinelOne Cloud Funnel command_script Events" + }, + { + "dataset": "indicators", + "index_pattern": "logs-sentinel_one_cloud_funnel.indicators-*", + "title": "SentinelOne Cloud Funnel Indicator Events" + }, + { + "dataset": "event", + "index_pattern": "logs-sentinel_one_cloud_funnel.event-*", + "title": "Collect Event logs from SentinelOne Cloud Funnel." + }, + { + "dataset": "ip", + "index_pattern": "logs-sentinel_one_cloud_funnel.ip-*", + "title": "SentinelOne Cloud Funnel IP Events" + }, + { + "dataset": "registry", + "index_pattern": "logs-sentinel_one_cloud_funnel.registry-*", + "title": "SentinelOne Cloud Funnel Registry Events" + } + ], + "elser_embedding": "SentinelOne Cloud Funnel - Collect logs from SentinelOne Cloud Funnel with Elastic Agent. - SentinelOne Cloud Funnel Threat Intelligence Indicator Events SentinelOne Cloud Funnel Scheduled Task Events SentinelOne Cloud Funnel cross_process Events SentinelOne Cloud Funnel URL Events SentinelOne Cloud Funnel File Events SentinelOne Cloud Funnel Module Events SentinelOne Cloud Funnel Process Events SentinelOne Cloud Funnel dns Events SentinelOne Cloud Funnel Logins Events SentinelOne Cloud Funnel command_script Events SentinelOne Cloud Funnel Indicator Events Collect Event logs from SentinelOne Cloud Funnel. SentinelOne Cloud Funnel IP Events SentinelOne Cloud Funnel Registry Events" + }, + { + "title": "Cisco Meraki", + "id": "cisco_meraki", + "description": "Collect logs from Cisco Meraki with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_meraki.log-*", + "title": "Cisco Meraki logs (via Syslog)" + }, + { + "dataset": "events", + "index_pattern": "logs-cisco_meraki.events-*", + "title": "Cisco Meraki webhook events" + } + ], + "elser_embedding": "Cisco Meraki - Collect logs from Cisco Meraki with Elastic Agent. - Cisco Meraki logs (via Syslog) Cisco Meraki webhook events" + }, + { + "title": "Osquery Manager", + "id": "osquery_manager", + "description": "Deploy Osquery with Elastic Agent, then run and schedule queries in Kibana", + "data_streams": [ + { + "dataset": "result", + "index_pattern": "logs-osquery_manager.result-*", + "title": "Osquery Manager queries" + }, + { + "dataset": "action_responses", + "index_pattern": "logs-osquery_manager.action_responses-*", + "title": "Osquery Manager queries" + } + ], + "elser_embedding": "Osquery Manager - Deploy Osquery with Elastic Agent, then run and schedule queries in Kibana - Osquery Manager queries Osquery Manager queries" + }, + { + "title": "ModSecurity Audit", + "id": "modsecurity", + "description": "Collect logs from ModSecurity with Elastic Agent", + "data_streams": [ + { + "dataset": "auditlog", + "index_pattern": "logs-modsecurity.auditlog-*", + "title": "Modsecurity Audit Log" + } + ], + "elser_embedding": "ModSecurity Audit - Collect logs from ModSecurity with Elastic Agent - Modsecurity Audit Log" + }, + { + "title": "pfSense", + "id": "pfsense", + "description": "Collect logs from pfSense and OPNsense with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-pfsense.log-*", + "title": "pfSense log logs" + } + ], + "elser_embedding": "pfSense - Collect logs from pfSense and OPNsense with Elastic Agent. - pfSense log logs" + }, + { + "title": "Ceph", + "id": "ceph", + "description": "This Elastic integration collects metrics from Ceph instance.", + "data_streams": [ + { + "dataset": "cluster_disk", + "index_pattern": "logs-ceph.cluster_disk-*", + "title": "Cluster Disk metrics" + }, + { + "dataset": "osd_pool_stats", + "index_pattern": "logs-ceph.osd_pool_stats-*", + "title": "OSD Pool Stats" + }, + { + "dataset": "cluster_status", + "index_pattern": "logs-ceph.cluster_status-*", + "title": "Cluster Status metrics" + }, + { + "dataset": "pool_disk", + "index_pattern": "logs-ceph.pool_disk-*", + "title": "Pool Disk metrics" + }, + { + "dataset": "osd_tree", + "index_pattern": "logs-ceph.osd_tree-*", + "title": "OSD Tree metrics" + }, + { + "dataset": "osd_performance", + "index_pattern": "logs-ceph.osd_performance-*", + "title": "OSD Performance metrics" + }, + { + "dataset": "cluster_health", + "index_pattern": "logs-ceph.cluster_health-*", + "title": "Cluster Health metrics" + } + ], + "elser_embedding": "Ceph - This Elastic integration collects metrics from Ceph instance. - Cluster Disk metrics OSD Pool Stats Cluster Status metrics Pool Disk metrics OSD Tree metrics OSD Performance metrics Cluster Health metrics" + }, + { + "title": "Maltiverse", + "id": "ti_maltiverse", + "description": "Ingest threat intelligence indicators from Maltiverse feeds with Elastic Agent", + "data_streams": [ + { + "dataset": "indicator", + "index_pattern": "logs-ti_maltiverse.indicator-*", + "title": "Maltiverse indicator" + } + ], + "elser_embedding": "Maltiverse - Ingest threat intelligence indicators from Maltiverse feeds with Elastic Agent - Maltiverse indicator" + }, + { + "title": "Imperva", + "id": "imperva", + "description": "Collect logs from Imperva devices with Elastic Agent.", + "data_streams": [ + { + "dataset": "securesphere", + "index_pattern": "logs-imperva.securesphere-*", + "title": "Collect logs from Imperva SecureSphere" + } + ], + "elser_embedding": "Imperva - Collect logs from Imperva devices with Elastic Agent. - Collect logs from Imperva SecureSphere" + }, + { + "title": "Linux Metrics", + "id": "linux", + "description": "Collect metrics from Linux servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "memory", + "index_pattern": "logs-linux.memory-*", + "title": "Linux-only memory metrics" + }, + { + "dataset": "socket", + "index_pattern": "logs-linux.socket-*", + "title": "System socket metrics" + }, + { + "dataset": "ksm", + "index_pattern": "logs-linux.ksm-*", + "title": "Kernel Samepage merging metrics" + }, + { + "dataset": "raid", + "index_pattern": "logs-linux.raid-*", + "title": "System raid metrics" + }, + { + "dataset": "conntrack", + "index_pattern": "logs-linux.conntrack-*", + "title": "System conntrack metrics" + }, + { + "dataset": "network_summary", + "index_pattern": "logs-linux.network_summary-*", + "title": "System network_summary metrics" + }, + { + "dataset": "users", + "index_pattern": "logs-linux.users-*", + "title": "System users metrics" + }, + { + "dataset": "service", + "index_pattern": "logs-linux.service-*", + "title": "System service metrics" + }, + { + "dataset": "pageinfo", + "index_pattern": "logs-linux.pageinfo-*", + "title": "System page info metrics" + }, + { + "dataset": "iostat", + "index_pattern": "logs-linux.iostat-*", + "title": "Linux disk iostat metrics" + }, + { + "dataset": "entropy", + "index_pattern": "logs-linux.entropy-*", + "title": "System entropy metrics" + } + ], + "elser_embedding": "Linux Metrics - Collect metrics from Linux servers with Elastic Agent. - Linux-only memory metrics System socket metrics Kernel Samepage merging metrics System raid metrics System conntrack metrics System network_summary metrics System users metrics System service metrics System page info metrics Linux disk iostat metrics System entropy metrics" + }, + { + "title": "Cybereason", + "id": "cybereason", + "description": "Collect logs from Cybereason with Elastic Agent.", + "data_streams": [ + { + "dataset": "logon_session", + "index_pattern": "logs-cybereason.logon_session-*", + "title": "Collect Logon Session logs from Cybereason." + }, + { + "dataset": "poll_malop", + "index_pattern": "logs-cybereason.poll_malop-*", + "title": "Collect Poll Malop logs from Cybereason." + }, + { + "dataset": "suspicions_process", + "index_pattern": "logs-cybereason.suspicions_process-*", + "title": "Collect Suspicions Process logs from Cybereason." + }, + { + "dataset": "malop_process", + "index_pattern": "logs-cybereason.malop_process-*", + "title": "Collect Malop Process logs from Cybereason." + }, + { + "dataset": "malop_connection", + "index_pattern": "logs-cybereason.malop_connection-*", + "title": "Collect Malop Connection logs from Cybereason." + }, + { + "dataset": "malware", + "index_pattern": "logs-cybereason.malware-*", + "title": "Collect Malware logs from Cybereason." + } + ], + "elser_embedding": "Cybereason - Collect logs from Cybereason with Elastic Agent. - Collect Logon Session logs from Cybereason. Collect Poll Malop logs from Cybereason. Collect Suspicions Process logs from Cybereason. Collect Malop Process logs from Cybereason. Collect Malop Connection logs from Cybereason. Collect Malware logs from Cybereason." + }, + { + "title": "Kafka", + "id": "kafka", + "description": "Collect logs and metrics from Kafka servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "broker", + "index_pattern": "logs-kafka.broker-*", + "title": "Kafka broker metrics" + }, + { + "dataset": "consumergroup", + "index_pattern": "logs-kafka.consumergroup-*", + "title": "Kafka consumergroup metrics" + }, + { + "dataset": "partition", + "index_pattern": "logs-kafka.partition-*", + "title": "Kafka partition metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-kafka.log-*", + "title": "Kafka log logs" + } + ], + "elser_embedding": "Kafka - Collect logs and metrics from Kafka servers with Elastic Agent. - Kafka broker metrics Kafka consumergroup metrics Kafka partition metrics Kafka log logs" + }, + { + "title": "Sophos Central", + "id": "sophos_central", + "description": "This Elastic integration collects logs from Sophos Central with Elastic Agent.", + "data_streams": [ + { + "dataset": "alert", + "index_pattern": "logs-sophos_central.alert-*", + "title": "Collect Sophos Central SIEM Alert logs" + }, + { + "dataset": "event", + "index_pattern": "logs-sophos_central.event-*", + "title": "Collect Sophos Central SIEM Events logs" + } + ], + "elser_embedding": "Sophos Central - This Elastic integration collects logs from Sophos Central with Elastic Agent. - Collect Sophos Central SIEM Alert logs Collect Sophos Central SIEM Events logs" + }, + { + "title": "PostgreSQL", + "id": "postgresql", + "description": "Collect logs and metrics from PostgreSQL servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "bgwriter", + "index_pattern": "logs-postgresql.bgwriter-*", + "title": "PostgreSQL bgwriter metrics" + }, + { + "dataset": "database", + "index_pattern": "logs-postgresql.database-*", + "title": "PostgreSQL database metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-postgresql.log-*", + "title": "PostgreSQL logs" + }, + { + "dataset": "statement", + "index_pattern": "logs-postgresql.statement-*", + "title": "PostgreSQL statement metrics" + }, + { + "dataset": "activity", + "index_pattern": "logs-postgresql.activity-*", + "title": "PostgreSQL activity metrics" + } + ], + "elser_embedding": "PostgreSQL - Collect logs and metrics from PostgreSQL servers with Elastic Agent. - PostgreSQL bgwriter metrics PostgreSQL database metrics PostgreSQL logs PostgreSQL statement metrics PostgreSQL activity metrics" + }, + { + "title": "Corelight", + "id": "corelight", + "description": "Collect logs from Corelight with Elastic Agent.", + "data_streams": [], + "elser_embedding": "Corelight - Collect logs from Corelight with Elastic Agent. - " + }, + { + "title": "Threat Intelligence Utilities", + "id": "ti_util", + "description": "Prebuilt Threat Intelligence dashboard for Elastic Security", + "data_streams": [], + "elser_embedding": "Threat Intelligence Utilities - Prebuilt Threat Intelligence dashboard for Elastic Security - " + }, + { + "title": "Imperva Cloud WAF", + "id": "imperva_cloud_waf", + "description": "Collect logs from Imperva Cloud WAF with Elastic Agent.", + "data_streams": [ + { + "dataset": "event", + "index_pattern": "logs-imperva_cloud_waf.event-*", + "title": "Collect Imperva Cloud WAF Events" + } + ], + "elser_embedding": "Imperva Cloud WAF - Collect logs from Imperva Cloud WAF with Elastic Agent. - Collect Imperva Cloud WAF Events" + }, + { + "title": "File Integrity Monitoring", + "id": "fim", + "description": "The File Integrity Monitoring integration reports filesystem changes in real time.", + "data_streams": [ + { + "dataset": "event", + "index_pattern": "logs-fim.event-*", + "title": "Filesystem events" + } + ], + "elser_embedding": "File Integrity Monitoring - The File Integrity Monitoring integration reports filesystem changes in real time. - Filesystem events" + }, + { + "title": "Custom Websocket logs", + "id": "websocket", + "description": "Collect custom events from a socket server with Elastic agent.", + "data_streams": [], + "elser_embedding": "Custom Websocket logs - Collect custom events from a socket server with Elastic agent. - " + }, + { + "title": "SpyCloud Enterprise Protection", + "id": "spycloud", + "description": "Collect data from SpyCloud Enterprise Protection with Elastic Agent.", + "data_streams": [ + { + "dataset": "compass", + "index_pattern": "logs-spycloud.compass-*", + "title": "Collect Compass logs from SpyCloud Enterprise Protection." + }, + { + "dataset": "breach_record", + "index_pattern": "logs-spycloud.breach_record-*", + "title": "Collect Breach Record logs from SpyCloud Enterprise Protection." + }, + { + "dataset": "breach_catalog", + "index_pattern": "logs-spycloud.breach_catalog-*", + "title": "Collect Breach Catalog logs from SpyCloud Enterprise Protection." + } + ], + "elser_embedding": "SpyCloud Enterprise Protection - Collect data from SpyCloud Enterprise Protection with Elastic Agent. - Collect Compass logs from SpyCloud Enterprise Protection. Collect Breach Record logs from SpyCloud Enterprise Protection. Collect Breach Catalog logs from SpyCloud Enterprise Protection." + }, + { + "title": "Canva", + "id": "canva", + "description": "Collect logs from Canva with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-canva.audit-*", + "title": "Collect Audit Logs from Canva" + } + ], + "elser_embedding": "Canva - Collect logs from Canva with Elastic Agent. - Collect Audit Logs from Canva" + }, + { + "title": "Microsoft Office 365", + "id": "o365", + "description": "Collect logs from Microsoft Office 365 with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-o365.audit-*", + "title": "Microsoft Office 365 audit logs" + } + ], + "elser_embedding": "Microsoft Office 365 - Collect logs from Microsoft Office 365 with Elastic Agent. - Microsoft Office 365 audit logs" + }, + { + "title": "AWS", + "id": "aws", + "description": "Collect logs and metrics from Amazon Web Services (AWS) with Elastic Agent.", + "data_streams": [ + { + "dataset": "ec2_metrics", + "index_pattern": "logs-aws.ec2_metrics-*", + "title": "AWS EC2 metrics" + }, + { + "dataset": "apigateway_metrics", + "index_pattern": "logs-aws.apigateway_metrics-*", + "title": "AWS API Gateway metrics" + }, + { + "dataset": "ec2_logs", + "index_pattern": "logs-aws.ec2_logs-*", + "title": "AWS EC2 logs" + }, + { + "dataset": "cloudwatch_logs", + "index_pattern": "logs-aws.cloudwatch_logs-*", + "title": "AWS CloudWatch logs" + }, + { + "dataset": "billing", + "index_pattern": "logs-aws.billing-*", + "title": "AWS Billing Metrics" + }, + { + "dataset": "ebs", + "index_pattern": "logs-aws.ebs-*", + "title": "AWS EBS metrics" + }, + { + "dataset": "awshealth", + "index_pattern": "logs-aws.awshealth-*", + "title": "AWS Health" + }, + { + "dataset": "transitgateway", + "index_pattern": "logs-aws.transitgateway-*", + "title": "AWS Transit Gateway metrics" + }, + { + "dataset": "cloudtrail", + "index_pattern": "logs-aws.cloudtrail-*", + "title": "AWS CloudTrail Logs" + }, + { + "dataset": "vpn", + "index_pattern": "logs-aws.vpn-*", + "title": "AWS VPN metrics" + }, + { + "dataset": "sns", + "index_pattern": "logs-aws.sns-*", + "title": "AWS SNS metrics" + }, + { + "dataset": "firewall_metrics", + "index_pattern": "logs-aws.firewall_metrics-*", + "title": "AWS Network Firewall metrics" + }, + { + "dataset": "waf", + "index_pattern": "logs-aws.waf-*", + "title": "AWS WAF logs" + }, + { + "dataset": "emr_metrics", + "index_pattern": "logs-aws.emr_metrics-*", + "title": "AWS EMR metrics" + }, + { + "dataset": "firewall_logs", + "index_pattern": "logs-aws.firewall_logs-*", + "title": "AWS Network Firewall logs" + }, + { + "dataset": "lambda", + "index_pattern": "logs-aws.lambda-*", + "title": "AWS Lambda metrics" + }, + { + "dataset": "securityhub_insights", + "index_pattern": "logs-aws.securityhub_insights-*", + "title": "Collect AWS Security Hub Insights logs from AWS" + }, + { + "dataset": "redshift", + "index_pattern": "logs-aws.redshift-*", + "title": "Amazon Redshift metrics" + }, + { + "dataset": "inspector", + "index_pattern": "logs-aws.inspector-*", + "title": "Collect AWS Inspector logs from AWS" + }, + { + "dataset": "route53_resolver_logs", + "index_pattern": "logs-aws.route53_resolver_logs-*", + "title": "AWS Route 53 Resolver Query Logs" + }, + { + "dataset": "emr_logs", + "index_pattern": "logs-aws.emr_logs-*", + "title": "AWS EMR logs" + }, + { + "dataset": "elb_metrics", + "index_pattern": "logs-aws.elb_metrics-*", + "title": "AWS ELB metrics" + }, + { + "dataset": "s3access", + "index_pattern": "logs-aws.s3access-*", + "title": "AWS s3access logs" + }, + { + "dataset": "securityhub_findings", + "index_pattern": "logs-aws.securityhub_findings-*", + "title": "Collect AWS Security Hub Findings logs from AWS" + }, + { + "dataset": "vpcflow", + "index_pattern": "logs-aws.vpcflow-*", + "title": "AWS vpcflow logs" + }, + { + "dataset": "elb_logs", + "index_pattern": "logs-aws.elb_logs-*", + "title": "AWS ELB logs" + }, + { + "dataset": "kafka_metrics", + "index_pattern": "logs-aws.kafka_metrics-*", + "title": "AWS Kafka metrics" + }, + { + "dataset": "kinesis", + "index_pattern": "logs-aws.kinesis-*", + "title": "AWS Kinesis Data Stream metrics" + }, + { + "dataset": "cloudwatch_metrics", + "index_pattern": "logs-aws.cloudwatch_metrics-*", + "title": "AWS CloudWatch metrics" + }, + { + "dataset": "s3_daily_storage", + "index_pattern": "logs-aws.s3_daily_storage-*", + "title": "AWS S3 daily storage metrics" + }, + { + "dataset": "guardduty", + "index_pattern": "logs-aws.guardduty-*", + "title": "Collect Amazon GuardDuty Findings logs from AWS" + }, + { + "dataset": "rds", + "index_pattern": "logs-aws.rds-*", + "title": "AWS RDS metrics" + }, + { + "dataset": "ecs_metrics", + "index_pattern": "logs-aws.ecs_metrics-*", + "title": "AWS ECS metrics" + }, + { + "dataset": "s3_storage_lens", + "index_pattern": "logs-aws.s3_storage_lens-*", + "title": "AWS S3 Storage Lens metrics" + }, + { + "dataset": "route53_public_logs", + "index_pattern": "logs-aws.route53_public_logs-*", + "title": "AWS Route 53 Public Zone Logs" + }, + { + "dataset": "cloudfront_logs", + "index_pattern": "logs-aws.cloudfront_logs-*", + "title": "AWS CloudFront logs" + }, + { + "dataset": "usage", + "index_pattern": "logs-aws.usage-*", + "title": "AWS usage metrics" + }, + { + "dataset": "dynamodb", + "index_pattern": "logs-aws.dynamodb-*", + "title": "AWS DynamoDB metrics" + }, + { + "dataset": "apigateway_logs", + "index_pattern": "logs-aws.apigateway_logs-*", + "title": "AWS API Gateway logs" + }, + { + "dataset": "s3_request", + "index_pattern": "logs-aws.s3_request-*", + "title": "AWS S3 request metrics" + }, + { + "dataset": "sqs", + "index_pattern": "logs-aws.sqs-*", + "title": "AWS SQS metrics" + }, + { + "dataset": "natgateway", + "index_pattern": "logs-aws.natgateway-*", + "title": "AWS NAT gateway metrics" + } + ], + "elser_embedding": "AWS - Collect logs and metrics from Amazon Web Services (AWS) with Elastic Agent. - AWS EC2 metrics AWS API Gateway metrics AWS EC2 logs AWS CloudWatch logs AWS Billing Metrics AWS EBS metrics AWS Health AWS Transit Gateway metrics AWS CloudTrail Logs AWS VPN metrics AWS SNS metrics AWS Network Firewall metrics AWS WAF logs AWS EMR metrics AWS Network Firewall logs AWS Lambda metrics Collect AWS Security Hub Insights logs from AWS Amazon Redshift metrics Collect AWS Inspector logs from AWS AWS Route 53 Resolver Query Logs AWS EMR logs AWS ELB metrics AWS s3access logs Collect AWS Security Hub Findings logs from AWS AWS vpcflow logs AWS ELB logs AWS Kafka metrics AWS Kinesis Data Stream metrics AWS CloudWatch metrics AWS S3 daily storage metrics Collect Amazon GuardDuty Findings logs from AWS AWS RDS metrics AWS ECS metrics AWS S3 Storage Lens metrics AWS Route 53 Public Zone Logs AWS CloudFront logs AWS usage metrics AWS DynamoDB metrics AWS API Gateway logs AWS S3 request metrics AWS SQS metrics AWS NAT gateway metrics" + }, + { + "title": "Nginx Ingress Controller Logs", + "id": "nginx_ingress_controller", + "description": "Collect Nginx Ingress Controller logs.", + "data_streams": [ + { + "dataset": "access", + "index_pattern": "logs-nginx_ingress_controller.access-*", + "title": "Nginx Ingress Controller access logs" + }, + { + "dataset": "error", + "index_pattern": "logs-nginx_ingress_controller.error-*", + "title": "Nginx Ingress Controller error logs" + } + ], + "elser_embedding": "Nginx Ingress Controller Logs - Collect Nginx Ingress Controller logs. - Nginx Ingress Controller access logs Nginx Ingress Controller error logs" + }, + { + "title": "Cisco Umbrella", + "id": "cisco_umbrella", + "description": "Collect logs from Cisco Umbrella with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_umbrella.log-*", + "title": "Cisco Umbrella logs" + } + ], + "elser_embedding": "Cisco Umbrella - Collect logs from Cisco Umbrella with Elastic Agent. - Cisco Umbrella logs" + }, + { + "title": "Cloudflare Logpush", + "id": "cloudflare_logpush", + "description": "Collect and parse logs from Cloudflare API with Elastic Agent.", + "data_streams": [ + { + "dataset": "http_request", + "index_pattern": "logs-cloudflare_logpush.http_request-*", + "title": "Collect HTTP Request logs from Cloudflare" + }, + { + "dataset": "access_request", + "index_pattern": "logs-cloudflare_logpush.access_request-*", + "title": "Collect Access Request logs from Cloudflare" + }, + { + "dataset": "network_session", + "index_pattern": "logs-cloudflare_logpush.network_session-*", + "title": "Collect Zero Trust Network Session logs from Cloudflare" + }, + { + "dataset": "spectrum_event", + "index_pattern": "logs-cloudflare_logpush.spectrum_event-*", + "title": "Collect Spectrum Event logs from Cloudflare" + }, + { + "dataset": "gateway_http", + "index_pattern": "logs-cloudflare_logpush.gateway_http-*", + "title": "Collect Gateway HTTP logs from Cloudflare" + }, + { + "dataset": "casb", + "index_pattern": "logs-cloudflare_logpush.casb-*", + "title": "Collect CASB Findings logs from Cloudflare" + }, + { + "dataset": "magic_ids", + "index_pattern": "logs-cloudflare_logpush.magic_ids-*", + "title": "Collect Magic IDS logs from Cloudflare" + }, + { + "dataset": "workers_trace", + "index_pattern": "logs-cloudflare_logpush.workers_trace-*", + "title": "Collect Workers Trace Event logs from Cloudflare" + }, + { + "dataset": "audit", + "index_pattern": "logs-cloudflare_logpush.audit-*", + "title": "Collect Audit logs from Cloudflare" + }, + { + "dataset": "nel_report", + "index_pattern": "logs-cloudflare_logpush.nel_report-*", + "title": "Collect NEL Report logs from Cloudflare" + }, + { + "dataset": "network_analytics", + "index_pattern": "logs-cloudflare_logpush.network_analytics-*", + "title": "Collect Network Analytics logs from Cloudflare" + }, + { + "dataset": "dns", + "index_pattern": "logs-cloudflare_logpush.dns-*", + "title": "Collect DNS logs from Cloudflare" + }, + { + "dataset": "device_posture", + "index_pattern": "logs-cloudflare_logpush.device_posture-*", + "title": "Collect Device Posture Results logs from Cloudflare" + }, + { + "dataset": "gateway_dns", + "index_pattern": "logs-cloudflare_logpush.gateway_dns-*", + "title": "Collect Gateway DNS logs from Cloudflare" + }, + { + "dataset": "dns_firewall", + "index_pattern": "logs-cloudflare_logpush.dns_firewall-*", + "title": "Collect DNS Firewall logs from Cloudflare" + }, + { + "dataset": "sinkhole_http", + "index_pattern": "logs-cloudflare_logpush.sinkhole_http-*", + "title": "Collect Sinkhole HTTP logs from Cloudflare" + }, + { + "dataset": "firewall_event", + "index_pattern": "logs-cloudflare_logpush.firewall_event-*", + "title": "Collect Firewall Event logs from Cloudflare" + }, + { + "dataset": "gateway_network", + "index_pattern": "logs-cloudflare_logpush.gateway_network-*", + "title": "Collect Gateway Network logs from Cloudflare" + } + ], + "elser_embedding": "Cloudflare Logpush - Collect and parse logs from Cloudflare API with Elastic Agent. - Collect HTTP Request logs from Cloudflare Collect Access Request logs from Cloudflare Collect Zero Trust Network Session logs from Cloudflare Collect Spectrum Event logs from Cloudflare Collect Gateway HTTP logs from Cloudflare Collect CASB Findings logs from Cloudflare Collect Magic IDS logs from Cloudflare Collect Workers Trace Event logs from Cloudflare Collect Audit logs from Cloudflare Collect NEL Report logs from Cloudflare Collect Network Analytics logs from Cloudflare Collect DNS logs from Cloudflare Collect Device Posture Results logs from Cloudflare Collect Gateway DNS logs from Cloudflare Collect DNS Firewall logs from Cloudflare Collect Sinkhole HTTP logs from Cloudflare Collect Firewall Event logs from Cloudflare Collect Gateway Network logs from Cloudflare" + }, + { + "title": "Microsoft DHCP", + "id": "microsoft_dhcp", + "description": "Collect logs from Microsoft DHCP with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-microsoft_dhcp.log-*", + "title": "Microsoft DHCP Logs" + } + ], + "elser_embedding": "Microsoft DHCP - Collect logs from Microsoft DHCP with Elastic Agent. - Microsoft DHCP Logs" + }, + { + "title": "Netskope", + "id": "netskope", + "description": "Collect logs from Netskope with Elastic Agent.", + "data_streams": [ + { + "dataset": "alerts", + "index_pattern": "logs-netskope.alerts-*", + "title": "Alerts" + }, + { + "dataset": "events", + "index_pattern": "logs-netskope.events-*", + "title": "Events" + } + ], + "elser_embedding": "Netskope - Collect logs from Netskope with Elastic Agent. - Alerts Events" + }, + { + "title": "Suricata", + "id": "suricata", + "description": "Collect logs from Suricata with Elastic Agent.", + "data_streams": [ + { + "dataset": "eve", + "index_pattern": "logs-suricata.eve-*", + "title": "Suricata eve logs" + } + ], + "elser_embedding": "Suricata - Collect logs from Suricata with Elastic Agent. - Suricata eve logs" + }, + { + "title": "Custom Azure Logs", + "id": "azure_logs", + "description": "Collect log events from Azure Event Hubs with Elastic Agent", + "data_streams": [], + "elser_embedding": "Custom Azure Logs - Collect log events from Azure Event Hubs with Elastic Agent - " + }, + { + "title": "Zscaler Private Access", + "id": "zscaler_zpa", + "description": "Collect logs from Zscaler Private Access (ZPA) with Elastic Agent.", + "data_streams": [ + { + "dataset": "browser_access", + "index_pattern": "logs-zscaler_zpa.browser_access-*", + "title": "Browser Access Logs" + }, + { + "dataset": "app_connector_status", + "index_pattern": "logs-zscaler_zpa.app_connector_status-*", + "title": "App Connector Status Logs" + }, + { + "dataset": "user_status", + "index_pattern": "logs-zscaler_zpa.user_status-*", + "title": "User Status Logs" + }, + { + "dataset": "audit", + "index_pattern": "logs-zscaler_zpa.audit-*", + "title": "Audit Logs" + }, + { + "dataset": "user_activity", + "index_pattern": "logs-zscaler_zpa.user_activity-*", + "title": "User Activity Logs" + } + ], + "elser_embedding": "Zscaler Private Access - Collect logs from Zscaler Private Access (ZPA) with Elastic Agent. - Browser Access Logs App Connector Status Logs User Status Logs Audit Logs User Activity Logs" + }, + { + "title": "Cisco Aironet", + "id": "cisco_aironet", + "description": "Integration for Cisco Aironet WLC Logs", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_aironet.log-*", + "title": "Cisco Aironet logs" + } + ], + "elser_embedding": "Cisco Aironet - Integration for Cisco Aironet WLC Logs - Cisco Aironet logs" + }, + { + "title": "Collective Intelligence Framework v3", + "id": "ti_cif3", + "description": "Ingest threat indicators from a Collective Intelligence Framework v3 instance with Elastic Agent.", + "data_streams": [ + { + "dataset": "feed", + "index_pattern": "logs-ti_cif3.feed-*", + "title": "CIFv3 Feed" + } + ], + "elser_embedding": "Collective Intelligence Framework v3 - Ingest threat indicators from a Collective Intelligence Framework v3 instance with Elastic Agent. - CIFv3 Feed" + }, + { + "title": "Bitwarden", + "id": "bitwarden", + "description": "Collect logs from Bitwarden with Elastic Agent.", + "data_streams": [ + { + "dataset": "group", + "index_pattern": "logs-bitwarden.group-*", + "title": "Collect Group logs from Bitwarden" + }, + { + "dataset": "policy", + "index_pattern": "logs-bitwarden.policy-*", + "title": "Collect Policy logs from Bitwarden" + }, + { + "dataset": "member", + "index_pattern": "logs-bitwarden.member-*", + "title": "Collect Member logs from Bitwarden" + }, + { + "dataset": "event", + "index_pattern": "logs-bitwarden.event-*", + "title": "Collect Event logs from Bitwarden" + }, + { + "dataset": "collection", + "index_pattern": "logs-bitwarden.collection-*", + "title": "Collect Collection logs from Bitwarden" + } + ], + "elser_embedding": "Bitwarden - Collect logs from Bitwarden with Elastic Agent. - Collect Group logs from Bitwarden Collect Policy logs from Bitwarden Collect Member logs from Bitwarden Collect Event logs from Bitwarden Collect Collection logs from Bitwarden" + }, + { + "title": "Kibana", + "id": "kibana", + "description": "Collect logs and metrics from Kibana with Elastic Agent.", + "data_streams": [ + { + "dataset": "node_actions", + "index_pattern": "logs-kibana.node_actions-*", + "title": "Kibana node_actions metrics" + }, + { + "dataset": "stats", + "index_pattern": "logs-kibana.stats-*", + "title": "Kibana stats metrics" + }, + { + "dataset": "audit", + "index_pattern": "logs-kibana.audit-*", + "title": "kibana audit logs" + }, + { + "dataset": "task_manager_metrics", + "index_pattern": "logs-kibana.task_manager_metrics-*", + "title": "Kibana task manager metrics" + }, + { + "dataset": "cluster_rules", + "index_pattern": "logs-kibana.cluster_rules-*", + "title": "Kibana cluster_rules metrics" + }, + { + "dataset": "background_task_utilization", + "index_pattern": "logs-kibana.background_task_utilization-*", + "title": "Kibana background task utilization metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-kibana.log-*", + "title": "Kibana logs" + }, + { + "dataset": "node_rules", + "index_pattern": "logs-kibana.node_rules-*", + "title": "Kibana node_rules metrics" + }, + { + "dataset": "status", + "index_pattern": "logs-kibana.status-*", + "title": "Kibana status metrics" + }, + { + "dataset": "cluster_actions", + "index_pattern": "logs-kibana.cluster_actions-*", + "title": "Kibana cluster_actions metrics" + } + ], + "elser_embedding": "Kibana - Collect logs and metrics from Kibana with Elastic Agent. - Kibana node_actions metrics Kibana stats metrics kibana audit logs Kibana task manager metrics Kibana cluster_rules metrics Kibana background task utilization metrics Kibana logs Kibana node_rules metrics Kibana status metrics Kibana cluster_actions metrics" + }, + { + "title": "Digital Guardian", + "id": "digital_guardian", + "description": "Collect logs from Digital Guardian with Elastic Agent.", + "data_streams": [ + { + "dataset": "arc", + "index_pattern": "logs-digital_guardian.arc-*", + "title": "Digital Guardian ARC Logs" + } + ], + "elser_embedding": "Digital Guardian - Collect logs from Digital Guardian with Elastic Agent. - Digital Guardian ARC Logs" + }, + { + "title": "MySQL", + "id": "mysql", + "description": "Collect logs and metrics from MySQL servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "performance", + "index_pattern": "logs-mysql.performance-*", + "title": "MySQL performance metrics" + }, + { + "dataset": "error", + "index_pattern": "logs-mysql.error-*", + "title": "MySQL error logs" + }, + { + "dataset": "slowlog", + "index_pattern": "logs-mysql.slowlog-*", + "title": "MySQL slowlog logs" + }, + { + "dataset": "galera_status", + "index_pattern": "logs-mysql.galera_status-*", + "title": "MySQL galera_status metrics" + }, + { + "dataset": "replica_status", + "index_pattern": "logs-mysql.replica_status-*", + "title": "Collect replica status metrics from mysql" + }, + { + "dataset": "status", + "index_pattern": "logs-mysql.status-*", + "title": "MySQL status metrics" + } + ], + "elser_embedding": "MySQL - Collect logs and metrics from MySQL servers with Elastic Agent. - MySQL performance metrics MySQL error logs MySQL slowlog logs MySQL galera_status metrics Collect replica status metrics from mysql MySQL status metrics" + }, + { + "title": "CISA Known Exploited Vulnerabilities", + "id": "cisa_kevs", + "description": "This package allows the ingest of known exploited vulnerabilities according to the Cybersecurity and Infrastructure Security Agency of the United States of America. This information could be used to enrich or track exisiting vulnerabilities that are known to be exploited in the wild.", + "data_streams": [ + { + "dataset": "vulnerability", + "index_pattern": "logs-cisa_kevs.vulnerability-*", + "title": "CISA Known Exploited Vulnerabilities List" + } + ], + "elser_embedding": "CISA Known Exploited Vulnerabilities - This package allows the ingest of known exploited vulnerabilities according to the Cybersecurity and Infrastructure Security Agency of the United States of America. This information could be used to enrich or track exisiting vulnerabilities that are known to be exploited in the wild. - CISA Known Exploited Vulnerabilities List" + }, + { + "title": "StormShield SNS", + "id": "stormshield", + "description": "Stormshield SNS integration.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-stormshield.log-*", + "title": "StormShield SNS logs" + } + ], + "elser_embedding": "StormShield SNS - Stormshield SNS integration. - StormShield SNS logs" + }, + { + "title": "1Password", + "id": "1password", + "description": "Collect logs from 1Password with Elastic Agent.", + "data_streams": [ + { + "dataset": "item_usages", + "index_pattern": "logs-1password.item_usages-*", + "title": "Collect 1Password item usages events" + }, + { + "dataset": "signin_attempts", + "index_pattern": "logs-1password.signin_attempts-*", + "title": "1Password sign-in attempt events" + }, + { + "dataset": "audit_events", + "index_pattern": "logs-1password.audit_events-*", + "title": "Collect 1Password audit events" + } + ], + "elser_embedding": "1Password - Collect logs from 1Password with Elastic Agent. - Collect 1Password item usages events 1Password sign-in attempt events Collect 1Password audit events" + }, + { + "title": "Azure Network Watcher NSG", + "id": "azure_network_watcher_nsg", + "description": "Collect logs from Azure Network Watcher NSG with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-azure_network_watcher_nsg.log-*", + "title": "Collect NSG logs from Azure Network Watcher" + } + ], + "elser_embedding": "Azure Network Watcher NSG - Collect logs from Azure Network Watcher NSG with Elastic Agent. - Collect NSG logs from Azure Network Watcher" + }, + { + "title": "WebSphere Application Server", + "id": "websphere_application_server", + "description": "Collects metrics from IBM WebSphere Application Server with Elastic Agent.", + "data_streams": [ + { + "dataset": "threadpool", + "index_pattern": "logs-websphere_application_server.threadpool-*", + "title": "ThreadPool metrics" + }, + { + "dataset": "servlet", + "index_pattern": "logs-websphere_application_server.servlet-*", + "title": "Servlet metrics" + }, + { + "dataset": "session_manager", + "index_pattern": "logs-websphere_application_server.session_manager-*", + "title": "Session Manager metrics" + }, + { + "dataset": "jdbc", + "index_pattern": "logs-websphere_application_server.jdbc-*", + "title": "JDBC metrics" + } + ], + "elser_embedding": "WebSphere Application Server - Collects metrics from IBM WebSphere Application Server with Elastic Agent. - ThreadPool metrics Servlet metrics Session Manager metrics JDBC metrics" + }, + { + "title": "GitLab", + "id": "gitlab", + "description": "Collect logs from GitLab with Elastic Agent.", + "data_streams": [ + { + "dataset": "sidekiq", + "index_pattern": "logs-gitlab.sidekiq-*", + "title": "GitLab Sidekiq logs" + }, + { + "dataset": "audit", + "index_pattern": "logs-gitlab.audit-*", + "title": "Audit" + }, + { + "dataset": "auth", + "index_pattern": "logs-gitlab.auth-*", + "title": "Auth" + }, + { + "dataset": "application", + "index_pattern": "logs-gitlab.application-*", + "title": "Application" + }, + { + "dataset": "pages", + "index_pattern": "logs-gitlab.pages-*", + "title": "GitLab Pages logs" + }, + { + "dataset": "production", + "index_pattern": "logs-gitlab.production-*", + "title": "GitLab Production logs" + }, + { + "dataset": "api", + "index_pattern": "logs-gitlab.api-*", + "title": "GitLab API logs" + } + ], + "elser_embedding": "GitLab - Collect logs from GitLab with Elastic Agent. - GitLab Sidekiq logs Audit Auth Application GitLab Pages logs GitLab Production logs GitLab API logs" + }, + { + "title": "Custom Logs", + "id": "log", + "description": "Collect custom logs with Elastic Agent.", + "data_streams": [], + "elser_embedding": "Custom Logs - Collect custom logs with Elastic Agent. - " + }, + { + "title": "Tenable Vulnerability Management", + "id": "tenable_io", + "description": "Collect logs from Tenable Vulnerability Management with Elastic Agent.", + "data_streams": [ + { + "dataset": "plugin", + "index_pattern": "logs-tenable_io.plugin-*", + "title": "Collect Plugin logs from Tenable Vulnerability Management" + }, + { + "dataset": "vulnerability", + "index_pattern": "logs-tenable_io.vulnerability-*", + "title": "Collect Vulnerability logs from Tenable Vulnerability Management" + }, + { + "dataset": "scan", + "index_pattern": "logs-tenable_io.scan-*", + "title": "Collect Scan logs from Tenable Vulnerability Management" + }, + { + "dataset": "asset", + "index_pattern": "logs-tenable_io.asset-*", + "title": "Collect Asset data from Tenable Vulnerability Management" + } + ], + "elser_embedding": "Tenable Vulnerability Management - Collect logs from Tenable Vulnerability Management with Elastic Agent. - Collect Plugin logs from Tenable Vulnerability Management Collect Vulnerability logs from Tenable Vulnerability Management Collect Scan logs from Tenable Vulnerability Management Collect Asset data from Tenable Vulnerability Management" + }, + { + "title": "Falco", + "id": "falco", + "description": "Collect events and alerts from Falco using Elastic Agent", + "data_streams": [ + { + "dataset": "alerts", + "index_pattern": "logs-falco.alerts-*", + "title": "Falco Alerts" + } + ], + "elser_embedding": "Falco - Collect events and alerts from Falco using Elastic Agent - Falco Alerts" + }, + { + "title": "Docker", + "id": "docker", + "description": "Collect metrics and logs from Docker instances with Elastic Agent.", + "data_streams": [ + { + "dataset": "memory", + "index_pattern": "logs-docker.memory-*", + "title": "Docker memory metrics" + }, + { + "dataset": "network", + "index_pattern": "logs-docker.network-*", + "title": "Docker network metrics" + }, + { + "dataset": "image", + "index_pattern": "logs-docker.image-*", + "title": "Docker image metrics" + }, + { + "dataset": "container", + "index_pattern": "logs-docker.container-*", + "title": "Docker container metrics" + }, + { + "dataset": "info", + "index_pattern": "logs-docker.info-*", + "title": "Docker info metrics" + }, + { + "dataset": "container_logs", + "index_pattern": "logs-docker.container_logs-*", + "title": "Docker container logs" + }, + { + "dataset": "diskio", + "index_pattern": "logs-docker.diskio-*", + "title": "Docker diskio metrics" + }, + { + "dataset": "event", + "index_pattern": "logs-docker.event-*", + "title": "Docker event metrics" + }, + { + "dataset": "healthcheck", + "index_pattern": "logs-docker.healthcheck-*", + "title": "Docker healthcheck metrics" + }, + { + "dataset": "cpu", + "index_pattern": "logs-docker.cpu-*", + "title": "Docker cpu metrics" + } + ], + "elser_embedding": "Docker - Collect metrics and logs from Docker instances with Elastic Agent. - Docker memory metrics Docker network metrics Docker image metrics Docker container metrics Docker info metrics Docker container logs Docker diskio metrics Docker event metrics Docker healthcheck metrics Docker cpu metrics" + }, + { + "title": "Elastic Synthetics Dashboards", + "id": "synthetics_dashboards", + "description": "Explore Elastic Synthetics metrics with these dashboards.", + "data_streams": [], + "elser_embedding": "Elastic Synthetics Dashboards - Explore Elastic Synthetics metrics with these dashboards. - " + }, + { + "title": "Azure Billing Metrics", + "id": "azure_billing", + "description": "Collect billing metrics with Elastic Agent.", + "data_streams": [ + { + "dataset": "billing", + "index_pattern": "logs-azure_billing.billing-*", + "title": "Azure Billing Metrics" + } + ], + "elser_embedding": "Azure Billing Metrics - Collect billing metrics with Elastic Agent. - Azure Billing Metrics" + }, + { + "title": "Couchbase", + "id": "couchbase", + "description": "Collect metrics from Couchbase databases with Elastic Agent.", + "data_streams": [ + { + "dataset": "cache", + "index_pattern": "logs-couchbase.cache-*", + "title": "Couchbase Sync Gateway Cache metrics." + }, + { + "dataset": "cbl_replication", + "index_pattern": "logs-couchbase.cbl_replication-*", + "title": "Couchbase Sync Gateway CBL Replications metrics" + }, + { + "dataset": "query_index", + "index_pattern": "logs-couchbase.query_index-*", + "title": "Query Index metrics" + }, + { + "dataset": "xdcr", + "index_pattern": "logs-couchbase.xdcr-*", + "title": "Couchbase XDCR Metrics" + }, + { + "dataset": "miscellaneous", + "index_pattern": "logs-couchbase.miscellaneous-*", + "title": "Couchbase Sync Gateway Delta Sync, Import, Security and GSI views metrics." + }, + { + "dataset": "node", + "index_pattern": "logs-couchbase.node-*", + "title": "Node metrics" + }, + { + "dataset": "resource", + "index_pattern": "logs-couchbase.resource-*", + "title": "Couchbase Sync Gateway Resource Utilization metrics." + }, + { + "dataset": "bucket", + "index_pattern": "logs-couchbase.bucket-*", + "title": "Couchbase bucket metrics" + }, + { + "dataset": "cluster", + "index_pattern": "logs-couchbase.cluster-*", + "title": "Couchbase cluster metrics" + }, + { + "dataset": "database_stats", + "index_pattern": "logs-couchbase.database_stats-*", + "title": "Couchbase Sync Gateway Database Stats metrics." + } + ], + "elser_embedding": "Couchbase - Collect metrics from Couchbase databases with Elastic Agent. - Couchbase Sync Gateway Cache metrics. Couchbase Sync Gateway CBL Replications metrics Query Index metrics Couchbase XDCR Metrics Couchbase Sync Gateway Delta Sync, Import, Security and GSI views metrics. Node metrics Couchbase Sync Gateway Resource Utilization metrics. Couchbase bucket metrics Couchbase cluster metrics Couchbase Sync Gateway Database Stats metrics." + }, + { + "title": "VMware Carbon Black Cloud", + "id": "carbon_black_cloud", + "description": "Collect logs from VMWare Carbon Black Cloud with Elastic Agent.", + "data_streams": [ + { + "dataset": "watchlist_hit", + "index_pattern": "logs-carbon_black_cloud.watchlist_hit-*", + "title": "Watchlist Hit" + }, + { + "dataset": "asset_vulnerability_summary", + "index_pattern": "logs-carbon_black_cloud.asset_vulnerability_summary-*", + "title": "Asset Vulnerability Summary" + }, + { + "dataset": "endpoint_event", + "index_pattern": "logs-carbon_black_cloud.endpoint_event-*", + "title": "Endpoint Event" + }, + { + "dataset": "audit", + "index_pattern": "logs-carbon_black_cloud.audit-*", + "title": "Audit" + }, + { + "dataset": "alert", + "index_pattern": "logs-carbon_black_cloud.alert-*", + "title": "Alert" + }, + { + "dataset": "alert_v7", + "index_pattern": "logs-carbon_black_cloud.alert_v7-*", + "title": "Alert V7" + } + ], + "elser_embedding": "VMware Carbon Black Cloud - Collect logs from VMWare Carbon Black Cloud with Elastic Agent. - Watchlist Hit Asset Vulnerability Summary Endpoint Event Audit Alert Alert V7" + }, + { + "title": "Universal Profiling Symbolizer", + "id": "profiler_symbolizer", + "description": "Fleet-wide, whole-system, continuous profiling with zero instrumentation.", + "data_streams": [], + "elser_embedding": "Universal Profiling Symbolizer - Fleet-wide, whole-system, continuous profiling with zero instrumentation. - " + }, + { + "title": "Fortinet FortiProxy", + "id": "fortinet_fortiproxy", + "description": "Collect logs from Fortinet FortiProxy with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-fortinet_fortiproxy.log-*", + "title": "Collect logs from Fortinet FortiProxy" + } + ], + "elser_embedding": "Fortinet FortiProxy - Collect logs from Fortinet FortiProxy with Elastic Agent. - Collect logs from Fortinet FortiProxy" + }, + { + "title": "MongoDB Atlas", + "id": "mongodb_atlas", + "description": "This Elastic integration collects logs and metrics from MongoDB Atlas instance.", + "data_streams": [ + { + "dataset": "mongod_database", + "index_pattern": "logs-mongodb_atlas.mongod_database-*", + "title": "Collect Mongod Database logs from MongoDB Atlas" + }, + { + "dataset": "disk", + "index_pattern": "logs-mongodb_atlas.disk-*", + "title": "Collect Disk metrics from MongoDB Atlas" + }, + { + "dataset": "project", + "index_pattern": "logs-mongodb_atlas.project-*", + "title": "Collect Project logs from MongoDB Atlas" + }, + { + "dataset": "process", + "index_pattern": "logs-mongodb_atlas.process-*", + "title": "Collect Process metrics from MongoDB Atlas" + }, + { + "dataset": "alert", + "index_pattern": "logs-mongodb_atlas.alert-*", + "title": "Collect Alert logs from MongoDB Atlas" + }, + { + "dataset": "mongod_audit", + "index_pattern": "logs-mongodb_atlas.mongod_audit-*", + "title": "Collect Mongod Audit logs from MongoDB Atlas" + }, + { + "dataset": "organization", + "index_pattern": "logs-mongodb_atlas.organization-*", + "title": "Collect Organization logs from MongoDB Atlas" + }, + { + "dataset": "hardware", + "index_pattern": "logs-mongodb_atlas.hardware-*", + "title": "Collect Hardware metrics from MongoDB Atlas" + } + ], + "elser_embedding": "MongoDB Atlas - This Elastic integration collects logs and metrics from MongoDB Atlas instance. - Collect Mongod Database logs from MongoDB Atlas Collect Disk metrics from MongoDB Atlas Collect Project logs from MongoDB Atlas Collect Process metrics from MongoDB Atlas Collect Alert logs from MongoDB Atlas Collect Mongod Audit logs from MongoDB Atlas Collect Organization logs from MongoDB Atlas Collect Hardware metrics from MongoDB Atlas" + }, + { + "title": "Zero Networks", + "id": "zeronetworks", + "description": "Zero Networks Logs integration", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-zeronetworks.audit-*", + "title": "Zero Networks Audit Logs" + } + ], + "elser_embedding": "Zero Networks - Zero Networks Logs integration - Zero Networks Audit Logs" + }, + { + "title": "CockroachDB Metrics", + "id": "cockroachdb", + "description": "Collect metrics from CockroachDB servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "status", + "index_pattern": "logs-cockroachdb.status-*", + "title": "Status" + } + ], + "elser_embedding": "CockroachDB Metrics - Collect metrics from CockroachDB servers with Elastic Agent. - Status" + }, + { + "title": "Microsoft Exchange Server", + "id": "microsoft_exchange_server", + "description": "Collect logs from Microsoft Exchange Server with Elastic Agent.", + "data_streams": [ + { + "dataset": "imap4_pop3", + "index_pattern": "logs-microsoft_exchange_server.imap4_pop3-*", + "title": "Exchange Server IMAP4 POP3" + }, + { + "dataset": "httpproxy", + "index_pattern": "logs-microsoft_exchange_server.httpproxy-*", + "title": "Exchange HTTPProxy" + }, + { + "dataset": "smtp", + "index_pattern": "logs-microsoft_exchange_server.smtp-*", + "title": "Exchange SMTP" + }, + { + "dataset": "messagetracking", + "index_pattern": "logs-microsoft_exchange_server.messagetracking-*", + "title": "Exchange Messagetracking" + } + ], + "elser_embedding": "Microsoft Exchange Server - Collect logs from Microsoft Exchange Server with Elastic Agent. - Exchange Server IMAP4 POP3 Exchange HTTPProxy Exchange SMTP Exchange Messagetracking" + }, + { + "title": "Cisco Secure Email Gateway", + "id": "cisco_secure_email_gateway", + "description": "Collect logs from Cisco Secure Email Gateway with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_secure_email_gateway.log-*", + "title": "Cisco Secure Email Gateway logs" + } + ], + "elser_embedding": "Cisco Secure Email Gateway - Collect logs from Cisco Secure Email Gateway with Elastic Agent. - Cisco Secure Email Gateway logs" + }, + { + "title": "Prometheus Input", + "id": "prometheus_input", + "description": "Collects metrics from Prometheus exporter.", + "data_streams": [], + "elser_embedding": "Prometheus Input - Collects metrics from Prometheus exporter. - " + }, + { + "title": "PingOne", + "id": "ping_one", + "description": "Collect logs from PingOne with Elastic-Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-ping_one.audit-*", + "title": "Collect Audit logs from PingOne" + } + ], + "elser_embedding": "PingOne - Collect logs from PingOne with Elastic-Agent. - Collect Audit logs from PingOne" + }, + { + "title": "Squid Proxy", + "id": "squid", + "description": "Collect and parse logs from Squid devices with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-squid.log-*", + "title": "Squid logs" + } + ], + "elser_embedding": "Squid Proxy - Collect and parse logs from Squid devices with Elastic Agent. - Squid logs" + }, + { + "title": "Zoom", + "id": "zoom", + "description": "Collect logs from Zoom with Elastic Agent.", + "data_streams": [ + { + "dataset": "webhook", + "index_pattern": "logs-zoom.webhook-*", + "title": "Zoom webhook logs" + } + ], + "elser_embedding": "Zoom - Collect logs from Zoom with Elastic Agent. - Zoom webhook logs" + }, + { + "title": "Auth0", + "id": "auth0", + "description": "Collect logs from Auth0 with Elastic Agent.", + "data_streams": [ + { + "dataset": "logs", + "index_pattern": "logs-auth0.logs-*", + "title": "Auth0 logs" + } + ], + "elser_embedding": "Auth0 - Collect logs from Auth0 with Elastic Agent. - Auth0 logs" + }, + { + "title": "Tomcat NetWitness Logs", + "id": "tomcat", + "description": "Collect and parse logs from Apache Tomcat servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-tomcat.log-*", + "title": "Apache Tomcat logs" + } + ], + "elser_embedding": "Tomcat NetWitness Logs - Collect and parse logs from Apache Tomcat servers with Elastic Agent. - Apache Tomcat logs" + }, + { + "title": "Auditd Logs", + "id": "auditd", + "description": "Collect logs from Linux audit daemon with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-auditd.log-*", + "title": "Auditd logs" + } + ], + "elser_embedding": "Auditd Logs - Collect logs from Linux audit daemon with Elastic Agent. - Auditd logs" + }, + { + "title": "SQL Input", + "id": "sql", + "description": "Collects Metrics by Quering on SQL Databases", + "data_streams": [], + "elser_embedding": "SQL Input - Collects Metrics by Quering on SQL Databases - " + }, + { + "title": "Azure Frontdoor", + "id": "azure_frontdoor", + "description": "This Elastic integration collects logs from Azure Frontdoor.", + "data_streams": [ + { + "dataset": "access", + "index_pattern": "logs-azure_frontdoor.access-*", + "title": "FrontDoor Access" + }, + { + "dataset": "waf", + "index_pattern": "logs-azure_frontdoor.waf-*", + "title": "FrontDoor WAF" + } + ], + "elser_embedding": "Azure Frontdoor - This Elastic integration collects logs from Azure Frontdoor. - FrontDoor Access FrontDoor WAF" + }, + { + "title": "Amazon Data Firehose", + "id": "awsfirehose", + "description": "Stream logs and metrics from Amazon Data Firehose into Elastic Cloud.", + "data_streams": [ + { + "dataset": "logs", + "index_pattern": "logs-awsfirehose.logs-*", + "title": "Logs from Amazon Data Firehose" + }, + { + "dataset": "metrics", + "index_pattern": "logs-awsfirehose.metrics-*", + "title": "Metrics ingested from Amazon Data Firehose" + } + ], + "elser_embedding": "Amazon Data Firehose - Stream logs and metrics from Amazon Data Firehose into Elastic Cloud. - Logs from Amazon Data Firehose Metrics ingested from Amazon Data Firehose" + }, + { + "title": "Zscaler Internet Access", + "id": "zscaler_zia", + "description": "Collect logs from Zscaler Internet Access (ZIA) with Elastic Agent.", + "data_streams": [ + { + "dataset": "sandbox_report", + "index_pattern": "logs-zscaler_zia.sandbox_report-*", + "title": "Sandbox Report Logs" + }, + { + "dataset": "tunnel", + "index_pattern": "logs-zscaler_zia.tunnel-*", + "title": "Tunnel Logs" + }, + { + "dataset": "audit", + "index_pattern": "logs-zscaler_zia.audit-*", + "title": "Audit Logs" + }, + { + "dataset": "dns", + "index_pattern": "logs-zscaler_zia.dns-*", + "title": "DNS logs" + }, + { + "dataset": "web", + "index_pattern": "logs-zscaler_zia.web-*", + "title": "Web Logs" + }, + { + "dataset": "endpoint_dlp", + "index_pattern": "logs-zscaler_zia.endpoint_dlp-*", + "title": "Endpoint DLP Logs" + }, + { + "dataset": "alerts", + "index_pattern": "logs-zscaler_zia.alerts-*", + "title": "Alerts" + }, + { + "dataset": "firewall", + "index_pattern": "logs-zscaler_zia.firewall-*", + "title": "Firewall Logs" + } + ], + "elser_embedding": "Zscaler Internet Access - Collect logs from Zscaler Internet Access (ZIA) with Elastic Agent. - Sandbox Report Logs Tunnel Logs Audit Logs DNS logs Web Logs Endpoint DLP Logs Alerts Firewall Logs" + }, + { + "title": "Broadcom ProxySG", + "id": "proxysg", + "description": "Collect access logs from Broadcom ProxySG with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-proxysg.log-*", + "title": "ProxySG Access Logs" + } + ], + "elser_embedding": "Broadcom ProxySG - Collect access logs from Broadcom ProxySG with Elastic Agent. - ProxySG Access Logs" + }, + { + "title": "Juniper SRX", + "id": "juniper_srx", + "description": "Collect logs from Juniper SRX devices with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-juniper_srx.log-*", + "title": "Juniper SRX logs" + } + ], + "elser_embedding": "Juniper SRX - Collect logs from Juniper SRX devices with Elastic Agent. - Juniper SRX logs" + }, + { + "title": "ServiceNow", + "id": "servicenow", + "description": "Collect logs from ServiceNow with Elastic Agent.", + "data_streams": [ + { + "dataset": "event", + "index_pattern": "logs-servicenow.event-*", + "title": "Event" + } + ], + "elser_embedding": "ServiceNow - Collect logs from ServiceNow with Elastic Agent. - Event" + }, + { + "title": "Defend for Containers", + "id": "cloud_defend", + "description": "Elastic Defend for Containers (BETA) provides cloud-native runtime protections for containerized environments.", + "data_streams": [ + { + "dataset": "heartbeat", + "index_pattern": "logs-cloud_defend.heartbeat-*", + "title": "Cloud Defend Liveness Heartbeat" + }, + { + "dataset": "file", + "index_pattern": "logs-cloud_defend.file-*", + "title": "File telemetry" + }, + { + "dataset": "process", + "index_pattern": "logs-cloud_defend.process-*", + "title": "Process telemetry" + }, + { + "dataset": "metrics", + "index_pattern": "logs-cloud_defend.metrics-*", + "title": "Cloud defend metrics" + }, + { + "dataset": "alerts", + "index_pattern": "logs-cloud_defend.alerts-*", + "title": "alerts" + } + ], + "elser_embedding": "Defend for Containers - Elastic Defend for Containers (BETA) provides cloud-native runtime protections for containerized environments. - Cloud Defend Liveness Heartbeat File telemetry Process telemetry Cloud defend metrics alerts" + }, + { + "title": "authentik", + "id": "authentik", + "description": "Collect logs from authentik with Elastic Agent.", + "data_streams": [ + { + "dataset": "group", + "index_pattern": "logs-authentik.group-*", + "title": "authentik group logs" + }, + { + "dataset": "event", + "index_pattern": "logs-authentik.event-*", + "title": "authentik event logs" + }, + { + "dataset": "user", + "index_pattern": "logs-authentik.user-*", + "title": "authentik user logs" + } + ], + "elser_embedding": "authentik - Collect logs from authentik with Elastic Agent. - authentik group logs authentik event logs authentik user logs" + }, + { + "title": "Wiz", + "id": "wiz", + "description": "Collect logs from Wiz with Elastic Agent.", + "data_streams": [ + { + "dataset": "issue", + "index_pattern": "logs-wiz.issue-*", + "title": "Collect Issue logs from Wiz." + }, + { + "dataset": "vulnerability", + "index_pattern": "logs-wiz.vulnerability-*", + "title": "Collect Vulnerability logs from Wiz." + }, + { + "dataset": "audit", + "index_pattern": "logs-wiz.audit-*", + "title": "Collect Audit logs from Wiz." + }, + { + "dataset": "cloud_configuration_finding", + "index_pattern": "logs-wiz.cloud_configuration_finding-*", + "title": "Collet Cloud Configuration Finding logs from Wiz." + } + ], + "elser_embedding": "Wiz - Collect logs from Wiz with Elastic Agent. - Collect Issue logs from Wiz. Collect Vulnerability logs from Wiz. Collect Audit logs from Wiz. Collet Cloud Configuration Finding logs from Wiz." + }, + { + "title": "Mattermost", + "id": "mattermost", + "description": "Collect logs from Mattermost with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-mattermost.audit-*", + "title": "Audit Logs" + } + ], + "elser_embedding": "Mattermost - Collect logs from Mattermost with Elastic Agent. - Audit Logs" + }, + { + "title": "Teleport", + "id": "teleport", + "description": "Collect logs from Teleport with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-teleport.audit-*", + "title": "Teleport audit logs" + } + ], + "elser_embedding": "Teleport - Collect logs from Teleport with Elastic Agent. - Teleport audit logs" + }, + { + "title": "Fleet Server", + "id": "fleet_server", + "description": "Centrally manage Elastic Agents with the Fleet Server integration.", + "data_streams": [ + { + "dataset": "agent_versions_metrics", + "index_pattern": "logs-fleet_server.agent_versions_metrics-*", + "title": "Fleet Agent Versions" + }, + { + "dataset": "agent_status_metrics", + "index_pattern": "logs-fleet_server.agent_status_metrics-*", + "title": "Fleet Agent Status" + }, + { + "dataset": "output_health_logs", + "index_pattern": "logs-fleet_server.output_health_logs-*", + "title": "Output Health" + } + ], + "elser_embedding": "Fleet Server - Centrally manage Elastic Agents with the Fleet Server integration. - Fleet Agent Versions Fleet Agent Status Output Health" + }, + { + "title": "Cisco Secure Endpoint", + "id": "cisco_secure_endpoint", + "description": "Collect logs from Cisco Secure Endpoint (AMP) with Elastic Agent.", + "data_streams": [ + { + "dataset": "event", + "index_pattern": "logs-cisco_secure_endpoint.event-*", + "title": "Cisco Secure Endpoint logs" + } + ], + "elser_embedding": "Cisco Secure Endpoint - Collect logs from Cisco Secure Endpoint (AMP) with Elastic Agent. - Cisco Secure Endpoint logs" + }, + { + "title": "Iptables", + "id": "iptables", + "description": "Collect logs from Iptables with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-iptables.log-*", + "title": "Iptables log logs" + } + ], + "elser_embedding": "Iptables - Collect logs from Iptables with Elastic Agent. - Iptables log logs" + }, + { + "title": "Google Workspace", + "id": "google_workspace", + "description": "Collect logs from Google Workspace with Elastic Agent.", + "data_streams": [ + { + "dataset": "user_accounts", + "index_pattern": "logs-google_workspace.user_accounts-*", + "title": "User accounts logs" + }, + { + "dataset": "device", + "index_pattern": "logs-google_workspace.device-*", + "title": "Device logs" + }, + { + "dataset": "admin", + "index_pattern": "logs-google_workspace.admin-*", + "title": "Admin logs" + }, + { + "dataset": "gcp", + "index_pattern": "logs-google_workspace.gcp-*", + "title": "GCP logs" + }, + { + "dataset": "group_enterprise", + "index_pattern": "logs-google_workspace.group_enterprise-*", + "title": "Group Enterprise logs" + }, + { + "dataset": "login", + "index_pattern": "logs-google_workspace.login-*", + "title": "Login logs" + }, + { + "dataset": "access_transparency", + "index_pattern": "logs-google_workspace.access_transparency-*", + "title": "Access Transparency logs" + }, + { + "dataset": "alert", + "index_pattern": "logs-google_workspace.alert-*", + "title": "Collect Alert logs from Google Workspace" + }, + { + "dataset": "context_aware_access", + "index_pattern": "logs-google_workspace.context_aware_access-*", + "title": "Context Aware Access logs" + }, + { + "dataset": "token", + "index_pattern": "logs-google_workspace.token-*", + "title": "Token logs" + }, + { + "dataset": "drive", + "index_pattern": "logs-google_workspace.drive-*", + "title": "Drive logs" + }, + { + "dataset": "groups", + "index_pattern": "logs-google_workspace.groups-*", + "title": "Groups logs" + }, + { + "dataset": "saml", + "index_pattern": "logs-google_workspace.saml-*", + "title": "SAML logs" + }, + { + "dataset": "rules", + "index_pattern": "logs-google_workspace.rules-*", + "title": "Rules logs" + } + ], + "elser_embedding": "Google Workspace - Collect logs from Google Workspace with Elastic Agent. - User accounts logs Device logs Admin logs GCP logs Group Enterprise logs Login logs Access Transparency logs Collect Alert logs from Google Workspace Context Aware Access logs Token logs Drive logs Groups logs SAML logs Rules logs" + }, + { + "title": "VMware Carbon Black EDR", + "id": "carbonblack_edr", + "description": "Collect logs from VMware Carbon Black EDR with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-carbonblack_edr.log-*", + "title": "Carbon Black EDR logs" + } + ], + "elser_embedding": "VMware Carbon Black EDR - Collect logs from VMware Carbon Black EDR with Elastic Agent. - Carbon Black EDR logs" + }, + { + "title": "Mimecast", + "id": "mimecast", + "description": "Collect logs from Mimecast with Elastic Agent.", + "data_streams": [ + { + "dataset": "dlp_logs", + "index_pattern": "logs-mimecast.dlp_logs-*", + "title": "DLP Mimecast Logs" + }, + { + "dataset": "ttp_url_logs", + "index_pattern": "logs-mimecast.ttp_url_logs-*", + "title": "TTP URL Logs" + }, + { + "dataset": "siem_logs", + "index_pattern": "logs-mimecast.siem_logs-*", + "title": "SIEM Mimecast Logs" + }, + { + "dataset": "message_release_logs", + "index_pattern": "logs-mimecast.message_release_logs-*", + "title": "Mimecast Message Release" + }, + { + "dataset": "ttp_ip_logs", + "index_pattern": "logs-mimecast.ttp_ip_logs-*", + "title": "TTP Impersonation Mimecast Logs" + }, + { + "dataset": "audit_events", + "index_pattern": "logs-mimecast.audit_events-*", + "title": "Audit Events Mimecast Logs" + }, + { + "dataset": "ttp_ap_logs", + "index_pattern": "logs-mimecast.ttp_ap_logs-*", + "title": "TTP Attachment Logs" + }, + { + "dataset": "archive_search_logs", + "index_pattern": "logs-mimecast.archive_search_logs-*", + "title": "Archive Search Mimecast Logs" + }, + { + "dataset": "threat_intel_malware_grid", + "index_pattern": "logs-mimecast.threat_intel_malware_grid-*", + "title": "Threat Intel Feed - Malware Grid" + }, + { + "dataset": "threat_intel_malware_customer", + "index_pattern": "logs-mimecast.threat_intel_malware_customer-*", + "title": "Threat Intel Feed - Malware Customer" + } + ], + "elser_embedding": "Mimecast - Collect logs from Mimecast with Elastic Agent. - DLP Mimecast Logs TTP URL Logs SIEM Mimecast Logs Mimecast Message Release TTP Impersonation Mimecast Logs Audit Events Mimecast Logs TTP Attachment Logs Archive Search Mimecast Logs Threat Intel Feed - Malware Grid Threat Intel Feed - Malware Customer" + }, + { + "title": "Oracle WebLogic", + "id": "oracle_weblogic", + "description": "Collect logs and metrics from Oracle WebLogic with Elastic Agent.", + "data_streams": [ + { + "dataset": "managed_server", + "index_pattern": "logs-oracle_weblogic.managed_server-*", + "title": "Managed Server logs" + }, + { + "dataset": "access", + "index_pattern": "logs-oracle_weblogic.access-*", + "title": "Access logs" + }, + { + "dataset": "threadpool", + "index_pattern": "logs-oracle_weblogic.threadpool-*", + "title": "Collect Oracle WebLogic ThreadPool metrics" + }, + { + "dataset": "deployed_application", + "index_pattern": "logs-oracle_weblogic.deployed_application-*", + "title": "Collect Oracle WebLogic Deployed Application metrics" + }, + { + "dataset": "admin_server", + "index_pattern": "logs-oracle_weblogic.admin_server-*", + "title": "Admin Server logs" + }, + { + "dataset": "domain", + "index_pattern": "logs-oracle_weblogic.domain-*", + "title": "Domain logs" + } + ], + "elser_embedding": "Oracle WebLogic - Collect logs and metrics from Oracle WebLogic with Elastic Agent. - Managed Server logs Access logs Collect Oracle WebLogic ThreadPool metrics Collect Oracle WebLogic Deployed Application metrics Admin Server logs Domain logs" + }, + { + "title": "System Audit", + "id": "system_audit", + "description": "Collect various logs & metrics from System Audit modules with Elastic Agent.", + "data_streams": [ + { + "dataset": "package", + "index_pattern": "logs-system_audit.package-*", + "title": "System Audit - [Package]" + } + ], + "elser_embedding": "System Audit - Collect various logs & metrics from System Audit modules with Elastic Agent. - System Audit - [Package]" + }, + { + "title": "Salesforce", + "id": "salesforce", + "description": "Collect logs from Salesforce instances using the Elastic Agent. This integration enables monitoring and analysis of various Salesforce logs, including Login, Logout, Setup Audit Trail, and Apex execution logs. Gain insights into user activity, security events, and application performance.\n", + "data_streams": [ + { + "dataset": "setupaudittrail", + "index_pattern": "logs-salesforce.setupaudittrail-*", + "title": "Salesforce setupaudittrail logs" + }, + { + "dataset": "login", + "index_pattern": "logs-salesforce.login-*", + "title": "Salesforce login logs" + }, + { + "dataset": "logout", + "index_pattern": "logs-salesforce.logout-*", + "title": "Salesforce logout logs" + }, + { + "dataset": "apex", + "index_pattern": "logs-salesforce.apex-*", + "title": "Salesforce Apex logs" + } + ], + "elser_embedding": "Salesforce - Collect logs from Salesforce instances using the Elastic Agent. This integration enables monitoring and analysis of various Salesforce logs, including Login, Logout, Setup Audit Trail, and Apex execution logs. Gain insights into user activity, security events, and application performance.\n - Salesforce setupaudittrail logs Salesforce login logs Salesforce logout logs Salesforce Apex logs" + }, + { + "title": "Azure Application Insights Metrics Overview", + "id": "azure_application_insights", + "description": "Collect application insights metrics from Azure Monitor with Elastic Agent.", + "data_streams": [ + { + "dataset": "app_insights", + "index_pattern": "logs-azure_application_insights.app_insights-*", + "title": "Azure Application Insights" + }, + { + "dataset": "app_state", + "index_pattern": "logs-azure_application_insights.app_state-*", + "title": "Azure Application State" + } + ], + "elser_embedding": "Azure Application Insights Metrics Overview - Collect application insights metrics from Azure Monitor with Elastic Agent. - Azure Application Insights Azure Application State" + }, + { + "title": "ForgeRock", + "id": "forgerock", + "description": "Collect audit logs from ForgeRock with Elastic Agent.", + "data_streams": [ + { + "dataset": "idm_sync", + "index_pattern": "logs-forgerock.idm_sync-*", + "title": "IDM-Sync audit logs" + }, + { + "dataset": "idm_core", + "index_pattern": "logs-forgerock.idm_core-*", + "title": "IDM-Core debug logs" + }, + { + "dataset": "am_access", + "index_pattern": "logs-forgerock.am_access-*", + "title": "AM-Access audit logs" + }, + { + "dataset": "idm_activity", + "index_pattern": "logs-forgerock.idm_activity-*", + "title": "IDM-Activity audit logs" + }, + { + "dataset": "idm_config", + "index_pattern": "logs-forgerock.idm_config-*", + "title": "IDM-Config audit logs" + }, + { + "dataset": "am_config", + "index_pattern": "logs-forgerock.am_config-*", + "title": "AM-Config audit logs" + }, + { + "dataset": "am_activity", + "index_pattern": "logs-forgerock.am_activity-*", + "title": "AM-Activity audit logs" + }, + { + "dataset": "am_authentication", + "index_pattern": "logs-forgerock.am_authentication-*", + "title": "AM-Authentication audit logs" + }, + { + "dataset": "idm_authentication", + "index_pattern": "logs-forgerock.idm_authentication-*", + "title": "IDM-Authentication audit logs" + }, + { + "dataset": "idm_access", + "index_pattern": "logs-forgerock.idm_access-*", + "title": "IDM-Access audit logs" + }, + { + "dataset": "am_core", + "index_pattern": "logs-forgerock.am_core-*", + "title": "AM-Core debug logs" + } + ], + "elser_embedding": "ForgeRock - Collect audit logs from ForgeRock with Elastic Agent. - IDM-Sync audit logs IDM-Core debug logs AM-Access audit logs IDM-Activity audit logs IDM-Config audit logs AM-Config audit logs AM-Activity audit logs AM-Authentication audit logs IDM-Authentication audit logs IDM-Access audit logs AM-Core debug logs" + }, + { + "title": "Tenable.sc", + "id": "tenable_sc", + "description": "Collect logs from Tenable.sc with Elastic Agent.\n", + "data_streams": [ + { + "dataset": "plugin", + "index_pattern": "logs-tenable_sc.plugin-*", + "title": "Tenable.sc plugin logs" + }, + { + "dataset": "vulnerability", + "index_pattern": "logs-tenable_sc.vulnerability-*", + "title": "Tenable.sc vulnerability logs" + }, + { + "dataset": "asset", + "index_pattern": "logs-tenable_sc.asset-*", + "title": "Tenable.sc asset logs" + } + ], + "elser_embedding": "Tenable.sc - Collect logs from Tenable.sc with Elastic Agent.\n - Tenable.sc plugin logs Tenable.sc vulnerability logs Tenable.sc asset logs" + }, + { + "title": "Cisco IOS", + "id": "cisco_ios", + "description": "Collect logs from Cisco IOS with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_ios.log-*", + "title": "Cisco IOS logs" + } + ], + "elser_embedding": "Cisco IOS - Collect logs from Cisco IOS with Elastic Agent. - Cisco IOS logs" + }, + { + "title": "ZooKeeper Metrics", + "id": "zookeeper", + "description": "Collect metrics from ZooKeeper service with Elastic Agent.", + "data_streams": [ + { + "dataset": "connection", + "index_pattern": "logs-zookeeper.connection-*", + "title": "ZooKeeper connection metrics" + }, + { + "dataset": "mntr", + "index_pattern": "logs-zookeeper.mntr-*", + "title": "ZooKeeper mntr metrics" + }, + { + "dataset": "server", + "index_pattern": "logs-zookeeper.server-*", + "title": "ZooKeeper server metrics" + } + ], + "elser_embedding": "ZooKeeper Metrics - Collect metrics from ZooKeeper service with Elastic Agent. - ZooKeeper connection metrics ZooKeeper mntr metrics ZooKeeper server metrics" + }, + { + "title": "Palo Alto Next-Gen Firewall", + "id": "panw", + "description": "Collect logs from Palo Alto next-gen firewalls with Elastic Agent.", + "data_streams": [ + { + "dataset": "panos", + "index_pattern": "logs-panw.panos-*", + "title": "Palo Alto Networks PAN-OS firewall logs" + } + ], + "elser_embedding": "Palo Alto Next-Gen Firewall - Collect logs from Palo Alto next-gen firewalls with Elastic Agent. - Palo Alto Networks PAN-OS firewall logs" + }, + { + "title": "Hadoop", + "id": "hadoop", + "description": "Collect metrics from Apache Hadoop with Elastic Agent.", + "data_streams": [ + { + "dataset": "namenode", + "index_pattern": "logs-hadoop.namenode-*", + "title": "NameNode Metrics" + }, + { + "dataset": "datanode", + "index_pattern": "logs-hadoop.datanode-*", + "title": "DataNode metrics" + }, + { + "dataset": "node_manager", + "index_pattern": "logs-hadoop.node_manager-*", + "title": "Node Manager metrics" + }, + { + "dataset": "application", + "index_pattern": "logs-hadoop.application-*", + "title": "Application metrics" + }, + { + "dataset": "cluster", + "index_pattern": "logs-hadoop.cluster-*", + "title": "Cluster metrics" + } + ], + "elser_embedding": "Hadoop - Collect metrics from Apache Hadoop with Elastic Agent. - NameNode Metrics DataNode metrics Node Manager metrics Application metrics Cluster metrics" + }, + { + "title": "InfluxDb", + "id": "influxdb", + "description": "Collect metrics from Influxdb database", + "data_streams": [ + { + "dataset": "advstatus", + "index_pattern": "logs-influxdb.advstatus-*", + "title": "InfluxDB database advanced status metrics" + }, + { + "dataset": "status", + "index_pattern": "logs-influxdb.status-*", + "title": "InfluxDB database status metrics" + } + ], + "elser_embedding": "InfluxDb - Collect metrics from Influxdb database - InfluxDB database advanced status metrics InfluxDB database status metrics" + }, + { + "title": "Sophos", + "id": "sophos", + "description": "Collect logs from Sophos with Elastic Agent.", + "data_streams": [ + { + "dataset": "xg", + "index_pattern": "logs-sophos.xg-*", + "title": "Sophos XG logs" + }, + { + "dataset": "utm", + "index_pattern": "logs-sophos.utm-*", + "title": "Sophos UTM logs" + } + ], + "elser_embedding": "Sophos - Collect logs from Sophos with Elastic Agent. - Sophos XG logs Sophos UTM logs" + }, + { + "title": "Menlo Security", + "id": "menlo", + "description": "Collect logs from Menlo Security products with Elastic Agent", + "data_streams": [ + { + "dataset": "dlp", + "index_pattern": "logs-menlo.dlp-*", + "title": "Collect Menlo DLP from Menlo Security API" + }, + { + "dataset": "web", + "index_pattern": "logs-menlo.web-*", + "title": "Collect Menlo Web from Menlo Security API" + } + ], + "elser_embedding": "Menlo Security - Collect logs from Menlo Security products with Elastic Agent - Collect Menlo DLP from Menlo Security API Collect Menlo Web from Menlo Security API" + }, + { + "title": "Barracuda Web Application Firewall", + "id": "barracuda", + "description": "Collect logs from Barracuda Web Application Firewall with Elastic Agent.", + "data_streams": [ + { + "dataset": "waf", + "index_pattern": "logs-barracuda.waf-*", + "title": "Barracuda WAF Logs" + } + ], + "elser_embedding": "Barracuda Web Application Firewall - Collect logs from Barracuda Web Application Firewall with Elastic Agent. - Barracuda WAF Logs" + }, + { + "title": "FireEye Network Security", + "id": "fireeye", + "description": "Collect logs from FireEye NX with Elastic Agent.", + "data_streams": [ + { + "dataset": "nx", + "index_pattern": "logs-fireeye.nx-*", + "title": "Fireeye NX" + } + ], + "elser_embedding": "FireEye Network Security - Collect logs from FireEye NX with Elastic Agent. - Fireeye NX" + }, + { + "title": "Tines", + "id": "tines", + "description": "Tines Logs & Time Saved Reports", + "data_streams": [ + { + "dataset": "time_saved", + "index_pattern": "logs-tines.time_saved-*", + "title": "Tines Time Saved Reports" + }, + { + "dataset": "audit_logs", + "index_pattern": "logs-tines.audit_logs-*", + "title": "Tines Audit Logs" + } + ], + "elser_embedding": "Tines - Tines Logs & Time Saved Reports - Tines Time Saved Reports Tines Audit Logs" + }, + { + "title": "Cisco Meraki Metrics", + "id": "cisco_meraki_metrics", + "description": "Collect metrics from Cisco Meraki with Elastic Agent.", + "data_streams": [ + { + "dataset": "device_health", + "index_pattern": "logs-cisco_meraki_metrics.device_health-*", + "title": "Cisco Meraki Device Health Metrics" + } + ], + "elser_embedding": "Cisco Meraki Metrics - Collect metrics from Cisco Meraki with Elastic Agent. - Cisco Meraki Device Health Metrics" + }, + { + "title": "VMware vSphere", + "id": "vsphere", + "description": "This Elastic integration collects metrics and logs from vSphere/vCenter servers", + "data_streams": [ + { + "dataset": "network", + "index_pattern": "logs-vsphere.network-*", + "title": "vSphere network metrics" + }, + { + "dataset": "resourcepool", + "index_pattern": "logs-vsphere.resourcepool-*", + "title": "vSphere resourcepool metrics" + }, + { + "dataset": "datastore", + "index_pattern": "logs-vsphere.datastore-*", + "title": "vSphere datastore metrics" + }, + { + "dataset": "virtualmachine", + "index_pattern": "logs-vsphere.virtualmachine-*", + "title": "vSphere virtual machine metrics" + }, + { + "dataset": "host", + "index_pattern": "logs-vsphere.host-*", + "title": "vSphere host metrics" + }, + { + "dataset": "datastorecluster", + "index_pattern": "logs-vsphere.datastorecluster-*", + "title": "vSphere DatastoreCluster metrics" + }, + { + "dataset": "log", + "index_pattern": "logs-vsphere.log-*", + "title": "vSphere Logs" + }, + { + "dataset": "cluster", + "index_pattern": "logs-vsphere.cluster-*", + "title": "vSphere cluster metrics" + } + ], + "elser_embedding": "VMware vSphere - This Elastic integration collects metrics and logs from vSphere/vCenter servers - vSphere network metrics vSphere resourcepool metrics vSphere datastore metrics vSphere virtual machine metrics vSphere host metrics vSphere DatastoreCluster metrics vSphere Logs vSphere cluster metrics" + }, + { + "title": "Platform Observability", + "id": "platform_observability", + "description": "Collect stack component logs with Elastic Agent", + "data_streams": [ + { + "dataset": "kibana_audit", + "index_pattern": "logs-platform_observability.kibana_audit-*", + "title": "Platform Observability Kibana audit logs" + }, + { + "dataset": "kibana_log", + "index_pattern": "logs-platform_observability.kibana_log-*", + "title": "Platform Observability Kibana logs" + } + ], + "elser_embedding": "Platform Observability - Collect stack component logs with Elastic Agent - Platform Observability Kibana audit logs Platform Observability Kibana logs" + }, + { + "title": "System", + "id": "system", + "description": "Collect system logs and metrics from your servers with Elastic Agent.", + "data_streams": [ + { + "dataset": "memory", + "index_pattern": "logs-system.memory-*", + "title": "System memory metrics" + }, + { + "dataset": "network", + "index_pattern": "logs-system.network-*", + "title": "System network metrics" + }, + { + "dataset": "uptime", + "index_pattern": "logs-system.uptime-*", + "title": "System uptime metrics" + }, + { + "dataset": "socket_summary", + "index_pattern": "logs-system.socket_summary-*", + "title": "System socket_summary metrics" + }, + { + "dataset": "auth", + "index_pattern": "logs-system.auth-*", + "title": "System auth logs" + }, + { + "dataset": "process", + "index_pattern": "logs-system.process-*", + "title": "System process metrics" + }, + { + "dataset": "load", + "index_pattern": "logs-system.load-*", + "title": "System load metrics" + }, + { + "dataset": "application", + "index_pattern": "logs-system.application-*", + "title": "Windows Application Events" + }, + { + "dataset": "diskio", + "index_pattern": "logs-system.diskio-*", + "title": "System diskio metrics" + }, + { + "dataset": "syslog", + "index_pattern": "logs-system.syslog-*", + "title": "System syslog logs" + }, + { + "dataset": "filesystem", + "index_pattern": "logs-system.filesystem-*", + "title": "System filesystem metrics" + }, + { + "dataset": "fsstat", + "index_pattern": "logs-system.fsstat-*", + "title": "System fsstat metrics" + }, + { + "dataset": "core", + "index_pattern": "logs-system.core-*", + "title": "System core metrics" + }, + { + "dataset": "cpu", + "index_pattern": "logs-system.cpu-*", + "title": "System cpu metrics" + }, + { + "dataset": "process_summary", + "index_pattern": "logs-system.process_summary-*", + "title": "System process_summary metrics" + }, + { + "dataset": "system", + "index_pattern": "logs-system.system-*", + "title": "Windows System Events" + }, + { + "dataset": "security", + "index_pattern": "logs-system.security-*", + "title": "Security logs" + } + ], + "elser_embedding": "System - Collect system logs and metrics from your servers with Elastic Agent. - System memory metrics System network metrics System uptime metrics System socket_summary metrics System auth logs System process metrics System load metrics Windows Application Events System diskio metrics System syslog logs System filesystem metrics System fsstat metrics System core metrics System cpu metrics System process_summary metrics Windows System Events Security logs" + }, + { + "title": "Airflow", + "id": "airflow", + "description": "Airflow Integration.", + "data_streams": [ + { + "dataset": "statsd", + "index_pattern": "logs-airflow.statsd-*", + "title": "Airflow metrics" + } + ], + "elser_embedding": "Airflow - Airflow Integration. - Airflow metrics" + }, + { + "title": "Custom Google Pub/Sub Logs", + "id": "gcp_pubsub", + "description": "Collect Logs from Google Pub/Sub topics", + "data_streams": [], + "elser_embedding": "Custom Google Pub/Sub Logs - Collect Logs from Google Pub/Sub topics - " + }, + { + "title": "Beat", + "id": "beat", + "description": "Beat Integration", + "data_streams": [], + "elser_embedding": "Beat - Beat Integration - " + }, + { + "title": "Cyberark Privileged Threat Analytics", + "id": "cyberark_pta", + "description": "Collect security logs from Cyberark PTA integration.", + "data_streams": [ + { + "dataset": "events", + "index_pattern": "logs-cyberark_pta.events-*", + "title": "CyberArk PTA logs" + } + ], + "elser_embedding": "Cyberark Privileged Threat Analytics - Collect security logs from Cyberark PTA integration. - CyberArk PTA logs" + }, + { + "title": "Trellix ePO Cloud", + "id": "trellix_epo_cloud", + "description": "Collect logs from Trellix ePO Cloud with Elastic Agent.", + "data_streams": [ + { + "dataset": "device", + "index_pattern": "logs-trellix_epo_cloud.device-*", + "title": "Collect Device logs from Trellix ePO Cloud." + }, + { + "dataset": "group", + "index_pattern": "logs-trellix_epo_cloud.group-*", + "title": "Collect Group logs from Trellix ePO Cloud." + }, + { + "dataset": "event", + "index_pattern": "logs-trellix_epo_cloud.event-*", + "title": "Collect Event logs from Trellix ePO Cloud." + } + ], + "elser_embedding": "Trellix ePO Cloud - Collect logs from Trellix ePO Cloud with Elastic Agent. - Collect Device logs from Trellix ePO Cloud. Collect Group logs from Trellix ePO Cloud. Collect Event logs from Trellix ePO Cloud." + }, + { + "title": "Vectra Detect", + "id": "vectra_detect", + "description": "Collect logs from Vectra Detect with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-vectra_detect.log-*", + "title": "Collect logs from Vectra Detect" + } + ], + "elser_embedding": "Vectra Detect - Collect logs from Vectra Detect with Elastic Agent. - Collect logs from Vectra Detect" + }, + { + "title": "Atlassian Confluence", + "id": "atlassian_confluence", + "description": "Collect logs from Atlassian Confluence with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-atlassian_confluence.audit-*", + "title": "Confluence Audit Logs" + } + ], + "elser_embedding": "Atlassian Confluence - Collect logs from Atlassian Confluence with Elastic Agent. - Confluence Audit Logs" + }, + { + "title": "QNAP NAS", + "id": "qnap_nas", + "description": "Collect logs from QNAP NAS devices with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-qnap_nas.log-*", + "title": "QNAP NAS logs" + } + ], + "elser_embedding": "QNAP NAS - Collect logs from QNAP NAS devices with Elastic Agent. - QNAP NAS logs" + }, + { + "title": "Memcached", + "id": "memcached", + "description": "Memcached Integration", + "data_streams": [ + { + "dataset": "stats", + "index_pattern": "logs-memcached.stats-*", + "title": "Memcached stats metrics" + } + ], + "elser_embedding": "Memcached - Memcached Integration - Memcached stats metrics" + }, + { + "title": "Azure Resource Metrics", + "id": "azure_metrics", + "description": "Collect metrics from Azure resources with Elastic Agent.", + "data_streams": [ + { + "dataset": "container_service", + "index_pattern": "logs-azure_metrics.container_service-*", + "title": "Container Service" + }, + { + "dataset": "container_instance", + "index_pattern": "logs-azure_metrics.container_instance-*", + "title": "Container Instance" + }, + { + "dataset": "compute_vm", + "index_pattern": "logs-azure_metrics.compute_vm-*", + "title": "Compute VM" + }, + { + "dataset": "monitor", + "index_pattern": "logs-azure_metrics.monitor-*", + "title": "Monitor" + }, + { + "dataset": "storage_account", + "index_pattern": "logs-azure_metrics.storage_account-*", + "title": "Storage Account" + }, + { + "dataset": "compute_vm_scaleset", + "index_pattern": "logs-azure_metrics.compute_vm_scaleset-*", + "title": "Compute VM Scaleset" + }, + { + "dataset": "database_account", + "index_pattern": "logs-azure_metrics.database_account-*", + "title": "Database Account" + }, + { + "dataset": "container_registry", + "index_pattern": "logs-azure_metrics.container_registry-*", + "title": "Container Registry" + } + ], + "elser_embedding": "Azure Resource Metrics - Collect metrics from Azure resources with Elastic Agent. - Container Service Container Instance Compute VM Monitor Storage Account Compute VM Scaleset Database Account Container Registry" + }, + { + "title": "Elastic Connectors", + "id": "elastic_connectors", + "description": "Sync data from source to the Elasticsearch index.", + "data_streams": [], + "elser_embedding": "Elastic Connectors - Sync data from source to the Elasticsearch index. - " + }, + { + "title": "StatsD Input", + "id": "statsd_input", + "description": "StatsD Input Package", + "data_streams": [], + "elser_embedding": "StatsD Input - StatsD Input Package - " + }, + { + "title": "Cloudflare", + "id": "cloudflare", + "description": "Collect logs from Cloudflare with Elastic Agent.", + "data_streams": [ + { + "dataset": "logpull", + "index_pattern": "logs-cloudflare.logpull-*", + "title": "Cloudflare Logpull" + }, + { + "dataset": "audit", + "index_pattern": "logs-cloudflare.audit-*", + "title": "Cloudflare Audit Logs" + } + ], + "elser_embedding": "Cloudflare - Collect logs from Cloudflare with Elastic Agent. - Cloudflare Logpull Cloudflare Audit Logs" + }, + { + "title": "Cribl", + "id": "cribl", + "description": "Stream logs from Cribl into Elastic.", + "data_streams": [ + { + "dataset": "logs", + "index_pattern": "logs-cribl.logs-*", + "title": "Logs" + } + ], + "elser_embedding": "Cribl - Stream logs from Cribl into Elastic. - Logs" + }, + { + "title": "PHP-FPM", + "id": "php_fpm", + "description": "This Elastic integration collects metrics from PHP-FPM.", + "data_streams": [ + { + "dataset": "process", + "index_pattern": "logs-php_fpm.process-*", + "title": "Process metrics" + }, + { + "dataset": "pool", + "index_pattern": "logs-php_fpm.pool-*", + "title": "Pool metrics" + } + ], + "elser_embedding": "PHP-FPM - This Elastic integration collects metrics from PHP-FPM. - Process metrics Pool metrics" + }, + { + "title": "Azure Logs", + "id": "azure", + "description": "This Elastic integration collects logs from Azure", + "data_streams": [ + { + "dataset": "platformlogs", + "index_pattern": "logs-azure.platformlogs-*", + "title": "Azure Platform Logs" + }, + { + "dataset": "auditlogs", + "index_pattern": "logs-azure.auditlogs-*", + "title": "Azure Audit Logs" + }, + { + "dataset": "springcloudlogs", + "index_pattern": "logs-azure.springcloudlogs-*", + "title": "Azure Spring Apps Logs" + }, + { + "dataset": "signinlogs", + "index_pattern": "logs-azure.signinlogs-*", + "title": "Azure Signin Logs" + }, + { + "dataset": "firewall_logs", + "index_pattern": "logs-azure.firewall_logs-*", + "title": "Collect Network rule logs from Azure Firewall" + }, + { + "dataset": "graphactivitylogs", + "index_pattern": "logs-azure.graphactivitylogs-*", + "title": "Microsoft Graph Activity Logs" + }, + { + "dataset": "application_gateway", + "index_pattern": "logs-azure.application_gateway-*", + "title": "Azure Application Gateway logs" + }, + { + "dataset": "eventhub", + "index_pattern": "logs-azure.eventhub-*", + "title": "Azure Event Hub Input" + }, + { + "dataset": "provisioning", + "index_pattern": "logs-azure.provisioning-*", + "title": "Microsoft Entra ID Provisioning Logs" + }, + { + "dataset": "activitylogs", + "index_pattern": "logs-azure.activitylogs-*", + "title": "Azure Activity Logs" + }, + { + "dataset": "identity_protection", + "index_pattern": "logs-azure.identity_protection-*", + "title": "Microsoft Entra ID Identity Protection Logs" + } + ], + "elser_embedding": "Azure Logs - This Elastic integration collects logs from Azure - Azure Platform Logs Azure Audit Logs Azure Spring Apps Logs Azure Signin Logs Collect Network rule logs from Azure Firewall Microsoft Graph Activity Logs Azure Application Gateway logs Azure Event Hub Input Microsoft Entra ID Provisioning Logs Azure Activity Logs Microsoft Entra ID Identity Protection Logs" + }, + { + "title": "Palo Alto Networks Metrics", + "id": "panw_metrics", + "description": "Collect metrics from Palo Alto Networks with Elastic Agent.", + "data_streams": [ + { + "dataset": "vpn", + "index_pattern": "logs-panw_metrics.vpn-*", + "title": "Palo Alto Networks VPN metrics" + }, + { + "dataset": "interfaces", + "index_pattern": "logs-panw_metrics.interfaces-*", + "title": "Palo Alto Networks Interfaces metrics" + }, + { + "dataset": "system", + "index_pattern": "logs-panw_metrics.system-*", + "title": "Palo Alto Networks System metrics" + }, + { + "dataset": "routing", + "index_pattern": "logs-panw_metrics.routing-*", + "title": "Palo Alto Networks Routing metrics" + } + ], + "elser_embedding": "Palo Alto Networks Metrics - Collect metrics from Palo Alto Networks with Elastic Agent. - Palo Alto Networks VPN metrics Palo Alto Networks Interfaces metrics Palo Alto Networks System metrics Palo Alto Networks Routing metrics" + }, + { + "title": "Custom Threat Intelligence", + "id": "ti_custom", + "description": "Ingest threat intelligence data in STIX 2.1 format with Elastic Agent", + "data_streams": [ + { + "dataset": "indicator", + "index_pattern": "logs-ti_custom.indicator-*", + "title": "STIX 2.1 indicators" + } + ], + "elser_embedding": "Custom Threat Intelligence - Ingest threat intelligence data in STIX 2.1 format with Elastic Agent - STIX 2.1 indicators" + }, + { + "title": "Check Point Harmony Endpoint", + "id": "checkpoint_harmony_endpoint", + "description": "Collect logs from Check Point Harmony Endpoint", + "data_streams": [ + { + "dataset": "urlfiltering", + "index_pattern": "logs-checkpoint_harmony_endpoint.urlfiltering-*", + "title": "URL Filtering" + }, + { + "dataset": "forensics", + "index_pattern": "logs-checkpoint_harmony_endpoint.forensics-*", + "title": "Forensics" + }, + { + "dataset": "antibot", + "index_pattern": "logs-checkpoint_harmony_endpoint.antibot-*", + "title": "Anti-Bot" + }, + { + "dataset": "threatemulation", + "index_pattern": "logs-checkpoint_harmony_endpoint.threatemulation-*", + "title": "Threat Emulation" + }, + { + "dataset": "threatextraction", + "index_pattern": "logs-checkpoint_harmony_endpoint.threatextraction-*", + "title": "Threat Extraction" + }, + { + "dataset": "zerophishing", + "index_pattern": "logs-checkpoint_harmony_endpoint.zerophishing-*", + "title": "Zero Phishing" + }, + { + "dataset": "antimalware", + "index_pattern": "logs-checkpoint_harmony_endpoint.antimalware-*", + "title": "Anti-Malware" + } + ], + "elser_embedding": "Check Point Harmony Endpoint - Collect logs from Check Point Harmony Endpoint - URL Filtering Forensics Anti-Bot Threat Emulation Threat Extraction Zero Phishing Anti-Malware" + }, + { + "title": "Thycotic Secret Server", + "id": "thycotic_ss", + "description": "Thycotic Secret Server logs", + "data_streams": [ + { + "dataset": "logs", + "index_pattern": "logs-thycotic_ss.logs-*", + "title": "Thycotic Secret Server Logs" + } + ], + "elser_embedding": "Thycotic Secret Server - Thycotic Secret Server logs - Thycotic Secret Server Logs" + }, + { + "title": "Custom HTTP Endpoint Logs", + "id": "http_endpoint", + "description": "Collect JSON data from listening HTTP port with Elastic Agent.", + "data_streams": [], + "elser_embedding": "Custom HTTP Endpoint Logs - Collect JSON data from listening HTTP port with Elastic Agent. - " + }, + { + "title": "Atlassian Bitbucket", + "id": "atlassian_bitbucket", + "description": "Collect logs from Atlassian Bitbucket with Elastic Agent.", + "data_streams": [ + { + "dataset": "audit", + "index_pattern": "logs-atlassian_bitbucket.audit-*", + "title": "Bitbucket Audit Logs" + } + ], + "elser_embedding": "Atlassian Bitbucket - Collect logs from Atlassian Bitbucket with Elastic Agent. - Bitbucket Audit Logs" + }, + { + "title": "TYCHON Agentless", + "id": "tychon", + "description": "Collect complete master endpoint datasets including vulnerability and STIG to comply with DISA endpoint requirements and C2C without adding services to your endpoints.", + "data_streams": [ + { + "dataset": "systemcerts", + "index_pattern": "logs-tychon.systemcerts-*", + "title": "System Certificates" + }, + { + "dataset": "stig", + "index_pattern": "logs-tychon.stig-*", + "title": "Endpoint STIG Results" + }, + { + "dataset": "softwareinventory", + "index_pattern": "logs-tychon.softwareinventory-*", + "title": "Endpoint Software Inventory Info" + }, + { + "dataset": "coams", + "index_pattern": "logs-tychon.coams-*", + "title": "Endpoint Operational Attributes (Requires DATT)" + }, + { + "dataset": "harddrive", + "index_pattern": "logs-tychon.harddrive-*", + "title": "Endpoint Harddrive Info" + }, + { + "dataset": "host", + "index_pattern": "logs-tychon.host-*", + "title": "Host Operating System Info" + }, + { + "dataset": "cve", + "index_pattern": "logs-tychon.cve-*", + "title": "Vulnerabilites" + }, + { + "dataset": "externaldevicecontrol", + "index_pattern": "logs-tychon.externaldevicecontrol-*", + "title": "Endpoint External Device Control" + }, + { + "dataset": "cmrs", + "index_pattern": "logs-tychon.cmrs-*", + "title": "DISA Continuous Monitoring and Risk Scoring Data" + }, + { + "dataset": "arp", + "index_pattern": "logs-tychon.arp-*", + "title": "Endpoint Arp Table Information" + }, + { + "dataset": "ciphers", + "index_pattern": "logs-tychon.ciphers-*", + "title": "Certificate Ciphers" + }, + { + "dataset": "features", + "index_pattern": "logs-tychon.features-*", + "title": "Features Info" + }, + { + "dataset": "epp", + "index_pattern": "logs-tychon.epp-*", + "title": "Endpoint Protection Platform Info" + }, + { + "dataset": "cpu", + "index_pattern": "logs-tychon.cpu-*", + "title": "Endpoint CPU Info" + }, + { + "dataset": "browser", + "index_pattern": "logs-tychon.browser-*", + "title": "Endpoint Browser Configurations" + }, + { + "dataset": "exposedservice", + "index_pattern": "logs-tychon.exposedservice-*", + "title": "Endpoint Exposed Services" + }, + { + "dataset": "volume", + "index_pattern": "logs-tychon.volume-*", + "title": "Endpoint Volumes Info" + }, + { + "dataset": "hardware", + "index_pattern": "logs-tychon.hardware-*", + "title": "Hardware Info" + }, + { + "dataset": "networkadapter", + "index_pattern": "logs-tychon.networkadapter-*", + "title": "Network Adapters" + } + ], + "elser_embedding": "TYCHON Agentless - Collect complete master endpoint datasets including vulnerability and STIG to comply with DISA endpoint requirements and C2C without adding services to your endpoints. - System Certificates Endpoint STIG Results Endpoint Software Inventory Info Endpoint Operational Attributes (Requires DATT) Endpoint Harddrive Info Host Operating System Info Vulnerabilites Endpoint External Device Control DISA Continuous Monitoring and Risk Scoring Data Endpoint Arp Table Information Certificate Ciphers Features Info Endpoint Protection Platform Info Endpoint CPU Info Endpoint Browser Configurations Endpoint Exposed Services Endpoint Volumes Info Hardware Info Network Adapters" + }, + { + "title": "Proofpoint On Demand", + "id": "proofpoint_on_demand", + "description": "Collect logs from Proofpoint On Demand with Elastic Agent.", + "data_streams": [ + { + "dataset": "message", + "index_pattern": "logs-proofpoint_on_demand.message-*", + "title": "Proofpoint On Demand Message logs" + }, + { + "dataset": "audit", + "index_pattern": "logs-proofpoint_on_demand.audit-*", + "title": "Proofpoint On Demand Audit logs" + }, + { + "dataset": "mail", + "index_pattern": "logs-proofpoint_on_demand.mail-*", + "title": "Proofpoint On Demand Mail logs" + } + ], + "elser_embedding": "Proofpoint On Demand - Collect logs from Proofpoint On Demand with Elastic Agent. - Proofpoint On Demand Message logs Proofpoint On Demand Audit logs Proofpoint On Demand Mail logs" + }, + { + "title": "Cisco ASA", + "id": "cisco_asa", + "description": "Collect logs from Cisco ASA with Elastic Agent.", + "data_streams": [ + { + "dataset": "log", + "index_pattern": "logs-cisco_asa.log-*", + "title": "Cisco ASA logs" + } + ], + "elser_embedding": "Cisco ASA - Collect logs from Cisco ASA with Elastic Agent. - Cisco ASA logs" + }, + { + "title": "Amazon Security Lake", + "id": "amazon_security_lake", + "description": "Collect logs from Amazon Security Lake with Elastic Agent.", + "data_streams": [ + { + "dataset": "network_activity", + "index_pattern": "logs-amazon_security_lake.network_activity-*", + "title": "Amazon Security Lake Network Activity Events" + }, + { + "dataset": "application_activity", + "index_pattern": "logs-amazon_security_lake.application_activity-*", + "title": "Amazon Security Lake Application Activity Events" + }, + { + "dataset": "discovery", + "index_pattern": "logs-amazon_security_lake.discovery-*", + "title": "Amazon Security Lake Discovery Events" + }, + { + "dataset": "findings", + "index_pattern": "logs-amazon_security_lake.findings-*", + "title": "Amazon Security Lake Findings Events" + }, + { + "dataset": "system_activity", + "index_pattern": "logs-amazon_security_lake.system_activity-*", + "title": "Amazon Security Lake System Activity Events" + }, + { + "dataset": "event", + "index_pattern": "logs-amazon_security_lake.event-*", + "title": "Collect Amazon Security Lake Events" + }, + { + "dataset": "iam", + "index_pattern": "logs-amazon_security_lake.iam-*", + "title": "Amazon Security Lake Identity and Access Management Events" + } + ], + "elser_embedding": "Amazon Security Lake - Collect logs from Amazon Security Lake with Elastic Agent. - Amazon Security Lake Network Activity Events Amazon Security Lake Application Activity Events Amazon Security Lake Discovery Events Amazon Security Lake Findings Events Amazon Security Lake System Activity Events Collect Amazon Security Lake Events Amazon Security Lake Identity and Access Management Events" + }, + { + "title": "Azure App Service", + "id": "azure_app_service", + "description": "Collect logs from Azure App Service with Elastic Agent.", + "data_streams": [ + { + "dataset": "app_service_logs", + "index_pattern": "logs-azure_app_service.app_service_logs-*", + "title": "Collect App Service logs from Azure" + } + ], + "elser_embedding": "Azure App Service - Collect logs from Azure App Service with Elastic Agent. - Collect App Service logs from Azure" + }, + { + "title": "ESET PROTECT", + "id": "eset_protect", + "description": "Collect logs from ESET PROTECT with Elastic Agent.", + "data_streams": [ + { + "dataset": "device_task", + "index_pattern": "logs-eset_protect.device_task-*", + "title": "Collect Device Task logs from ESET PROTECT" + }, + { + "dataset": "detection", + "index_pattern": "logs-eset_protect.detection-*", + "title": "Collect Detection logs from ESET PROTECT" + }, + { + "dataset": "event", + "index_pattern": "logs-eset_protect.event-*", + "title": "Collect Event logs from ESET PROTECT" + } + ], + "elser_embedding": "ESET PROTECT - Collect logs from ESET PROTECT with Elastic Agent. - Collect Device Task logs from ESET PROTECT Collect Detection logs from ESET PROTECT Collect Event logs from ESET PROTECT" + }, + { + "id": "endpoint", + "title": "Elastic Defend", + "description": "windows linux osx dns network process suspicious user registry host host-based endpoint analysis commandline cli command exfiltration ransomware detection system os operating traffic prevention file user modification integrity obfuscation powershell anomaly edr xdr", + "data_streams": [ + { + "dataset": "endpoint.events.api", + "title": "Endpoint API Events", + "index_pattern": "logs-endpoint.events.api-*" + }, + { + "dataset": "endpoint.events.file", + "title": "Endpoint File Events", + "index_pattern": "logs-endpoint.events.file-*" + }, + { + "dataset": "endpoint.events.library", + "title": "Endpoint Library and Driver Events", + "index_pattern": "logs-endpoint.events.library-*" + }, + { + "dataset": "endpoint.events.network", + "title": "Endpoint Network Events", + "index_pattern": "logs-endpoint.events.network-*" + }, + { + "dataset": "endpoint.events.process", + "title": "Endpoint Process Events", + "index_pattern": "logs-endpoint.events.process-*" + }, + { + "dataset": "endpoint.events.registry", + "title": "Endpoint Registry Events", + "index_pattern": "logs-endpoint.events.registry-*" + }, + { + "dataset": "endpoint.events.security", + "title": "Endpoint Security Events", + "index_pattern": "logs-endpoint.events.security-*" + } + ], + "elser_embedding": "Elastic Defend - windows linux osx dns network process suspicious user registry host host-based endpoint analysis commandline cli command exfiltration ransomware detection system os operating traffic prevention file user modification integrity obfuscation powershell anomaly edr xdr - Endpoint API Events Endpoint File Events Endpoint Library and Driver Events Endpoint Network Events Endpoint Process Events Endpoint Registry Events Endpoint Security Events" + } +] \ No newline at end of file diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_base_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_base_client.ts index 4f0b65e063b7..14825326eee0 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_base_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_base_client.ts @@ -5,9 +5,9 @@ * 2.0. */ +import type { SearchHit, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import assert from 'assert'; -import type { SearchHit, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { Stored } from '../types'; import type { IndexNameProvider } from './rule_migrations_data_client'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_client.ts index 40f4aa6bf786..8960edd0cce2 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_client.ts @@ -6,8 +6,9 @@ */ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; -import { RuleMigrationsDataRulesClient } from './rule_migrations_data_rules_client'; +import { RuleMigrationsDataIntegrationsClient } from './rule_migrations_data_integrations_client'; import { RuleMigrationsDataResourcesClient } from './rule_migrations_data_resources_client'; +import { RuleMigrationsDataRulesClient } from './rule_migrations_data_rules_client'; import type { AdapterId } from './rule_migrations_data_service'; export type IndexNameProvider = () => Promise<string>; @@ -16,6 +17,7 @@ export type IndexNameProviders = Record<AdapterId, IndexNameProvider>; export class RuleMigrationsDataClient { public readonly rules: RuleMigrationsDataRulesClient; public readonly resources: RuleMigrationsDataResourcesClient; + public readonly integrations: RuleMigrationsDataIntegrationsClient; constructor( indexNameProviders: IndexNameProviders, @@ -35,5 +37,11 @@ export class RuleMigrationsDataClient { esClient, logger ); + this.integrations = new RuleMigrationsDataIntegrationsClient( + indexNameProviders.integrations, + username, + esClient, + logger + ); } } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_integrations_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_integrations_client.ts new file mode 100644 index 000000000000..3fdf1d11de36 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_integrations_client.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Integration } from '../types'; +import { RuleMigrationsDataBaseClient } from './rule_migrations_data_base_client'; + +/* This will be removed once the package registry changes is performed */ +import integrationsFile from './integrations_temp.json'; + +/* The minimum score required for a integration to be considered correct, might need to change this later */ +const MIN_SCORE = 40 as const; +/* The number of integrations the RAG will return, sorted by score */ +const RETURNED_INTEGRATIONS = 5 as const; + +/* This is a temp implementation to allow further development until https://github.com/elastic/package-registry/issues/1252 */ +const INTEGRATIONS = integrationsFile as Integration[]; +/* BULK_MAX_SIZE defines the number to break down the bulk operations by. + * The 500 number was chosen as a reasonable number to avoid large payloads. It can be adjusted if needed. + */ +export class RuleMigrationsDataIntegrationsClient extends RuleMigrationsDataBaseClient { + /** Indexes an array of integrations to be used with ELSER semantic search queries */ + async create(): Promise<void> { + const index = await this.getIndexName(); + await this.esClient + .bulk({ + refresh: 'wait_for', + operations: INTEGRATIONS.flatMap((integration) => [ + { update: { _index: index, _id: integration.id } }, + { + doc: { + title: integration.title, + description: integration.description, + data_streams: integration.data_streams, + elser_embedding: integration.elser_embedding, + '@timestamp': new Date().toISOString(), + }, + doc_as_upsert: true, + }, + ]), + }) + .catch((error) => { + this.logger.error(`Error indexing integration details for ELSER: ${error.message}`); + throw error; + }); + } + + /** Based on a LLM generated semantic string, returns the 5 best results with a score above 40 */ + async retrieveIntegrations(semanticString: string): Promise<Integration[]> { + const index = await this.getIndexName(); + const query = { + bool: { + should: [ + { + semantic: { + query: semanticString, + field: 'elser_embedding', + boost: 1.5, + }, + }, + { + multi_match: { + query: semanticString, + fields: ['title^2', 'description'], + boost: 3, + }, + }, + ], + }, + }; + const results = await this.esClient + .search<Integration>({ + index, + query, + size: RETURNED_INTEGRATIONS, + min_score: MIN_SCORE, + }) + .then(this.processResponseHits.bind(this)) + .catch((error) => { + this.logger.error(`Error querying integration details for ELSER: ${error.message}`); + throw error; + }); + + return results; + } +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts index a01d36e9a119..06e257b9862c 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts @@ -6,8 +6,10 @@ */ import type { + AggregationsAggregationContainer, AggregationsFilterAggregate, AggregationsMaxAggregate, + AggregationsMinAggregate, AggregationsStringTermsAggregate, AggregationsStringTermsBucket, QueryDslQueryContainer, @@ -30,7 +32,7 @@ export type UpdateRuleMigrationInput = { elastic_rule?: Partial<ElasticRule> } & 'id' | 'translation_result' | 'comments' >; export type RuleMigrationDataStats = Omit<RuleMigrationTaskStats, 'status'>; -export type RuleMigrationAllDataStats = Array<RuleMigrationDataStats & { migration_id: string }>; +export type RuleMigrationAllDataStats = RuleMigrationDataStats[]; /* BULK_MAX_SIZE defines the number to break down the bulk operations by. * The 500 number was chosen as a reasonable number to avoid large payloads. It can be adjusted if needed. @@ -217,6 +219,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient processing: { filter: { term: { status: SiemMigrationStatus.PROCESSING } } }, completed: { filter: { term: { status: SiemMigrationStatus.COMPLETED } } }, failed: { filter: { term: { status: SiemMigrationStatus.FAILED } } }, + createdAt: { min: { field: '@timestamp' } }, lastUpdatedAt: { max: { field: 'updated_at' } }, }; const result = await this.esClient @@ -226,30 +229,33 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient throw error; }); - const { pending, processing, completed, lastUpdatedAt, failed } = result.aggregations ?? {}; + const bucket = result.aggregations ?? {}; return { + id: migrationId, rules: { total: this.getTotalHits(result), - pending: (pending as AggregationsFilterAggregate)?.doc_count ?? 0, - processing: (processing as AggregationsFilterAggregate)?.doc_count ?? 0, - completed: (completed as AggregationsFilterAggregate)?.doc_count ?? 0, - failed: (failed as AggregationsFilterAggregate)?.doc_count ?? 0, + pending: (bucket.pending as AggregationsFilterAggregate)?.doc_count ?? 0, + processing: (bucket.processing as AggregationsFilterAggregate)?.doc_count ?? 0, + completed: (bucket.completed as AggregationsFilterAggregate)?.doc_count ?? 0, + failed: (bucket.failed as AggregationsFilterAggregate)?.doc_count ?? 0, }, - last_updated_at: (lastUpdatedAt as AggregationsMaxAggregate)?.value_as_string, + created_at: (bucket.createdAt as AggregationsMinAggregate)?.value_as_string ?? '', + last_updated_at: (bucket.lastUpdatedAt as AggregationsMaxAggregate)?.value_as_string ?? '', }; } - /** Retrieves the stats for all the rule migrations aggregated by migration id */ + /** Retrieves the stats for all the rule migrations aggregated by migration id, in creation order */ async getAllStats(): Promise<RuleMigrationAllDataStats> { const index = await this.getIndexName(); - const aggregations = { + const aggregations: { migrationIds: AggregationsAggregationContainer } = { migrationIds: { - terms: { field: 'migration_id' }, + terms: { field: 'migration_id', order: { createdAt: 'asc' } }, aggregations: { pending: { filter: { term: { status: SiemMigrationStatus.PENDING } } }, processing: { filter: { term: { status: SiemMigrationStatus.PROCESSING } } }, completed: { filter: { term: { status: SiemMigrationStatus.COMPLETED } } }, failed: { filter: { term: { status: SiemMigrationStatus.FAILED } } }, + createdAt: { min: { field: '@timestamp' } }, lastUpdatedAt: { max: { field: 'updated_at' } }, }, }, @@ -264,7 +270,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient const migrationsAgg = result.aggregations?.migrationIds as AggregationsStringTermsAggregate; const buckets = (migrationsAgg?.buckets as AggregationsStringTermsBucket[]) ?? []; return buckets.map((bucket) => ({ - migration_id: bucket.key, + id: bucket.key, rules: { total: bucket.doc_count, pending: bucket.pending?.doc_count ?? 0, @@ -272,6 +278,7 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient completed: bucket.completed?.doc_count ?? 0, failed: bucket.failed?.doc_count ?? 0, }, + created_at: bucket.createdAt?.value_as_string, last_updated_at: bucket.lastUpdatedAt?.value_as_string, })); } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.test.ts index e738bd85e2f1..f8cc0c3f1c07 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.test.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { INDEX_PATTERN, RuleMigrationsDataService } from './rule_migrations_data_service'; -import { Subject } from 'rxjs'; -import type { InstallParams } from '@kbn/index-adapter'; -import { IndexPatternAdapter } from '@kbn/index-adapter'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { loggerMock } from '@kbn/logging-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { securityServiceMock } from '@kbn/core-security-server-mocks'; +import type { InstallParams } from '@kbn/index-adapter'; +import { IndexPatternAdapter } from '@kbn/index-adapter'; +import { loggerMock } from '@kbn/logging-mocks'; +import { Subject } from 'rxjs'; import type { IndexNameProviders } from './rule_migrations_data_client'; +import { INDEX_PATTERN, RuleMigrationsDataService } from './rule_migrations_data_service'; jest.mock('@kbn/index-adapter'); @@ -42,7 +42,7 @@ describe('SiemRuleMigrationsDataService', () => { describe('constructor', () => { it('should create IndexPatternAdapters', () => { new RuleMigrationsDataService(logger, kibanaVersion); - expect(MockedIndexPatternAdapter).toHaveBeenCalledTimes(2); + expect(MockedIndexPatternAdapter).toHaveBeenCalledTimes(3); }); it('should create component templates', () => { @@ -54,6 +54,9 @@ describe('SiemRuleMigrationsDataService', () => { expect(indexPatternAdapter.setComponentTemplate).toHaveBeenCalledWith( expect.objectContaining({ name: `${INDEX_PATTERN}-resources` }) ); + expect(indexPatternAdapter.setComponentTemplate).toHaveBeenCalledWith( + expect.objectContaining({ name: `${INDEX_PATTERN}-integrations` }) + ); }); it('should create index templates', () => { @@ -65,6 +68,9 @@ describe('SiemRuleMigrationsDataService', () => { expect(indexPatternAdapter.setIndexTemplate).toHaveBeenCalledWith( expect.objectContaining({ name: `${INDEX_PATTERN}-resources` }) ); + expect(indexPatternAdapter.setIndexTemplate).toHaveBeenCalledWith( + expect.objectContaining({ name: `${INDEX_PATTERN}-integrations` }) + ); }); }); @@ -92,8 +98,11 @@ describe('SiemRuleMigrationsDataService', () => { logger: loggerMock.create(), pluginStop$: new Subject(), }; - const [rulesIndexPatternAdapter, resourcesIndexPatternAdapter] = - MockedIndexPatternAdapter.mock.instances; + const [ + rulesIndexPatternAdapter, + resourcesIndexPatternAdapter, + integrationsIndexPatternAdapter, + ] = MockedIndexPatternAdapter.mock.instances; (rulesIndexPatternAdapter.install as jest.Mock).mockResolvedValueOnce(undefined); await index.install(params); @@ -101,12 +110,16 @@ describe('SiemRuleMigrationsDataService', () => { await mockIndexNameProviders.rules(); await mockIndexNameProviders.resources(); + await mockIndexNameProviders.integrations(); expect(rulesIndexPatternAdapter.createIndex).toHaveBeenCalledWith('space1'); expect(rulesIndexPatternAdapter.getIndexName).toHaveBeenCalledWith('space1'); expect(resourcesIndexPatternAdapter.createIndex).toHaveBeenCalledWith('space1'); expect(resourcesIndexPatternAdapter.getIndexName).toHaveBeenCalledWith('space1'); + + expect(integrationsIndexPatternAdapter.createIndex).toHaveBeenCalledWith('space1'); + expect(integrationsIndexPatternAdapter.getIndexName).toHaveBeenCalledWith('space1'); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.ts index c19a89cefd81..ceff8e05f9f2 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.ts @@ -6,17 +6,18 @@ */ import type { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { IndexPatternAdapter, type FieldMap, type InstallParams } from '@kbn/index-adapter'; +import type { IndexNameProvider, IndexNameProviders } from './rule_migrations_data_client'; +import { RuleMigrationsDataClient } from './rule_migrations_data_client'; import { - ruleMigrationsFieldMap, + integrationsFieldMap, ruleMigrationResourcesFieldMap, + ruleMigrationsFieldMap, } from './rule_migrations_field_maps'; -import type { IndexNameProvider, IndexNameProviders } from './rule_migrations_data_client'; -import { RuleMigrationsDataClient } from './rule_migrations_data_client'; const TOTAL_FIELDS_LIMIT = 2500; export const INDEX_PATTERN = '.kibana-siem-rule-migrations'; -export type AdapterId = 'rules' | 'resources'; +export type AdapterId = 'rules' | 'resources' | 'integrations'; interface CreateClientParams { spaceId: string; @@ -31,6 +32,7 @@ export class RuleMigrationsDataService { this.adapters = { rules: this.createAdapter({ id: 'rules', fieldMap: ruleMigrationsFieldMap }), resources: this.createAdapter({ id: 'resources', fieldMap: ruleMigrationResourcesFieldMap }), + integrations: this.createAdapter({ id: 'integrations', fieldMap: integrationsFieldMap }), }; } @@ -49,6 +51,7 @@ export class RuleMigrationsDataService { await Promise.all([ this.adapters.rules.install({ ...params, logger: this.logger }), this.adapters.resources.install({ ...params, logger: this.logger }), + this.adapters.integrations.install({ ...params, logger: this.logger }), ]); } @@ -56,6 +59,7 @@ export class RuleMigrationsDataService { const indexNameProviders: IndexNameProviders = { rules: this.createIndexNameProvider('rules', spaceId), resources: this.createIndexNameProvider('resources', spaceId), + integrations: this.createIndexNameProvider('integrations', spaceId), }; return new RuleMigrationsDataClient( diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts index 3811ff74b5ca..8e8a3c5ee0f2 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts @@ -26,6 +26,7 @@ export const ruleMigrationsFieldMap: FieldMap<SchemaFieldMapKeys<Omit<RuleMigrat 'original_rule.mitre_attack_ids': { type: 'keyword', array: true, required: false }, elastic_rule: { type: 'nested', required: false }, 'elastic_rule.title': { type: 'keyword', required: true }, + 'elastic_rule.integration_ids': { type: 'keyword', array: true, required: false }, 'elastic_rule.query': { type: 'text', required: true }, 'elastic_rule.query_language': { type: 'keyword', required: true }, 'elastic_rule.description': { type: 'keyword', required: false }, @@ -49,3 +50,14 @@ export const ruleMigrationResourcesFieldMap: FieldMap< updated_at: { type: 'date', required: false }, updated_by: { type: 'keyword', required: false }, }; + +export const integrationsFieldMap: FieldMap = { + '@timestamp': { type: 'date', required: true }, + title: { type: 'text', required: true }, + description: { type: 'text', required: true }, + data_streams: { type: 'nested', array: true, required: true }, + 'data_streams.dataset': { type: 'keyword', required: true }, + 'data_streams.title': { type: 'text', required: true }, + 'data_streams.index_pattern': { type: 'keyword', required: true }, + elser_embeddings: { type: 'semantic_text', required: true }, +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts index 0db4bbe18fee..0ec705a9268d 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts @@ -6,24 +6,26 @@ */ import { END, START, StateGraph } from '@langchain/langgraph'; +import { getMatchPrebuiltRuleNode } from './nodes/match_prebuilt_rule'; import { migrateRuleState } from './state'; +import { getTranslateRuleGraph } from './sub_graphs/translate_rule'; import type { MigrateRuleGraphParams, MigrateRuleState } from './types'; -import { getTranslateQueryNode } from './nodes/translate_query'; -import { getMatchPrebuiltRuleNode } from './nodes/match_prebuilt_rule'; export function getRuleMigrationAgent({ model, inferenceClient, prebuiltRulesMap, resourceRetriever, + integrationRetriever, connectorId, logger, }: MigrateRuleGraphParams) { const matchPrebuiltRuleNode = getMatchPrebuiltRuleNode({ model, prebuiltRulesMap, logger }); - const translationNode = getTranslateQueryNode({ + const translationSubGraph = getTranslateRuleGraph({ model, inferenceClient, resourceRetriever, + integrationRetriever, connectorId, logger, }); @@ -31,7 +33,7 @@ export function getRuleMigrationAgent({ const translateRuleGraph = new StateGraph(migrateRuleState) // Nodes .addNode('matchPrebuiltRule', matchPrebuiltRuleNode) - .addNode('translation', translationNode) + .addNode('translation', translationSubGraph) // Edges .addEdge(START, 'matchPrebuiltRule') .addConditionalEdges('matchPrebuiltRule', matchedPrebuiltRuleConditional) diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompts/replace_resources_prompt.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompts/replace_resources_prompt.ts deleted file mode 100644 index 45b7a36dd292..000000000000 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompts/replace_resources_prompt.ts +++ /dev/null @@ -1,83 +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 type { RuleMigrationResources } from '../../../../util/rule_resource_retriever'; -import type { MigrateRuleState } from '../../../types'; - -const getResourcesContext = (resources: RuleMigrationResources): string => { - const resourcesContext = []; - if (resources.macro?.length) { - const macrosSummary = resources.macro - .map((macro) => `\`${macro.name}\`: ${macro.content}`) - .join('\n'); - resourcesContext.push('<<MACROS>>', macrosSummary, '<</MACROS>>'); - } - if (resources.list?.length) { - const lookupsSummary = resources.list - .map((list) => `lookup ${list.name}: ${list.content}`) - .join('\n'); - resourcesContext.push('<<LOOKUP_TABLES>>', lookupsSummary, '<</LOOKUP_TABLES>>'); - } - return resourcesContext.join('\n'); -}; - -export const getReplaceQueryResourcesPrompt = ( - state: MigrateRuleState, - resources: RuleMigrationResources -): string => { - const resourcesContext = getResourcesContext(resources); - return `You are an agent expert in Splunk SPL (Search Processing Language). -Your task is to inline a set of macros and lookup table values in a SPL query. - -## Guidelines: - -- You will be provided with a SPL query and also the resources reference with the values of macros and lookup tables. -- You have to replace the macros and lookup tables in the SPL query with their actual values. -- The original and modified queries must be equivalent. -- Macros names have the number of arguments in parentheses, e.g., \`macroName(2)\`. You must replace the correct macro accounting for the number of arguments. - -## Process: - -- Go through the SPL query and identify all the macros and lookup tables that are used. Two scenarios may arise: - - The macro or lookup table is provided in the resources: Replace the call by their actual value in the query. - - The macro or lookup table is not provided in the resources: Keep the call in the query as it is. - -## Example: - Having the following macros: - \`someSource\`: sourcetype="somesource" - \`searchTitle(1)\`: search title="$value$" - \`searchTitle\`: search title=* - \`searchType\`: search type=* - And the following SPL query: - \`\`\`spl - \`someSource\` \`someFilter\` - | \`searchTitle("sometitle")\` - | \`searchType("sometype")\` - | table * - \`\`\` - The correct replacement would be: - \`\`\`spl - sourcetype="somesource" \`someFilter\` - | search title="sometitle" - | \`searchType("sometype")\` - | table * - \`\`\` - -## Important: You must respond only with the modified query inside a \`\`\`spl code block, nothing else. - -# Find the macros and lookup tables below: - -${resourcesContext} - -# Find the SPL query below: - -\`\`\`spl -${state.original_rule.query} -\`\`\` - -`; -}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/graph.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/graph.ts new file mode 100644 index 000000000000..f986098e9deb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/graph.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { END, START, StateGraph } from '@langchain/langgraph'; +import { getProcessQueryNode } from './nodes/process_query'; +import { getRetrieveIntegrationsNode } from './nodes/retrieve_integrations'; +import { getTranslateRuleNode } from './nodes/translate_rule'; +import { translateRuleState } from './state'; +import type { TranslateRuleGraphParams } from './types'; + +export function getTranslateRuleGraph({ + model, + inferenceClient, + resourceRetriever, + integrationRetriever, + connectorId, + logger, +}: TranslateRuleGraphParams) { + const translateRuleNode = getTranslateRuleNode({ + model, + inferenceClient, + resourceRetriever, + connectorId, + logger, + }); + const processQueryNode = getProcessQueryNode({ + model, + resourceRetriever, + }); + const retrieveIntegrationsNode = getRetrieveIntegrationsNode({ + model, + integrationRetriever, + }); + + const translateRuleGraph = new StateGraph(translateRuleState) + // Nodes + .addNode('processQuery', processQueryNode) + .addNode('retrieveIntegrations', retrieveIntegrationsNode) + .addNode('translateRule', translateRuleNode) + // Edges + .addEdge(START, 'processQuery') + .addEdge('processQuery', 'retrieveIntegrations') + .addEdge('retrieveIntegrations', 'translateRule') + .addEdge('translateRule', END); + + const graph = translateRuleGraph.compile(); + graph.name = 'Translate Rule Graph'; + return graph; +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/index.ts new file mode 100644 index 000000000000..6a2b7e9cebd7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/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 { getTranslateRuleGraph } from './graph'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/index.ts new file mode 100644 index 000000000000..6feb852eba47 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { getProcessQueryNode } from './process_query'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/process_query.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/process_query.ts new file mode 100644 index 000000000000..0f90d74dafba --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/process_query.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { StringOutputParser } from '@langchain/core/output_parsers'; +import { isEmpty } from 'lodash/fp'; +import type { ChatModel } from '../../../../../util/actions_client_chat'; +import type { RuleResourceRetriever } from '../../../../../util/rule_resource_retriever'; +import type { GraphNode } from '../../types'; +import { getReplaceQueryResourcesPrompt } from './prompts'; + +interface GetProcessQueryNodeParams { + model: ChatModel; + resourceRetriever: RuleResourceRetriever; +} + +export const getProcessQueryNode = ({ + model, + resourceRetriever, +}: GetProcessQueryNodeParams): GraphNode => { + return async (state) => { + let query = state.original_rule.query; + const resources = await resourceRetriever.getResources(state.original_rule); + if (!isEmpty(resources)) { + const replaceQueryResourcesPrompt = getReplaceQueryResourcesPrompt(state, resources); + const stringParser = new StringOutputParser(); + query = await model.pipe(stringParser).invoke(replaceQueryResourcesPrompt); + } + return { inline_query: query }; + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/prompts.ts new file mode 100644 index 000000000000..5d2e6648c1d8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/prompts.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RuleMigrationResources } from '../../../../../util/rule_resource_retriever'; +import type { TranslateRuleState } from '../../types'; + +const getResourcesContext = (resources: RuleMigrationResources): string => { + const resourcesContext = []; + if (resources.macro?.length) { + const macrosSummary = resources.macro + .map((macro) => `\`${macro.name}\`: ${macro.content}`) + .join('\n'); + resourcesContext.push('<<MACROS>>', macrosSummary, '<</MACROS>>'); + } + if (resources.list?.length) { + const lookupsSummary = resources.list + .map((list) => `lookup ${list.name}: ${list.content}`) + .join('\n'); + resourcesContext.push('<<LOOKUP_TABLES>>', lookupsSummary, '<</LOOKUP_TABLES>>'); + } + return resourcesContext.join('\n'); +}; + +export const getReplaceQueryResourcesPrompt = ( + state: TranslateRuleState, + resources: RuleMigrationResources +): string => { + const resourcesContext = getResourcesContext(resources); + return `You are an agent expert in Splunk SPL (Search Processing Language). +Your task is to inline a set of macros and lookup tables syntax using their values in a SPL query. + +# Guidelines +- You will be provided with a SPL query and also the resources reference with the values of macros and lookup tables. +- You have to replace the macros and lookup tables syntax in the SPL query and use their values inline, if provided. +- The original and modified queries must be equivalent. + +# Process +- Go through the SPL query and identify all the macros and lookup tables that are used. Two scenarios may arise: + - The macro or lookup table is provided in the resources: Replace it using its actual content. + - The macro or lookup table is not provided in the resources: Do not replace it, keep it in the query as it is. + +## Macros replacements + +### Notes: +- Macros names have the number of arguments in parentheses, e.g., \`macroName(2)\`. You must replace the correct macro accounting for the number of arguments. + +### Example: + Having the following macros: + \`someSource\`: sourcetype="somesource" + \`searchTitle(1)\`: search title="$value$" + \`searchTitle\`: search title=* + \`searchType\`: search type=* + And the following SPL query: + \`\`\`spl + \`someSource\` \`someFilter\` + | \`searchTitle("sometitle")\` + | \`searchType("sometype")\` + | table * + \`\`\` + The correct replacement would be: + \`\`\`spl + sourcetype="somesource" \`someFilter\` + | search title="sometitle" + | \`searchType("sometype")\` + | table * + \`\`\` + +## Lookups replacements + +### Notes: +- OUTPUTNEW and OUTPUT fields should be replaced with the values from the lookup table. +- Use the \`case\` function to evaluate conditions in the same order provided by the lookup table. +- Ensure all lookup matching fields are correctly matched to their respective case conditions. +- If there are more than one field to match, use the \`AND\` operator to combine them inside the \`case\` function. +- The transformed SPL query should function equivalently to the original query with the \`lookup\` command. + +### Example: + Having the following lookup table: + uid,username,department + 1066,Claudia Garcia,Engineering + 1690,Rutherford Sullivan,Engineering + 1815,Vanya Patel,IT + 1862,Wei Zhang,Engineering + 1916,Alex Martin,Personnel + And the following SPL query: + \`\`\`spl + ... | lookup users uid OUTPUTNEW username, department + \`\`\` + The correct replacement would be: + \`\`\`spl + ... | eval username=case(uid=1066, "Claudia Garcia", + uid=1690, "Rutherford Sullivan", + uid=1815, "Vanya Patel", + uid=1862, "Wei Zhang", + uid=1916, "Alex Martin", + true, null), + department=case(uid=1066, "Engineering", + uid=1690, "Engineering", + uid=1815, "IT", + uid=1862, "Engineering", + uid=1916, "Personnel", + true, null) + \`\`\` + + +## Important: You must respond only with the modified query inside a \`\`\`spl code block, nothing else. + +# Find the macros and lookup tables below: + +${resourcesContext} + +# Find the SPL query below: + +\`\`\`spl +${state.original_rule.query} +\`\`\` + +`; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/index.ts new file mode 100644 index 000000000000..7db89035c6ad --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { getRetrieveIntegrationsNode } from './retrieve_integrations'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts new file mode 100644 index 000000000000..4d15ad71d679 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ChatPromptTemplate } from '@langchain/core/prompts'; + +export const CREATE_SEMANTIC_QUERY_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful assistant that helps in translating provided titles, descriptions and data sources into a single summary of keywords specifically crafted to be used as a semantic search query, which are usually short and includes keywords that are valid for the usecase. + The data provided are collected from SIEM detection rules, and it is trying to match the description of a list of data sources, so provide good keywords that match this usecase. + Try to also detect what sort of vendor, solution or technology is required and add these as keywords as well. + Some examples would be to identify if its cloud, which vendor, network, host, endpoint, etc.`, + ], + [ + 'human', + `<query> +Title: {title} +Description: {description} +Query: {query} +</query> + +Go through the relevant title, description and data sources from the above query and create a collection of keywords specifically crafted to be used as a semantic search query. + +<guidelines> +- The query should be short and concise. +- Include keywords that are relevant to the use case. +- Add related keywords you detected from the above query, like one or more vendor, product, cloud provider, OS platform etc. +- Always reply with a JSON object with the key "query" and the value as the semantic search query inside three backticks as shown in the below example. +</guidelines> + +<example> +U: <query> +Title: Processes created by netsh +Description: This search looks for processes launching netsh.exe to execute various commands via the netsh command-line utility. Netsh.exe is a command-line scripting utility that allows you to, either locally or remotely, display or modify the network configuration of a computer that is currently running. Netsh can be used as a persistence proxy technique to execute a helper .dll when netsh.exe is executed. In this search, we are looking for processes spawned by netsh.exe that are executing commands via the command line. Deprecated because we have another detection of the same type. +Data Sources: +</query> +A: Please find the query keywords JSON object below: +\`\`\`json +{{"query": "windows host endpoint netsh.exe process creation command-line utility network configuration persistence proxy dll execution sysmon event id 1"}} +\`\`\` +</example>`, + ], + ['ai', 'Please find the query keywords JSON object below:'], +]); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/retrieve_integrations.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/retrieve_integrations.ts new file mode 100644 index 000000000000..18577532fdf6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/retrieve_integrations.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { ChatModel } from '../../../../../util/actions_client_chat'; +import type { IntegrationRetriever } from '../../../../../util/integration_retriever'; +import type { GraphNode } from '../../types'; +import { CREATE_SEMANTIC_QUERY_PROMPT } from './prompts'; + +interface GetRetrieveIntegrationsNodeParams { + model: ChatModel; + integrationRetriever: IntegrationRetriever; +} + +interface GetSemanticQueryResponse { + query: string; +} + +export const getRetrieveIntegrationsNode = ({ + model, + integrationRetriever, +}: GetRetrieveIntegrationsNodeParams): GraphNode => { + const jsonParser = new JsonOutputParser(); + const semanticQueryChain = CREATE_SEMANTIC_QUERY_PROMPT.pipe(model).pipe(jsonParser); + + return async (state) => { + const query = state.inline_query; + + const integrationQuery = (await semanticQueryChain.invoke({ + title: state.original_rule.title, + description: state.original_rule.description, + query, + })) as GetSemanticQueryResponse; + + const integrations = await integrationRetriever.getIntegrations(integrationQuery.query); + return { + integrations, + }; + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/esql_knowledge_base_caller.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/esql_knowledge_base_caller.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/esql_knowledge_base_caller.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/esql_knowledge_base_caller.ts diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/index.ts new file mode 100644 index 000000000000..c8c5678b7f2f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { getTranslateRuleNode } from './translate_rule'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompts/esql_translation_prompt.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/prompts.ts similarity index 85% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompts/esql_translation_prompt.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/prompts.ts index 05e3c5c63bbe..3e77353bba8b 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompts/esql_translation_prompt.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/prompts.ts @@ -5,9 +5,12 @@ * 2.0. */ -import type { MigrateRuleState } from '../../../types'; +import type { TranslateRuleState } from '../../types'; -export const getEsqlTranslationPrompt = (state: MigrateRuleState, query: string): string => { +export const getEsqlTranslationPrompt = ( + state: TranslateRuleState, + indexPatterns: string +): string => { return `You are a helpful cybersecurity (SIEM) expert agent. Your task is to migrate "detection rules" from Splunk to Elastic Security. Your goal is to translate the SPL query into an equivalent Elastic Security Query Language (ES|QL) query. @@ -19,7 +22,7 @@ Your goal is to translate the SPL query into an equivalent Elastic Security Quer ## Guidelines: - Analyze the SPL query and identify the key components. - Translate the SPL query into an equivalent ES|QL query using ECS (Elastic Common Schema) field names. -- Always use logs* index pattern for the ES|QL translated query. +- Always start the generated ES|QL query by filtering FROM using these index patterns in the translated query: ${indexPatterns}. - If, in the SPL query, you find a lookup list or macro call, mention it in the summary and add a placeholder in the query with the format [macro:<macro_name>(argumentCount)] or [lookup:<lookup_name>] including the [] keys, - Examples: - \`get_duration(firstDate,secondDate)\` -> [macro:get_duration(2)] @@ -40,7 +43,7 @@ ${state.original_rule.description} <</DESCRIPTION>> <<SPL_QUERY>> -${query} +${state.inline_query} <</SPL_QUERY>> `; }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/translate_query.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts similarity index 61% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/translate_query.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts index e12d3b96ceb3..3fcd968b5565 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/translate_query.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts @@ -7,17 +7,14 @@ import type { Logger } from '@kbn/core/server'; import type { InferenceClient } from '@kbn/inference-plugin/server'; -import { StringOutputParser } from '@langchain/core/output_parsers'; -import { isEmpty } from 'lodash/fp'; +import { SiemMigrationRuleTranslationResult } from '../../../../../../../../../../common/siem_migrations/constants'; +import type { ChatModel } from '../../../../../util/actions_client_chat'; +import type { RuleResourceRetriever } from '../../../../../util/rule_resource_retriever'; import type { GraphNode } from '../../types'; import { getEsqlKnowledgeBase } from './esql_knowledge_base_caller'; -import { getReplaceQueryResourcesPrompt } from './prompts/replace_resources_prompt'; -import { getEsqlTranslationPrompt } from './prompts/esql_translation_prompt'; -import { SiemMigrationRuleTranslationResult } from '../../../../../../../../common/siem_migrations/constants'; -import type { RuleResourceRetriever } from '../../../util/rule_resource_retriever'; -import type { ChatModel } from '../../../util/actions_client_chat'; +import { getEsqlTranslationPrompt } from './prompts'; -interface GetTranslateQueryNodeParams { +interface GetTranslateRuleNodeParams { model: ChatModel; inferenceClient: InferenceClient; resourceRetriever: RuleResourceRetriever; @@ -25,25 +22,19 @@ interface GetTranslateQueryNodeParams { logger: Logger; } -export const getTranslateQueryNode = ({ - model, +export const getTranslateRuleNode = ({ inferenceClient, - resourceRetriever, connectorId, logger, -}: GetTranslateQueryNodeParams): GraphNode => { +}: GetTranslateRuleNodeParams): GraphNode => { const esqlKnowledgeBaseCaller = getEsqlKnowledgeBase({ inferenceClient, connectorId, logger }); return async (state) => { - let query = state.original_rule.query; + const indexPatterns = state.integrations.flatMap((integration) => + integration.data_streams.map((dataStream) => dataStream.index_pattern) + ); + const integrationIds = state.integrations.map((integration) => integration.id); - const resources = await resourceRetriever.getResources(state.original_rule); - if (!isEmpty(resources)) { - const replaceQueryResourcesPrompt = getReplaceQueryResourcesPrompt(state, resources); - const stringParser = new StringOutputParser(); - query = await model.pipe(stringParser).invoke(replaceQueryResourcesPrompt); - } - - const prompt = getEsqlTranslationPrompt(state, query); + const prompt = getEsqlTranslationPrompt(state, indexPatterns.join(' ')); const response = await esqlKnowledgeBaseCaller(prompt); const esqlQuery = response.match(/```esql\n([\s\S]*?)\n```/)?.[1] ?? ''; @@ -57,6 +48,7 @@ export const getTranslateQueryNode = ({ translation_result: translationResult, elastic_rule: { title: state.original_rule.title, + integration_ids: integrationIds, description: state.original_rule.description, severity: 'low', query: esqlQuery, diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/state.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/state.ts new file mode 100644 index 000000000000..8c8e9780aedf --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/state.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { BaseMessage } from '@langchain/core/messages'; +import { Annotation, messagesStateReducer } from '@langchain/langgraph'; +import { SiemMigrationRuleTranslationResult } from '../../../../../../../../common/siem_migrations/constants'; +import type { + ElasticRule, + OriginalRule, + RuleMigration, +} from '../../../../../../../../common/siem_migrations/model/rule_migration.gen'; +import type { Integration } from '../../../../types'; + +export const translateRuleState = Annotation.Root({ + messages: Annotation<BaseMessage[]>({ + reducer: messagesStateReducer, + default: () => [], + }), + original_rule: Annotation<OriginalRule>(), + integrations: Annotation<Integration[]>({ + reducer: (current, value) => value ?? current, + default: () => [], + }), + inline_query: Annotation<string>({ + reducer: (current, value) => value ?? current, + default: () => '', + }), + elastic_rule: Annotation<ElasticRule>({ + reducer: (state, action) => ({ ...state, ...action }), + default: () => ({} as ElasticRule), + }), + translation_result: Annotation<SiemMigrationRuleTranslationResult>({ + reducer: (current, value) => value ?? current, + default: () => SiemMigrationRuleTranslationResult.UNTRANSLATABLE, + }), + comments: Annotation<RuleMigration['comments']>({ + reducer: (current, value) => (value ? (current ?? []).concat(value) : current), + default: () => [], + }), + response: Annotation<string>({ + reducer: (current, value) => value ?? current, + default: () => '', + }), +}); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/types.ts new file mode 100644 index 000000000000..42bf8e14c592 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import type { InferenceClient } from '@kbn/inference-plugin/server'; +import type { ChatModel } from '../../../util/actions_client_chat'; +import type { IntegrationRetriever } from '../../../util/integration_retriever'; +import type { RuleResourceRetriever } from '../../../util/rule_resource_retriever'; +import type { translateRuleState } from './state'; + +export type TranslateRuleState = typeof translateRuleState.State; +export type GraphNode = (state: TranslateRuleState) => Promise<Partial<TranslateRuleState>>; + +export interface TranslateRuleGraphParams { + inferenceClient: InferenceClient; + model: ChatModel; + connectorId: string; + resourceRetriever: RuleResourceRetriever; + integrationRetriever: IntegrationRetriever; + logger: Logger; +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/types.ts index 975c03439842..046083140e5e 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/types.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/types.ts @@ -7,10 +7,11 @@ import type { Logger } from '@kbn/core/server'; import type { InferenceClient } from '@kbn/inference-plugin/server'; -import type { migrateRuleState } from './state'; import type { ChatModel } from '../util/actions_client_chat'; +import type { IntegrationRetriever } from '../util/integration_retriever'; import type { PrebuiltRulesMapByName } from '../util/prebuilt_rules'; import type { RuleResourceRetriever } from '../util/rule_resource_retriever'; +import type { migrateRuleState } from './state'; export type MigrateRuleState = typeof migrateRuleState.State; export type GraphNode = (state: MigrateRuleState) => Promise<Partial<MigrateRuleState>>; @@ -21,5 +22,6 @@ export interface MigrateRuleGraphParams { connectorId: string; prebuiltRulesMap: PrebuiltRulesMapByName; resourceRetriever: RuleResourceRetriever; + integrationRetriever: IntegrationRetriever; logger: Logger; } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_client.ts index 989c33a44cb3..56c7e8485d31 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_client.ts @@ -9,24 +9,22 @@ import type { AuthenticatedUser, Logger } from '@kbn/core/server'; import { AbortError, abortSignalToPromise } from '@kbn/kibana-utils-plugin/server'; import type { RunnableConfig } from '@langchain/core/runnables'; import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants'; -import type { - RuleMigrationAllTaskStats, - RuleMigrationTaskStats, -} from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import type { RuleMigrationTaskStats } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import type { RuleMigrationsDataClient } from '../data/rule_migrations_data_client'; import type { RuleMigrationDataStats } from '../data/rule_migrations_data_rules_client'; +import { getRuleMigrationAgent } from './agent'; +import type { MigrateRuleState } from './agent/types'; import type { + MigrationAgent, + RuleMigrationTaskPrepareParams, + RuleMigrationTaskRunParams, RuleMigrationTaskStartParams, RuleMigrationTaskStartResult, RuleMigrationTaskStopResult, - RuleMigrationTaskPrepareParams, - RuleMigrationTaskRunParams, - MigrationAgent, } from './types'; -import { getRuleMigrationAgent } from './agent'; -import type { MigrateRuleState } from './agent/types'; -import { retrievePrebuiltRulesMap } from './util/prebuilt_rules'; import { ActionsClientChat } from './util/actions_client_chat'; +import { IntegrationRetriever } from './util/integration_retriever'; +import { retrievePrebuiltRulesMap } from './util/prebuilt_rules'; import { RuleResourceRetriever } from './util/rule_resource_retriever'; const ITERATION_BATCH_SIZE = 50 as const; @@ -89,6 +87,7 @@ export class RuleMigrationsTaskClient { }: RuleMigrationTaskPrepareParams): Promise<MigrationAgent> { const prebuiltRulesMap = await retrievePrebuiltRulesMap({ soClient, rulesClient }); const resourceRetriever = new RuleResourceRetriever(migrationId, this.data); + const integrationRetriever = new IntegrationRetriever(this.data); const actionsClientChat = new ActionsClientChat(connectorId, actionsClient, this.logger); const model = await actionsClientChat.createModel({ @@ -102,6 +101,7 @@ export class RuleMigrationsTaskClient { inferenceClient, prebuiltRulesMap, resourceRetriever, + integrationRetriever, logger: this.logger, }); return agent; @@ -226,10 +226,10 @@ export class RuleMigrationsTaskClient { } /** Returns the stats of all migrations */ - async getAllStats(): Promise<RuleMigrationAllTaskStats> { + async getAllStats(): Promise<RuleMigrationTaskStats[]> { const allDataStats = await this.data.rules.getAllStats(); return allDataStats.map((dataStats) => { - const status = this.getTaskStatus(dataStats.migration_id, dataStats.rules); + const status = this.getTaskStatus(dataStats.id, dataStats.rules); return { status, ...dataStats }; }); } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/integration_retriever.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/integration_retriever.test.ts new file mode 100644 index 000000000000..2aa01a9c9c41 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/integration_retriever.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MockRuleMigrationsDataClient } from '../../data/__mocks__/mocks'; +import { IntegrationRetriever } from './integration_retriever'; + +describe('IntegrationRetriever', () => { + let integrationRetriever: IntegrationRetriever; + const mockRuleMigrationsDataClient = new MockRuleMigrationsDataClient(); + const mockIntegrationItem = { + id: '1', + title: 'Integration 1', + description: 'Integration 1 description', + data_streams: [{ dataset: 'test', title: 'dstitle', index_pattern: 'logs-*' }], + elser_embedding: 'elser_embedding', + }; + beforeEach(() => { + integrationRetriever = new IntegrationRetriever(mockRuleMigrationsDataClient); + mockRuleMigrationsDataClient.integrations.retrieveIntegrations.mockImplementation( + async (_: string) => { + return mockIntegrationItem; + } + ); + }); + + it('should retrieve integrations', async () => { + const result = await integrationRetriever.getIntegrations('test'); + + expect(mockRuleMigrationsDataClient.integrations.retrieveIntegrations).toHaveBeenCalledWith( + 'test' + ); + expect(result).toEqual(mockIntegrationItem); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/integration_retriever.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/integration_retriever.ts new file mode 100644 index 000000000000..7913e2c43808 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/integration_retriever.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RuleMigrationsDataClient } from '../../data/rule_migrations_data_client'; +import type { Integration } from '../../types'; + +export class IntegrationRetriever { + constructor(private readonly dataClient: RuleMigrationsDataClient) {} + + public async getIntegrations(semanticString: string): Promise<Integration[]> { + return this.integrationRetriever(semanticString); + } + + private integrationRetriever = async (semanticString: string): Promise<Integration[]> => { + const integrations = await this.dataClient.integrations.retrieveIntegrations(semanticString); + + return integrations; + }; +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts index e506b43cc323..f8a0f0b3b25a 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts @@ -14,3 +14,11 @@ export type Stored<T extends object> = T & { id: string }; export type StoredRuleMigration = Stored<RuleMigration>; export type StoredRuleMigrationResource = Stored<RuleMigrationResource>; + +export interface Integration { + title: string; + id: string; + description: string; + data_streams: Array<{ dataset: string; title: string; index_pattern: string }>; + elser_embedding: string; +} diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts index aee26dc06bc7..fcccd38191a7 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/create_timelines.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; + export const mockTemplate = { columns: [ { @@ -172,13 +174,13 @@ export const mockTemplate = { kqlQuery: { filterQuery: { kuery: { kind: 'kuery', expression: '' }, serializedQuery: '' } }, indexNames: [], title: 'Generic Process Timeline - Duplicate - Duplicate', - timelineType: 'template', + timelineType: TimelineTypeEnum.template, templateTimelineVersion: null, templateTimelineId: null, dateRange: { start: '2020-10-01T11:37:31.655Z', end: '2020-10-02T11:37:31.655Z' }, savedQueryId: null, sort: { columnId: '@timestamp', sortDirection: 'desc' }, - status: 'active', + status: TimelineStatusEnum.active, }; export const mockTimeline = { @@ -210,11 +212,11 @@ export const mockTimeline = { '.siem-signals-angelachuang-default', ], title: 'my timeline', - timelineType: 'default', + timelineType: TimelineTypeEnum.default, templateTimelineVersion: null, templateTimelineId: null, dateRange: { start: '2020-11-03T13:34:40.339Z', end: '2020-11-04T13:34:40.339Z' }, savedQueryId: null, sort: { columnId: '@timestamp', columnType: 'number', sortDirection: 'desc' }, - status: 'draft', + status: TimelineStatusEnum.draft, }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/README.md b/x-pack/plugins/security_solution/server/lib/timeline/routes/README.md deleted file mode 100644 index 6878e21e1445..000000000000 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/README.md +++ /dev/null @@ -1,1439 +0,0 @@ -**Timeline apis** - - 1. Create timeline api - 2. Update timeline api - 3. Create template timeline api - 4. Update template timeline api - - -## Create timeline api -#### POST /api/timeline -##### Authorization -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` -##### Request body -```json -{ - "timeline": { - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [], - "description": "", - "eventType": "all", - "filters": [], - "kqlMode": "filter", - "kqlQuery": { - "filterQuery": null - }, - "title": "abd", - "dateRange": { - "start": 1587370079200, - "end": 1587456479201 - }, - "savedQueryId": null, - "sort": { - "columnId": "@timestamp", - "sortDirection": "desc" - } - }, - "timelineId":null, // Leave this as null - "version":null // Leave this as null -} -``` - - -## Update timeline api -#### PATCH /api/timeline -##### Authorization -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` -##### Request body -```json -{ - "timeline": { - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [], - "description": "", - "eventType": "all", - "filters": [], - "kqlMode": "filter", - "kqlQuery": { - "filterQuery": null - }, - "title": "abd", - "dateRange": { - "start": 1587370079200, - "end": 1587456479201 - }, - "savedQueryId": null, - "sort": { - "columnId": "@timestamp", - "sortDirection": "desc" - }, - "created": 1587468588922, - "createdBy": "casetester", - "updated": 1587468588922, - "updatedBy": "casetester", - "timelineType": "default" - }, - "timelineId":"68ea5330-83c3-11ea-bff9-ab01dd7cb6cc", // Have to match the existing timeline savedObject id - "version":"WzYwLDFd" // Have to match the existing timeline version -} -``` - -## Create template timeline api -#### POST /api/timeline -##### Authorization -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` -##### Request body -```json -{ - "timeline": { - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [ - - ], - "description": "", - "eventType": "all", - "filters": [ - - ], - "kqlMode": "filter", - "kqlQuery": { - "filterQuery": null - }, - "title": "abd", - "dateRange": { - "start": 1587370079200, - "end": 1587456479201 - }, - "savedQueryId": null, - "sort": { - "columnId": "@timestamp", - "sortDirection": "desc" - }, - "timelineType": "template" // This is the difference between create timeline - }, - "timelineId":null, // Leave this as null - "version":null // Leave this as null -} -``` - - -## Update template timeline api -#### PATCH /api/timeline -##### Authorization -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` -##### Request body -```json -{ - "timeline": { - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [], - "description": "", - "eventType": "all", - "filters": [], - "kqlMode": "filter", - "kqlQuery": { - "filterQuery": null - }, - "title": "abd", - "dateRange": { - "start": 1587370079200, - "end": 1587456479201 - }, - "savedQueryId": null, - "sort": { - "columnId": "@timestamp", - "sortDirection": "desc" - }, - "timelineType": "template", - "created": 1587473119992, - "createdBy": "casetester", - "updated": 1587473119992, - "updatedBy": "casetester", - "templateTimelineId": "745d0316-6af7-43bf-afd6-9747119754fb", // Please provide the existing template timeline version - "templateTimelineVersion": 2 // Please provide a template timeline version grater than existing one - }, - "timelineId":"f5a4bd10-83cd-11ea-bf78-0547a65f1281", // This is a must as well - "version":"Wzg2LDFd" // Please provide the existing timeline version -} -``` - -## Export timeline api - -#### POST /api/timeline/_export - -##### Authorization - -Type: Basic Auth - -username: Your Kibana username - -password: Your Kibana password - - - - -##### Request header - -``` - -Content-Type: application/json - -kbn-version: 8.0.0 - -``` - -##### Request param - -``` -file_name: ${filename}.ndjson -``` - -##### Request body -```json -{ - ids: [ - ${timelineId} - ] -} -``` - -## Import timeline api - -#### POST /api/timeline/_import - -##### Authorization - -Type: Basic Auth - -username: Your Kibana username - -password: Your Kibana password - - - - -##### Request header - -``` - -Content-Type: application/json - -kbn-version: 8.0.0 - -``` - -##### Request body - -``` -{ - file: sample.ndjson -} -``` - - -(each json in the file should match this format) -example: -``` -{"savedObjectId":"a3002fd0-781b-11ea-85e4-df9002f1452c","version":"WzIzLDFd","columns":[{"columnHeaderType":"not-filtered","id":"@timestamp"},{"columnHeaderType":"not-filtered","id":"message"},{"columnHeaderType":"not-filtered","id":"event.category"},{"columnHeaderType":"not-filtered","id":"event.action"},{"columnHeaderType":"not-filtered","id":"host.name"},{"columnHeaderType":"not-filtered","id":"source.ip"},{"columnHeaderType":"not-filtered","id":"destination.ip"},{"columnHeaderType":"not-filtered","id":"user.name"}],"dataProviders":[],"description":"tes description","eventType":"all","filters":[{"meta":{"field":null,"negate":false,"alias":null,"disabled":false,"params":"{\"query\":\"MacBook-Pro-de-Gloria.local\"}","type":"phrase","key":"host.name"},"query":"{\"match_phrase\":{\"host.name\":\"MacBook-Pro-de-Gloria.local\"}}","missing":null,"exists":null,"match_all":null,"range":null,"script":null}],"kqlMode":"filter","kqlQuery":{"filterQuery":{"serializedQuery":"{\"bool\":{\"should\":[{\"exists\":{\"field\":\"host.name\"}}],\"minimum_should_match\":1}}","kuery":{"expression":"host.name: *","kind":"kuery"}}},"title":"Test","dateRange":{"start":1585227005527,"end":1585313405527},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1586187068132,"createdBy":"angela","updated":1586187068132,"updatedBy":"angela","eventNotes":[],"globalNotes":[{"noteId":"a3b4d9d0-781b-11ea-85e4-df9002f1452c","version":"WzI1LDFd","note":"this is a note","timelineId":"a3002fd0-781b-11ea-85e4-df9002f1452c","created":1586187069313,"createdBy":"angela","updated":1586187069313,"updatedBy":"angela"}],"pinnedEventIds":[]} -``` - -##### Response -``` -{"success":true,"success_count":1,"errors":[]} -``` - -## Get draft timeline api - -#### GET /api/timeline/_draft - -##### Authorization - -Type: Basic Auth - -username: Your Kibana username - -password: Your Kibana password - - -##### Request header - -``` - -Content-Type: application/json - -kbn-version: 8.0.0 - -``` - -##### Request param -``` -timelineType: `default` or `template` -``` - -##### Response -```json -{ - "data": { - "persistTimeline": { - "timeline": { - "savedObjectId": "ababbd90-99de-11ea-8446-1d7fd9f03ebf", - "version": "WzM2MiwzXQ==", - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [], - "description": "", - "eventType": "all", - "filters": [], - "kqlMode": "filter", - "timelineType": "default", - "kqlQuery": { - "filterQuery": null - }, - "title": "", - "sort": { - "columnId": "@timestamp", - "sortDirection": "desc" - }, - "status": "draft", - "created": 1589899222908, - "createdBy": "casetester", - "updated": 1589899222908, - "updatedBy": "casetester", - "templateTimelineId": null, - "templateTimelineVersion": null, - "favorite": [], - "eventIdToNoteIds": [], - "noteIds": [], - "notes": [], - "pinnedEventIds": [], - "pinnedEventsSaveObject": [] - } - } - } -} -``` - -## Create draft timeline api - -#### POST /api/timeline/_draft - -##### Authorization - -Type: Basic Auth - -username: Your Kibana username - -password: Your Kibana password - - -##### Request header - -``` - -Content-Type: application/json - -kbn-version: 8.0.0 - -``` - -##### Request body - -```json -{ - "timelineType": "default" or "template" -} -``` - -##### Response -```json -{ - "data": { - "persistTimeline": { - "timeline": { - "savedObjectId": "ababbd90-99de-11ea-8446-1d7fd9f03ebf", - "version": "WzQyMywzXQ==", - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [], - "description": "", - "eventType": "all", - "filters": [], - "kqlMode": "filter", - "timelineType": "default", - "kqlQuery": { - "filterQuery": null - }, - "title": "", - "sort": { - "columnId": "@timestamp", - "sortDirection": "desc" - }, - "status": "draft", - "created": 1589903306582, - "createdBy": "casetester", - "updated": 1589903306582, - "updatedBy": "casetester", - "templateTimelineId": null, - "templateTimelineVersion": null, - "favorite": [], - "eventIdToNoteIds": [], - "noteIds": [], - "notes": [], - "pinnedEventIds": [], - "pinnedEventsSaveObject": [] - } - } - } -} -``` - -## Get timelines / timeline templates api - -#### GET /api/timelines - - -##### Authorization - -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header - -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` - -##### Query params - -optional: -only_user_favorite={boolean} -page_index={number} -page_size={number} -search={string} -sort_field={title|description|updated|created} -sort_order={asc|desc} -status={active|draft|immutable} -timeline_type={default|template} - -##### example -api/timelines?page_size=10&page_index=1&sort_field=updated&sort_order=desc&timeline_type=default - -##### Response - -```json -{ - "totalCount": 2, - "timeline": [ - { - "savedObjectId": "de9a3620-8e23-11eb-ad8a-a192243e45e8", - "version": "WzM1NzQ4NywzXQ==", - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp", - "type": "number" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [ - { - "excluded": false, - "and": [], - "kqlQuery": "", - "name": "", - "queryMatch": { - "field": "host.name", - "value": "", - "operator": ":*" - }, - "id": "timeline-1-db9f4fc8-9420-420e-8e67-b12dd36691f6", - "type": "default", - "enabled": true - } - ], - "dataViewId": "security-solution", - "description": "", - "eqlOptions": { - "tiebreakerField": "", - "size": 100, - "query": "", - "eventCategoryField": "event.category", - "timestampField": "@timestamp" - }, - "eventType": "all", - "excludedRowRendererIds": [], - "filters": [], - "kqlMode": "filter", - "kqlQuery": { - "filterQuery": null - }, - "indexNames": [ - ".siem-signals-angelachuang-default", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*" - ], - "title": "timeline - Duplicate", - "timelineType": "default", - "templateTimelineVersion": null, - "templateTimelineId": null, - "dateRange": { - "start": "2021-03-25T05:38:55.593Z", - "end": "2021-03-26T15:59:59.999Z" - }, - "savedQueryId": null, - "sort": [ - { - "columnType": "number", - "sortDirection": "desc", - "columnId": "@timestamp" - } - ], - "status": "active", - "created": 1616757027458, - "createdBy": "angela", - "updated": 1616758738320, - "updatedBy": "angela", - "favorite": [], - "eventIdToNoteIds": [ - { - "noteId": "e6f3a9a0-8e23-11eb-ad8a-a192243e45e8", - "version": "WzM1NzQ4MywzXQ==", - "eventId": "QN84bngBYJMSg9tnAi1V", - "note": "note!", - "timelineId": "de9a3620-8e23-11eb-ad8a-a192243e45e8", - "created": 1616757041466, - "createdBy": "angela", - "updated": 1616757041466, - "updatedBy": "angela" - } - ], - "noteIds": [ - "221524f0-8e24-11eb-ad8a-a192243e45e8" - ], - "notes": [ - { - "noteId": "e6f3a9a0-8e23-11eb-ad8a-a192243e45e8", - "version": "WzM1NzQ4MywzXQ==", - "eventId": "QN84bngBYJMSg9tnAi1V", - "note": "note!", - "timelineId": "de9a3620-8e23-11eb-ad8a-a192243e45e8", - "created": 1616757041466, - "createdBy": "angela", - "updated": 1616757041466, - "updatedBy": "angela" - }, - { - "noteId": "221524f0-8e24-11eb-ad8a-a192243e45e8", - "version": "WzM1NzQ4NiwzXQ==", - "note": "global note!", - "timelineId": "de9a3620-8e23-11eb-ad8a-a192243e45e8", - "created": 1616757140671, - "createdBy": "angela", - "updated": 1616757140671, - "updatedBy": "angela" - } - ], - "pinnedEventIds": [ - "QN84bngBYJMSg9tnAi1V", - "P984bngBYJMSg9tnAi1V" - ], - "pinnedEventsSaveObject": [ - { - "pinnedEventId": "e85339a0-8e23-11eb-ad8a-a192243e45e8", - "version": "WzM1NzQ4NCwzXQ==", - "eventId": "QN84bngBYJMSg9tnAi1V", - "timelineId": "de9a3620-8e23-11eb-ad8a-a192243e45e8", - "created": 1616757043770, - "createdBy": "angela", - "updated": 1616757043770, - "updatedBy": "angela" - }, - { - "pinnedEventId": "2945cfe0-8e24-11eb-ad8a-a192243e45e8", - "version": "WzM1NzQ4NSwzXQ==", - "eventId": "P984bngBYJMSg9tnAi1V", - "timelineId": "de9a3620-8e23-11eb-ad8a-a192243e45e8", - "created": 1616757152734, - "createdBy": "angela", - "updated": 1616757152734, - "updatedBy": "angela" - } - ] - }, - { - "savedObjectId": "48870270-8e1f-11eb-9cbd-7f6324a02fb7", - "version": "WzM1NzQ4MiwzXQ==", - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp", - "type": "number" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [ - { - "excluded": false, - "and": [], - "kqlQuery": "", - "name": "", - "queryMatch": { - "field": "host.name", - "value": "", - "operator": ":*" - }, - "id": "timeline-1-db9f4fc8-9420-420e-8e67-b12dd36691f6", - "type": "default", - "enabled": true - } - ], - "dataViewId": "security-solution", - "description": "", - "eventType": "all", - "filters": [], - "kqlMode": "filter", - "timelineType": "default", - "kqlQuery": { - "filterQuery": null - }, - "title": "timeline", - "sort": [ - { - "columnType": "number", - "sortDirection": "desc", - "columnId": "@timestamp" - } - ], - "status": "active", - "created": 1616755057686, - "createdBy": "angela", - "updated": 1616756755376, - "updatedBy": "angela", - "templateTimelineId": null, - "templateTimelineVersion": null, - "excludedRowRendererIds": [], - "dateRange": { - "start": "2021-03-25T16:00:00.000Z", - "end": "2021-03-26T15:59:59.999Z" - }, - "indexNames": [ - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*", - ".siem-signals-angelachuang-default" - ], - "eqlOptions": { - "tiebreakerField": "", - "size": 100, - "query": "", - "eventCategoryField": "event.category", - "timestampField": "@timestamp" - }, - "savedQueryId": null, - "favorite": [ - { - "favoriteDate": 1616756755376, - "keySearch": "YW5nZWxh", - "fullName": "Angela", - "userName": "angela" - } - ], - "eventIdToNoteIds": [], - "noteIds": [], - "notes": [], - "pinnedEventIds": [], - "pinnedEventsSaveObject": [] - } - ], - "defaultTimelineCount": 2, - "templateTimelineCount": 4, - "elasticTemplateTimelineCount": 3, - "customTemplateTimelineCount": 1, - "favoriteCount": 1 -} -``` - -## Get timeline api - -#### GET /api/id?id={savedObjectId} - -##### Authorization - -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header - -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` - -##### Response -```json -{ - "data": { - "getOneTimeline": { - "savedObjectId": "48870270-8e1f-11eb-9cbd-7f6324a02fb7", - "version": "WzM1NzQ4MiwzXQ==", - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp", - "type": "number" - }, - { - "columnHeaderType": "not-filtered", - "id": "message" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.category" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "host.name" - }, - { - "columnHeaderType": "not-filtered", - "id": "source.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "destination.ip" - }, - { - "columnHeaderType": "not-filtered", - "id": "user.name" - } - ], - "dataProviders": [ - { - "excluded": false, - "and": [], - "kqlQuery": "", - "name": "", - "queryMatch": { - "field": "host.name", - "value": "", - "operator": ":*" - }, - "id": "timeline-1-db9f4fc8-9420-420e-8e67-b12dd36691f6", - "type": "default", - "enabled": true - } - ], - "dataViewId": "security-solution", - "description": "", - "eventType": "all", - "filters": [], - "kqlMode": "filter", - "timelineType": "default", - "kqlQuery": { - "filterQuery": null - }, - "title": "timeline", - "sort": [ - { - "columnType": "number", - "sortDirection": "desc", - "columnId": "@timestamp" - } - ], - "status": "active", - "created": 1616755057686, - "createdBy": "angela", - "updated": 1616756755376, - "updatedBy": "angela", - "templateTimelineId": null, - "templateTimelineVersion": null, - "excludedRowRendererIds": [], - "dateRange": { - "start": "2021-03-25T16:00:00.000Z", - "end": "2021-03-26T15:59:59.999Z" - }, - "indexNames": [ - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*", - ".siem-signals-angelachuang-default" - ], - "eqlOptions": { - "tiebreakerField": "", - "size": 100, - "query": "", - "eventCategoryField": "event.category", - "timestampField": "@timestamp" - }, - "savedQueryId": null, - "favorite": [ - { - "favoriteDate": 1616756755376, - "keySearch": "YW5nZWxh", - "fullName": "Angela", - "userName": "angela" - } - ], - "eventIdToNoteIds": [], - "noteIds": [], - "notes": [], - "pinnedEventIds": [], - "pinnedEventsSaveObject": [] - } - } -} -``` - - -## Get timeline template api - -#### GET /api/timeline?template_timeline_id={templateTimelineId} - -##### Authorization - -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header - -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` - -##### Response -```json -{ - "data": { - "getOneTimeline": { - "savedObjectId": "bf662160-9788-11eb-8277-3516cc4109c3", - "version": "WzM1NzU2MCwzXQ==", - "columns": [ - { - "columnHeaderType": "not-filtered", - "id": "@timestamp" - }, - { - "columnHeaderType": "not-filtered", - "id": "signal.rule.description" - }, - { - "columnHeaderType": "not-filtered", - "id": "event.action" - }, - { - "columnHeaderType": "not-filtered", - "id": "process.name" - }, - { - "aggregatable": true, - "description": "The working directory of the process.", - "columnHeaderType": "not-filtered", - "id": "process.working_directory", - "category": "process", - "type": "string", - "example": "/home/alice" - }, - { - "aggregatable": true, - "description": "Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.", - "columnHeaderType": "not-filtered", - "id": "process.args", - "category": "process", - "type": "string", - "example": "[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]" - }, - { - "columnHeaderType": "not-filtered", - "id": "process.pid" - }, - { - "aggregatable": true, - "description": "Absolute path to the process executable.", - "columnHeaderType": "not-filtered", - "id": "process.parent.executable", - "category": "process", - "type": "string", - "example": "/usr/bin/ssh" - }, - { - "aggregatable": true, - "description": "Array of process arguments.\n\nMay be filtered to protect sensitive information.", - "columnHeaderType": "not-filtered", - "id": "process.parent.args", - "category": "process", - "type": "string", - "example": "[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]" - }, - { - "aggregatable": true, - "description": "Process id.", - "columnHeaderType": "not-filtered", - "id": "process.parent.pid", - "category": "process", - "type": "number", - "example": "4242" - }, - { - "aggregatable": true, - "description": "Short name or login of the user.", - "columnHeaderType": "not-filtered", - "id": "user.name", - "category": "user", - "type": "string", - "example": "albert" - }, - { - "aggregatable": true, - "description": "Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.", - "columnHeaderType": "not-filtered", - "id": "host.name", - "category": "host", - "type": "string" - } - ], - "dataProviders": [ - { - "excluded": false, - "and": [], - "kqlQuery": "", - "name": "{process.name}", - "queryMatch": { - "displayValue": null, - "field": "process.name", - "displayField": null, - "value": "{process.name}", - "operator": ":" - }, - "id": "timeline-1-8622010a-61fb-490d-b162-beac9c36a853", - "type": "template", - "enabled": true - } - ], - "dataViewId": "security-solution", - "description": "", - "eqlOptions": { - "eventCategoryField": "event.category", - "tiebreakerField": "", - "timestampField": "@timestamp", - "query": "", - "size": 100 - }, - "eventType": "all", - "excludedRowRendererIds": [], - "filters": [], - "kqlMode": "filter", - "kqlQuery": { - "filterQuery": { - "kuery": { - "kind": "kuery", - "expression": "" - }, - "serializedQuery": "" - } - }, - "indexNames": [], - "title": "Generic Process Timeline - Duplicate", - "timelineType": "template", - "templateTimelineVersion": 1, - "templateTimelineId": "94dd7443-97ea-4461-864d-fa96803ec111", - "dateRange": { - "start": "2021-04-06T07:57:57.922Z", - "end": "2021-04-07T07:57:57.922Z" - }, - "savedQueryId": null, - "sort": [ - { - "sortDirection": "desc", - "columnId": "@timestamp" - } - ], - "status": "active", - "created": 1617789914742, - "createdBy": "angela", - "updated": 1617790158569, - "updatedBy": "angela", - "favorite": [ - { - "favoriteDate": 1617790158569, - "keySearch": "YW5nZWxh", - "fullName": "Angela", - "userName": "angela" - } - ], - "eventIdToNoteIds": [], - "noteIds": [], - "notes": [], - "pinnedEventIds": [], - "pinnedEventsSaveObject": [] - } - } -} -``` - -## Delete timeline api - -#### DELETE /api/timeline - -##### Authorization - -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header - -``` - -Content-Type: application/json - -kbn-version: 8.0.0 - -``` - -##### Request body - -```json -{ - "savedObjectIds": [savedObjectId1, savedObjectId2] -} -``` - -##### Response -```json -{"data":{"deleteTimeline":true}} -``` - -## Persist note api - -#### POST /api/note - -##### Authorization - -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header - -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` - -##### Request body - -```json -{ - "note": { - "timelineId": {timeline id that the note is linked to}, - "eventId" (optional): {event id the note is linked to. Not available is it is a global note}, - "note"(optional): {note content}, - }, - "noteId"(optional): note savedObjectId, - "version" (optional): note savedObjectVersion -} -``` -##### Example -```json -{ - "noteId": null, - "version": null, - "note": { - "eventId": "Q9tqqXgBc4D54_cxJnHV", - "note": "note", - "timelineId": "1ec3b430-908e-11eb-94fa-c9122cbc0213" - } -} -``` - -##### Response -``` -{ - "data": { - "persistNote": { - "code": 200, - "message": "success", - "note": { - "noteId": "fe8f6980-97ad-11eb-862e-850f4426d3d0", - "version": "WzM1MDAyNSwzXQ==", - "eventId": "UNtqqXgBc4D54_cxIGi-", - "note": "event note", - "timelineId": "1ec3b430-908e-11eb-94fa-c9122cbc0213", - "created": 1617805912088, - "createdBy": "angela", - "updated": 1617805912088, - "updatedBy": "angela" - } - } - } -} -``` - -## Persist pinned event api - -#### POST /api/pinned_event - -##### Authorization - -Type: Basic Auth -username: Your Kibana username -password: Your Kibana password - - -##### Request header - -``` -Content-Type: application/json -kbn-version: 8.0.0 -``` - -##### Request body - -```json -{ - "eventId": {event which is pinned} - "pinnedEventId" (optional): {pinned event savedObjectId} - "timelineId": {timeline which this pinned event is linked to} -} -``` - -##### example - -``` -{ - "eventId":"UdtqqXgBc4D54_cxIGi", - "pinnedEventId":null, - "timelineId":"1ec3b430-908e-11eb-94fa-c9122cbc0213" -} -``` - -##### Response -```json -{ - "data": { - "persistPinnedEventOnTimeline": { - "pinnedEventId": "5b8f1720-97ae-11eb-862e-850f4426d3d0", - "version": "WzM1MDA1OSwzXQ==", - "eventId": "UdtqqXgBc4D54_cxIGi-", - "timelineId": "1ec3b430-908e-11eb-94fa-c9122cbc0213", - "created": 1617806068114, - "createdBy": "angela", - "updated": 1617806068114, - "updatedBy": "angela" - } - } -} -``` - - - diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts index ba727f6c0d70..72976ab3bf9f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts @@ -92,13 +92,7 @@ describe('clean draft timelines', () => { timelineType: req.body.timelineType, }); expect(response.status).toEqual(200); - expect(response.body).toEqual({ - data: { - persistTimeline: { - timeline: createTimelineWithTimelineId, - }, - }, - }); + expect(response.body).toEqual(createTimelineWithTimelineId); }); test('should return clean existing draft if draft available ', async () => { @@ -121,12 +115,6 @@ describe('clean draft timelines', () => { expect(mockGetTimeline).toHaveBeenCalled(); expect(response.status).toEqual(200); - expect(response.body).toEqual({ - data: { - persistTimeline: { - timeline: mockGetDraftTimelineValue, - }, - }, - }); + expect(response.body).toEqual(mockGetDraftTimelineValue); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts index fb6ffba7995b..639a5a2ae0e8 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts @@ -66,13 +66,7 @@ export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) = ); return response.ok({ - body: { - data: { - persistTimeline: { - timeline: cleanedDraftTimeline, - }, - }, - }, + body: cleanedDraftTimeline, }); } const templateTimelineData = @@ -91,17 +85,14 @@ export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) = if (newTimelineResponse.code === 200) { return response.ok({ - body: { - data: { - persistTimeline: { - timeline: newTimelineResponse.timeline, - }, - }, - }, + body: newTimelineResponse.timeline, + }); + } else { + return siemResponse.error({ + body: newTimelineResponse.message, + statusCode: newTimelineResponse.code, }); } - - return response.ok({}); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts index 75c9b361bc78..49e276f8a4c5 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts @@ -90,13 +90,7 @@ describe('get draft timelines', () => { }); expect(response.status).toEqual(200); - expect(response.body).toEqual({ - data: { - persistTimeline: { - timeline: createTimelineWithTimelineId, - }, - }, - }); + expect(response.body).toEqual(createTimelineWithTimelineId); }); test('should return an existing draft if available', async () => { @@ -110,13 +104,7 @@ describe('get draft timelines', () => { ); expect(mockPersistTimeline).not.toHaveBeenCalled(); expect(response.status).toEqual(200); - expect(response.body).toEqual({ - data: { - persistTimeline: { - timeline: mockGetDraftTimelineValue, - }, - }, - }); + expect(response.body).toEqual(mockGetDraftTimelineValue); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts index e83d2cc839db..37db10f5393b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts @@ -49,13 +49,7 @@ export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) => if (draftTimeline?.savedObjectId) { return response.ok({ - body: { - data: { - persistTimeline: { - timeline: draftTimeline, - }, - }, - }, + body: draftTimeline, }); } @@ -65,18 +59,13 @@ export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) => }); if (newTimelineResponse.code === 200) { - return response.ok({ - body: { - data: { - persistTimeline: { - timeline: newTimelineResponse.timeline, - }, - }, - }, + return response.ok({ body: newTimelineResponse.timeline }); + } else { + return siemResponse.error({ + body: newTimelineResponse.message, + statusCode: newTimelineResponse.code, }); } - - return response.ok({}); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts index 7308801030f4..c2bc36e18c35 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts @@ -15,7 +15,7 @@ import { NOTE_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../utils/common'; -import { DeleteNoteRequestBody, type DeleteNoteResponse } from '../../../../../common/api/timeline'; +import { DeleteNoteRequestBody } from '../../../../../common/api/timeline'; import { deleteNote } from '../../saved_object/notes'; export const deleteNoteRoute = (router: SecuritySolutionPluginRouter) => { @@ -36,7 +36,7 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter) => { }, version: '2023-10-31', }, - async (context, request, response): Promise<IKibanaResponse<DeleteNoteResponse>> => { + async (context, request, response): Promise<IKibanaResponse> => { const siemResponse = buildSiemResponse(response); try { @@ -56,9 +56,7 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter) => { noteIds, }); - return response.ok({ - body: { data: {} }, - }); + return response.ok(); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts index 3a1ae1ba27e2..dc8fffbc0a86 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts @@ -76,8 +76,7 @@ export const getNotesRoute = ( perPage: maxUnassociatedNotes, }; const res = await getAllSavedNote(frameworkRequest, options); - const body: GetNotesResponse = res ?? {}; - return response.ok({ body }); + return response.ok({ body: res }); } // searching for all the notes associated with a specific document id @@ -88,7 +87,7 @@ export const getNotesRoute = ( perPage: maxUnassociatedNotes, }; const res = await getAllSavedNote(frameworkRequest, options); - return response.ok({ body: res ?? {} }); + return response.ok({ body: res }); } // if savedObjectIds is provided, we will search for all the notes associated with the savedObjectIds @@ -106,8 +105,7 @@ export const getNotesRoute = ( perPage: maxUnassociatedNotes, }; const res = await getAllSavedNote(frameworkRequest, options); - const body: GetNotesResponse = res ?? {}; - return response.ok({ body }); + return response.ok({ body: res }); } // searching for all the notes associated with a specific saved object id @@ -120,8 +118,7 @@ export const getNotesRoute = ( perPage: maxUnassociatedNotes, }; const res = await getAllSavedNote(frameworkRequest, options); - const body: GetNotesResponse = res ?? {}; - return response.ok({ body }); + return response.ok({ body: res }); } // retrieving all the notes following the query parameters @@ -236,8 +233,7 @@ export const getNotesRoute = ( options.filter = nodeBuilder.and(filterKueryNodeArray); const res = await getAllSavedNote(frameworkRequest, options); - const body: GetNotesResponse = res ?? {}; - return response.ok({ body }); + return response.ok({ body: res }); } catch (err) { const error = transformError(err); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts index f9759444b26d..12fed18a5c39 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts @@ -53,10 +53,9 @@ export const persistNoteRoute = (router: SecuritySolutionPluginRouter) => { note, overrideOwner: true, }); - const body: PersistNoteRouteResponse = { data: { persistNote: res } }; return response.ok({ - body, + body: res, }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts index 51b001c9ea29..7dd66f86245a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts @@ -61,9 +61,7 @@ export const persistPinnedEventRoute = (router: SecuritySolutionPluginRouter) => ); return response.ok({ - body: { - data: { persistPinnedEventOnTimeline: res }, - }, + body: res, }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts index 502b43d4e347..2241de72b330 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts @@ -44,9 +44,17 @@ export const copyTimelineRoute = (router: SecuritySolutionPluginRouter) => { const frameworkRequest = await buildFrameworkRequest(context, request); const { timeline, timelineIdToCopy } = request.body; const copiedTimeline = await copyTimeline(frameworkRequest, timeline, timelineIdToCopy); - return response.ok({ - body: { data: { persistTimeline: copiedTimeline } }, - }); + + if (copiedTimeline.code === 200) { + return response.ok({ + body: copiedTimeline.timeline, + }); + } else { + return siemResponse.error({ + body: copiedTimeline.message, + statusCode: copiedTimeline.code, + }); + } } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts index a510f24a3563..67e7547b4b5a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.test.ts @@ -69,8 +69,8 @@ describe('create timelines', () => { beforeEach(async () => { jest.doMock('../../../saved_object/timelines', () => { return { - getTimeline: mockGetTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline.mockReturnValue({ + code: 200, timeline: createTimelineWithTimelineId, }), }; @@ -173,6 +173,7 @@ describe('create timelines', () => { return { getTimelineTemplateOrNull: mockGetTimeline.mockReturnValue(null), persistTimeline: mockPersistTimeline.mockReturnValue({ + code: 200, timeline: createTemplateTimelineWithTimelineId, }), }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts index a91fefc20f93..e6af5abd7842 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts @@ -81,13 +81,16 @@ export const createTimelinesRoute = (router: SecuritySolutionPluginRouter) => { timelineVersion: version, }); - return response.ok({ - body: { - data: { - persistTimeline: newTimeline, - }, - }, - }); + if (newTimeline.code === 200) { + return response.ok({ + body: newTimeline.timeline, + }); + } else { + return siemResponse.error({ + statusCode: newTimeline.code, + body: newTimeline.message, + }); + } } else { return siemResponse.error( compareTimelinesStatus.checkIsFailureCases(TimelineStatusActions.create) || { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts index 07cffb3e13bf..4d64ab88c88f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts @@ -8,10 +8,7 @@ import type { IKibanaResponse } from '@kbn/core-http-server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import { - DeleteTimelinesRequestBody, - type DeleteTimelinesResponse, -} from '../../../../../../common/api/timeline'; +import { DeleteTimelinesRequestBody } from '../../../../../../common/api/timeline'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { TIMELINE_URL } from '../../../../../../common/constants'; import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; @@ -37,7 +34,7 @@ export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter) => { request: { body: buildRouteValidationWithZod(DeleteTimelinesRequestBody) }, }, }, - async (context, request, response): Promise<IKibanaResponse<DeleteTimelinesResponse>> => { + async (context, request, response): Promise<IKibanaResponse> => { const siemResponse = buildSiemResponse(response); try { @@ -45,8 +42,7 @@ export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter) => { const { savedObjectIds, searchIds } = request.body; await deleteTimeline(frameworkRequest, savedObjectIds, searchIds); - const body: DeleteTimelinesResponse = { data: { deleteTimeline: true } }; - return response.ok({ body }); + return response.ok(); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts index a1ae2178fb6f..75d61987e775 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts @@ -20,7 +20,6 @@ import { type GetTimelineResponse, } from '../../../../../../common/api/timeline'; import { getTimelineTemplateOrNull, getTimelineOrNull } from '../../../saved_object/timelines'; -import type { ResolvedTimeline, TimelineResponse } from '../../../../../../common/api/timeline'; export const getTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -41,26 +40,33 @@ export const getTimelineRoute = (router: SecuritySolutionPluginRouter) => { }, }, async (context, request, response): Promise<IKibanaResponse<GetTimelineResponse>> => { + const siemResponse = buildSiemResponse(response); + try { const frameworkRequest = await buildFrameworkRequest(context, request); const query = request.query ?? {}; const { template_timeline_id: templateTimelineId, id } = query; - let res: TimelineResponse | ResolvedTimeline | null = null; - if (templateTimelineId != null && id == null) { - res = await getTimelineTemplateOrNull(frameworkRequest, templateTimelineId); + const timeline = await getTimelineTemplateOrNull(frameworkRequest, templateTimelineId); + if (timeline) { + return response.ok({ body: timeline }); + } } else if (templateTimelineId == null && id != null) { - res = await getTimelineOrNull(frameworkRequest, id); + const timelineOrNull = await getTimelineOrNull(frameworkRequest, id); + if (timelineOrNull) { + return response.ok({ body: timelineOrNull }); + } } else { throw new Error('please provide id or template_timeline_id'); } - return response.ok({ body: res ? { data: { getOneTimeline: res } } : {} }); + return siemResponse.error({ + statusCode: 404, + body: 'Could not find timeline', + }); } catch (err) { const error = transformError(err); - const siemResponse = buildSiemResponse(response); - return siemResponse.error({ body: error.message, statusCode: error.statusCode, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts index 01a3801ad867..2ebcb4c38c37 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts @@ -59,7 +59,6 @@ export const getTimelinesRoute = (router: SecuritySolutionPluginRouter) => { sortOrder, } : null; - let res = null; let totalCount = null; if (pageSize == null && pageIndex == null) { @@ -75,7 +74,7 @@ export const getTimelinesRoute = (router: SecuritySolutionPluginRouter) => { totalCount = allActiveTimelines.totalCount; } - res = await getAllTimeline( + const res = await getAllTimeline( frameworkRequest, onlyUserFavorite, { @@ -88,7 +87,7 @@ export const getTimelinesRoute = (router: SecuritySolutionPluginRouter) => { timelineType ); - return response.ok({ body: res ?? {} }); + return response.ok({ body: res }); } catch (err) { const error = transformError(err); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts index f66c5456c039..3e40a7a7ebcb 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts @@ -83,7 +83,7 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi if (res instanceof Error || typeof res === 'string') { throw res; } else { - return response.ok({ body: res ?? {} }); + return response.ok({ body: res }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts index bb2ba80526d0..0c74d259bd22 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.test.ts @@ -69,6 +69,7 @@ describe('update timelines', () => { getTimelineOrNull: mockGetTimeline.mockReturnValue(mockGetTimelineValue), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: updateTimelineWithTimelineId.timeline, + code: 200, }), }; }); @@ -177,6 +178,7 @@ describe('update timelines', () => { }), persistTimeline: mockPersistTimeline.mockReturnValue({ timeline: updateTemplateTimelineWithTimelineId.timeline, + code: 200, }), }; }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts index 7ddea9bd5ffe..340b8611901e 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts @@ -73,13 +73,16 @@ export const patchTimelinesRoute = (router: SecuritySolutionPluginRouter) => { timelineVersion: version, }); - return response.ok({ - body: { - data: { - persistTimeline: updatedTimeline, - }, - }, - }); + if (updatedTimeline.code === 200) { + return response.ok({ + body: updatedTimeline.timeline, + }); + } else { + return siemResponse.error({ + statusCode: updatedTimeline.code, + body: updatedTimeline.message, + }); + } } else { const error = compareTimelinesStatus.checkIsFailureCases(TimelineStatusActions.update); return siemResponse.error( diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts index 22d579229a73..ed3531d8bd74 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts @@ -52,7 +52,7 @@ export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter) => { const { timelineId, templateTimelineId, templateTimelineVersion, timelineType } = request.body; - const timeline = await persistFavorite( + const persistFavoriteResponse = await persistFavorite( frameworkRequest, timelineId || null, templateTimelineId || null, @@ -60,15 +60,16 @@ export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter) => { timelineType || TimelineTypeEnum.default ); - const body: PersistFavoriteRouteResponse = { - data: { - persistFavorite: timeline, - }, - }; - - return response.ok({ - body, - }); + if (persistFavoriteResponse.code !== 200) { + return siemResponse.error({ + body: persistFavoriteResponse.message, + statusCode: persistFavoriteResponse.code, + }); + } else { + return response.ok({ + body: persistFavoriteResponse.favoriteTimeline, + }); + } } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts index 773e74faaaf4..8e6ff9e8adca 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts @@ -21,7 +21,6 @@ import { type ResolveTimelineResponse, } from '../../../../../../common/api/timeline'; import { getTimelineTemplateOrNull, resolveTimelineOrNull } from '../../../saved_object/timelines'; -import type { SavedTimeline, ResolvedTimeline } from '../../../../../../common/api/timeline'; export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned @@ -42,28 +41,39 @@ export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter) => { }, }, async (context, request, response): Promise<IKibanaResponse<ResolveTimelineResponse>> => { + const siemResponse = buildSiemResponse(response); + try { const frameworkRequest = await buildFrameworkRequest(context, request); const query = request.query ?? {}; const { template_timeline_id: templateTimelineId, id } = query; - let res: SavedTimeline | ResolvedTimeline | null = null; - if (templateTimelineId != null && id == null) { // Template timelineId is not a SO id, so it does not need to be updated to use resolve - res = await getTimelineTemplateOrNull(frameworkRequest, templateTimelineId); + const timeline = await getTimelineTemplateOrNull(frameworkRequest, templateTimelineId); + if (timeline) { + return response.ok({ + body: { timeline, outcome: 'exactMatch' }, + }); + } } else if (templateTimelineId == null && id != null) { // In the event the objectId is defined, run the resolve call - res = await resolveTimelineOrNull(frameworkRequest, id); + const timelineOrNull = await resolveTimelineOrNull(frameworkRequest, id); + if (timelineOrNull) { + return response.ok({ + body: timelineOrNull, + }); + } } else { throw new Error('please provide id or template_timeline_id'); } - return response.ok({ body: res ? { data: res } : {} }); + return siemResponse.error({ + statusCode: 404, + body: 'Could not resolve timeline', + }); } catch (err) { const error = transformError(err); - const siemResponse = buildSiemResponse(response); - return siemResponse.error({ body: error.message, statusCode: error.statusCode, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts index 571e0f4da861..3a211efc9565 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/persist_notes.ts @@ -6,7 +6,7 @@ */ import type { FrameworkRequest } from '../../../framework'; -import { persistNote } from './saved_object'; +import { persistNote, type InternalNoteResponse } from './saved_object'; import { getOverridableNote } from './get_overridable_note'; import type { Note } from '../../../../../common/api/timeline'; @@ -16,7 +16,7 @@ export const persistNotes = async ( existingNoteIds?: string[] | null, newNotes?: Note[], overrideOwner: boolean = true -) => { +): Promise<InternalNoteResponse[]> => { return Promise.all( newNotes?.map(async (note) => { const newNote = await getOverridableNote( @@ -31,6 +31,6 @@ export const persistNotes = async ( note: newNote, overrideOwner, }); - }) ?? [] + }) ?? ([] as InternalNoteResponse[]) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts index ff353efe0fb5..7614ff1c1d1d 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/notes/saved_object.ts @@ -6,7 +6,6 @@ */ import { failure } from 'io-ts/lib/PathReporter'; -import { getOr } from 'lodash/fp'; import { v1 as uuidv1 } from 'uuid'; import { pipe } from 'fp-ts/lib/pipeable'; @@ -81,6 +80,11 @@ export const getNotesByTimelineId = async ( return notesByTimelineId.notes; }; +export interface InternalNoteResponse extends ResponseNote { + message: string; + code: number; +} + export const persistNote = async ({ request, noteId, @@ -91,35 +95,18 @@ export const persistNote = async ({ noteId: string | null; note: BareNote | BareNoteWithoutExternalRefs; overrideOwner?: boolean; -}): Promise<ResponseNote> => { - try { - if (noteId == null) { - return await createNote({ - request, - noteId, - note, - overrideOwner, - }); - } - - // Update existing note - return await updateNote({ request, noteId, note, overrideOwner }); - } catch (err) { - if (getOr(null, 'output.statusCode', err) === 403) { - const noteToReturn: Note = { - ...note, - noteId: uuidv1(), - version: '', - timelineId: '', - }; - return { - code: 403, - message: err.message, - note: noteToReturn, - }; - } - throw err; +}): Promise<InternalNoteResponse> => { + if (noteId == null) { + return createNote({ + request, + noteId, + note, + overrideOwner, + }); } + + // Update existing note + return updateNote({ request, noteId, note, overrideOwner }); }; export const createNote = async ({ @@ -132,7 +119,7 @@ export const createNote = async ({ noteId: string | null; note: BareNote | BareNoteWithoutExternalRefs; overrideOwner?: boolean; -}): Promise<ResponseNote> => { +}): Promise<InternalNoteResponse> => { const { savedObjects: { client: savedObjectsClient }, uiSettings: { client: uiSettingsClient }, @@ -203,7 +190,7 @@ export const updateNote = async ({ noteId: string; note: BareNote | BareNoteWithoutExternalRefs; overrideOwner?: boolean; -}): Promise<ResponseNote> => { +}): Promise<InternalNoteResponse> => { const savedObjectsClient = (await request.context.core).savedObjects.client; const userInfo = request.user; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts index 5181a099ae7f..e7537b94a5d6 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/pinned_events/index.ts @@ -6,7 +6,6 @@ */ import { failure } from 'io-ts/lib/PathReporter'; -import { getOr } from 'lodash/fp'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; @@ -77,44 +76,24 @@ export const persistPinnedEventOnTimeline = async ( eventId: string, timelineId: string ): Promise<PersistPinnedEventResponse> => { - try { - if (pinnedEventId != null) { - // Delete Pinned Event on Timeline - await deletePinnedEventOnTimeline(request, [pinnedEventId]); - return null; - } - - const pinnedEvents = await getPinnedEventsInTimelineWithEventId(request, timelineId, eventId); - - // we already had this event pinned so let's just return the one we already had - if (pinnedEvents.length > 0) { - return { ...pinnedEvents[0], code: 200 }; - } - - return await createPinnedEvent({ - request, - eventId, - timelineId, - }); - } catch (err) { - if (getOr(null, 'output.statusCode', err) === 404) { - /* - * Why we are doing that, because if it is not found for sure that it will be unpinned - * There is no need to bring back this error since we can assume that it is unpinned - */ - return null; - } - if (getOr(null, 'output.statusCode', err) === 403) { - return pinnedEventId != null - ? { - code: 403, - message: err.message, - pinnedEventId: eventId, - } - : null; - } - throw err; + if (pinnedEventId != null) { + // Delete Pinned Event on Timeline + await deletePinnedEventOnTimeline(request, [pinnedEventId]); + return { unpinned: true }; + } + + const pinnedEvents = await getPinnedEventsInTimelineWithEventId(request, timelineId, eventId); + + // we already had this event pinned so let's just return the one we already had + if (pinnedEvents.length > 0) { + return { ...pinnedEvents[0] }; } + + return createPinnedEvent({ + request, + eventId, + timelineId, + }); }; const getPinnedEventsInTimelineWithEventId = async ( @@ -172,7 +151,6 @@ const createPinnedEvent = async ({ // create Pinned Event on Timeline return { ...convertSavedObjectToSavedPinnedEvent(repopulatedSavedObject), - code: 200, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts index c3016164d4e7..f8e99f2831ae 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts @@ -298,61 +298,67 @@ export const getDraftTimeline = async ( return getAllSavedTimeline(request, options); }; +interface InternalPersistFavoriteResponse { + code: number; + message: string; + favoriteTimeline: FavoriteTimelineResponse; +} + export const persistFavorite = async ( request: FrameworkRequest, timelineId: string | null, templateTimelineId: string | null, templateTimelineVersion: number | null, timelineType: TimelineType -): Promise<FavoriteTimelineResponse> => { +): Promise<InternalPersistFavoriteResponse> => { const userName = request.user?.username ?? UNAUTHENTICATED_USER; const fullName = request.user?.full_name ?? ''; - try { - let timeline: SavedTimeline = {}; - if (timelineId != null) { - const { - eventIdToNoteIds, - notes, - noteIds, - pinnedEventIds, - pinnedEventsSaveObject, - savedObjectId, - version, - ...savedTimeline - } = await getBasicSavedTimeline(request, timelineId); - timelineId = savedObjectId; // eslint-disable-line no-param-reassign - timeline = savedTimeline; - } + let timeline: SavedTimeline = {}; + if (timelineId != null) { + const { + eventIdToNoteIds, + notes, + noteIds, + pinnedEventIds, + pinnedEventsSaveObject, + savedObjectId, + version, + ...savedTimeline + } = await getBasicSavedTimeline(request, timelineId); + timelineId = savedObjectId; // eslint-disable-line no-param-reassign + timeline = savedTimeline; + } - const userFavoriteTimeline = { - keySearch: userName != null ? convertStringToBase64(userName) : null, - favoriteDate: new Date().valueOf(), - fullName, - userName, - }; - if (timeline.favorite != null) { - const alreadyExistsTimelineFavoriteByUser = timeline.favorite.findIndex( - (user) => user.userName === userName - ); - - timeline.favorite = - alreadyExistsTimelineFavoriteByUser > -1 - ? [ - ...timeline.favorite.slice(0, alreadyExistsTimelineFavoriteByUser), - ...timeline.favorite.slice(alreadyExistsTimelineFavoriteByUser + 1), - ] - : [...timeline.favorite, userFavoriteTimeline]; - } else if (timeline.favorite == null) { - timeline.favorite = [userFavoriteTimeline]; - } + const userFavoriteTimeline = { + keySearch: userName != null ? convertStringToBase64(userName) : null, + favoriteDate: new Date().valueOf(), + fullName, + userName, + }; + if (timeline.favorite != null) { + const alreadyExistsTimelineFavoriteByUser = timeline.favorite.findIndex( + (user) => user.userName === userName + ); - const persistResponse = await persistTimeline(request, timelineId, null, { - ...timeline, - templateTimelineId, - templateTimelineVersion, - timelineType, - }); - return { + timeline.favorite = + alreadyExistsTimelineFavoriteByUser > -1 + ? [ + ...timeline.favorite.slice(0, alreadyExistsTimelineFavoriteByUser), + ...timeline.favorite.slice(alreadyExistsTimelineFavoriteByUser + 1), + ] + : [...timeline.favorite, userFavoriteTimeline]; + } else if (timeline.favorite == null) { + timeline.favorite = [userFavoriteTimeline]; + } + + const persistResponse = await persistTimeline(request, timelineId, null, { + ...timeline, + templateTimelineId, + templateTimelineVersion, + timelineType, + }); + return { + favoriteTimeline: { savedObjectId: persistResponse.timeline.savedObjectId, version: persistResponse.timeline.version, favorite: @@ -362,19 +368,10 @@ export const persistFavorite = async ( templateTimelineId, templateTimelineVersion, timelineType, - }; - } catch (err) { - if (getOr(null, 'output.statusCode', err) === 403) { - return { - savedObjectId: '', - version: '', - favorite: [], - code: 403, - message: err.message, - }; - } - throw err; - } + }, + code: persistResponse.code, + message: persistResponse.message, + }; }; export interface InternalTimelineResponse { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index e2ec9d0e1b53..98bbf3a80777 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -56,7 +56,11 @@ import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerPolicyRoutes } from './endpoint/routes/policy'; import { registerActionRoutes } from './endpoint/routes/actions'; import { registerEndpointSuggestionsRoutes } from './endpoint/routes/suggestions'; -import { EndpointArtifactClient, ManifestManager } from './endpoint/services'; +import { + EndpointArtifactClient, + ManifestManager, + securityWorkflowInsightsService, +} from './endpoint/services'; import { EndpointAppContextService } from './endpoint/endpoint_app_context_services'; import type { EndpointAppContext } from './endpoint/types'; import { initUsageCollectors } from './usage'; @@ -519,6 +523,12 @@ export class Plugin implements ISecuritySolutionPlugin { featureUsageService.setup(plugins.licensing); + securityWorkflowInsightsService.setup({ + kibanaVersion: pluginContext.env.packageInfo.version, + logger: this.logger, + isFeatureEnabled: config.experimentalFeatures.defendInsights, + }); + return { setProductFeaturesConfigurator: productFeaturesService.setProductFeaturesConfigurator.bind(productFeaturesService), @@ -672,6 +682,12 @@ export class Plugin implements ISecuritySolutionPlugin { this.telemetryReceiver ); + securityWorkflowInsightsService + .start({ + esClient: core.elasticsearch.client.asInternalUser, + }) + .catch(() => {}); + const endpointPkgInstallationPromise = this.endpointContext.service .getInternalFleetServices() .packages.getInstallation(FLEET_ENDPOINT_PACKAGE); @@ -727,6 +743,7 @@ export class Plugin implements ISecuritySolutionPlugin { this.policyWatcher?.stop(); this.completeExternalResponseActionsTask.stop().catch(() => {}); this.siemMigrationsService.stop(); + securityWorkflowInsightsService.stop(); licenseService.stop(); } } diff --git a/x-pack/plugins/security_solution/server/routes/limited_concurrency.ts b/x-pack/plugins/security_solution/server/routes/limited_concurrency.ts index bce6c835a7ed..396bad7d2ce2 100644 --- a/x-pack/plugins/security_solution/server/routes/limited_concurrency.ts +++ b/x-pack/plugins/security_solution/server/routes/limited_concurrency.ts @@ -6,11 +6,11 @@ */ import type { - CoreSetup, KibanaRequest, LifecycleResponseFactory, OnPreAuthToolkit, -} from '@kbn/core/server'; +} from '@kbn/core-http-server'; +import type { CoreSetup } from '@kbn/core-lifecycle-server'; import { LIMITED_CONCURRENCY_ROUTE_TAG_PREFIX } from '../../common/constants'; class MaxCounter { diff --git a/x-pack/plugins/security_solution/server/search_strategy/helpers/format_response_object_values.ts b/x-pack/plugins/security_solution/server/search_strategy/helpers/format_response_object_values.ts index f44ad77e6792..d80be5f4a421 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/helpers/format_response_object_values.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/helpers/format_response_object_values.ts @@ -7,9 +7,7 @@ import { mapValues, isObject, isArray } from 'lodash/fp'; import { set } from '@kbn/safer-lodash-set'; - -import { toArray } from '../../../common/utils/to_array'; -import { isGeoField } from '../../../common/utils/field_formatters'; +import { toArray, isGeoField } from '@kbn/timelines-plugin/common'; export const mapObjectValuesToStringArray = (object: object): object => mapValues((o) => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/helpers/get_flattened_fields.ts b/x-pack/plugins/security_solution/server/search_strategy/helpers/get_flattened_fields.ts index f40edfc5914d..baed5b3c3560 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/helpers/get_flattened_fields.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/helpers/get_flattened_fields.ts @@ -6,7 +6,7 @@ */ import { set } from '@kbn/safer-lodash-set'; import { get, isEmpty } from 'lodash/fp'; -import { toObjectArrayOfStrings } from '../../../common/utils/to_array'; +import { toObjectArrayOfStrings } from '@kbn/timelines-plugin/common'; export function getFlattenedFields<T>( fields: string[], diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts index 8707f10ed01c..61ab1c5bca58 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts @@ -8,12 +8,12 @@ import { set } from '@kbn/safer-lodash-set/fp'; import { get, has } from 'lodash/fp'; import { hostFieldsMap } from '@kbn/securitysolution-ecs'; +import { toObjectArrayOfStrings } from '@kbn/timelines-plugin/common'; import type { HostAggEsItem, HostsEdges, HostValue, } from '../../../../../../common/search_strategy/security_solution/hosts'; -import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; export const HOSTS_FIELDS: readonly string[] = [ '_id', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts index 94d45b2b63e0..cc1a084f6b5a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts @@ -13,6 +13,7 @@ import type { SavedObjectsClientContract, } from '@kbn/core/server'; import { hostFieldsMap } from '@kbn/securitysolution-ecs'; +import { toObjectArrayOfStrings } from '@kbn/timelines-plugin/common'; import { Direction } from '../../../../../../common/search_strategy/common'; import type { AggregationRequest, @@ -22,7 +23,6 @@ import type { HostItem, HostValue, } from '../../../../../../common/search_strategy/security_solution/hosts'; -import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; import type { EndpointAppContext } from '../../../../../endpoint/types'; import { getPendingActionsSummary } from '../../../../../endpoint/services'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.ts index 06452c915009..b92b67f244eb 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/authentications/helpers.ts @@ -8,7 +8,7 @@ import { get, getOr, isEmpty } from 'lodash/fp'; import { set } from '@kbn/safer-lodash-set/fp'; import { sourceFieldsMap, hostFieldsMap } from '@kbn/securitysolution-ecs'; -import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; +import { toObjectArrayOfStrings } from '@kbn/timelines-plugin/common'; import type { AuthenticationsEdges, AuthenticationHit, diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 43b4665b8d9d..4a00ef93abe6 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -221,7 +221,6 @@ "@kbn/entities-schema", "@kbn/inference-plugin", "@kbn/core-saved-objects-server-mocks", - "@kbn/core-http-router-server-internal", "@kbn/core-security-server-mocks", "@kbn/serverless", "@kbn/core-user-profile-browser", @@ -230,6 +229,7 @@ "@kbn/core-user-profile-common", "@kbn/langchain", "@kbn/react-hooks", - "@kbn/index-adapter" + "@kbn/index-adapter", + "@kbn/core-http-server-utils" ] } diff --git a/x-pack/plugins/serverless_observability/kibana.jsonc b/x-pack/plugins/serverless_observability/kibana.jsonc index fce943c44865..ad2c1f76ce56 100644 --- a/x-pack/plugins/serverless_observability/kibana.jsonc +++ b/x-pack/plugins/serverless_observability/kibana.jsonc @@ -25,7 +25,9 @@ "discover", "security" ], - "optionalPlugins": [], + "optionalPlugins": [ + "streams" + ], "requiredBundles": [] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/serverless_observability/public/navigation_tree.ts b/x-pack/plugins/serverless_observability/public/navigation_tree.ts index 7501a75abe87..e6fb3c910730 100644 --- a/x-pack/plugins/serverless_observability/public/navigation_tree.ts +++ b/x-pack/plugins/serverless_observability/public/navigation_tree.ts @@ -8,374 +8,387 @@ import { i18n } from '@kbn/i18n'; import type { NavigationTreeDefinition } from '@kbn/core-chrome-browser'; -export const navigationTree: NavigationTreeDefinition = { - body: [ - { type: 'recentlyAccessed' }, - { - type: 'navGroup', - id: 'observability_project_nav', - title: 'Observability', - icon: 'logoObservability', - defaultIsCollapsed: false, - isCollapsible: false, - breadcrumbStatus: 'hidden', - children: [ - { - title: i18n.translate('xpack.serverlessObservability.nav.discover', { - defaultMessage: 'Discover', - }), - link: 'last-used-logs-viewer', - // avoid duplicate "Discover" breadcrumbs - breadcrumbStatus: 'hidden', - renderAs: 'item', - children: [ - { - link: 'discover', - children: [ - { - link: 'observability-logs-explorer', - }, - ], - }, - ], - }, - { - title: i18n.translate('xpack.serverlessObservability.nav.dashboards', { - defaultMessage: 'Dashboards', - }), - link: 'dashboards', - getIsActive: ({ pathNameSerialized, prepend }) => { - return pathNameSerialized.startsWith(prepend('/app/dashboards')); +export const createNavigationTree = ({ + streamsAvailable, +}: { + streamsAvailable?: boolean; +}): NavigationTreeDefinition => { + return { + body: [ + { type: 'recentlyAccessed' }, + { + type: 'navGroup', + id: 'observability_project_nav', + title: 'Observability', + icon: 'logoObservability', + defaultIsCollapsed: false, + isCollapsible: false, + breadcrumbStatus: 'hidden', + children: [ + { + title: i18n.translate('xpack.serverlessObservability.nav.discover', { + defaultMessage: 'Discover', + }), + link: 'last-used-logs-viewer', + // avoid duplicate "Discover" breadcrumbs + breadcrumbStatus: 'hidden', + renderAs: 'item', + children: [ + { + link: 'discover', + children: [ + { + link: 'observability-logs-explorer', + }, + ], + }, + ], }, - }, - { - link: 'observability-overview:alerts', - }, - { - link: 'observability-overview:cases', - renderAs: 'item', - children: [ - { - link: 'observability-overview:cases_configure', - }, - { - link: 'observability-overview:cases_create', - }, - ], - }, - { - title: i18n.translate('xpack.serverlessObservability.nav.slo', { - defaultMessage: 'SLOs', - }), - link: 'slo', - }, - { - link: 'observabilityAIAssistant', - title: i18n.translate('xpack.serverlessObservability.nav.aiAssistant', { - defaultMessage: 'AI Assistant', - }), - }, - { link: 'inventory', spaceBefore: 'm' }, - { - id: 'apm', - title: i18n.translate('xpack.serverlessObservability.nav.applications', { - defaultMessage: 'Applications', - }), - renderAs: 'panelOpener', - children: [ - { - children: [ - { - link: 'apm:services', - title: i18n.translate('xpack.serverlessObservability.nav.apm.services', { - defaultMessage: 'Service inventory', - }), - }, - { link: 'apm:traces' }, - { link: 'apm:dependencies' }, - { link: 'apm:settings' }, - { - id: 'synthetics', - title: i18n.translate('xpack.serverlessObservability.nav.synthetics', { - defaultMessage: 'Synthetics', - }), - children: [ - { - title: i18n.translate( - 'xpack.serverlessObservability.nav.synthetics.overviewItem', - { - defaultMessage: 'Overview', - } - ), - id: 'synthetics-overview', - link: 'synthetics:overview', - breadcrumbStatus: 'hidden', - }, - { - link: 'synthetics:certificates', - title: i18n.translate( - 'xpack.serverlessObservability.nav.synthetics.certificatesItem', - { - defaultMessage: 'TLS certificates', - } - ), - id: 'synthetics-certificates', - breadcrumbStatus: 'hidden', - }, - ], - }, - ], + { + title: i18n.translate('xpack.serverlessObservability.nav.dashboards', { + defaultMessage: 'Dashboards', + }), + link: 'dashboards', + getIsActive: ({ pathNameSerialized, prepend }) => { + return pathNameSerialized.startsWith(prepend('/app/dashboards')); }, - ], - }, - { - id: 'metrics', - title: i18n.translate('xpack.serverlessObservability.nav.infrastructure', { - defaultMessage: 'Infrastructure', - }), - renderAs: 'panelOpener', - children: [ - { - children: [ - { - link: 'metrics:inventory', - title: i18n.translate( - 'xpack.serverlessObservability.nav.infrastructureInventory', - { - defaultMessage: 'Infrastructure inventory', - } - ), - }, - { link: 'metrics:hosts' }, - { link: 'metrics:settings' }, - { link: 'metrics:assetDetails' }, - ], - }, - ], - }, - { - id: 'machine_learning-landing', - renderAs: 'panelOpener', - title: i18n.translate('xpack.serverlessObservability.nav.machineLearning', { - defaultMessage: 'Machine learning', - }), - children: [ - { - children: [ - { - link: 'ml:overview', - }, - { - link: 'ml:notifications', - }, - { - link: 'ml:memoryUsage', - title: i18n.translate( - 'xpack.serverlessObservability.nav.machineLearning.memoryUsage', - { - defaultMessage: 'Memory usage', - } - ), - }, - ], - }, - { - id: 'category-anomaly_detection', - title: i18n.translate('xpack.serverlessObservability.nav.ml.anomaly_detection', { - defaultMessage: 'Anomaly detection', - }), - breadcrumbStatus: 'hidden', - children: [ - { - link: 'ml:anomalyDetection', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.anomaly_detection.jobs', - { - defaultMessage: 'Jobs', - } - ), - }, - { - link: 'ml:anomalyExplorer', - }, - { - link: 'ml:singleMetricViewer', - }, - { - link: 'ml:settings', - }, - { - link: 'ml:suppliedConfigurations', - }, - ], - }, - { - id: 'category-data_frame analytics', - title: i18n.translate('xpack.serverlessObservability.nav.ml.data_frame_analytics', { - defaultMessage: 'Data frame analytics', - }), - breadcrumbStatus: 'hidden', - children: [ - { - link: 'ml:dataFrameAnalytics', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.data_frame_analytics.jobs', - { - defaultMessage: 'Jobs', - } - ), - }, - { - link: 'ml:resultExplorer', - }, - { - link: 'ml:analyticsMap', - }, - ], - }, - { - id: 'category-model_management', - title: i18n.translate('xpack.serverlessObservability.nav.ml.model_management', { - defaultMessage: 'Model management', - }), - breadcrumbStatus: 'hidden', - children: [ - { - link: 'ml:nodesOverview', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.model_management.trainedModels', - { - defaultMessage: 'Trained models', - } - ), - }, - ], - }, - { - id: 'category-data_visualizer', - title: i18n.translate('xpack.serverlessObservability.nav.ml.data_visualizer', { - defaultMessage: 'Data visualizer', - }), - breadcrumbStatus: 'hidden', - children: [ - { - link: 'ml:fileUpload', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.data_visualizer.file_data_visualizer', - { - defaultMessage: 'File data visualizer', - } - ), - }, - { - link: 'ml:indexDataVisualizer', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.data_visualizer.data_view_data_visualizer', - { - defaultMessage: 'Data view data visualizer', - } - ), - }, - { - link: 'ml:dataDrift', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.data_visualizer.data_drift', - { - defaultMessage: 'Data drift', - } - ), - }, - ], - }, - { - id: 'category-aiops_labs', - title: i18n.translate('xpack.serverlessObservability.nav.ml.aiops_labs', { - defaultMessage: 'Aiops labs', - }), - breadcrumbStatus: 'hidden', - children: [ - { - link: 'ml:logRateAnalysis', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.aiops_labs.log_rate_analysis', - { - defaultMessage: 'Log rate analysis', - } - ), - }, - { - link: 'ml:logPatternAnalysis', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.aiops_labs.log_pattern_analysis', - { - defaultMessage: 'Log pattern analysis', - } - ), - }, + }, + { + link: 'observability-overview:alerts', + }, + { + link: 'observability-overview:cases', + renderAs: 'item', + children: [ + { + link: 'observability-overview:cases_configure', + }, + { + link: 'observability-overview:cases_create', + }, + ], + }, + { + title: i18n.translate('xpack.serverlessObservability.nav.slo', { + defaultMessage: 'SLOs', + }), + link: 'slo', + }, + { + link: 'observabilityAIAssistant', + title: i18n.translate('xpack.serverlessObservability.nav.aiAssistant', { + defaultMessage: 'AI Assistant', + }), + }, + { link: 'inventory', spaceBefore: 'm' }, + ...(streamsAvailable + ? [ { - link: 'ml:changePointDetections', - title: i18n.translate( - 'xpack.serverlessObservability.nav.ml.aiops_labs.change_point_detection', - { - defaultMessage: 'Change point detection', - } - ), + link: 'streams' as const, }, - ], - }, - ], - }, - ], - }, - ], - footer: [ - { - type: 'navItem', - title: i18n.translate('xpack.serverlessObservability.nav.getStarted', { - defaultMessage: 'Add data', - }), - link: 'observabilityOnboarding', - icon: 'launch', - }, - { - type: 'navItem', - id: 'devTools', - title: i18n.translate('xpack.serverlessObservability.nav.devTools', { - defaultMessage: 'Developer tools', - }), - link: 'dev_tools', - icon: 'editorCodeBlock', - }, - { - type: 'navGroup', - id: 'project_settings_project_nav', - title: i18n.translate('xpack.serverlessObservability.nav.projectSettings', { - defaultMessage: 'Project settings', - }), - icon: 'gear', - breadcrumbStatus: 'hidden', - children: [ - { - link: 'management', - title: i18n.translate('xpack.serverlessObservability.nav.mngt', { - defaultMessage: 'Management', - }), - }, - { - link: 'integrations', - }, - { - link: 'fleet', - }, - { - id: 'cloudLinkUserAndRoles', - cloudLink: 'userAndRoles', - }, - { - id: 'cloudLinkBilling', - cloudLink: 'billingAndSub', - }, - ], - }, - ], + ] + : []), + { + id: 'apm', + title: i18n.translate('xpack.serverlessObservability.nav.applications', { + defaultMessage: 'Applications', + }), + renderAs: 'panelOpener', + children: [ + { + children: [ + { + link: 'apm:services', + title: i18n.translate('xpack.serverlessObservability.nav.apm.services', { + defaultMessage: 'Service inventory', + }), + }, + { link: 'apm:traces' }, + { link: 'apm:dependencies' }, + { link: 'apm:settings' }, + { + id: 'synthetics', + title: i18n.translate('xpack.serverlessObservability.nav.synthetics', { + defaultMessage: 'Synthetics', + }), + children: [ + { + title: i18n.translate( + 'xpack.serverlessObservability.nav.synthetics.overviewItem', + { + defaultMessage: 'Overview', + } + ), + id: 'synthetics-overview', + link: 'synthetics:overview', + breadcrumbStatus: 'hidden', + }, + { + link: 'synthetics:certificates', + title: i18n.translate( + 'xpack.serverlessObservability.nav.synthetics.certificatesItem', + { + defaultMessage: 'TLS certificates', + } + ), + id: 'synthetics-certificates', + breadcrumbStatus: 'hidden', + }, + ], + }, + ], + }, + ], + }, + { + id: 'metrics', + title: i18n.translate('xpack.serverlessObservability.nav.infrastructure', { + defaultMessage: 'Infrastructure', + }), + renderAs: 'panelOpener', + children: [ + { + children: [ + { + link: 'metrics:inventory', + title: i18n.translate( + 'xpack.serverlessObservability.nav.infrastructureInventory', + { + defaultMessage: 'Infrastructure inventory', + } + ), + }, + { link: 'metrics:hosts' }, + { link: 'metrics:settings' }, + { link: 'metrics:assetDetails' }, + ], + }, + ], + }, + { + id: 'machine_learning-landing', + renderAs: 'panelOpener', + title: i18n.translate('xpack.serverlessObservability.nav.machineLearning', { + defaultMessage: 'Machine learning', + }), + children: [ + { + children: [ + { + link: 'ml:overview', + }, + { + link: 'ml:notifications', + }, + { + link: 'ml:memoryUsage', + title: i18n.translate( + 'xpack.serverlessObservability.nav.machineLearning.memoryUsage', + { + defaultMessage: 'Memory usage', + } + ), + }, + ], + }, + { + id: 'category-anomaly_detection', + title: i18n.translate('xpack.serverlessObservability.nav.ml.anomaly_detection', { + defaultMessage: 'Anomaly detection', + }), + breadcrumbStatus: 'hidden', + children: [ + { + link: 'ml:anomalyDetection', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.anomaly_detection.jobs', + { + defaultMessage: 'Jobs', + } + ), + }, + { + link: 'ml:anomalyExplorer', + }, + { + link: 'ml:singleMetricViewer', + }, + { + link: 'ml:settings', + }, + { + link: 'ml:suppliedConfigurations', + }, + ], + }, + { + id: 'category-data_frame analytics', + title: i18n.translate('xpack.serverlessObservability.nav.ml.data_frame_analytics', { + defaultMessage: 'Data frame analytics', + }), + breadcrumbStatus: 'hidden', + children: [ + { + link: 'ml:dataFrameAnalytics', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.data_frame_analytics.jobs', + { + defaultMessage: 'Jobs', + } + ), + }, + { + link: 'ml:resultExplorer', + }, + { + link: 'ml:analyticsMap', + }, + ], + }, + { + id: 'category-model_management', + title: i18n.translate('xpack.serverlessObservability.nav.ml.model_management', { + defaultMessage: 'Model management', + }), + breadcrumbStatus: 'hidden', + children: [ + { + link: 'ml:nodesOverview', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.model_management.trainedModels', + { + defaultMessage: 'Trained models', + } + ), + }, + ], + }, + { + id: 'category-data_visualizer', + title: i18n.translate('xpack.serverlessObservability.nav.ml.data_visualizer', { + defaultMessage: 'Data visualizer', + }), + breadcrumbStatus: 'hidden', + children: [ + { + link: 'ml:fileUpload', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.data_visualizer.file_data_visualizer', + { + defaultMessage: 'File data visualizer', + } + ), + }, + { + link: 'ml:indexDataVisualizer', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.data_visualizer.data_view_data_visualizer', + { + defaultMessage: 'Data view data visualizer', + } + ), + }, + { + link: 'ml:dataDrift', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.data_visualizer.data_drift', + { + defaultMessage: 'Data drift', + } + ), + }, + ], + }, + { + id: 'category-aiops_labs', + title: i18n.translate('xpack.serverlessObservability.nav.ml.aiops_labs', { + defaultMessage: 'Aiops labs', + }), + breadcrumbStatus: 'hidden', + children: [ + { + link: 'ml:logRateAnalysis', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.aiops_labs.log_rate_analysis', + { + defaultMessage: 'Log rate analysis', + } + ), + }, + { + link: 'ml:logPatternAnalysis', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.aiops_labs.log_pattern_analysis', + { + defaultMessage: 'Log pattern analysis', + } + ), + }, + { + link: 'ml:changePointDetections', + title: i18n.translate( + 'xpack.serverlessObservability.nav.ml.aiops_labs.change_point_detection', + { + defaultMessage: 'Change point detection', + } + ), + }, + ], + }, + ], + }, + ], + }, + ], + footer: [ + { + type: 'navItem', + title: i18n.translate('xpack.serverlessObservability.nav.getStarted', { + defaultMessage: 'Add data', + }), + link: 'observabilityOnboarding', + icon: 'launch', + }, + { + type: 'navItem', + id: 'devTools', + title: i18n.translate('xpack.serverlessObservability.nav.devTools', { + defaultMessage: 'Developer tools', + }), + link: 'dev_tools', + icon: 'editorCodeBlock', + }, + { + type: 'navGroup', + id: 'project_settings_project_nav', + title: i18n.translate('xpack.serverlessObservability.nav.projectSettings', { + defaultMessage: 'Project settings', + }), + icon: 'gear', + breadcrumbStatus: 'hidden', + children: [ + { + link: 'management', + title: i18n.translate('xpack.serverlessObservability.nav.mngt', { + defaultMessage: 'Management', + }), + }, + { + link: 'integrations', + }, + { + link: 'fleet', + }, + { + id: 'cloudLinkUserAndRoles', + cloudLink: 'userAndRoles', + }, + { + id: 'cloudLinkBilling', + cloudLink: 'billingAndSub', + }, + ], + }, + ], + }; }; diff --git a/x-pack/plugins/serverless_observability/public/plugin.ts b/x-pack/plugins/serverless_observability/public/plugin.ts index 05d598b2b3a7..774a76749f8d 100644 --- a/x-pack/plugins/serverless_observability/public/plugin.ts +++ b/x-pack/plugins/serverless_observability/public/plugin.ts @@ -8,8 +8,8 @@ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { appCategories, appIds } from '@kbn/management-cards-navigation'; -import { of } from 'rxjs'; -import { navigationTree } from './navigation_tree'; +import { map, of } from 'rxjs'; +import { createNavigationTree } from './navigation_tree'; import { createObservabilityDashboardRegistration } from './logs_signal/overview_registration'; import { ServerlessObservabilityPublicSetup, @@ -50,7 +50,11 @@ export class ServerlessObservabilityPlugin setupDeps: ServerlessObservabilityPublicStartDependencies ): ServerlessObservabilityPublicStart { const { serverless, management, security } = setupDeps; - const navigationTree$ = of(navigationTree); + const navigationTree$ = (setupDeps.streams?.status$ || of({ status: 'disabled' })).pipe( + map(({ status }) => { + return createNavigationTree({ streamsAvailable: status === 'enabled' }); + }) + ); serverless.setProjectHome('/app/observability/landing'); serverless.initNavigation('oblt', navigationTree$, { dataTestSubj: 'svlObservabilitySideNav' }); const aiAssistantIsEnabled = core.application.capabilities.observabilityAIAssistant?.show; diff --git a/x-pack/plugins/serverless_observability/public/types.ts b/x-pack/plugins/serverless_observability/public/types.ts index c93865f0f596..23da6c12637d 100644 --- a/x-pack/plugins/serverless_observability/public/types.ts +++ b/x-pack/plugins/serverless_observability/public/types.ts @@ -8,13 +8,14 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DiscoverSetup } from '@kbn/discover-plugin/public'; import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; -import { ObservabilityPublicSetup } from '@kbn/observability-plugin/public'; -import { +import type { ObservabilityPublicSetup } from '@kbn/observability-plugin/public'; +import type { ObservabilitySharedPluginSetup, ObservabilitySharedPluginStart, } from '@kbn/observability-shared-plugin/public'; -import { SecurityPluginStart } from '@kbn/security-plugin/public'; -import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import type { SecurityPluginStart } from '@kbn/security-plugin/public'; +import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import type { StreamsPluginStart, StreamsPluginSetup } from '@kbn/streams-plugin/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessObservabilityPublicSetup {} @@ -28,6 +29,7 @@ export interface ServerlessObservabilityPublicSetupDependencies { serverless: ServerlessPluginSetup; management: ManagementSetup; discover: DiscoverSetup; + streams?: StreamsPluginSetup; } export interface ServerlessObservabilityPublicStartDependencies { @@ -36,4 +38,5 @@ export interface ServerlessObservabilityPublicStartDependencies { management: ManagementStart; data: DataPublicPluginStart; security: SecurityPluginStart; + streams?: StreamsPluginStart; } diff --git a/x-pack/plugins/serverless_observability/tsconfig.json b/x-pack/plugins/serverless_observability/tsconfig.json index 3d909bf88b65..5aa97143107a 100644 --- a/x-pack/plugins/serverless_observability/tsconfig.json +++ b/x-pack/plugins/serverless_observability/tsconfig.json @@ -30,5 +30,6 @@ "@kbn/discover-plugin", "@kbn/security-plugin", "@kbn/search-types", + "@kbn/streams-plugin", ] } diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx index e631fac8d6d2..5531210e10a3 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock'; import { useIOLines, useXtermPlayer, XtermPlayerDeps } from './hooks'; import type { ProcessEventsPage } from '../../../common'; @@ -31,7 +32,7 @@ describe('TTYPlayer/hooks', () => { // test memoization let last = result.current.lines; - rerender(); + rerender({ pages: initial }); expect(result.current.lines === last).toBeTruthy(); last = result.current.lines; rerender({ pages: [...initial] }); diff --git a/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx index 7918a6fdda69..cf79e32dfce8 100644 --- a/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock'; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.test.ts b/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.test.ts index 72bfa570df5c..dcc9fe1932f5 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.test.ts +++ b/x-pack/plugins/snapshot_restore/public/application/components/collapsible_lists/use_collapsible_list.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useCollapsibleList } from './use_collapsible_list'; diff --git a/x-pack/plugins/spaces/public/management/components/solution_view/solution_view.tsx b/x-pack/plugins/spaces/public/management/components/solution_view/solution_view.tsx index 701490be4c4c..350e9151d8ea 100644 --- a/x-pack/plugins/spaces/public/management/components/solution_view/solution_view.tsx +++ b/x-pack/plugins/spaces/public/management/components/solution_view/solution_view.tsx @@ -8,7 +8,6 @@ import type { EuiSuperSelectOption, EuiThemeComputed } from '@elastic/eui'; import { EuiBetaBadge, - EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -43,7 +42,7 @@ const getOptions = ({ size }: EuiThemeComputed): Array<EuiSuperSelectOption<Solu <EuiIcon type="logoElasticsearch" css={iconCss} /> {i18n.translate( 'xpack.spaces.management.manageSpacePage.solutionViewSelect.searchOptionLabel', - { defaultMessage: 'Search' } + { defaultMessage: 'Elasticsearch' } )} </> ), @@ -181,21 +180,6 @@ export const SolutionView: FunctionComponent<Props> = ({ isInvalid={validator.validateSolutionView(space, isEditing).isInvalid} /> </EuiFormRow> - - {showClassicDefaultViewCallout && ( - <> - <EuiSpacer size="m" /> - <EuiCallOut - color="primary" - size="s" - iconType="iInCircle" - title={i18n.translate( - 'xpack.spaces.management.manageSpacePage.solutionViewSelect.classicDefaultViewCallout', - { defaultMessage: 'By default your current view is Classic' } - )} - /> - </> - )} </EuiFlexItem> </EuiFlexGroup> </SectionPanel> diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx index 2e3d40527dbd..18e11110d756 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiConfirmModal, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import type { FC } from 'react'; -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import type { KibanaFeature } from '@kbn/features-plugin/common'; import { i18n } from '@kbn/i18n'; @@ -40,6 +40,8 @@ export const EditSpaceAssignedRolesTab: FC<Props> = ({ space, features, isReadOn invokeClient, } = services; + const [removeRoleConfirm, setRemoveRoleConfirm] = useState<Role | null>(null); + // Roles are already loaded in app state, refresh them when user navigates to this tab useEffect(() => { const getRoles = async () => { @@ -175,7 +177,7 @@ export const EditSpaceAssignedRolesTab: FC<Props> = ({ space, features, isReadOn ); return ( - <React.Fragment> + <> <EuiFlexGroup direction="column"> <EuiFlexItem> <EuiText> @@ -194,8 +196,8 @@ export const EditSpaceAssignedRolesTab: FC<Props> = ({ space, features, isReadOn onClickBulkRemove={async (selectedRoles) => { await removeRole(selectedRoles); }} - onClickRowRemoveAction={async (rowRecord) => { - await removeRole([rowRecord]); + onClickRemoveRoleConfirm={async (rowRecord) => { + setRemoveRoleConfirm(rowRecord); }} onClickAssignNewRole={async () => { showRolesPrivilegeEditor(); @@ -203,6 +205,36 @@ export const EditSpaceAssignedRolesTab: FC<Props> = ({ space, features, isReadOn /> </EuiFlexItem> </EuiFlexGroup> - </React.Fragment> + {removeRoleConfirm && ( + <EuiConfirmModal + aria-labelledby="remove-role-confirm-modal" + titleProps={{ id: 'remove-role-confirm-modal' }} + title={i18n.translate('xpack.spaces.management.spaceDetails.roles.removeRoleModalTitle', { + defaultMessage: 'Remove role "{roleName}" from the space?', + values: { roleName: removeRoleConfirm.name }, + })} + cancelButtonText={i18n.translate( + 'xpack.spaces.management.spaceDetails.roles.removeRoleModalCancel', + { defaultMessage: 'Cancel' } + )} + confirmButtonText={i18n.translate( + 'xpack.spaces.management.spaceDetails.roles.removeRoleModalConfirm', + { defaultMessage: 'Confirm' } + )} + onCancel={() => setRemoveRoleConfirm(null)} + onConfirm={() => { + removeRole([removeRoleConfirm]); + setRemoveRoleConfirm(null); + }} + > + <p> + <FormattedMessage + id="xpack.spaces.management.spaceDetails.roles.removeRoleModalBody" + defaultMessage="You can't undo this operation." + /> + </p> + </EuiConfirmModal> + )} + </> ); }; diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx index 74f2b2fde466..84859631cdb7 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx @@ -20,7 +20,6 @@ import { EuiFormRow, EuiLink, EuiLoadingSpinner, - EuiSpacer, EuiText, EuiTitle, useGeneratedHtmlId, @@ -31,7 +30,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { KibanaFeature, KibanaFeatureConfig } from '@kbn/features-plugin/common'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import type { RawKibanaPrivileges, Role, @@ -157,7 +155,7 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { const [roleSpacePrivilege, setRoleSpacePrivilege] = useState<KibanaRolePrivilege>( !selectedRoles.length || !selectedRolesCombinedPrivileges.length - ? FEATURE_PRIVILEGES_ALL + ? FEATURE_PRIVILEGES_CUSTOM : selectedRolesCombinedPrivileges[0] ); @@ -378,17 +376,19 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { { defaultMessage: 'Select roles' } )} labelAppend={ - <EuiLink - id={manageRoleLinkId} - href={getUrlForApp('management', { deepLinkId: 'roles' })} - external={false} - target="_blank" - > - {i18n.translate( - 'xpack.spaces.management.spaceDetails.roles.selectRolesFormRowLabelAnchor', - { defaultMessage: 'Manage roles' } - )} - </EuiLink> + <EuiText size="xs"> + <EuiLink + id={manageRoleLinkId} + href={getUrlForApp('management', { deepLinkId: 'roles' })} + external={false} + target="_blank" + > + {i18n.translate( + 'xpack.spaces.management.spaceDetails.roles.selectRolesFormRowLabelAnchor', + { defaultMessage: 'Manage roles' } + )} + </EuiLink> + </EuiText> } helpText={i18n.translate( 'xpack.spaces.management.spaceDetails.roles.selectRolesHelp', @@ -409,7 +409,7 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { )} placeholder={i18n.translate( 'xpack.spaces.management.spaceDetails.roles.selectRolesPlaceholder', - { defaultMessage: 'Add a role...' } + { defaultMessage: 'Add roles...' } )} isLoading={fetchingDataDeps} options={createRolesComboBoxOptions(spaceUnallocatedRoles)} @@ -452,9 +452,9 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { iconType="iInCircle" data-test-subj="privilege-info-callout" title={i18n.translate( - 'xpack.spaces.management.spaceDetails.roles.assign.privilegeConflictMsg.title', + 'xpack.spaces.management.spaceDetails.roles.assign.privilegeCombinationMsg.title', { - defaultMessage: 'Privileges will apply only to this space.', + defaultMessage: `The user's resulting access depends on a combination of their role's global space privileges and specific privileges applied to this space.`, } )} /> @@ -464,7 +464,14 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { label={i18n.translate( 'xpack.spaces.management.spaceDetails.roles.assign.privilegesLabelText', { - defaultMessage: 'Define role privileges', + defaultMessage: 'Define privileges', + } + )} + helpText={i18n.translate( + 'xpack.spaces.management.spaceDetails.roles.assign.privilegesHelpText', + { + defaultMessage: + 'Assign the privilege level you wish to grant to all present and future features across this space.', } )} > @@ -518,7 +525,6 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { <EuiLoadingSpinner size="l" /> ) : ( <KibanaPrivilegeTable - showTitle={false} disabled={roleSpacePrivilege !== FEATURE_PRIVILEGES_CUSTOM} role={roleCustomizationAnchor.value!} privilegeIndex={roleCustomizationAnchor.privilegeIndex} @@ -597,6 +603,7 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { canCustomizeSubFeaturePrivileges={ license?.getFeatures().allowSubFeaturePrivileges ?? false } + showAdditionalPermissionsMessage={false} /> )} </React.Fragment> @@ -643,10 +650,10 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { > {isEditOperation.current ? i18n.translate('xpack.spaces.management.spaceDetails.roles.updateRoleButton', { - defaultMessage: 'Update', + defaultMessage: 'Update role privileges', }) : i18n.translate('xpack.spaces.management.spaceDetails.roles.assignRoleButton', { - defaultMessage: 'Assign', + defaultMessage: 'Assign roles', })} </EuiButton> ); @@ -659,7 +666,7 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { <h2> {isEditOperation.current ? i18n.translate('xpack.spaces.management.spaceDetails.roles.assignRoleButton', { - defaultMessage: 'Edit role privileges', + defaultMessage: 'Edit role privileges for space', }) : i18n.translate( 'xpack.spaces.management.spaceDetails.roles.assign.privileges.custom', @@ -669,15 +676,6 @@ export const PrivilegesRolesForm: FC<PrivilegesRolesFormProps> = (props) => { )} </h2> </EuiTitle> - <EuiSpacer size="s" /> - <EuiText size="s"> - <p> - <FormattedMessage - id="xpack.spaces.management.spaceDetails.privilegeForm.heading" - defaultMessage="Define the privileges a given role should have in this space." - /> - </p> - </EuiText> </EuiFlyoutHeader> <EuiFlyoutBody>{getForm()}</EuiFlyoutBody> <EuiFlyoutFooter> diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assigned_roles_table.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assigned_roles_table.test.tsx index f909dba415c4..0ddb633cd1f5 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assigned_roles_table.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assigned_roles_table.test.tsx @@ -18,7 +18,7 @@ const defaultProps: Pick< | 'onClickAssignNewRole' | 'onClickBulkRemove' | 'onClickRowEditAction' - | 'onClickRowRemoveAction' + | 'onClickRemoveRoleConfirm' | 'currentSpace' > = { currentSpace: { @@ -29,7 +29,7 @@ const defaultProps: Pick< onClickBulkRemove: jest.fn(), onClickRowEditAction: jest.fn(), onClickAssignNewRole: jest.fn(), - onClickRowRemoveAction: jest.fn(), + onClickRemoveRoleConfirm: jest.fn(), }; const renderTestComponent = ( diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assigned_roles_table.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assigned_roles_table.tsx index ffe7ecba85ec..f59bd0056167 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assigned_roles_table.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assigned_roles_table.tsx @@ -41,7 +41,7 @@ interface ISpaceAssignedRolesTableProps { assignedRoles: Map<Role['name'], Role>; onClickAssignNewRole: () => Promise<void>; onClickRowEditAction: (role: Role) => void; - onClickRowRemoveAction: (role: Role) => void; + onClickRemoveRoleConfirm: (role: Role) => void; supportsBulkAction?: boolean; onClickBulkRemove?: (selectedRoles: Role[]) => void; } @@ -67,10 +67,10 @@ const getTableColumns = ({ isReadOnly, currentSpace, onClickRowEditAction, - onClickRowRemoveAction, + onClickRemoveRoleConfirm, }: Pick< ISpaceAssignedRolesTableProps, - 'isReadOnly' | 'onClickRowEditAction' | 'onClickRowRemoveAction' | 'currentSpace' + 'isReadOnly' | 'onClickRowEditAction' | 'onClickRemoveRoleConfirm' | 'currentSpace' >) => { const columns: Array<EuiBasicTableColumn<Role>> = [ { @@ -205,7 +205,7 @@ const getTableColumns = ({ { defaultMessage: 'Click this action to remove the user from this space.' } ), available: (rowRecord) => isEditableRole(rowRecord), - onClick: onClickRowRemoveAction, + onClick: onClickRemoveRoleConfirm, }, ], }); @@ -237,14 +237,19 @@ export const SpaceAssignedRolesTable = ({ onClickAssignNewRole, onClickBulkRemove, onClickRowEditAction, - onClickRowRemoveAction, + onClickRemoveRoleConfirm, isReadOnly = false, supportsBulkAction = false, }: ISpaceAssignedRolesTableProps) => { const tableColumns = useMemo( () => - getTableColumns({ isReadOnly, onClickRowEditAction, onClickRowRemoveAction, currentSpace }), - [currentSpace, isReadOnly, onClickRowEditAction, onClickRowRemoveAction] + getTableColumns({ + isReadOnly, + onClickRowEditAction, + onClickRemoveRoleConfirm, + currentSpace, + }), + [currentSpace, isReadOnly, onClickRowEditAction, onClickRemoveRoleConfirm] ); const [rolesInView, setRolesInView] = useState<Role[]>([]); const [selectedRoles, setSelectedRoles] = useState<Role[]>([]); @@ -262,14 +267,17 @@ export const SpaceAssignedRolesTable = ({ const onSearchQueryChange = useCallback<NonNullable<NonNullable<EuiSearchBarProps['onChange']>>>( ({ query }) => { - const _assignedRolesTransformed = Array.from(assignedRoles.values()); + const assignedRolesTransformed = Array.from(assignedRoles.values()); + const sortedAssignedRolesTransformed = assignedRolesTransformed.sort(sortRolesForListing); if (query?.text) { setRolesInView( - _assignedRolesTransformed.filter((role) => role.name.includes(query.text.toLowerCase())) + sortedAssignedRolesTransformed.filter((role) => + role.name.includes(query.text.toLowerCase()) + ) ); } else { - setRolesInView(_assignedRolesTransformed); + setRolesInView(sortedAssignedRolesTransformed); } }, [assignedRoles] diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx index 169f12c3487c..7a9cf421d02c 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx @@ -154,12 +154,21 @@ describe('SpacesGridPage', () => { wrapper.update(); expect(wrapper.find('EuiInMemoryTable').prop('items')).toBe(spacesWithSolution); - expect(wrapper.find('EuiInMemoryTable').prop('columns')).toContainEqual({ - field: 'solution', - name: 'Solution view', - sortable: true, - render: expect.any(Function), - }); + expect(wrapper.find('EuiInMemoryTable').prop('columns')).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: '', field: 'initials' }), + expect.objectContaining({ name: 'Space', field: 'name' }), + expect.objectContaining({ name: 'Description', field: 'description' }), + expect.objectContaining({ name: 'Solution view', field: 'solution' }), + expect.objectContaining({ + actions: expect.arrayContaining([ + expect.objectContaining({ name: 'Edit', icon: 'pencil' }), + expect.objectContaining({ name: 'Switch', icon: 'merge' }), + expect.objectContaining({ name: 'Delete', icon: 'trash' }), + ]), + }), + ]) + ); }); it('renders a "current" badge for the current space', async () => { @@ -413,44 +422,6 @@ describe('SpacesGridPage', () => { }); }); - it(`renders the 'Features visible' column when not serverless`, async () => { - const httpStart = httpServiceMock.createStartContract(); - httpStart.get.mockResolvedValue([]); - - const error = new Error('something awful happened'); - - const notifications = notificationServiceMock.createStartContract(); - - const wrapper = shallowWithIntl( - <SpacesGridPage - spacesManager={spacesManager} - getFeatures={() => Promise.reject(error)} - notifications={notifications} - getUrlForApp={getUrlForApp} - history={history} - capabilities={{ - navLinks: {}, - management: {}, - catalogue: {}, - spaces: { manage: true }, - }} - allowSolutionVisibility - {...spacesGridCommonProps} - /> - ); - - // allow spacesManager to load spaces and lazy-load SpaceAvatar - await act(async () => {}); - wrapper.update(); - - expect(wrapper.find('EuiInMemoryTable').prop('columns')).toContainEqual( - expect.objectContaining({ - field: 'disabledFeatures', - name: 'Features visible', - }) - ); - }); - it(`does not render the 'Features visible' column when serverless`, async () => { const httpStart = httpServiceMock.createStartContract(); httpStart.get.mockResolvedValue([]); diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx index 586992c1b6b4..10bbde47a106 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx @@ -18,8 +18,6 @@ import { EuiPageHeader, EuiPageSection, EuiSpacer, - EuiText, - useIsWithinBreakpoints, } from '@elastic/eui'; import React, { Component, lazy, Suspense } from 'react'; @@ -36,17 +34,12 @@ import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; import { addSpaceIdToPath, type Space } from '../../../common'; import { isReservedSpace } from '../../../common'; -import { - DEFAULT_SPACE_ID, - ENTER_SPACE_PATH, - SOLUTION_VIEW_CLASSIC, -} from '../../../common/constants'; +import { DEFAULT_SPACE_ID, ENTER_SPACE_PATH } from '../../../common/constants'; import { getSpacesFeatureDescription } from '../../constants'; import { getSpaceAvatarComponent } from '../../space_avatar'; import { SpaceSolutionBadge } from '../../space_solution_badge'; import type { SpacesManager } from '../../spaces_manager'; import { ConfirmDeleteModal, UnauthorizedPrompt } from '../components'; -import { getEnabledFeatures } from '../lib/feature_utils'; // No need to wrap LazySpaceAvatar in an error boundary, because it is one of the first chunks loaded when opening Kibana. const LazySpaceAvatar = lazy(() => @@ -255,8 +248,7 @@ export class SpacesGridPage extends Component<Props, State> { }; public getColumnConfig() { - const { activeSpace, features } = this.state; - const { solution: activeSolution } = activeSpace ?? {}; + const { activeSpace } = this.state; const config: Array<EuiBasicTableColumn<Space>> = [ { @@ -284,15 +276,8 @@ export class SpacesGridPage extends Component<Props, State> { render: (value: string, rowRecord: Space) => { const SpaceName = () => { const isCurrent = this.state.activeSpace?.id === rowRecord.id; - const isWide = useIsWithinBreakpoints(['xl']); - const gridColumns = isCurrent && isWide ? 2 : 1; return ( - <EuiFlexGrid - responsive={false} - columns={gridColumns} - alignItems="center" - gutterSize="s" - > + <EuiFlexGrid responsive={false} columns={2} alignItems="center" gutterSize="s"> <EuiFlexItem> <EuiLink {...reactRouterNavigate(this.props.history, this.getEditSpacePath(rowRecord))} @@ -323,7 +308,7 @@ export class SpacesGridPage extends Component<Props, State> { return <SpaceName />; }, 'data-test-subj': 'spacesListTableRowNameCell', - width: '15%', + width: '20%', }, { field: 'description', @@ -332,55 +317,10 @@ export class SpacesGridPage extends Component<Props, State> { }), sortable: true, truncateText: true, - width: '45%', + width: '40%', }, ]; - const shouldShowFeaturesColumn = - !this.props.isServerless && (!activeSolution || activeSolution === SOLUTION_VIEW_CLASSIC); - if (shouldShowFeaturesColumn) { - config.push({ - field: 'disabledFeatures', - name: i18n.translate('xpack.spaces.management.spacesGridPage.featuresColumnName', { - defaultMessage: 'Features visible', - }), - sortable: (space: Space) => { - return getEnabledFeatures(features, space).length; - }, - render: (_disabledFeatures: string[], rowRecord: Space) => { - const enabledFeatureCount = getEnabledFeatures(features, rowRecord).length; - if (enabledFeatureCount === features.length) { - return ( - <FormattedMessage - id="xpack.spaces.management.spacesGridPage.allFeaturesEnabled" - defaultMessage="All features" - /> - ); - } - if (enabledFeatureCount === 0) { - return ( - <EuiText color={'danger'} size="s"> - <FormattedMessage - id="xpack.spaces.management.spacesGridPage.noFeaturesEnabled" - defaultMessage="No features visible" - /> - </EuiText> - ); - } - return ( - <FormattedMessage - id="xpack.spaces.management.spacesGridPage.someFeaturesEnabled" - defaultMessage="{enabledFeatureCount} / {totalFeatureCount}" - values={{ - enabledFeatureCount, - totalFeatureCount: features.length, - }} - /> - ); - }, - }); - } - config.push({ field: 'id', name: i18n.translate('xpack.spaces.management.spacesGridPage.identifierColumnName', { @@ -405,6 +345,7 @@ export class SpacesGridPage extends Component<Props, State> { render: (solution: Space['solution'], record: Space) => ( <SpaceSolutionBadge solution={solution} data-test-subj={`${record.id}-solution`} /> ), + width: '10%', }); } diff --git a/x-pack/plugins/spaces/public/nav_control/solution_view_tour/solution_view_tour.tsx b/x-pack/plugins/spaces/public/nav_control/solution_view_tour/solution_view_tour.tsx index caa9cc17b053..eda87809c66b 100644 --- a/x-pack/plugins/spaces/public/nav_control/solution_view_tour/solution_view_tour.tsx +++ b/x-pack/plugins/spaces/public/nav_control/solution_view_tour/solution_view_tour.tsx @@ -28,7 +28,7 @@ const LearnMoreLink = () => ( const solutionMap: Record<SolutionId, string> = { es: i18n.translate('xpack.spaces.navControl.tour.esSolution', { - defaultMessage: 'Search', + defaultMessage: 'Elasticsearch', }), security: i18n.translate('xpack.spaces.navControl.tour.securitySolution', { defaultMessage: 'Security', diff --git a/x-pack/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap b/x-pack/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap index 4419707ab45f..2b088f54f353 100644 --- a/x-pack/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap +++ b/x-pack/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap @@ -4,7 +4,6 @@ exports[`it renders with custom logo 1`] = ` <_KibanaPageTemplate className="spcSpaceSelector" data-test-subj="kibanaSpaceSelector" - panelled={true} > <EuiPortal> <div @@ -65,7 +64,6 @@ exports[`it renders without crashing 1`] = ` <_KibanaPageTemplate className="spcSpaceSelector" data-test-subj="kibanaSpaceSelector" - panelled={true} > <EuiPortal> <div diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx index 3d72392552e3..d334ee9efab3 100644 --- a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx @@ -117,11 +117,7 @@ export class SpaceSelector extends Component<Props, State> { } return ( - <KibanaPageTemplate - className="spcSpaceSelector" - data-test-subj="kibanaSpaceSelector" - panelled - > + <KibanaPageTemplate className="spcSpaceSelector" data-test-subj="kibanaSpaceSelector"> {/* Portal the fixed background graphic so it doesn't affect page positioning or overlap on top of global banners */} <EuiPortal> <div diff --git a/x-pack/plugins/spaces/public/space_solution_badge/badge.tsx b/x-pack/plugins/spaces/public/space_solution_badge/badge.tsx index e9c6a15d3bd9..7cdd605a9de6 100644 --- a/x-pack/plugins/spaces/public/space_solution_badge/badge.tsx +++ b/x-pack/plugins/spaces/public/space_solution_badge/badge.tsx @@ -21,7 +21,7 @@ const SolutionOptions: Record< label: ( <FormattedMessage id="xpack.spaces.spaceSolutionBadge.elasticsearch" - defaultMessage="Search" + defaultMessage="Elasticsearch" /> ), }, diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts index 61d7ac23818a..812d7177a416 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts @@ -6,11 +6,11 @@ */ import type { - CoreSetup, KibanaRequest, LifecycleResponseFactory, OnPreRoutingToolkit, -} from '@kbn/core/server'; +} from '@kbn/core-http-server'; +import type { CoreSetup } from '@kbn/core-lifecycle-server'; import { getSpaceIdFromPath } from '../../../common'; diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json index 1099a054bd70..771f1b2e6139 100644 --- a/x-pack/plugins/spaces/tsconfig.json +++ b/x-pack/plugins/spaces/tsconfig.json @@ -53,7 +53,8 @@ "@kbn/core-http-router-server-mocks", "@kbn/core-application-browser-mocks", "@kbn/ui-theme", - "@kbn/core-chrome-browser" + "@kbn/core-chrome-browser", + "@kbn/core-lifecycle-server" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx index acb65de2a9a1..988afb8d2182 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx @@ -8,6 +8,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import { EuiButtonEmpty, EuiButtonIcon, @@ -19,6 +20,7 @@ import { EuiPopoverTitle, EuiText, useEuiPaddingCSS, + useIsWithinBreakpoints, } from '@elastic/eui'; import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { @@ -30,6 +32,9 @@ import { DataViewSelector } from '@kbn/unified-search-plugin/public'; import type { DataViewListItemEnhanced } from '@kbn/unified-search-plugin/public/dataview_picker/dataview_list'; import { EsQueryRuleMetaData } from '../es_query/types'; +const DESKTOP_WIDTH = 450; +const MOBILE_WIDTH = 350; + export interface DataViewSelectPopoverProps { dependencies: { dataViews: DataViewsPublicPluginStart; @@ -61,6 +66,8 @@ export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopove const [dataViewItems, setDataViewsItems] = useState<DataViewListItemEnhanced[]>([]); const [dataViewPopoverOpen, setDataViewPopoverOpen] = useState(false); + const isMobile = useIsWithinBreakpoints(['xs']); + const closeDataViewEditor = useRef<() => void | undefined>(); const allDataViewItems = useMemo( @@ -179,9 +186,14 @@ export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopove anchorPosition="downLeft" display="block" > - <div style={{ width: '450px' }} data-test-subj="chooseDataViewPopoverContent"> + <div + css={css` + width: ${isMobile ? `${MOBILE_WIDTH}px` : `${DESKTOP_WIDTH}px`}; + `} + data-test-subj="chooseDataViewPopoverContent" + > <EuiPopoverTitle> - <EuiFlexGroup alignItems="center" gutterSize="s"> + <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}> <EuiFlexItem> {i18n.translate('xpack.stackAlerts.components.ui.alertParams.dataViewPopoverTitle', { defaultMessage: 'Data view', diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts index f476d7d896b6..b22c1a81537c 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/test_query_row/use_test_query.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { act } from 'react-test-renderer'; import { useTestQuery } from './use_test_query'; diff --git a/x-pack/plugins/stack_connectors/common/bedrock/constants.ts b/x-pack/plugins/stack_connectors/common/bedrock/constants.ts index d2ffa0b116bd..de6c10246298 100644 --- a/x-pack/plugins/stack_connectors/common/bedrock/constants.ts +++ b/x-pack/plugins/stack_connectors/common/bedrock/constants.ts @@ -21,8 +21,7 @@ export enum SUB_ACTION { INVOKE_STREAM = 'invokeStream', DASHBOARD = 'getDashboard', TEST = 'test', - CONVERSE = 'converse', - CONVERSE_STREAM = 'converseStream', + BEDROCK_CLIENT_SEND = 'bedrockClientSend', } export const DEFAULT_TIMEOUT_MS = 120000; diff --git a/x-pack/plugins/stack_connectors/common/bedrock/schema.ts b/x-pack/plugins/stack_connectors/common/bedrock/schema.ts index c444159c010b..e9194a752300 100644 --- a/x-pack/plugins/stack_connectors/common/bedrock/schema.ts +++ b/x-pack/plugins/stack_connectors/common/bedrock/schema.ts @@ -26,11 +26,6 @@ export const RunActionParamsSchema = schema.object({ signal: schema.maybe(schema.any()), timeout: schema.maybe(schema.number()), raw: schema.maybe(schema.boolean()), - apiType: schema.maybe( - schema.oneOf([schema.literal('converse'), schema.literal('invoke')], { - defaultValue: 'invoke', - }) - ), }); export const BedrockMessageSchema = schema.object( @@ -154,53 +149,11 @@ export const DashboardActionResponseSchema = schema.object({ available: schema.boolean(), }); -export const ConverseActionParamsSchema = schema.object({ - // Bedrock API Properties - modelId: schema.maybe(schema.string()), - messages: schema.arrayOf( - schema.object({ - role: schema.string(), - content: schema.any(), - }) - ), - system: schema.arrayOf( - schema.object({ - text: schema.string(), - }) - ), - inferenceConfig: schema.object({ - temperature: schema.maybe(schema.number()), - maxTokens: schema.maybe(schema.number()), - stopSequences: schema.maybe(schema.arrayOf(schema.string())), - topP: schema.maybe(schema.number()), - }), - toolConfig: schema.maybe( - schema.object({ - tools: schema.arrayOf( - schema.object({ - toolSpec: schema.object({ - name: schema.string(), - description: schema.string(), - inputSchema: schema.object({ - json: schema.object({ - type: schema.string(), - properties: schema.object({}, { unknowns: 'allow' }), - required: schema.maybe(schema.arrayOf(schema.string())), - additionalProperties: schema.boolean(), - $schema: schema.maybe(schema.string()), - }), - }), - }), - }) - ), - toolChoice: schema.maybe(schema.object({}, { unknowns: 'allow' })), - }) - ), - additionalModelRequestFields: schema.maybe(schema.any()), - additionalModelResponseFieldPaths: schema.maybe(schema.any()), - guardrailConfig: schema.maybe(schema.any()), +export const BedrockClientSendParamsSchema = schema.object({ + // ConverseCommand | ConverseStreamCommand from @aws-sdk/client-bedrock-runtime + command: schema.any(), // Kibana related properties signal: schema.maybe(schema.any()), }); -export const ConverseActionResponseSchema = schema.object({}, { unknowns: 'allow' }); +export const BedrockClientSendResponseSchema = schema.object({}, { unknowns: 'allow' }); diff --git a/x-pack/plugins/stack_connectors/common/bedrock/types.ts b/x-pack/plugins/stack_connectors/common/bedrock/types.ts index e3dd49538176..2e716a52547c 100644 --- a/x-pack/plugins/stack_connectors/common/bedrock/types.ts +++ b/x-pack/plugins/stack_connectors/common/bedrock/types.ts @@ -21,8 +21,8 @@ import { RunApiLatestResponseSchema, BedrockMessageSchema, BedrockToolChoiceSchema, - ConverseActionParamsSchema, - ConverseActionResponseSchema, + BedrockClientSendParamsSchema, + BedrockClientSendResponseSchema, } from './schema'; export type Config = TypeOf<typeof ConfigSchema>; @@ -39,5 +39,5 @@ export type DashboardActionParams = TypeOf<typeof DashboardActionParamsSchema>; export type DashboardActionResponse = TypeOf<typeof DashboardActionResponseSchema>; export type BedrockMessage = TypeOf<typeof BedrockMessageSchema>; export type BedrockToolChoice = TypeOf<typeof BedrockToolChoiceSchema>; -export type ConverseActionParams = TypeOf<typeof ConverseActionParamsSchema>; -export type ConverseActionResponse = TypeOf<typeof ConverseActionResponseSchema>; +export type ConverseActionParams = TypeOf<typeof BedrockClientSendParamsSchema>; +export type ConverseActionResponse = TypeOf<typeof BedrockClientSendResponseSchema>; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/email/use_email_config.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/email/use_email_config.test.ts index ee5ba95bf577..0830b68fc1d5 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/email/use_email_config.test.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/email/use_email_config.test.ts @@ -6,7 +6,7 @@ */ import { httpServiceMock, notificationServiceMock } from '@kbn/core/public/mocks'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useEmailConfig } from './use_email_config'; const http = httpServiceMock.createStartContract(); @@ -16,6 +16,12 @@ const renderUseEmailConfigHook = (currentService?: string) => renderHook(() => useEmailConfig({ http, toasts })); describe('useEmailConfig', () => { + let res: ReturnType<ReturnType<typeof useEmailConfig>['getEmailServiceConfig']> extends Promise< + infer R + > + ? R + : never; + beforeEach(() => jest.clearAllMocks()); it('should return the correct result when requesting the config of a service', async () => { @@ -26,14 +32,16 @@ describe('useEmailConfig', () => { }); const { result } = renderUseEmailConfigHook(); + await act(async () => { - const res = await result.current.getEmailServiceConfig('gmail'); - expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/gmail'); - expect(res).toEqual({ - host: 'smtp.gmail.com', - port: 465, - secure: true, - }); + res = await result.current.getEmailServiceConfig('gmail'); + }); + + expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/gmail'); + expect(res).toEqual({ + host: 'smtp.gmail.com', + port: 465, + secure: true, }); }); @@ -44,14 +52,16 @@ describe('useEmailConfig', () => { }); const { result } = renderUseEmailConfigHook(); + await act(async () => { - const res = await result.current.getEmailServiceConfig('gmail'); - expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/gmail'); - expect(res).toEqual({ - host: 'smtp.gmail.com', - port: 465, - secure: false, - }); + res = await result.current.getEmailServiceConfig('gmail'); + }); + + expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/gmail'); + expect(res).toEqual({ + host: 'smtp.gmail.com', + port: 465, + secure: false, }); }); @@ -60,13 +70,14 @@ describe('useEmailConfig', () => { const { result } = renderUseEmailConfigHook(); await act(async () => { - const res = await result.current.getEmailServiceConfig('foo'); - expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/foo'); - expect(res).toEqual({ - host: '', - port: 0, - secure: false, - }); + res = await result.current.getEmailServiceConfig('foo'); + }); + + expect(http.get).toHaveBeenCalledWith('/internal/stack_connectors/_email_config/foo'); + expect(res).toEqual({ + host: '', + port: 0, + secure: false, }); }); @@ -75,13 +86,13 @@ describe('useEmailConfig', () => { throw new Error('no!'); }); - const { result, waitForNextUpdate } = renderUseEmailConfigHook(); + const { result } = renderUseEmailConfigHook(); await act(async () => { result.current.getEmailServiceConfig('foo'); - await waitForNextUpdate(); - expect(toasts.addDanger).toHaveBeenCalled(); }); + + await waitFor(() => expect(toasts.addDanger).toHaveBeenCalled()); }); it('should not make an API call if the service is empty', async () => { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.test.ts index 18bcdc623279..39706e6d22c0 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.test.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/gen_ai/use_get_dashboard.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useGetDashboard } from './use_get_dashboard'; import { getDashboard } from './api'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; @@ -58,44 +58,44 @@ describe('useGetDashboard', () => { ])( 'fetches the %p dashboard and sets the dashboard URL with %p', async (selectedProvider, urlKey) => { - const { result, waitForNextUpdate } = renderHook(() => - useGetDashboard({ ...defaultArgs, selectedProvider }) - ); - await waitForNextUpdate(); - expect(mockDashboard).toHaveBeenCalledWith( - expect.objectContaining({ - connectorId, + const { result } = renderHook(() => useGetDashboard({ ...defaultArgs, selectedProvider })); + await waitFor(() => { + expect(mockDashboard).toHaveBeenCalledWith( + expect.objectContaining({ + connectorId, + dashboardId: `generative-ai-token-usage-${urlKey}-space`, + }) + ); + expect(mockGetRedirectUrl).toHaveBeenCalledWith({ + query: { + language: 'kuery', + query: `kibana.saved_objects: { id : ${connectorId} }`, + }, dashboardId: `generative-ai-token-usage-${urlKey}-space`, - }) - ); - expect(mockGetRedirectUrl).toHaveBeenCalledWith({ - query: { - language: 'kuery', - query: `kibana.saved_objects: { id : ${connectorId} }`, - }, - dashboardId: `generative-ai-token-usage-${urlKey}-space`, + }); + expect(result.current.isLoading).toBe(false); + expect(result.current.dashboardUrl).toBe( + `http://localhost:5601/app/dashboards#/view/generative-ai-token-usage-${urlKey}-space` + ); }); - expect(result.current.isLoading).toBe(false); - expect(result.current.dashboardUrl).toBe( - `http://localhost:5601/app/dashboards#/view/generative-ai-token-usage-${urlKey}-space` - ); } ); it('handles the case where the dashboard is not available.', async () => { mockDashboard.mockResolvedValue({ data: { available: false } }); - const { result, waitForNextUpdate } = renderHook(() => useGetDashboard(defaultArgs)); - await waitForNextUpdate(); - expect(mockDashboard).toHaveBeenCalledWith( - expect.objectContaining({ - connectorId, - dashboardId: 'generative-ai-token-usage-openai-space', - }) - ); - expect(mockGetRedirectUrl).not.toHaveBeenCalled(); + const { result } = renderHook(() => useGetDashboard(defaultArgs)); + await waitFor(() => { + expect(mockDashboard).toHaveBeenCalledWith( + expect.objectContaining({ + connectorId, + dashboardId: 'generative-ai-token-usage-openai-space', + }) + ); + expect(mockGetRedirectUrl).not.toHaveBeenCalled(); - expect(result.current.isLoading).toBe(false); - expect(result.current.dashboardUrl).toBe(null); + expect(result.current.isLoading).toBe(false); + expect(result.current.dashboardUrl).toBe(null); + }); }); it('handles the case where the spaces API is not available.', async () => { @@ -111,34 +111,35 @@ describe('useGetDashboard', () => { }); it('handles the case where connectorId is empty string', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useGetDashboard({ ...defaultArgs, connectorId: '' }) - ); - await waitForNextUpdate(); - expect(mockDashboard).not.toHaveBeenCalled(); - expect(mockGetRedirectUrl).not.toHaveBeenCalled(); - expect(result.current.isLoading).toBe(false); - expect(result.current.dashboardUrl).toBe(null); + const { result } = renderHook(() => useGetDashboard({ ...defaultArgs, connectorId: '' })); + await waitFor(() => { + expect(mockDashboard).not.toHaveBeenCalled(); + expect(mockGetRedirectUrl).not.toHaveBeenCalled(); + expect(result.current.isLoading).toBe(false); + expect(result.current.dashboardUrl).toBe(null); + }); }); it('handles the case where the dashboard locator is not available.', async () => { mockKibana.mockReturnValue({ services: { ...mockServices, dashboard: {} }, }); - const { result, waitForNextUpdate } = renderHook(() => useGetDashboard(defaultArgs)); - await waitForNextUpdate(); - expect(result.current.isLoading).toBe(false); - expect(result.current.dashboardUrl).toBe(null); + const { result } = renderHook(() => useGetDashboard(defaultArgs)); + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + expect(result.current.dashboardUrl).toBe(null); + }); }); it('correctly handles errors and displays the appropriate toast messages.', async () => { mockDashboard.mockRejectedValue(new Error('Error fetching dashboard')); - const { result, waitForNextUpdate } = renderHook(() => useGetDashboard(defaultArgs)); - await waitForNextUpdate(); - expect(result.current.isLoading).toBe(false); - expect(mockToasts.addDanger).toHaveBeenCalledWith({ - title: 'Error finding OpenAI Token Usage Dashboard.', - text: 'Error fetching dashboard', + const { result } = renderHook(() => useGetDashboard(defaultArgs)); + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + expect(mockToasts.addDanger).toHaveBeenCalledWith({ + title: 'Error finding OpenAI Token Usage Dashboard.', + text: 'Error fetching dashboard', + }); }); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_choices.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_choices.test.tsx index 721897ece726..3033cebd3ccf 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_choices.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_choices.test.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; -import { useChoices, UseChoices, UseChoicesProps } from './use_choices'; +import { useChoices } from './use_choices'; import { getChoices } from './api'; jest.mock('./api'); @@ -73,7 +73,7 @@ describe('UseChoices', () => { const fields = ['category']; it('init', async () => { - const { result, waitForNextUpdate } = renderHook<UseChoicesProps, UseChoices>(() => + const { result } = renderHook(() => useChoices({ http: services.http, actionConnector, @@ -82,13 +82,11 @@ describe('UseChoices', () => { }) ); - await waitForNextUpdate(); - - expect(result.current).toEqual(useChoicesResponse); + await waitFor(() => expect(result.current).toEqual(useChoicesResponse)); }); it('returns an empty array if the field is not in response', async () => { - const { result, waitForNextUpdate } = renderHook<UseChoicesProps, UseChoices>(() => + const { result } = renderHook(() => useChoices({ http: services.http, actionConnector, @@ -97,16 +95,16 @@ describe('UseChoices', () => { }) ); - await waitForNextUpdate(); - - expect(result.current).toEqual({ - isLoading: false, - choices: { priority: [], category: getChoicesResponse }, - }); + await waitFor(() => + expect(result.current).toEqual({ + isLoading: false, + choices: { priority: [], category: getChoicesResponse }, + }) + ); }); it('returns an empty array when connector is not presented', async () => { - const { result } = renderHook<UseChoicesProps, UseChoices>(() => + const { result } = renderHook(() => useChoices({ http: services.http, actionConnector: undefined, @@ -127,7 +125,7 @@ describe('UseChoices', () => { serviceMessage: 'An error occurred', }); - const { waitForNextUpdate } = renderHook<UseChoicesProps, UseChoices>(() => + renderHook(() => useChoices({ http: services.http, actionConnector, @@ -136,12 +134,12 @@ describe('UseChoices', () => { }) ); - await waitForNextUpdate(); - - expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ - text: 'An error occurred', - title: 'Unable to get choices', - }); + await waitFor(() => + expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ + text: 'An error occurred', + title: 'Unable to get choices', + }) + ); }); it('it displays an error when http throws an error', async () => { @@ -149,7 +147,7 @@ describe('UseChoices', () => { throw new Error('An error occurred'); }); - renderHook<UseChoicesProps, UseChoices>(() => + renderHook(() => useChoices({ http: services.http, actionConnector, @@ -158,9 +156,11 @@ describe('UseChoices', () => { }) ); - expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ - text: 'An error occurred', - title: 'Unable to get choices', - }); + await waitFor(() => + expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ + text: 'An error occurred', + title: 'Unable to get choices', + }) + ); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_get_app_info.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_get_app_info.test.tsx index c8c061c9d07f..c4cf65a59133 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_get_app_info.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_get_app_info.test.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; -import { useGetAppInfo, UseGetAppInfo, UseGetAppInfoProps } from './use_get_app_info'; +import { useGetAppInfo } from './use_get_app_info'; import { getAppInfo } from './api'; import { ServiceNowActionConnector } from './types'; import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -50,7 +50,7 @@ describe('useGetAppInfo', () => { }); it('init', async () => { - const { result } = renderHook<UseGetAppInfoProps, UseGetAppInfo>(() => + const { result } = renderHook(() => useGetAppInfo({ actionTypeId, http, @@ -64,7 +64,7 @@ describe('useGetAppInfo', () => { }); it('returns the application information', async () => { - const { result } = renderHook<UseGetAppInfoProps, UseGetAppInfo>(() => + const { result } = renderHook(() => useGetAppInfo({ actionTypeId, http, @@ -86,7 +86,7 @@ describe('useGetAppInfo', () => { throw new Error('An error occurred'); }); - const { result } = renderHook<UseGetAppInfoProps, UseGetAppInfo>(() => + const { result } = renderHook(() => useGetAppInfo({ actionTypeId, http, @@ -108,7 +108,7 @@ describe('useGetAppInfo', () => { throw error; }); - const { result } = renderHook<UseGetAppInfoProps, UseGetAppInfo>(() => + const { result } = renderHook(() => useGetAppInfo({ actionTypeId, http, diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_get_choices.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_get_choices.test.tsx index 38ea6d55b4e1..fd9808139b8b 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_get_choices.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/use_get_choices.test.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; -import { useGetChoices, UseGetChoices, UseGetChoicesProps } from './use_get_choices'; +import { useGetChoices } from './use_get_choices'; import { getChoices } from './api'; jest.mock('./api'); @@ -69,7 +69,7 @@ describe('useGetChoices', () => { const fields = ['priority']; it('init', async () => { - const { result, waitForNextUpdate } = renderHook<UseGetChoicesProps, UseGetChoices>(() => + const { result } = renderHook(() => useGetChoices({ http: services.http, actionConnector, @@ -79,16 +79,16 @@ describe('useGetChoices', () => { }) ); - await waitForNextUpdate(); - - expect(result.current).toEqual({ - isLoading: false, - choices: getChoicesResponse, - }); + await waitFor(() => + expect(result.current).toEqual({ + isLoading: false, + choices: getChoicesResponse, + }) + ); }); it('returns an empty array when connector is not presented', async () => { - const { result } = renderHook<UseGetChoicesProps, UseGetChoices>(() => + const { result } = renderHook(() => useGetChoices({ http: services.http, actionConnector: undefined, @@ -105,7 +105,7 @@ describe('useGetChoices', () => { }); it('it calls onSuccess', async () => { - const { waitForNextUpdate } = renderHook<UseGetChoicesProps, UseGetChoices>(() => + renderHook(() => useGetChoices({ http: services.http, actionConnector, @@ -115,9 +115,7 @@ describe('useGetChoices', () => { }) ); - await waitForNextUpdate(); - - expect(onSuccess).toHaveBeenCalledWith(getChoicesResponse); + await waitFor(() => expect(onSuccess).toHaveBeenCalledWith(getChoicesResponse)); }); it('it displays an error when service fails', async () => { @@ -126,7 +124,7 @@ describe('useGetChoices', () => { serviceMessage: 'An error occurred', }); - const { waitForNextUpdate } = renderHook<UseGetChoicesProps, UseGetChoices>(() => + renderHook(() => useGetChoices({ http: services.http, actionConnector, @@ -136,12 +134,12 @@ describe('useGetChoices', () => { }) ); - await waitForNextUpdate(); - - expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ - text: 'An error occurred', - title: 'Unable to get choices', - }); + await waitFor(() => + expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ + text: 'An error occurred', + title: 'Unable to get choices', + }) + ); }); it('it displays an error when http throws an error', async () => { @@ -149,7 +147,7 @@ describe('useGetChoices', () => { throw new Error('An error occurred'); }); - renderHook<UseGetChoicesProps, UseGetChoices>(() => + renderHook(() => useGetChoices({ http: services.http, actionConnector, @@ -159,10 +157,12 @@ describe('useGetChoices', () => { }) ); - expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ - text: 'An error occurred', - title: 'Unable to get choices', - }); + await waitFor(() => + expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ + text: 'An error occurred', + title: 'Unable to get choices', + }) + ); }); it('returns an empty array if the response is not an array', async () => { @@ -171,7 +171,7 @@ describe('useGetChoices', () => { data: {}, }); - const { result } = renderHook<UseGetChoicesProps, UseGetChoices>(() => + const { result } = renderHook(() => useGetChoices({ http: services.http, actionConnector: undefined, @@ -181,9 +181,11 @@ describe('useGetChoices', () => { }) ); - expect(result.current).toEqual({ - isLoading: false, - choices: [], + await waitFor(() => { + expect(result.current).toEqual({ + isLoading: false, + choices: [], + }); }); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/swimlane/use_get_application.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/swimlane/use_get_application.test.tsx index 82e514ec51fd..db33c7aa6801 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/swimlane/use_get_application.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/swimlane/use_get_application.test.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'; import { getApplication } from './api'; -import { useGetApplication, UseGetApplication } from './use_get_application'; +import { useGetApplication } from './use_get_application'; jest.mock('./api'); jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); @@ -43,87 +43,86 @@ describe('useGetApplication', () => { }); it('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseGetApplication>(() => - useGetApplication({ - toastNotifications: services.notifications.toasts, - }) - ); - - await waitForNextUpdate(); - expect(result.current).toEqual({ - isLoading: false, - getApplication: result.current.getApplication, - }); + const { result } = renderHook(() => + useGetApplication({ + toastNotifications: services.notifications.toasts, + }) + ); + + expect(result.current).toEqual({ + isLoading: false, + getApplication: result.current.getApplication, }); }); it('calls getApplication with correct arguments', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseGetApplication>(() => - useGetApplication({ - toastNotifications: services.notifications.toasts, - }) - ); + const { result } = renderHook(() => + useGetApplication({ + toastNotifications: services.notifications.toasts, + }) + ); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); + act(() => { result.current.getApplication({ appId: action.config.appId, apiToken: action.secrets.apiToken, apiUrl: action.config.apiUrl, }); + }); - await waitForNextUpdate(); + await waitFor(() => expect(getApplicationMock).toBeCalledWith({ signal: abortCtrl.signal, appId: action.config.appId, apiToken: action.secrets.apiToken, url: action.config.apiUrl, - }); - }); + }) + ); }); it('get application', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseGetApplication>(() => - useGetApplication({ - toastNotifications: services.notifications.toasts, - }) - ); - - await waitForNextUpdate(); + const { result } = renderHook(() => + useGetApplication({ + toastNotifications: services.notifications.toasts, + }) + ); + + await waitFor(() => new Promise((resolve) => resolve(null))); + act(() => { result.current.getApplication({ appId: action.config.appId, apiToken: action.secrets.apiToken, apiUrl: action.config.apiUrl, }); - await waitForNextUpdate(); - + }); + await waitFor(() => expect(result.current).toEqual({ isLoading: false, getApplication: result.current.getApplication, - }); - }); + }) + ); }); it('set isLoading to true when getting the application', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseGetApplication>(() => - useGetApplication({ - toastNotifications: services.notifications.toasts, - }) - ); - - await waitForNextUpdate(); + const { result } = renderHook(() => + useGetApplication({ + toastNotifications: services.notifications.toasts, + }) + ); + + await waitFor(() => new Promise((resolve) => resolve(null))); + + act(() => { result.current.getApplication({ appId: action.config.appId, apiToken: action.secrets.apiToken, apiUrl: action.config.apiUrl, }); - - expect(result.current.isLoading).toBe(true); }); + + expect(result.current.isLoading).toBe(true); }); it('it displays an error when http throws an error', async () => { @@ -131,52 +130,52 @@ describe('useGetApplication', () => { throw new Error('Something went wrong'); }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseGetApplication>(() => - useGetApplication({ - toastNotifications: services.notifications.toasts, - }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => + useGetApplication({ + toastNotifications: services.notifications.toasts, + }) + ); + await waitFor(() => new Promise((resolve) => resolve(null))); + act(() => { result.current.getApplication({ appId: action.config.appId, apiToken: action.secrets.apiToken, apiUrl: action.config.apiUrl, }); + }); - expect(result.current).toEqual({ - isLoading: false, - getApplication: result.current.getApplication, - }); + expect(result.current).toEqual({ + isLoading: false, + getApplication: result.current.getApplication, + }); - expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ - title: 'Unable to get application with id bcq16kdTbz5jlwM6h', - text: 'Something went wrong', - }); + expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ + title: 'Unable to get application with id bcq16kdTbz5jlwM6h', + text: 'Something went wrong', }); }); it('it displays an error when the response does not contain the correct fields', async () => { getApplicationMock.mockResolvedValue({}); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, UseGetApplication>(() => - useGetApplication({ - toastNotifications: services.notifications.toasts, - }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => + useGetApplication({ + toastNotifications: services.notifications.toasts, + }) + ); + await waitFor(() => new Promise((resolve) => resolve(null))); + act(() => { result.current.getApplication({ appId: action.config.appId, apiToken: action.secrets.apiToken, apiUrl: action.config.apiUrl, }); - await waitForNextUpdate(); - + }); + await waitFor(() => expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ title: 'Unable to get application with id bcq16kdTbz5jlwM6h', text: 'Unable to get application fields', - }); - }); + }) + ); }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.test.ts index 2a4d91a07f1d..ce3dd90942cf 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.test.ts @@ -30,6 +30,7 @@ jest.mock('../lib/gen_ai/create_gen_ai_dashboard'); // @ts-ignore const mockSigner = jest.spyOn(aws, 'sign').mockReturnValue({ signed: true }); +const mockSend = jest.fn(); describe('BedrockConnector', () => { let mockRequest: jest.Mock; let mockError: jest.Mock; @@ -89,6 +90,8 @@ describe('BedrockConnector', () => { beforeEach(() => { // @ts-ignore connector.request = mockRequest; + // @ts-ignore + connector.bedrockClient.send = mockSend; }); describe('runApi', () => { @@ -630,6 +633,57 @@ describe('BedrockConnector', () => { ); }); }); + + describe('bedrockClientSend', () => { + it('should send the command and return the response', async () => { + const command = { input: 'test' }; + const response = { result: 'success' }; + mockSend.mockResolvedValue(response); + + const result = await connector.bedrockClientSend( + { signal: undefined, command }, + connectorUsageCollector + ); + + expect(mockSend).toHaveBeenCalledWith(command, { abortSignal: undefined }); + expect(result).toEqual(response); + }); + + it('should handle and split streaming response', async () => { + const command = { input: 'test' }; + const stream = new PassThrough(); + const response = { stream }; + mockSend.mockResolvedValue(response); + + const result = (await connector.bedrockClientSend( + { signal: undefined, command }, + connectorUsageCollector + )) as unknown as { + stream?: unknown; + tokenStream?: unknown; + }; + + expect(mockSend).toHaveBeenCalledWith(command, { abortSignal: undefined }); + expect(result.stream).toBeDefined(); + expect(result.tokenStream).toBeDefined(); + }); + + it('should handle non-streaming response', async () => { + const command = { input: 'test' }; + const usage = { stats: 0 }; + const response = { usage }; + mockSend.mockResolvedValue(response); + + const result = (await connector.bedrockClientSend( + { signal: undefined, command }, + connectorUsageCollector + )) as unknown as { + usage?: unknown; + }; + expect(result.usage).toBeDefined(); + }); + }); + describe('getResponseErrorMessage', () => { it('returns an unknown error message', () => { // @ts-expect-error expects an axios error as the parameter diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts index 55b631ba9441..339efa49f69b 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts @@ -7,6 +7,8 @@ import { ServiceParams, SubActionConnector } from '@kbn/actions-plugin/server'; import aws from 'aws4'; +import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime'; +import { SmithyMessageDecoderStream } from '@smithy/eventstream-codec'; import { AxiosError, Method } from 'axios'; import { IncomingMessage } from 'http'; import { PassThrough } from 'stream'; @@ -21,7 +23,7 @@ import { StreamingResponseSchema, RunActionResponseSchema, RunApiLatestResponseSchema, - ConverseActionParamsSchema, + BedrockClientSendParamsSchema, } from '../../../common/bedrock/schema'; import { Config, @@ -60,13 +62,20 @@ interface SignedRequest { export class BedrockConnector extends SubActionConnector<Config, Secrets> { private url; private model; + private bedrockClient; constructor(params: ServiceParams<Config, Secrets>) { super(params); this.url = this.config.apiUrl; this.model = this.config.defaultModel; - + this.bedrockClient = new BedrockRuntimeClient({ + region: extractRegionId(this.config.apiUrl), + credentials: { + accessKeyId: this.secrets.accessKey, + secretAccessKey: this.secrets.secret, + }, + }); this.registerSubActions(); } @@ -108,15 +117,9 @@ export class BedrockConnector extends SubActionConnector<Config, Secrets> { }); this.registerSubAction({ - name: SUB_ACTION.CONVERSE, - method: 'converse', - schema: ConverseActionParamsSchema, - }); - - this.registerSubAction({ - name: SUB_ACTION.CONVERSE_STREAM, - method: 'converseStream', - schema: ConverseActionParamsSchema, + name: SUB_ACTION.BEDROCK_CLIENT_SEND, + method: 'bedrockClientSend', + schema: BedrockClientSendParamsSchema, }); } @@ -240,15 +243,14 @@ The Kibana Connector in use may need to be reconfigured with an updated Amazon B * @param signal Optional signal to cancel the request. * @param timeout Optional timeout for the request. * @param raw Optional flag to indicate if the response should be returned as raw data. - * @param apiType Optional type of API to be called. Defaults to 'invoke', . */ public async runApi( - { body, model: reqModel, signal, timeout, raw, apiType = 'invoke' }: RunActionParams, + { body, model: reqModel, signal, timeout, raw }: RunActionParams, connectorUsageCollector: ConnectorUsageCollector ): Promise<RunActionResponse | InvokeAIRawActionResponse> { // set model on per request basis const currentModel = reqModel ?? this.model; - const path = `/model/${currentModel}/${apiType}`; + const path = `/model/${currentModel}/invoke`; const signed = this.signRequest(body, path, false); const requestArgs = { ...signed, @@ -281,22 +283,18 @@ The Kibana Connector in use may need to be reconfigured with an updated Amazon B /** * NOT INTENDED TO BE CALLED DIRECTLY - * call invokeStream or converseStream instead + * call invokeStream instead * responsible for making a POST request to a specified URL with a given request body. * The response is then processed based on whether it is a streaming response or a regular response. * @param body The stringified request body to be sent in the POST request. * @param model Optional model to be used for the API request. If not provided, the default model from the connector will be used. */ private async streamApi( - { body, model: reqModel, signal, timeout, apiType = 'invoke' }: RunActionParams, + { body, model: reqModel, signal, timeout }: RunActionParams, connectorUsageCollector: ConnectorUsageCollector ): Promise<StreamingResponse> { - const streamingApiRoute = { - invoke: 'invoke-with-response-stream', - converse: 'converse-stream', - }; // set model on per request basis - const path = `/model/${reqModel ?? this.model}/${streamingApiRoute[apiType]}`; + const path = `/model/${reqModel ?? this.model}/invoke-with-response-stream`; const signed = this.signRequest(body, path, true); const response = await this.request( @@ -436,45 +434,28 @@ The Kibana Connector in use may need to be reconfigured with an updated Amazon B } /** - * Sends a request to the Bedrock API to perform a conversation action. - * @param input - The parameters for the conversation action. + * Sends a request via the BedrockRuntimeClient to perform a conversation action. + * @param params - The parameters for the conversation action. + * @param params.signal - The signal to cancel the request. + * @param params.command - The command class to be sent to the API. (ConverseCommand | ConverseStreamCommand) * @param connectorUsageCollector - The usage collector for the connector. * @returns A promise that resolves to the response of the conversation action. */ - public async converse( - { signal, ...converseApiInput }: ConverseActionParams, + public async bedrockClientSend( + { signal, command }: ConverseActionParams, connectorUsageCollector: ConnectorUsageCollector ): Promise<ConverseActionResponse> { - const res = await this.runApi( - { - body: JSON.stringify(converseApiInput), - raw: true, - apiType: 'converse', - signal, - }, - connectorUsageCollector - ); - return res; - } + connectorUsageCollector.addRequestBodyBytes(undefined, command); + const res = await this.bedrockClient.send(command, { + abortSignal: signal, + }); - /** - * Sends a request to the Bedrock API to perform a streaming conversation action. - * @param input - The parameters for the streaming conversation action. - * @param connectorUsageCollector - The usage collector for the connector. - * @returns A promise that resolves to the streaming response of the conversation action. - */ - public async converseStream( - { signal, ...converseApiInput }: ConverseActionParams, - connectorUsageCollector: ConnectorUsageCollector - ): Promise<IncomingMessage> { - const res = await this.streamApi( - { - body: JSON.stringify(converseApiInput), - apiType: 'converse', - signal, - }, - connectorUsageCollector - ); + if ('stream' in res) { + const resultStream = res.stream as SmithyMessageDecoderStream<unknown>; + // splits the stream in two, [stream = consumer, tokenStream = token tracking] + const [stream, tokenStream] = tee(resultStream); + return { ...res, stream, tokenStream }; + } return res; } @@ -571,3 +552,91 @@ function parseContent(content: Array<{ text?: string; type: string }>): string { } const usesDeprecatedArguments = (body: string): boolean => JSON.parse(body)?.prompt != null; + +function extractRegionId(url: string) { + const match = (url ?? '').match(/bedrock\.(.*?)\.amazonaws\./); + if (match) { + return match[1]; + } else { + // fallback to us-east-1 + return 'us-east-1'; + } +} + +/** + * Splits an async iterator into two independent async iterators which can be independently read from at different speeds. + * @param asyncIterator The async iterator returned from Bedrock to split + */ +function tee<T>( + asyncIterator: SmithyMessageDecoderStream<T> +): [SmithyMessageDecoderStream<T>, SmithyMessageDecoderStream<T>] { + // @ts-ignore options is private, but we need it to create the new streams + const streamOptions = asyncIterator.options; + + const streamLeft = new SmithyMessageDecoderStream<T>(streamOptions); + const streamRight = new SmithyMessageDecoderStream<T>(streamOptions); + + // Queues to store chunks for each stream + const leftQueue: T[] = []; + const rightQueue: T[] = []; + + // Promises for managing when a chunk is available + let leftPending: ((chunk: T | null) => void) | null = null; + let rightPending: ((chunk: T | null) => void) | null = null; + + const distribute = async () => { + for await (const chunk of asyncIterator) { + // Push the chunk into both queues + if (leftPending) { + leftPending(chunk); + leftPending = null; + } else { + leftQueue.push(chunk); + } + + if (rightPending) { + rightPending(chunk); + rightPending = null; + } else { + rightQueue.push(chunk); + } + } + + // Signal the end of the iterator + if (leftPending) { + leftPending(null); + } + if (rightPending) { + rightPending(null); + } + }; + + // Start distributing chunks from the iterator + distribute().catch(() => { + // swallow errors + }); + + // Helper to create an async iterator for each stream + const createIterator = ( + queue: T[], + setPending: (fn: ((chunk: T | null) => void) | null) => void + ) => { + return async function* () { + while (true) { + if (queue.length > 0) { + yield queue.shift()!; + } else { + const chunk = await new Promise<T | null>((resolve) => setPending(resolve)); + if (chunk === null) break; // End of the stream + yield chunk; + } + } + }; + }; + + // Assign independent async iterators to each stream + streamLeft[Symbol.asyncIterator] = createIterator(leftQueue, (fn) => (leftPending = fn)); + streamRight[Symbol.asyncIterator] = createIterator(rightQueue, (fn) => (rightPending = fn)); + + return [streamLeft, streamRight]; +} diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/azure_openai_utils.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/azure_openai_utils.test.ts index 6023d7715f4e..628ab7adcd36 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/azure_openai_utils.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/azure_openai_utils.test.ts @@ -101,9 +101,50 @@ describe('Azure Open AI Utils', () => { }; [chatUrl, completionUrl, completionExtensionsUrl].forEach((url: string) => { const sanitizedBodyString = getRequestWithStreamOption(url, JSON.stringify(body), true); - expect(sanitizedBodyString).toEqual( - `{\"messages\":[{\"role\":\"user\",\"content\":\"This is a test\"}],\"stream\":true}` - ); + expect(JSON.parse(sanitizedBodyString)).toEqual({ + messages: [{ content: 'This is a test', role: 'user' }], + stream: true, + stream_options: { + include_usage: true, + }, + }); + }); + }); + it('sets stream_options when stream is true', () => { + const body = { + messages: [ + { + role: 'user', + content: 'This is a test', + }, + ], + }; + [chatUrl, completionUrl, completionExtensionsUrl].forEach((url: string) => { + const sanitizedBodyString = getRequestWithStreamOption(url, JSON.stringify(body), true); + expect(JSON.parse(sanitizedBodyString)).toEqual({ + messages: [{ content: 'This is a test', role: 'user' }], + stream: true, + stream_options: { + include_usage: true, + }, + }); + }); + }); + it('does not sets stream_options when stream is false', () => { + const body = { + messages: [ + { + role: 'user', + content: 'This is a test', + }, + ], + }; + [chatUrl, completionUrl, completionExtensionsUrl].forEach((url: string) => { + const sanitizedBodyString = getRequestWithStreamOption(url, JSON.stringify(body), false); + expect(JSON.parse(sanitizedBodyString)).toEqual({ + messages: [{ content: 'This is a test', role: 'user' }], + stream: false, + }); }); }); it('overrides stream parameter if defined in body', () => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/azure_openai_utils.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/azure_openai_utils.ts index 02bff6ea2f63..8825e719f010 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/azure_openai_utils.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/azure_openai_utils.ts @@ -48,6 +48,11 @@ export const getRequestWithStreamOption = (url: string, body: string, stream: bo const jsonBody = JSON.parse(body); if (jsonBody) { jsonBody.stream = stream; + if (stream) { + jsonBody.stream_options = { + include_usage: true, + }; + } } return JSON.stringify(jsonBody); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/openai_utils.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/openai_utils.test.ts index b480b7285918..cd65084badc9 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/openai_utils.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/openai_utils.test.ts @@ -118,6 +118,31 @@ describe('Open AI Utils', () => { ], }; + [OPENAI_CHAT_URL, OPENAI_LEGACY_COMPLETION_URL].forEach((url: string) => { + const sanitizedBodyString = getRequestWithStreamOption( + url, + JSON.stringify(body), + false, + DEFAULT_OPENAI_MODEL + ); + expect(JSON.parse(sanitizedBodyString)).toEqual({ + messages: [{ content: 'This is a test', role: 'user' }], + model: 'gpt-4', + stream: false, + }); + }); + }); + it('sets stream_options when stream is true', () => { + const body = { + model: 'gpt-4', + messages: [ + { + role: 'user', + content: 'This is a test', + }, + ], + }; + [OPENAI_CHAT_URL, OPENAI_LEGACY_COMPLETION_URL].forEach((url: string) => { const sanitizedBodyString = getRequestWithStreamOption( url, @@ -125,9 +150,39 @@ describe('Open AI Utils', () => { true, DEFAULT_OPENAI_MODEL ); - expect(sanitizedBodyString).toEqual( - `{\"model\":\"gpt-4\",\"messages\":[{\"role\":\"user\",\"content\":\"This is a test\"}],\"stream\":true}` + expect(JSON.parse(sanitizedBodyString)).toEqual({ + messages: [{ content: 'This is a test', role: 'user' }], + model: 'gpt-4', + stream: true, + stream_options: { + include_usage: true, + }, + }); + }); + }); + it('does not set stream_options when stream is false', () => { + const body = { + model: 'gpt-4', + messages: [ + { + role: 'user', + content: 'This is a test', + }, + ], + }; + + [OPENAI_CHAT_URL, OPENAI_LEGACY_COMPLETION_URL].forEach((url: string) => { + const sanitizedBodyString = getRequestWithStreamOption( + url, + JSON.stringify(body), + false, + DEFAULT_OPENAI_MODEL ); + expect(JSON.parse(sanitizedBodyString)).toEqual({ + messages: [{ content: 'This is a test', role: 'user' }], + model: 'gpt-4', + stream: false, + }); }); }); @@ -182,6 +237,7 @@ describe('Open AI Utils', () => { expect(sanitizedBodyString).toEqual(bodyString); }); }); + describe('removeEndpointFromUrl', () => { test('removes "/chat/completions" from the end of the URL', () => { const originalUrl = 'https://api.openai.com/v1/chat/completions'; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/openai_utils.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/openai_utils.ts index 7dac5f4692bd..89a29105cd0c 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/openai_utils.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/lib/openai_utils.ts @@ -38,6 +38,11 @@ export const getRequestWithStreamOption = ( if (jsonBody) { if (APIS_ALLOWING_STREAMING.has(url)) { jsonBody.stream = stream; + if (stream) { + jsonBody.stream_options = { + include_usage: true, + }; + } } jsonBody.model = jsonBody.model || defaultModel; } diff --git a/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.test.ts index 1362b7610e2c..33d96451054f 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.test.ts @@ -292,6 +292,7 @@ describe('OpenAIConnector', () => { data: JSON.stringify({ ...sampleOpenAiBody, stream: true, + stream_options: { include_usage: true }, model: DEFAULT_OPENAI_MODEL, }), headers: { @@ -338,6 +339,7 @@ describe('OpenAIConnector', () => { data: JSON.stringify({ ...body, stream: true, + stream_options: { include_usage: true }, }), headers: { Authorization: 'Bearer 123', @@ -397,6 +399,7 @@ describe('OpenAIConnector', () => { data: JSON.stringify({ ...sampleOpenAiBody, stream: true, + stream_options: { include_usage: true }, model: DEFAULT_OPENAI_MODEL, }), headers: { @@ -422,6 +425,7 @@ describe('OpenAIConnector', () => { data: JSON.stringify({ ...sampleOpenAiBody, stream: true, + stream_options: { include_usage: true }, model: DEFAULT_OPENAI_MODEL, }), headers: { @@ -448,6 +452,7 @@ describe('OpenAIConnector', () => { data: JSON.stringify({ ...sampleOpenAiBody, stream: true, + stream_options: { include_usage: true }, model: DEFAULT_OPENAI_MODEL, }), headers: { @@ -1274,7 +1279,11 @@ describe('OpenAIConnector', () => { url: 'https://My-test-resource-123.openai.azure.com/openai/deployments/NEW-DEPLOYMENT-321/chat/completions?api-version=2023-05-15', method: 'post', responseSchema: StreamingResponseSchema, - data: JSON.stringify({ ...sampleAzureAiBody, stream: true }), + data: JSON.stringify({ + ...sampleAzureAiBody, + stream: true, + stream_options: { include_usage: true }, + }), headers: { 'api-key': '123', 'content-type': 'application/json', @@ -1314,6 +1323,7 @@ describe('OpenAIConnector', () => { data: JSON.stringify({ ...body, stream: true, + stream_options: { include_usage: true }, }), headers: { 'api-key': '123', diff --git a/x-pack/plugins/streams/common/index.ts b/x-pack/plugins/streams/common/index.ts new file mode 100644 index 000000000000..3a7306e46cae --- /dev/null +++ b/x-pack/plugins/streams/common/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 type { StreamDefinition } from './types'; diff --git a/x-pack/plugins/streams/common/types.ts b/x-pack/plugins/streams/common/types.ts index 6cdb2f923f6f..d3aa43911ec2 100644 --- a/x-pack/plugins/streams/common/types.ts +++ b/x-pack/plugins/streams/common/types.ts @@ -72,7 +72,7 @@ export const streamWithoutIdDefinitonSchema = z.object({ .array( z.object({ id: z.string(), - condition: conditionSchema, + condition: z.optional(conditionSchema), }) ) .default([]), diff --git a/x-pack/plugins/streams/public/api/index.ts b/x-pack/plugins/streams/public/api/index.ts new file mode 100644 index 000000000000..f64fc7f2fdce --- /dev/null +++ b/x-pack/plugins/streams/public/api/index.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, CoreStart, HttpFetchOptions } from '@kbn/core/public'; +import type { + ClientRequestParamsOf, + ReturnOf, + RouteRepositoryClient, +} from '@kbn/server-route-repository'; +import { createRepositoryClient } from '@kbn/server-route-repository-client'; +import type { StreamsRouteRepository } from '../../server'; + +type FetchOptions = Omit<HttpFetchOptions, 'body'> & { + body?: any; +}; + +export type StreamsRepositoryClientOptions = Omit< + FetchOptions, + 'query' | 'body' | 'pathname' | 'signal' +> & { + signal: AbortSignal | null; +}; + +export type StreamsRepositoryClient = RouteRepositoryClient< + StreamsRouteRepository, + StreamsRepositoryClientOptions +>; + +export type AutoAbortedStreamsRepositoryClient = RouteRepositoryClient< + StreamsRouteRepository, + Omit<StreamsRepositoryClientOptions, 'signal'> +>; + +export type StreamsRepositoryEndpoint = keyof StreamsRouteRepository; + +export type APIReturnType<TEndpoint extends StreamsRepositoryEndpoint> = ReturnOf< + StreamsRouteRepository, + TEndpoint +>; + +export type StreamsAPIClientRequestParamsOf<TEndpoint extends StreamsRepositoryEndpoint> = + ClientRequestParamsOf<StreamsRouteRepository, TEndpoint>; + +export function createStreamsRepositoryClient( + core: CoreStart | CoreSetup +): StreamsRepositoryClient { + return createRepositoryClient(core); +} diff --git a/x-pack/plugins/streams/public/index.ts b/x-pack/plugins/streams/public/index.ts index 5b83ea1d297d..bc90fb0f4006 100644 --- a/x-pack/plugins/streams/public/index.ts +++ b/x-pack/plugins/streams/public/index.ts @@ -7,7 +7,12 @@ import { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; import { Plugin } from './plugin'; +import { StreamsPluginSetup, StreamsPluginStart } from './types'; -export const plugin: PluginInitializer<{}, {}> = (context: PluginInitializerContext) => { +export type { StreamsPluginSetup, StreamsPluginStart }; + +export const plugin: PluginInitializer<StreamsPluginSetup, StreamsPluginStart> = ( + context: PluginInitializerContext +) => { return new Plugin(context); }; diff --git a/x-pack/plugins/streams/public/plugin.ts b/x-pack/plugins/streams/public/plugin.ts index f35d18e06ff7..5a2ae3e06684 100644 --- a/x-pack/plugins/streams/public/plugin.ts +++ b/x-pack/plugins/streams/public/plugin.ts @@ -8,25 +8,55 @@ import { CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/public'; import { Logger } from '@kbn/logging'; +import { createRepositoryClient } from '@kbn/server-route-repository-client'; +import { from, shareReplay, startWith } from 'rxjs'; +import { once } from 'lodash'; import type { StreamsPublicConfig } from '../common/config'; import { StreamsPluginClass, StreamsPluginSetup, StreamsPluginStart } from './types'; +import { StreamsRepositoryClient } from './api'; export class Plugin implements StreamsPluginClass { public config: StreamsPublicConfig; public logger: Logger; + private repositoryClient!: StreamsRepositoryClient; + constructor(context: PluginInitializerContext<{}>) { this.config = context.config.get(); this.logger = context.logger.get(); } - setup(core: CoreSetup<StreamsPluginStart>, pluginSetup: StreamsPluginSetup) { - return {}; + setup(core: CoreSetup<{}>, pluginSetup: {}): StreamsPluginSetup { + this.repositoryClient = createRepositoryClient(core); + return { + status$: createStatusObservable(this.logger, this.repositoryClient), + }; } - start(core: CoreStart) { - return {}; + start(core: CoreStart, pluginsStart: {}): StreamsPluginStart { + return { + streamsRepositoryClient: this.repositoryClient, + status$: createStatusObservable(this.logger, this.repositoryClient), + }; } stop() {} } + +const createStatusObservable = once((logger: Logger, repositoryClient: StreamsRepositoryClient) => { + return from( + repositoryClient + .fetch('GET /api/streams/_status', { + signal: new AbortController().signal, + }) + .then( + (response) => ({ + status: response.enabled ? ('enabled' as const) : ('disabled' as const), + }), + (error) => { + logger.error(error); + return { status: 'unknown' as const }; + } + ) + ).pipe(startWith({ status: 'unknown' as const }), shareReplay(1)); +}); diff --git a/x-pack/plugins/streams/public/types.ts b/x-pack/plugins/streams/public/types.ts index 61e5fa94098f..fc88f2a6c20f 100644 --- a/x-pack/plugins/streams/public/types.ts +++ b/x-pack/plugins/streams/public/types.ts @@ -6,11 +6,16 @@ */ import type { Plugin as PluginClass } from '@kbn/core/public'; +import { Observable } from 'rxjs'; +import type { StreamsRepositoryClient } from './api'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface StreamsPluginSetup {} +export interface StreamsPluginSetup { + status$: Observable<{ status: 'unknown' | 'enabled' | 'disabled' }>; +} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface StreamsPluginStart {} +export interface StreamsPluginStart { + streamsRepositoryClient: StreamsRepositoryClient; + status$: Observable<{ status: 'unknown' | 'enabled' | 'disabled' }>; +} -export type StreamsPluginClass = PluginClass<{}, {}, StreamsPluginSetup, StreamsPluginStart>; +export type StreamsPluginClass = PluginClass<StreamsPluginSetup, StreamsPluginStart, {}, {}>; diff --git a/x-pack/plugins/streams/server/index.ts b/x-pack/plugins/streams/server/index.ts index bd8aee304ad1..9ef13c62d6b7 100644 --- a/x-pack/plugins/streams/server/index.ts +++ b/x-pack/plugins/streams/server/index.ts @@ -17,3 +17,5 @@ export const plugin = async (context: PluginInitializerContext<StreamsConfig>) = const { StreamsPlugin } = await import('./plugin'); return new StreamsPlugin(context); }; + +export type { ListStreamResponse } from './lib/streams/stream_crud'; diff --git a/x-pack/plugins/streams/server/lib/streams/component_templates/generate_layer.ts b/x-pack/plugins/streams/server/lib/streams/component_templates/generate_layer.ts index 82c89c9ab917..4763aacb4447 100644 --- a/x-pack/plugins/streams/server/lib/streams/component_templates/generate_layer.ts +++ b/x-pack/plugins/streams/server/lib/streams/component_templates/generate_layer.ts @@ -30,7 +30,8 @@ export function generateLayer( template: { settings: isRoot(definition.id) ? logsSettings : {}, mappings: { - subobjects: false, + subobjects: true, // TODO set to false once this works on Elasticsearch side - right now fields are not properly indexed. + dynamic: false, properties, }, }, diff --git a/x-pack/plugins/streams/server/lib/streams/data_streams/manage_data_streams.ts b/x-pack/plugins/streams/server/lib/streams/data_streams/manage_data_streams.ts index 812739db56c7..a9b667906fdf 100644 --- a/x-pack/plugins/streams/server/lib/streams/data_streams/manage_data_streams.ts +++ b/x-pack/plugins/streams/server/lib/streams/data_streams/manage_data_streams.ts @@ -6,6 +6,7 @@ */ import { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; import { retryTransientEsErrors } from '../helpers/retry'; interface DataStreamManagementOptions { @@ -23,6 +24,7 @@ interface DeleteDataStreamOptions { interface RolloverDataStreamOptions { esClient: ElasticsearchClient; name: string; + mappings: MappingTypeMapping['properties'] | undefined; logger: Logger; } @@ -56,38 +58,37 @@ export async function rolloverDataStreamIfNecessary({ esClient, name, logger, + mappings, }: RolloverDataStreamOptions) { const dataStreams = await esClient.indices.getDataStream({ name: `${name},${name}.*` }); for (const dataStream of dataStreams.data_streams) { - const currentMappings = - Object.values( - await esClient.indices.getMapping({ - index: dataStream.indices.at(-1)?.index_name, - }) - )[0].mappings.properties || {}; - const simulatedIndex = await esClient.indices.simulateIndexTemplate({ name: dataStream.name }); - const simulatedMappings = simulatedIndex.template.mappings.properties || {}; - - // check whether the same fields and same types are listed (don't check for other mapping attributes) - const isDifferent = - Object.values(simulatedMappings).length !== Object.values(currentMappings).length || - Object.entries(simulatedMappings || {}).some(([fieldName, { type }]) => { - const currentType = currentMappings[fieldName]?.type; - return currentType !== type; - }); - - if (!isDifferent) { + const writeIndex = dataStream.indices.at(-1); + if (!writeIndex) { continue; } - try { - await retryTransientEsErrors(() => esClient.indices.rollover({ alias: dataStream.name }), { - logger, - }); - logger.debug(() => `Rolled over data stream: ${dataStream.name}`); + await retryTransientEsErrors( + () => esClient.indices.putMapping({ index: writeIndex.index_name, properties: mappings }), + { + logger, + } + ); } catch (error: any) { - logger.error(`Error rolling over data stream: ${error.message}`); - throw error; + if ( + typeof error.message !== 'string' || + !error.message.includes('illegal_argument_exception') + ) { + throw error; + } + try { + await retryTransientEsErrors(() => esClient.indices.rollover({ alias: dataStream.name }), { + logger, + }); + logger.debug(() => `Rolled over data stream: ${dataStream.name}`); + } catch (rolloverError: any) { + logger.error(`Error rolling over data stream: ${error.message}`); + throw error; + } } } } diff --git a/x-pack/plugins/streams/server/lib/streams/helpers/condition_to_painless.test.ts b/x-pack/plugins/streams/server/lib/streams/helpers/condition_to_painless.test.ts index aab7f27f12d1..8c63d7caa881 100644 --- a/x-pack/plugins/streams/server/lib/streams/helpers/condition_to_painless.test.ts +++ b/x-pack/plugins/streams/server/lib/streams/helpers/condition_to_painless.test.ts @@ -7,48 +7,48 @@ import { conditionToPainless } from './condition_to_painless'; -const operatorConditionAndResutls = [ +const operatorConditionAndResults = [ { condition: { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' }, - result: 'ctx.log?.logger == "nginx_proxy"', + result: '(ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy")', }, { condition: { field: 'log.logger', operator: 'neq' as const, value: 'nginx_proxy' }, - result: 'ctx.log?.logger != "nginx_proxy"', + result: '(ctx.log?.logger !== null && ctx.log?.logger != "nginx_proxy")', }, { condition: { field: 'http.response.status_code', operator: 'lt' as const, value: 500 }, - result: 'ctx.http?.response?.status_code < 500', + result: '(ctx.http?.response?.status_code !== null && ctx.http?.response?.status_code < 500)', }, { condition: { field: 'http.response.status_code', operator: 'lte' as const, value: 500 }, - result: 'ctx.http?.response?.status_code <= 500', + result: '(ctx.http?.response?.status_code !== null && ctx.http?.response?.status_code <= 500)', }, { condition: { field: 'http.response.status_code', operator: 'gt' as const, value: 500 }, - result: 'ctx.http?.response?.status_code > 500', + result: '(ctx.http?.response?.status_code !== null && ctx.http?.response?.status_code > 500)', }, { condition: { field: 'http.response.status_code', operator: 'gte' as const, value: 500 }, - result: 'ctx.http?.response?.status_code >= 500', + result: '(ctx.http?.response?.status_code !== null && ctx.http?.response?.status_code >= 500)', }, { condition: { field: 'log.logger', operator: 'startsWith' as const, value: 'nginx' }, - result: 'ctx.log?.logger.startsWith("nginx")', + result: '(ctx.log?.logger !== null && ctx.log?.logger.startsWith("nginx"))', }, { condition: { field: 'log.logger', operator: 'endsWith' as const, value: 'proxy' }, - result: 'ctx.log?.logger.endsWith("proxy")', + result: '(ctx.log?.logger !== null && ctx.log?.logger.endsWith("proxy"))', }, { condition: { field: 'log.logger', operator: 'contains' as const, value: 'proxy' }, - result: 'ctx.log?.logger.contains("proxy")', + result: '(ctx.log?.logger !== null && ctx.log?.logger.contains("proxy"))', }, ]; describe('conditionToPainless', () => { describe('operators', () => { - operatorConditionAndResutls.forEach((setup) => { + operatorConditionAndResults.forEach((setup) => { test(`${setup.condition.operator}`, () => { expect(conditionToPainless(setup.condition)).toEqual(setup.result); }); @@ -65,7 +65,7 @@ describe('conditionToPainless', () => { }; expect( expect(conditionToPainless(condition)).toEqual( - 'ctx.log?.logger == "nginx_proxy" && ctx.log?.level == "error"' + '(ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && (ctx.log?.level !== null && ctx.log?.level == "error")' ) ); }); @@ -81,7 +81,7 @@ describe('conditionToPainless', () => { }; expect( expect(conditionToPainless(condition)).toEqual( - 'ctx.log?.logger == "nginx_proxy" || ctx.log?.level == "error"' + '(ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") || (ctx.log?.level !== null && ctx.log?.level == "error")' ) ); }); @@ -102,7 +102,7 @@ describe('conditionToPainless', () => { }; expect( expect(conditionToPainless(condition)).toEqual( - 'ctx.log?.logger == "nginx_proxy" && (ctx.log?.level == "error" || ctx.log?.level == "ERROR")' + '(ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))' ) ); }); @@ -125,7 +125,7 @@ describe('conditionToPainless', () => { }; expect( expect(conditionToPainless(condition)).toEqual( - '(ctx.log?.logger == "nginx_proxy" || ctx.service?.name == "nginx") && (ctx.log?.level == "error" || ctx.log?.level == "ERROR")' + '((ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") || (ctx.service?.name !== null && ctx.service?.name == "nginx")) && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))' ) ); }); diff --git a/x-pack/plugins/streams/server/lib/streams/helpers/condition_to_painless.ts b/x-pack/plugins/streams/server/lib/streams/helpers/condition_to_painless.ts index 539ad3603535..2cccef260d7e 100644 --- a/x-pack/plugins/streams/server/lib/streams/helpers/condition_to_painless.ts +++ b/x-pack/plugins/streams/server/lib/streams/helpers/condition_to_painless.ts @@ -69,7 +69,7 @@ function toPainless(condition: FilterCondition) { export function conditionToPainless(condition: Condition, nested = false): string { if (isFilterCondition(condition)) { - return toPainless(condition); + return `(${safePainlessField(condition)} !== null && ${toPainless(condition)})`; } if (isAndCondition(condition)) { const and = condition.and.map((filter) => conditionToPainless(filter, true)).join(' && '); diff --git a/x-pack/plugins/streams/server/lib/streams/stream_crud.ts b/x-pack/plugins/streams/server/lib/streams/stream_crud.ts index 78a126905d9a..da5f74d3e69e 100644 --- a/x-pack/plugins/streams/server/lib/streams/stream_crud.ts +++ b/x-pack/plugins/streams/server/lib/streams/stream_crud.ts @@ -7,29 +7,29 @@ import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; -import { FieldDefinition, StreamDefinition } from '../../../common/types'; import { STREAMS_INDEX } from '../../../common/constants'; -import { DefinitionNotFound } from './errors'; -import { deleteTemplate, upsertTemplate } from './index_templates/manage_index_templates'; +import { FieldDefinition, StreamDefinition } from '../../../common/types'; import { generateLayer } from './component_templates/generate_layer'; -import { generateIngestPipeline } from './ingest_pipelines/generate_ingest_pipeline'; -import { generateReroutePipeline } from './ingest_pipelines/generate_reroute_pipeline'; -import { generateIndexTemplate } from './index_templates/generate_index_template'; import { deleteComponent, upsertComponent } from './component_templates/manage_component_templates'; -import { getIndexTemplateName } from './index_templates/name'; import { getComponentTemplateName } from './component_templates/name'; -import { getProcessingPipelineName, getReroutePipelineName } from './ingest_pipelines/name'; -import { - deleteIngestPipeline, - upsertIngestPipeline, -} from './ingest_pipelines/manage_ingest_pipelines'; -import { getAncestors } from './helpers/hierarchy'; -import { MalformedFields } from './errors/malformed_fields'; import { deleteDataStream, rolloverDataStreamIfNecessary, upsertDataStream, } from './data_streams/manage_data_streams'; +import { DefinitionNotFound } from './errors'; +import { MalformedFields } from './errors/malformed_fields'; +import { getAncestors } from './helpers/hierarchy'; +import { generateIndexTemplate } from './index_templates/generate_index_template'; +import { deleteTemplate, upsertTemplate } from './index_templates/manage_index_templates'; +import { getIndexTemplateName } from './index_templates/name'; +import { generateIngestPipeline } from './ingest_pipelines/generate_ingest_pipeline'; +import { generateReroutePipeline } from './ingest_pipelines/generate_reroute_pipeline'; +import { + deleteIngestPipeline, + upsertIngestPipeline, +} from './ingest_pipelines/manage_ingest_pipelines'; +import { getProcessingPipelineName, getReroutePipelineName } from './ingest_pipelines/name'; interface BaseParams { scopedClusterClient: IScopedClusterClient; @@ -88,29 +88,54 @@ async function upsertInternalStream({ definition, scopedClusterClient }: BasePar type ListStreamsParams = BaseParams; -export async function listStreams({ scopedClusterClient }: ListStreamsParams) { +export interface ListStreamResponse { + total: number; + definitions: StreamDefinition[]; +} + +export async function listStreams({ + scopedClusterClient, +}: ListStreamsParams): Promise<ListStreamResponse> { const response = await scopedClusterClient.asInternalUser.search<StreamDefinition>({ index: STREAMS_INDEX, size: 10000, - fields: ['id'], - _source: false, sort: [{ id: 'asc' }], }); - const definitions = response.hits.hits.map((hit) => hit.fields as { id: string[] }); - return definitions; + const definitions = response.hits.hits.map((hit) => hit._source!); + const total = response.hits.total!; + + return { + definitions, + total: typeof total === 'number' ? total : total.value, + }; } interface ReadStreamParams extends BaseParams { id: string; + skipAccessCheck?: boolean; } -export async function readStream({ id, scopedClusterClient }: ReadStreamParams) { +export interface ReadStreamResponse { + definition: StreamDefinition; +} + +export async function readStream({ + id, + scopedClusterClient, + skipAccessCheck, +}: ReadStreamParams): Promise<ReadStreamResponse> { try { const response = await scopedClusterClient.asInternalUser.get<StreamDefinition>({ id, index: STREAMS_INDEX, }); const definition = response._source as StreamDefinition; + if (!skipAccessCheck) { + const hasAccess = await checkReadAccess({ id, scopedClusterClient }); + if (!hasAccess) { + throw new DefinitionNotFound(`Stream definition for ${id} not found.`); + } + } return { definition, }; @@ -126,12 +151,21 @@ interface ReadAncestorsParams extends BaseParams { id: string; } -export async function readAncestors({ id, scopedClusterClient }: ReadAncestorsParams) { +export interface ReadAncestorsResponse { + ancestors: Array<{ definition: StreamDefinition }>; +} + +export async function readAncestors({ + id, + scopedClusterClient, +}: ReadAncestorsParams): Promise<ReadAncestorsResponse> { const ancestorIds = getAncestors(id); - return await Promise.all( - ancestorIds.map((ancestorId) => readStream({ scopedClusterClient, id: ancestorId })) - ); + return { + ancestors: await Promise.all( + ancestorIds.map((ancestorId) => readStream({ scopedClusterClient, id: ancestorId })) + ), + }; } interface ReadDescendantsParams extends BaseParams { @@ -167,7 +201,7 @@ export async function validateAncestorFields( id: string, fields: FieldDefinition[] ) { - const ancestors = await readAncestors({ + const { ancestors } = await readAncestors({ id, scopedClusterClient, }); @@ -223,6 +257,21 @@ export async function checkStreamExists({ id, scopedClusterClient }: ReadStreamP } } +interface CheckReadAccessParams extends BaseParams { + id: string; +} + +export async function checkReadAccess({ + id, + scopedClusterClient, +}: CheckReadAccessParams): Promise<boolean> { + try { + return await scopedClusterClient.asCurrentUser.indices.exists({ index: id }); + } catch (e) { + return false; + } +} + interface SyncStreamParams { scopedClusterClient: IScopedClusterClient; definition: StreamDefinition; @@ -236,10 +285,11 @@ export async function syncStream({ rootDefinition, logger, }: SyncStreamParams) { + const componentTemplate = generateLayer(definition.id, definition); await upsertComponent({ esClient: scopedClusterClient.asCurrentUser, logger, - component: generateLayer(definition.id, definition), + component: componentTemplate, }); await upsertIngestPipeline({ esClient: scopedClusterClient.asCurrentUser, @@ -282,5 +332,6 @@ export async function syncStream({ esClient: scopedClusterClient.asCurrentUser, name: definition.id, logger, + mappings: componentTemplate.template.mappings?.properties, }); } diff --git a/x-pack/plugins/streams/server/plugin.ts b/x-pack/plugins/streams/server/plugin.ts index ef070984803d..937f8c22b5be 100644 --- a/x-pack/plugins/streams/server/plugin.ts +++ b/x-pack/plugins/streams/server/plugin.ts @@ -16,8 +16,7 @@ import { } from '@kbn/core/server'; import { registerRoutes } from '@kbn/server-route-repository'; import { StreamsConfig, configSchema, exposeToBrowserConfig } from '../common/config'; -import { StreamsRouteRepository } from './routes'; -import { RouteDependencies } from './routes/types'; +import { streamsRouteRepository } from './routes'; import { StreamsPluginSetupDependencies, StreamsPluginStartDependencies, @@ -58,8 +57,8 @@ export class StreamsPlugin logger: this.logger, } as StreamsServer; - registerRoutes<RouteDependencies>({ - repository: StreamsRouteRepository, + registerRoutes({ + repository: streamsRouteRepository, dependencies: { server: this.server, getScopedClients: async ({ request }: { request: KibanaRequest }) => { diff --git a/x-pack/plugins/streams/server/routes/esql/route.ts b/x-pack/plugins/streams/server/routes/esql/route.ts new file mode 100644 index 000000000000..0e0e41eee3c7 --- /dev/null +++ b/x-pack/plugins/streams/server/routes/esql/route.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { excludeFrozenQuery } from '@kbn/observability-utils-common/es/queries/exclude_frozen_query'; +import { kqlQuery } from '@kbn/observability-utils-common/es/queries/kql_query'; +import { rangeQuery } from '@kbn/observability-utils-common/es/queries/range_query'; +import { + UnparsedEsqlResponse, + createObservabilityEsClient, +} from '@kbn/observability-utils-server/es/client/create_observability_es_client'; +import { z } from '@kbn/zod'; +import { isNumber } from 'lodash'; +import { createServerRoute } from '../create_server_route'; + +export const executeEsqlRoute = createServerRoute({ + endpoint: 'POST /internal/streams/esql', + params: z.object({ + body: z.object({ + query: z.string(), + operationName: z.string(), + filter: z.object({}).passthrough().optional(), + kuery: z.string().optional(), + start: z.number().optional(), + end: z.number().optional(), + }), + }), + handler: async ({ params, request, logger, getScopedClients }): Promise<UnparsedEsqlResponse> => { + const { scopedClusterClient } = await getScopedClients({ request }); + const observabilityEsClient = createObservabilityEsClient({ + client: scopedClusterClient.asCurrentUser, + logger, + plugin: 'streams', + }); + + const { + body: { operationName, query, filter, kuery, start, end }, + } = params; + + const response = await observabilityEsClient.esql( + operationName, + { + query, + filter: { + bool: { + filter: [ + filter || { match_all: {} }, + ...kqlQuery(kuery), + ...excludeFrozenQuery(), + ...(isNumber(start) && isNumber(end) ? rangeQuery(start, end) : []), + ], + }, + }, + }, + { transform: 'none' } + ); + + return response; + }, +}); + +export const esqlRoutes = { + ...executeEsqlRoute, +}; diff --git a/x-pack/plugins/streams/server/routes/index.ts b/x-pack/plugins/streams/server/routes/index.ts index 6fc734d3371b..7267dbedeacf 100644 --- a/x-pack/plugins/streams/server/routes/index.ts +++ b/x-pack/plugins/streams/server/routes/index.ts @@ -5,15 +5,18 @@ * 2.0. */ +import { esqlRoutes } from './esql/route'; import { deleteStreamRoute } from './streams/delete'; +import { disableStreamsRoute } from './streams/disable'; import { editStreamRoute } from './streams/edit'; import { enableStreamsRoute } from './streams/enable'; import { forkStreamsRoute } from './streams/fork'; import { listStreamsRoute } from './streams/list'; import { readStreamRoute } from './streams/read'; import { resyncStreamsRoute } from './streams/resync'; +import { streamsStatusRoutes } from './streams/settings'; -export const StreamsRouteRepository = { +export const streamsRouteRepository = { ...enableStreamsRoute, ...resyncStreamsRoute, ...forkStreamsRoute, @@ -21,6 +24,9 @@ export const StreamsRouteRepository = { ...editStreamRoute, ...deleteStreamRoute, ...listStreamsRoute, + ...streamsStatusRoutes, + ...esqlRoutes, + ...disableStreamsRoute, }; -export type StreamsRouteRepository = typeof StreamsRouteRepository; +export type StreamsRouteRepository = typeof streamsRouteRepository; diff --git a/x-pack/plugins/streams/server/routes/streams/delete.ts b/x-pack/plugins/streams/server/routes/streams/delete.ts index 3820975dbe16..a2092838792c 100644 --- a/x-pack/plugins/streams/server/routes/streams/delete.ts +++ b/x-pack/plugins/streams/server/routes/streams/delete.ts @@ -8,6 +8,7 @@ import { z } from '@kbn/zod'; import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; +import { badRequest, internal, notFound } from '@hapi/boom'; import { DefinitionNotFound, ForkConditionMissing, @@ -20,18 +21,15 @@ import { MalformedStreamId } from '../../lib/streams/errors/malformed_stream_id' import { getParentId } from '../../lib/streams/helpers/hierarchy'; export const deleteStreamRoute = createServerRoute({ - endpoint: 'DELETE /api/streams/{id} 2023-10-31', + endpoint: 'DELETE /api/streams/{id}', options: { - access: 'public', - availability: { - stability: 'experimental', - }, - security: { - authz: { - enabled: false, - reason: - 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', - }, + access: 'internal', + }, + security: { + authz: { + enabled: false, + reason: + 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', }, }, params: z.object({ @@ -39,7 +37,13 @@ export const deleteStreamRoute = createServerRoute({ id: z.string(), }), }), - handler: async ({ response, params, logger, request, getScopedClients }) => { + handler: async ({ + response, + params, + logger, + request, + getScopedClients, + }): Promise<{ acknowledged: true }> => { try { const { scopedClusterClient } = await getScopedClients({ request }); @@ -48,14 +52,15 @@ export const deleteStreamRoute = createServerRoute({ throw new MalformedStreamId('Cannot delete root stream'); } + // need to update parent first to cut off documents streaming down await updateParentStream(scopedClusterClient, params.path.id, parentId, logger); await deleteStream(scopedClusterClient, params.path.id, logger); - return response.ok({ body: { acknowledged: true } }); + return { acknowledged: true }; } catch (e) { if (e instanceof IndexTemplateNotFound || e instanceof DefinitionNotFound) { - return response.notFound({ body: e }); + throw notFound(e); } if ( @@ -63,15 +68,19 @@ export const deleteStreamRoute = createServerRoute({ e instanceof ForkConditionMissing || e instanceof MalformedStreamId ) { - return response.customError({ body: e, statusCode: 400 }); + throw badRequest(e); } - return response.customError({ body: e, statusCode: 500 }); + throw internal(e); } }, }); -async function deleteStream(scopedClusterClient: IScopedClusterClient, id: string, logger: Logger) { +export async function deleteStream( + scopedClusterClient: IScopedClusterClient, + id: string, + logger: Logger +) { try { const { definition } = await readStream({ scopedClusterClient, id }); for (const child of definition.children) { diff --git a/x-pack/plugins/streams/server/routes/streams/disable.ts b/x-pack/plugins/streams/server/routes/streams/disable.ts new file mode 100644 index 000000000000..b760b58f1faf --- /dev/null +++ b/x-pack/plugins/streams/server/routes/streams/disable.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { badRequest, internal } from '@hapi/boom'; +import { z } from '@kbn/zod'; +import { SecurityException } from '../../lib/streams/errors'; +import { createServerRoute } from '../create_server_route'; +import { deleteStream } from './delete'; + +export const disableStreamsRoute = createServerRoute({ + endpoint: 'POST /api/streams/_disable', + params: z.object({}), + options: { + access: 'internal', + }, + security: { + authz: { + requiredPrivileges: ['streams_write'], + }, + }, + handler: async ({ + request, + response, + logger, + getScopedClients, + }): Promise<{ acknowledged: true }> => { + try { + const { scopedClusterClient } = await getScopedClients({ request }); + + await deleteStream(scopedClusterClient, 'logs', logger); + + return { acknowledged: true }; + } catch (e) { + if (e instanceof SecurityException) { + throw badRequest(e); + } + throw internal(e); + } + }, +}); diff --git a/x-pack/plugins/streams/server/routes/streams/edit.ts b/x-pack/plugins/streams/server/routes/streams/edit.ts index b82b4d54044d..cda73907d230 100644 --- a/x-pack/plugins/streams/server/routes/streams/edit.ts +++ b/x-pack/plugins/streams/server/routes/streams/edit.ts @@ -8,6 +8,7 @@ import { z } from '@kbn/zod'; import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; +import { badRequest, internal, notFound } from '@hapi/boom'; import { DefinitionNotFound, ForkConditionMissing, @@ -28,18 +29,15 @@ import { getParentId } from '../../lib/streams/helpers/hierarchy'; import { MalformedChildren } from '../../lib/streams/errors/malformed_children'; export const editStreamRoute = createServerRoute({ - endpoint: 'PUT /api/streams/{id} 2023-10-31', + endpoint: 'PUT /api/streams/{id}', options: { - access: 'public', - availability: { - stability: 'experimental', - }, - security: { - authz: { - enabled: false, - reason: - 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', - }, + access: 'internal', + }, + security: { + authz: { + enabled: false, + reason: + 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', }, }, params: z.object({ @@ -59,22 +57,10 @@ export const editStreamRoute = createServerRoute({ const parentId = getParentId(params.path.id); let parentDefinition: StreamDefinition | undefined; - if (parentId) { - parentDefinition = await updateParentStream( - scopedClusterClient, - parentId, - params.path.id, - logger - ); - } const streamDefinition = { ...params.body }; - await syncStream({ - scopedClusterClient, - definition: { ...streamDefinition, id: params.path.id }, - rootDefinition: parentDefinition, - logger, - }); + // always need to go from the leaves to the parent when syncing ingest pipelines, otherwise data + // will be routed before the data stream is ready for (const child of streamDefinition.children) { const streamExists = await checkStreamExists({ @@ -99,10 +85,26 @@ export const editStreamRoute = createServerRoute({ }); } - return response.ok({ body: { acknowledged: true } }); + await syncStream({ + scopedClusterClient, + definition: { ...streamDefinition, id: params.path.id }, + rootDefinition: parentDefinition, + logger, + }); + + if (parentId) { + parentDefinition = await updateParentStream( + scopedClusterClient, + parentId, + params.path.id, + logger + ); + } + + return { acknowledged: true }; } catch (e) { if (e instanceof IndexTemplateNotFound || e instanceof DefinitionNotFound) { - return response.notFound({ body: e }); + throw notFound(e); } if ( @@ -110,10 +112,10 @@ export const editStreamRoute = createServerRoute({ e instanceof ForkConditionMissing || e instanceof MalformedStreamId ) { - return response.customError({ body: e, statusCode: 400 }); + throw badRequest(e); } - return response.customError({ body: e, statusCode: 500 }); + throw internal(e); } }, }); diff --git a/x-pack/plugins/streams/server/routes/streams/enable.ts b/x-pack/plugins/streams/server/routes/streams/enable.ts index 27d8929b28e5..e163c6cbc8bb 100644 --- a/x-pack/plugins/streams/server/routes/streams/enable.ts +++ b/x-pack/plugins/streams/server/routes/streams/enable.ts @@ -6,6 +6,7 @@ */ import { z } from '@kbn/zod'; +import { badRequest, internal } from '@hapi/boom'; import { SecurityException } from '../../lib/streams/errors'; import { createServerRoute } from '../create_server_route'; import { syncStream } from '../../lib/streams/stream_crud'; @@ -13,22 +14,24 @@ import { rootStreamDefinition } from '../../lib/streams/root_stream_definition'; import { createStreamsIndex } from '../../lib/streams/internal_stream_mapping'; export const enableStreamsRoute = createServerRoute({ - endpoint: 'POST /api/streams/_enable 2023-10-31', + endpoint: 'POST /api/streams/_enable', params: z.object({}), options: { - access: 'public', - availability: { - stability: 'experimental', - }, - security: { - authz: { - enabled: false, - reason: - 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', - }, + access: 'internal', + }, + security: { + authz: { + enabled: false, + reason: + 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', }, }, - handler: async ({ request, response, logger, getScopedClients }) => { + handler: async ({ + request, + response, + logger, + getScopedClients, + }): Promise<{ acknowledged: true }> => { try { const { scopedClusterClient } = await getScopedClients({ request }); await createStreamsIndex(scopedClusterClient); @@ -37,12 +40,12 @@ export const enableStreamsRoute = createServerRoute({ definition: rootStreamDefinition, logger, }); - return response.ok({ body: { acknowledged: true } }); + return { acknowledged: true }; } catch (e) { if (e instanceof SecurityException) { - return response.customError({ body: e, statusCode: 400 }); + throw badRequest(e); } - return response.customError({ body: e, statusCode: 500 }); + throw internal(e); } }, }); diff --git a/x-pack/plugins/streams/server/routes/streams/fork.ts b/x-pack/plugins/streams/server/routes/streams/fork.ts index 44f405287800..12dce248dcdd 100644 --- a/x-pack/plugins/streams/server/routes/streams/fork.ts +++ b/x-pack/plugins/streams/server/routes/streams/fork.ts @@ -6,6 +6,7 @@ */ import { z } from '@kbn/zod'; +import { badRequest, internal, notFound } from '@hapi/boom'; import { DefinitionNotFound, ForkConditionMissing, @@ -19,18 +20,15 @@ import { MalformedStreamId } from '../../lib/streams/errors/malformed_stream_id' import { isChildOf } from '../../lib/streams/helpers/hierarchy'; export const forkStreamsRoute = createServerRoute({ - endpoint: 'POST /api/streams/{id}/_fork 2023-10-31', + endpoint: 'POST /api/streams/{id}/_fork', options: { - access: 'public', - availability: { - stability: 'experimental', - }, - security: { - authz: { - enabled: false, - reason: - 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', - }, + access: 'internal', + }, + security: { + authz: { + enabled: false, + reason: + 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', }, }, params: z.object({ @@ -39,7 +37,12 @@ export const forkStreamsRoute = createServerRoute({ }), body: z.object({ stream: streamDefinitonWithoutChildrenSchema, condition: conditionSchema }), }), - handler: async ({ response, params, logger, request, getScopedClients }) => { + handler: async ({ + params, + logger, + request, + getScopedClients, + }): Promise<{ acknowledged: true }> => { try { if (!params.body.condition) { throw new ForkConditionMissing('You must provide a condition to fork a stream'); @@ -73,29 +76,30 @@ export const forkStreamsRoute = createServerRoute({ params.body.stream.fields ); - rootDefinition.children.push({ - id: params.body.stream.id, - condition: params.body.condition, - }); - + // need to create the child first, otherwise we risk streaming data even though the child data stream is not ready await syncStream({ scopedClusterClient, - definition: rootDefinition, + definition: childDefinition, rootDefinition, logger, }); + rootDefinition.children.push({ + id: params.body.stream.id, + condition: params.body.condition, + }); + await syncStream({ scopedClusterClient, - definition: childDefinition, + definition: rootDefinition, rootDefinition, logger, }); - return response.ok({ body: { acknowledged: true } }); + return { acknowledged: true }; } catch (e) { if (e instanceof IndexTemplateNotFound || e instanceof DefinitionNotFound) { - return response.notFound({ body: e }); + throw notFound(e); } if ( @@ -103,10 +107,10 @@ export const forkStreamsRoute = createServerRoute({ e instanceof ForkConditionMissing || e instanceof MalformedStreamId ) { - return response.customError({ body: e, statusCode: 400 }); + throw badRequest(e); } - return response.customError({ body: e, statusCode: 500 }); + throw internal(e); } }, }); diff --git a/x-pack/plugins/streams/server/routes/streams/list.ts b/x-pack/plugins/streams/server/routes/streams/list.ts index 2e4f13a89bb4..d3b88ffc36a4 100644 --- a/x-pack/plugins/streams/server/routes/streams/list.ts +++ b/x-pack/plugins/streams/server/routes/streams/list.ts @@ -6,65 +6,71 @@ */ import { z } from '@kbn/zod'; +import { notFound, internal } from '@hapi/boom'; import { createServerRoute } from '../create_server_route'; import { DefinitionNotFound } from '../../lib/streams/errors'; import { listStreams } from '../../lib/streams/stream_crud'; +import { StreamDefinition } from '../../../common'; export const listStreamsRoute = createServerRoute({ - endpoint: 'GET /api/streams 2023-10-31', + endpoint: 'GET /api/streams', options: { - access: 'public', - availability: { - stability: 'experimental', - }, - security: { - authz: { - enabled: false, - reason: - 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', - }, + access: 'internal', + }, + security: { + authz: { + enabled: false, + reason: + 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', }, }, params: z.object({}), - handler: async ({ response, request, getScopedClients }) => { + handler: async ({ + response, + request, + getScopedClients, + }): Promise<{ definitions: StreamDefinition[]; trees: StreamTree[] }> => { try { const { scopedClusterClient } = await getScopedClients({ request }); - const definitions = await listStreams({ scopedClusterClient }); + const { definitions } = await listStreams({ scopedClusterClient }); const trees = asTrees(definitions); - return response.ok({ body: { streams: trees } }); + return { definitions, trees }; } catch (e) { if (e instanceof DefinitionNotFound) { - return response.notFound({ body: e }); + throw notFound(e); } - return response.customError({ body: e, statusCode: 500 }); + throw internal(e); } }, }); -interface ListStreamDefinition { +export interface StreamTree { id: string; - children: ListStreamDefinition[]; + children: StreamTree[]; } -function asTrees(definitions: Array<{ id: string[] }>) { - const trees: ListStreamDefinition[] = []; - definitions.forEach((definition) => { - const path = definition.id[0].split('.'); +function asTrees(definitions: Array<{ id: string }>) { + const trees: StreamTree[] = []; + const ids = definitions.map((definition) => definition.id); + + ids.sort((a, b) => a.split('.').length - b.split('.').length); + + ids.forEach((id) => { let currentTree = trees; - path.forEach((_id, index) => { - const partialPath = path.slice(0, index + 1).join('.'); - const existingNode = currentTree.find((node) => node.id === partialPath); - if (existingNode) { - currentTree = existingNode.children; - } else { - const newNode = { id: partialPath, children: [] }; - currentTree.push(newNode); - currentTree = newNode.children; - } - }); + let existingNode: StreamTree | undefined; + // traverse the tree following the prefix of the current id. + // once we reach the leaf, the current id is added as child - this works because the ids are sorted by depth + while ((existingNode = currentTree.find((node) => id.startsWith(node.id)))) { + currentTree = existingNode.children; + } + if (!existingNode) { + const newNode = { id, children: [] }; + currentTree.push(newNode); + } }); + return trees; } diff --git a/x-pack/plugins/streams/server/routes/streams/read.ts b/x-pack/plugins/streams/server/routes/streams/read.ts index 5ea2aaf5f254..b9d21ef25b67 100644 --- a/x-pack/plugins/streams/server/routes/streams/read.ts +++ b/x-pack/plugins/streams/server/routes/streams/read.ts @@ -6,29 +6,38 @@ */ import { z } from '@kbn/zod'; +import { notFound, internal } from '@hapi/boom'; import { createServerRoute } from '../create_server_route'; import { DefinitionNotFound } from '../../lib/streams/errors'; import { readAncestors, readStream } from '../../lib/streams/stream_crud'; +import { StreamDefinition } from '../../../common'; export const readStreamRoute = createServerRoute({ - endpoint: 'GET /api/streams/{id} 2023-10-31', + endpoint: 'GET /api/streams/{id}', options: { - access: 'public', - availability: { - stability: 'experimental', - }, - security: { - authz: { - enabled: false, - reason: - 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', - }, + access: 'internal', + }, + security: { + authz: { + enabled: false, + reason: + 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', }, }, params: z.object({ path: z.object({ id: z.string() }), }), - handler: async ({ response, params, request, getScopedClients }) => { + handler: async ({ + response, + params, + request, + logger, + getScopedClients, + }): Promise< + StreamDefinition & { + inheritedFields: Array<StreamDefinition['fields'][number] & { from: string }>; + } + > => { try { const { scopedClusterClient } = await getScopedClients({ request }); const streamEntity = await readStream({ @@ -36,7 +45,7 @@ export const readStreamRoute = createServerRoute({ id: params.path.id, }); - const ancestors = await readAncestors({ + const { ancestors } = await readAncestors({ id: streamEntity.definition.id, scopedClusterClient, }); @@ -48,13 +57,13 @@ export const readStreamRoute = createServerRoute({ ), }; - return response.ok({ body }); + return body; } catch (e) { if (e instanceof DefinitionNotFound) { - return response.notFound({ body: e }); + throw notFound(e); } - return response.customError({ body: e, statusCode: 500 }); + throw internal(e); } }, }); diff --git a/x-pack/plugins/streams/server/routes/streams/resync.ts b/x-pack/plugins/streams/server/routes/streams/resync.ts index 2365252ab00e..8e520410ca5c 100644 --- a/x-pack/plugins/streams/server/routes/streams/resync.ts +++ b/x-pack/plugins/streams/server/routes/streams/resync.ts @@ -10,31 +10,34 @@ import { createServerRoute } from '../create_server_route'; import { syncStream, readStream, listStreams } from '../../lib/streams/stream_crud'; export const resyncStreamsRoute = createServerRoute({ - endpoint: 'POST /api/streams/_resync 2023-10-31', + endpoint: 'POST /api/streams/_resync', options: { - access: 'public', - availability: { - stability: 'experimental', - }, - security: { - authz: { - enabled: false, - reason: - 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', - }, + access: 'internal', + }, + security: { + authz: { + enabled: false, + reason: + 'This API delegates security to the currently logged in user and their Elasticsearch permissions.', }, }, params: z.object({}), - handler: async ({ response, logger, request, getScopedClients }) => { + handler: async ({ + response, + logger, + request, + getScopedClients, + }): Promise<{ acknowledged: true }> => { const { scopedClusterClient } = await getScopedClients({ request }); - const streams = await listStreams({ scopedClusterClient }); + const { definitions: streams } = await listStreams({ scopedClusterClient }); for (const stream of streams) { const { definition } = await readStream({ scopedClusterClient, - id: stream.id[0], + id: stream.id, }); + await syncStream({ scopedClusterClient, definition, @@ -42,6 +45,6 @@ export const resyncStreamsRoute = createServerRoute({ }); } - return response.ok({}); + return { acknowledged: true }; }, }); diff --git a/x-pack/plugins/streams/server/routes/streams/settings.ts b/x-pack/plugins/streams/server/routes/streams/settings.ts new file mode 100644 index 000000000000..6e133b3948dd --- /dev/null +++ b/x-pack/plugins/streams/server/routes/streams/settings.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { STREAMS_INDEX } from '../../../common/constants'; +import { createServerRoute } from '../create_server_route'; + +export const getStreamsStatusRoute = createServerRoute({ + endpoint: 'GET /api/streams/_status', + options: { + access: 'internal', + }, + security: { + authz: { + requiredPrivileges: ['streams_read'], + }, + }, + handler: async ({ request, getScopedClients }): Promise<{ enabled: boolean }> => { + const { scopedClusterClient } = await getScopedClients({ request }); + + return { + enabled: await scopedClusterClient.asInternalUser.indices.exists({ + index: STREAMS_INDEX, + }), + }; + }, +}); + +export const streamsStatusRoutes = { + ...getStreamsStatusRoute, +}; diff --git a/x-pack/plugins/streams/tsconfig.json b/x-pack/plugins/streams/tsconfig.json index c2fde35f9ca2..3f863145f4d2 100644 --- a/x-pack/plugins/streams/tsconfig.json +++ b/x-pack/plugins/streams/tsconfig.json @@ -27,5 +27,8 @@ "@kbn/zod", "@kbn/encrypted-saved-objects-plugin", "@kbn/licensing-plugin", + "@kbn/server-route-repository-client", + "@kbn/observability-utils-server", + "@kbn/observability-utils-common" ] } diff --git a/x-pack/plugins/streams_app/.storybook/get_mock_streams_app_context.tsx b/x-pack/plugins/streams_app/.storybook/get_mock_streams_app_context.tsx new file mode 100644 index 000000000000..1660042b2cb6 --- /dev/null +++ b/x-pack/plugins/streams_app/.storybook/get_mock_streams_app_context.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { coreMock } from '@kbn/core/public/mocks'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { StreamsPluginStart } from '@kbn/streams-plugin/public'; +import type { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { SharePublicStart } from '@kbn/share-plugin/public/plugin'; +import type { StreamsAppKibanaContext } from '../public/hooks/use_kibana'; + +export function getMockStreamsAppContext(): StreamsAppKibanaContext { + const core = coreMock.createStart(); + + return { + core, + dependencies: { + start: { + observabilityShared: {} as unknown as ObservabilitySharedPluginStart, + dataViews: {} as unknown as DataViewsPublicPluginStart, + data: {} as unknown as DataPublicPluginStart, + unifiedSearch: {} as unknown as UnifiedSearchPublicPluginStart, + streams: {} as unknown as StreamsPluginStart, + share: {} as unknown as SharePublicStart, + }, + }, + services: { + query: jest.fn(), + }, + }; +} diff --git a/x-pack/plugins/streams_app/.storybook/jest_setup.js b/x-pack/plugins/streams_app/.storybook/jest_setup.js new file mode 100644 index 000000000000..32071b8aa3f6 --- /dev/null +++ b/x-pack/plugins/streams_app/.storybook/jest_setup.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setGlobalConfig } from '@storybook/testing-react'; +import * as globalStorybookConfig from './preview'; + +setGlobalConfig(globalStorybookConfig); diff --git a/x-pack/plugins/streams_app/.storybook/main.js b/x-pack/plugins/streams_app/.storybook/main.js new file mode 100644 index 000000000000..86b48c32f103 --- /dev/null +++ b/x-pack/plugins/streams_app/.storybook/main.js @@ -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. + */ + +module.exports = require('@kbn/storybook').defaultConfig; diff --git a/x-pack/plugins/streams_app/.storybook/preview.js b/x-pack/plugins/streams_app/.storybook/preview.js new file mode 100644 index 000000000000..c8155e9c3d92 --- /dev/null +++ b/x-pack/plugins/streams_app/.storybook/preview.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common'; +import * as jest from 'jest-mock'; + +window.jest = jest; + +export const decorators = [EuiThemeProviderDecorator]; diff --git a/x-pack/plugins/streams_app/.storybook/storybook_decorator.tsx b/x-pack/plugins/streams_app/.storybook/storybook_decorator.tsx new file mode 100644 index 000000000000..617b5aee8128 --- /dev/null +++ b/x-pack/plugins/streams_app/.storybook/storybook_decorator.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, { ComponentType, useMemo } from 'react'; +import { StreamsAppContextProvider } from '../public/components/streams_app_context_provider'; +import { getMockStreamsAppContext } from './get_mock_streams_app_context'; + +export function KibanaReactStorybookDecorator(Story: ComponentType) { + const context = useMemo(() => getMockStreamsAppContext(), []); + return ( + <StreamsAppContextProvider context={context}> + <Story /> + </StreamsAppContextProvider> + ); +} diff --git a/x-pack/plugins/streams_app/README.md b/x-pack/plugins/streams_app/README.md new file mode 100644 index 000000000000..6e03524f9274 --- /dev/null +++ b/x-pack/plugins/streams_app/README.md @@ -0,0 +1,3 @@ +# Streams app + +Home of the Streams app plugin, which allows users to manage Streams via the UI. diff --git a/x-pack/plugins/streams_app/common/entity_source_query.ts b/x-pack/plugins/streams_app/common/entity_source_query.ts new file mode 100644 index 000000000000..c076d1f6bd87 --- /dev/null +++ b/x-pack/plugins/streams_app/common/entity_source_query.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function entitySourceQuery({ entity }: { entity: Record<string, string> }) { + return { + bool: { + filter: Object.entries(entity).map(([key, value]) => ({ term: { [key]: value } })), + }, + }; +} diff --git a/x-pack/plugins/streams_app/common/index.ts b/x-pack/plugins/streams_app/common/index.ts new file mode 100644 index 000000000000..c41a05b84d30 --- /dev/null +++ b/x-pack/plugins/streams_app/common/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { StreamDefinition } from '@kbn/streams-plugin/common'; + +interface EntityBase { + type: string; + displayName: string; + properties: Record<string, unknown>; +} + +export type StreamEntity = EntityBase & { type: 'stream'; properties: StreamDefinition }; + +export type Entity = StreamEntity; + +export interface EntityTypeDefinition { + displayName: string; +} diff --git a/x-pack/plugins/streams_app/jest.config.js b/x-pack/plugins/streams_app/jest.config.js new file mode 100644 index 000000000000..d9c01c40a322 --- /dev/null +++ b/x-pack/plugins/streams_app/jest.config.js @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: [ + '<rootDir>/x-pack/plugins/streams_app/public', + '<rootDir>/x-pack/plugins/streams_app/common', + '<rootDir>/x-pack/plugins/streams_app/server', + ], + setupFiles: ['<rootDir>/x-pack/plugins/streams_app/.storybook/jest_setup.js'], + collectCoverage: true, + collectCoverageFrom: [ + '<rootDir>/x-pack/plugins/streams_app/{public,common,server}/**/*.{js,ts,tsx}', + ], + + coverageReporters: ['html'], +}; diff --git a/x-pack/plugins/streams_app/kibana.jsonc b/x-pack/plugins/streams_app/kibana.jsonc new file mode 100644 index 000000000000..16666084c53e --- /dev/null +++ b/x-pack/plugins/streams_app/kibana.jsonc @@ -0,0 +1,26 @@ +{ + "type": "plugin", + "id": "@kbn/streams-app-plugin", + "owner": "@simianhacker @flash1293 @dgieselaar", + "group": "observability", + "visibility": "private", + "plugin": { + "id": "streamsApp", + "server": true, + "browser": true, + "configPath": ["xpack", "streamsApp"], + "requiredPlugins": [ + "streams", + "observabilityShared", + "data", + "dataViews", + "unifiedSearch", + "share" + ], + "requiredBundles": [ + "kibanaReact" + ], + "optionalPlugins": [], + "extraPublicDirs": [] + } +} diff --git a/x-pack/plugins/streams_app/public/application.tsx b/x-pack/plugins/streams_app/public/application.tsx new file mode 100644 index 000000000000..720f785ecde4 --- /dev/null +++ b/x-pack/plugins/streams_app/public/application.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { APP_WRAPPER_CLASS, type AppMountParameters, type CoreStart } from '@kbn/core/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { css } from '@emotion/css'; +import type { StreamsAppStartDependencies } from './types'; +import { StreamsAppServices } from './services/types'; +import { AppRoot } from './components/app_root'; + +export const renderApp = ({ + coreStart, + pluginsStart, + services, + appMountParameters, +}: { + coreStart: CoreStart; + pluginsStart: StreamsAppStartDependencies; + services: StreamsAppServices; +} & { appMountParameters: AppMountParameters }) => { + const { element } = appMountParameters; + + const appWrapperClassName = css` + overflow: auto; + `; + const appWrapperElement = document.getElementsByClassName(APP_WRAPPER_CLASS)[1]; + appWrapperElement.classList.add(appWrapperClassName); + + ReactDOM.render( + <KibanaRenderContextProvider {...coreStart}> + <AppRoot + appMountParameters={appMountParameters} + coreStart={coreStart} + pluginsStart={pluginsStart} + services={services} + /> + </KibanaRenderContextProvider>, + element + ); + return () => { + ReactDOM.unmountComponentAtNode(element); + appWrapperElement.classList.remove(APP_WRAPPER_CLASS); + }; +}; diff --git a/x-pack/plugins/streams_app/public/components/app_root/index.tsx b/x-pack/plugins/streams_app/public/components/app_root/index.tsx new file mode 100644 index 000000000000..c5e8b78ae215 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/app_root/index.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import React from 'react'; +import { type AppMountParameters, type CoreStart } from '@kbn/core/public'; +import { + BreadcrumbsContextProvider, + RouteRenderer, + RouterProvider, +} from '@kbn/typed-react-router-config'; +import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { StreamsAppContextProvider } from '../streams_app_context_provider'; +import { streamsAppRouter } from '../../routes/config'; +import { StreamsAppStartDependencies } from '../../types'; +import { StreamsAppServices } from '../../services/types'; + +export function AppRoot({ + coreStart, + pluginsStart, + services, + appMountParameters, +}: { + coreStart: CoreStart; + pluginsStart: StreamsAppStartDependencies; + services: StreamsAppServices; +} & { appMountParameters: AppMountParameters }) { + const { history } = appMountParameters; + + const context = { + core: coreStart, + dependencies: { + start: pluginsStart, + }, + services, + }; + + return ( + <StreamsAppContextProvider context={context}> + <RedirectAppLinks coreStart={coreStart}> + <RouterProvider history={history} router={streamsAppRouter}> + <BreadcrumbsContextProvider> + <RouteRenderer /> + </BreadcrumbsContextProvider> + <StreamsAppHeaderActionMenu appMountParameters={appMountParameters} /> + </RouterProvider> + </RedirectAppLinks> + </StreamsAppContextProvider> + ); +} + +export function StreamsAppHeaderActionMenu({ + appMountParameters, +}: { + appMountParameters: AppMountParameters; +}) { + const { setHeaderActionMenu, theme$ } = appMountParameters; + + return ( + <HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu} theme$={theme$}> + <EuiFlexGroup responsive={false} gutterSize="s"> + <EuiFlexItem> + <></> + </EuiFlexItem> + </EuiFlexGroup> + </HeaderMenuPortal> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/entity_detail_view/index.tsx b/x-pack/plugins/streams_app/public/components/entity_detail_view/index.tsx new file mode 100644 index 000000000000..d2ce4859e66d --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/entity_detail_view/index.tsx @@ -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 { EuiFlexGroup, EuiIcon, EuiLink, EuiPanel } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useStreamsAppBreadcrumbs } from '../../hooks/use_streams_app_breadcrumbs'; +import { useStreamsAppRouter } from '../../hooks/use_streams_app_router'; +import { EntityOverviewTabList } from '../entity_overview_tab_list'; +import { LoadingPanel } from '../loading_panel'; +import { StreamsAppPageBody } from '../streams_app_page_body'; +import { StreamsAppPageHeader } from '../streams_app_page_header'; +import { StreamsAppPageHeaderTitle } from '../streams_app_page_header/streams_app_page_header_title'; + +export interface EntityViewTab { + name: string; + label: string; + content: React.ReactElement; +} + +export function EntityDetailViewWithoutParams({ + selectedTab, + tabs, + entity, +}: { + selectedTab: string; + tabs: EntityViewTab[]; + entity: { + displayName?: string; + id: string; + }; +}) { + const router = useStreamsAppRouter(); + useStreamsAppBreadcrumbs(() => { + if (!entity.displayName) { + return []; + } + + return [ + { + title: entity.displayName, + path: `/{key}`, + params: { path: { key: entity.id } }, + } as const, + ]; + }, [entity.displayName, entity.id]); + + if (!entity.displayName) { + return <LoadingPanel />; + } + + const tabMap = Object.fromEntries( + tabs.map((tab) => { + return [ + tab.name, + { + href: router.link('/{key}/{tab}', { + path: { key: entity.id, tab: tab.name }, + }), + label: tab.label, + content: tab.content, + }, + ]; + }) + ); + + const selectedTabObject = tabMap[selectedTab]; + + return ( + <EuiFlexGroup direction="column" gutterSize="none"> + <EuiPanel color="transparent"> + <EuiLink data-test-subj="streamsEntityDetailViewGoBackHref" href={router.link('/')}> + <EuiFlexGroup direction="row" alignItems="center" gutterSize="s"> + <EuiIcon type="arrowLeft" /> + {i18n.translate('xpack.streams.entityDetailView.goBackLinkLabel', { + defaultMessage: 'Back', + })} + </EuiFlexGroup> + </EuiLink> + </EuiPanel> + <StreamsAppPageHeader + verticalPaddingSize="none" + title={<StreamsAppPageHeaderTitle title={entity.displayName} />} + > + <EntityOverviewTabList + tabs={Object.entries(tabMap).map(([tabKey, { label, href }]) => { + return { + name: tabKey, + label, + href, + selected: selectedTab === tabKey, + }; + })} + /> + </StreamsAppPageHeader> + <StreamsAppPageBody>{selectedTabObject.content}</StreamsAppPageBody> + </EuiFlexGroup> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/entity_detail_view_header_section/index.tsx b/x-pack/plugins/streams_app/public/components/entity_detail_view_header_section/index.tsx new file mode 100644 index 000000000000..4aafb7a7e9bc --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/entity_detail_view_header_section/index.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexGroup, EuiText } from '@elastic/eui'; +import { css } from '@emotion/css'; +import React from 'react'; + +export function EntityDetailViewHeaderSection({ + title, + children, +}: { + title: React.ReactNode; + children: React.ReactNode; +}) { + return ( + <EuiFlexGroup direction="column" gutterSize="s"> + <EuiText + className={css` + font-weight: 600; + `} + > + {title} + </EuiText> + {children} + </EuiFlexGroup> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/entity_overview_tab_list/index.tsx b/x-pack/plugins/streams_app/public/components/entity_overview_tab_list/index.tsx new file mode 100644 index 000000000000..08502b26f7ca --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/entity_overview_tab_list/index.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiTab, EuiTabs, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/css'; + +export function EntityOverviewTabList< + T extends { name: string; label: string; href: string; selected: boolean } +>({ tabs }: { tabs: T[] }) { + const theme = useEuiTheme().euiTheme; + + return ( + <EuiTabs + size="m" + className={css` + padding: 0 ${theme.size.l}; + `} + > + {tabs.map((tab) => { + return ( + <EuiTab key={tab.name} href={tab.href} isSelected={tab.selected}> + {tab.label} + </EuiTab> + ); + })} + </EuiTabs> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/esql_chart/controlled_esql_chart.tsx b/x-pack/plugins/streams_app/public/components/esql_chart/controlled_esql_chart.tsx new file mode 100644 index 000000000000..9008f8ee4709 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/esql_chart/controlled_esql_chart.tsx @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useMemo } from 'react'; +import { + AreaSeries, + Axis, + BarSeries, + Chart, + CurveType, + LineSeries, + Position, + ScaleType, + Settings, + Tooltip, + niceTimeFormatter, +} from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { getTimeZone } from '@kbn/observability-utils-browser/utils/ui_settings/get_timezone'; +import { css } from '@emotion/css'; +import { AbortableAsyncState } from '@kbn/observability-utils-browser/hooks/use_abortable_async'; +import type { UnparsedEsqlResponse } from '@kbn/observability-utils-server/es/client/create_observability_es_client'; +import { esqlResultToTimeseries } from '../../util/esql_result_to_timeseries'; +import { useKibana } from '../../hooks/use_kibana'; +import { LoadingPanel } from '../loading_panel'; + +const END_ZONE_LABEL = i18n.translate('xpack.streams.esqlChart.endzone', { + defaultMessage: + 'The selected time range does not include this entire bucket. It might contain partial data.', +}); + +function getChartType(type: 'area' | 'bar' | 'line') { + switch (type) { + case 'area': + return AreaSeries; + case 'bar': + return BarSeries; + default: + return LineSeries; + } +} + +export function ControlledEsqlChart<T extends string>({ + id, + result, + metricNames, + chartType = 'line', + height, +}: { + id: string; + result: AbortableAsyncState<UnparsedEsqlResponse>; + metricNames: T[]; + chartType?: 'area' | 'bar' | 'line'; + height: number; +}) { + const { + core: { uiSettings }, + } = useKibana(); + + const allTimeseries = useMemo( + () => + esqlResultToTimeseries<T>({ + result, + metricNames, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [result, ...metricNames] + ); + + if (result.loading && !result.value?.values.length) { + return ( + <LoadingPanel + loading + className={css` + height: ${height}px; + `} + /> + ); + } + + const xValues = allTimeseries.flatMap(({ data }) => data.map(({ x }) => x)); + + const min = Math.min(...xValues); + const max = Math.max(...xValues); + + const isEmpty = min === 0 && max === 0; + + const xFormatter = niceTimeFormatter([min, max]); + + const xDomain = isEmpty ? { min: 0, max: 1 } : { min, max }; + + const yTickFormat = (value: number | null) => (value === null ? '' : String(value)); + const yLabelFormat = (label: string) => label; + + const timeZone = getTimeZone(uiSettings); + + return ( + <Chart + id={id} + className={css` + height: ${height}px; + `} + > + <Tooltip + stickTo="top" + showNullValues={false} + headerFormatter={({ value }) => { + const formattedValue = xFormatter(value); + if (max === value) { + return ( + <> + <EuiFlexGroup + alignItems="center" + responsive={false} + gutterSize="xs" + style={{ fontWeight: 'normal' }} + > + <EuiFlexItem grow={false}> + <EuiIcon type="iInCircle" /> + </EuiFlexItem> + <EuiFlexItem>{END_ZONE_LABEL}</EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xs" /> + {formattedValue} + </> + ); + } + return formattedValue; + }} + /> + <Settings + showLegend + legendPosition={Position.Bottom} + xDomain={xDomain} + locale={i18n.getLocale()} + /> + <Axis + id="x-axis" + position={Position.Bottom} + showOverlappingTicks + tickFormat={xFormatter} + gridLine={{ visible: false }} + /> + <Axis + id="y-axis" + ticks={3} + position={Position.Left} + tickFormat={yTickFormat} + labelFormat={yLabelFormat} + /> + {allTimeseries.map((serie) => { + const Series = getChartType(chartType); + + return ( + <Series + timeZone={timeZone} + key={serie.id} + id={serie.id} + xScaleType={ScaleType.Time} + yScaleType={ScaleType.Linear} + xAccessor="x" + yAccessors={serie.metricNames} + data={serie.data} + curve={CurveType.CURVE_MONOTONE_X} + /> + ); + })} + </Chart> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/loading_panel/index.tsx b/x-pack/plugins/streams_app/public/components/loading_panel/index.tsx new file mode 100644 index 000000000000..58e432b84a4b --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/loading_panel/index.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexGroup, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import React from 'react'; + +export function LoadingPanel({ + loading = true, + size, + className, +}: { + loading?: boolean; + size?: React.ComponentProps<typeof EuiLoadingSpinner>['size']; + className?: string; +}) { + if (!loading) { + return null; + } + + return ( + <EuiFlexGroup + className={className} + alignItems="center" + justifyContent="center" + gutterSize="none" + > + <EuiSpacer size="xl" /> + <EuiLoadingSpinner size={size} /> + <EuiSpacer size="xl" /> + </EuiFlexGroup> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/not_found/index.tsx b/x-pack/plugins/streams_app/public/components/not_found/index.tsx new file mode 100644 index 000000000000..c0c1d50000cf --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/not_found/index.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiCallOut } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +export function NotFound() { + return ( + <EuiCallOut + color="danger" + title={i18n.translate('xpack.streams.notFound.callOutTitle', { + defaultMessage: 'Page not found', + })} + > + {i18n.translate('xpack.streams.notFound.calloutLabel', { + defaultMessage: 'The current page can not be found.', + })} + </EuiCallOut> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/redirect_to/index.tsx b/x-pack/plugins/streams_app/public/components/redirect_to/index.tsx new file mode 100644 index 000000000000..2bde67faa3b9 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/redirect_to/index.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useLayoutEffect } from 'react'; +import { PathsOf, TypeOf } from '@kbn/typed-react-router-config'; +import { DeepPartial } from 'utility-types'; +import { merge } from 'lodash'; +import { StreamsAppRoutes } from '../../routes/config'; +import { useStreamsAppRouter } from '../../hooks/use_streams_app_router'; +import { useStreamsAppParams } from '../../hooks/use_streams_app_params'; + +export function RedirectTo< + TPath extends PathsOf<StreamsAppRoutes>, + TParams extends TypeOf<StreamsAppRoutes, TPath, false> +>({ path, params }: { path: TPath; params?: DeepPartial<TParams> }) { + const router = useStreamsAppRouter(); + const currentParams = useStreamsAppParams('/*'); + useLayoutEffect(() => { + router.replace(path, ...([merge({}, currentParams, params)] as any)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return <></>; +} diff --git a/x-pack/plugins/streams_app/public/components/stream_detail_overview/index.tsx b/x-pack/plugins/streams_app/public/components/stream_detail_overview/index.tsx new file mode 100644 index 000000000000..93a573fd4c01 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/stream_detail_overview/index.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 { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { calculateAuto } from '@kbn/calculate-auto'; +import { i18n } from '@kbn/i18n'; +import { useDateRange } from '@kbn/observability-utils-browser/hooks/use_date_range'; +import { StreamDefinition } from '@kbn/streams-plugin/common'; +import moment from 'moment'; +import React, { useMemo } from 'react'; +import { useKibana } from '../../hooks/use_kibana'; +import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch'; +import { ControlledEsqlChart } from '../esql_chart/controlled_esql_chart'; +import { StreamsAppSearchBar } from '../streams_app_search_bar'; + +export function StreamDetailOverview({ definition }: { definition?: StreamDefinition }) { + const { + dependencies: { + start: { + data, + dataViews, + streams: { streamsRepositoryClient }, + share, + }, + }, + } = useKibana(); + + const { + timeRange, + absoluteTimeRange: { start, end }, + setTimeRange, + } = useDateRange({ data }); + + const indexPatterns = useMemo(() => { + if (!definition?.id) { + return undefined; + } + + const isRoot = definition.id.indexOf('.') === -1; + + const dataStreamOfDefinition = definition.id; + + return isRoot + ? [dataStreamOfDefinition, `${dataStreamOfDefinition}.*`] + : [`${dataStreamOfDefinition}*`]; + }, [definition?.id]); + + const discoverLocator = useMemo( + () => share.url.locators.get('DISCOVER_APP_LOCATOR'), + [share.url.locators] + ); + + const queries = useMemo(() => { + if (!indexPatterns) { + return undefined; + } + + const baseQuery = `FROM ${indexPatterns.join(', ')}`; + + const bucketSize = Math.round( + calculateAuto.atLeast(50, moment.duration(1, 'minute'))!.asSeconds() + ); + + const histogramQuery = `${baseQuery} | STATS metric = COUNT(*) BY @timestamp = BUCKET(@timestamp, ${bucketSize} seconds)`; + + return { + baseQuery, + histogramQuery, + }; + }, [indexPatterns]); + + const discoverLink = useMemo(() => { + if (!discoverLocator || !queries?.baseQuery) { + return undefined; + } + + return discoverLocator.getRedirectUrl({ + query: { + esql: queries.baseQuery, + }, + }); + }, [queries?.baseQuery, discoverLocator]); + + const histogramQueryFetch = useStreamsAppFetch( + async ({ signal }) => { + if (!queries?.histogramQuery || !indexPatterns) { + return undefined; + } + + const existingIndices = await dataViews.getExistingIndices(indexPatterns); + + if (existingIndices.length === 0) { + return undefined; + } + + return streamsRepositoryClient.fetch('POST /internal/streams/esql', { + params: { + body: { + operationName: 'get_histogram_for_stream', + query: queries.histogramQuery, + start, + end, + }, + }, + signal, + }); + }, + [indexPatterns, dataViews, streamsRepositoryClient, queries?.histogramQuery, start, end] + ); + + return ( + <> + <EuiFlexGroup direction="column"> + <EuiFlexGroup direction="row" gutterSize="s"> + <EuiFlexItem grow> + <StreamsAppSearchBar + onQuerySubmit={({ dateRange }, isUpdate) => { + if (!isUpdate) { + histogramQueryFetch.refresh(); + return; + } + + if (dateRange) { + setTimeRange({ from: dateRange.from, to: dateRange?.to, mode: dateRange.mode }); + } + }} + onRefresh={() => { + histogramQueryFetch.refresh(); + }} + placeholder={i18n.translate( + 'xpack.streams.entityDetailOverview.searchBarPlaceholder', + { + defaultMessage: 'Filter data by using KQL', + } + )} + dateRangeFrom={timeRange.from} + dateRangeTo={timeRange.to} + /> + </EuiFlexItem> + <EuiButton + data-test-subj="streamsDetailOverviewOpenInDiscoverButton" + iconType="discoverApp" + href={discoverLink} + color="text" + > + {i18n.translate('xpack.streams.streamDetailOverview.openInDiscoverButtonLabel', { + defaultMessage: 'Open in Discover', + })} + </EuiButton> + </EuiFlexGroup> + <EuiPanel hasShadow={false} hasBorder> + <EuiFlexGroup direction="column"> + <ControlledEsqlChart + result={histogramQueryFetch} + id="entity_log_rate" + metricNames={['metric']} + height={200} + chartType={'bar'} + /> + </EuiFlexGroup> + </EuiPanel> + </EuiFlexGroup> + </> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/stream_detail_view/index.tsx b/x-pack/plugins/streams_app/public/components/stream_detail_view/index.tsx new file mode 100644 index 000000000000..ebf72a58d32a --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/stream_detail_view/index.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 { EntityDetailViewWithoutParams, EntityViewTab } from '../entity_detail_view'; +import { useStreamsAppParams } from '../../hooks/use_streams_app_params'; +import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch'; +import { useKibana } from '../../hooks/use_kibana'; +import { StreamDetailOverview } from '../stream_detail_overview'; + +export function StreamDetailView() { + const { + path: { key, tab }, + } = useStreamsAppParams('/{key}/{tab}'); + + const { + dependencies: { + start: { + streams: { streamsRepositoryClient }, + }, + }, + } = useKibana(); + + const { value: streamEntity } = useStreamsAppFetch( + ({ signal }) => { + return streamsRepositoryClient.fetch('GET /api/streams/{id}', { + signal, + params: { + path: { + id: key, + }, + }, + }); + }, + [streamsRepositoryClient, key] + ); + + const entity = { + id: key, + displayName: key, + }; + + const tabs: EntityViewTab[] = [ + { + name: 'overview', + content: <StreamDetailOverview definition={streamEntity} />, + label: i18n.translate('xpack.streams.streamDetailView.overviewTab', { + defaultMessage: 'Overview', + }), + }, + { + name: 'management', + content: <></>, + label: i18n.translate('xpack.streams.streamDetailView.managementTab', { + defaultMessage: 'Management', + }), + }, + ]; + + return <EntityDetailViewWithoutParams tabs={tabs} entity={entity} selectedTab={tab} />; +} diff --git a/x-pack/plugins/streams_app/public/components/stream_list_view/index.tsx b/x-pack/plugins/streams_app/public/components/stream_list_view/index.tsx new file mode 100644 index 000000000000..e0530fc6bf5f --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/stream_list_view/index.tsx @@ -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 React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiSearchBar } from '@elastic/eui'; +import { useKibana } from '../../hooks/use_kibana'; +import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch'; +import { StreamsAppPageHeader } from '../streams_app_page_header'; +import { StreamsAppPageHeaderTitle } from '../streams_app_page_header/streams_app_page_header_title'; +import { StreamsAppPageBody } from '../streams_app_page_body'; +import { StreamsTable } from '../streams_table'; + +export function StreamListView() { + const { + dependencies: { + start: { + streams: { streamsRepositoryClient }, + }, + }, + } = useKibana(); + + const [query, setQuery] = useState(''); + + const streamsListFetch = useStreamsAppFetch( + ({ signal }) => { + return streamsRepositoryClient.fetch('GET /api/streams', { + signal, + }); + }, + [streamsRepositoryClient] + ); + + return ( + <EuiFlexGroup direction="column" gutterSize="none"> + <StreamsAppPageHeader + title={ + <StreamsAppPageHeaderTitle + title={i18n.translate('xpack.streams.streamsListViewPageHeaderTitle', { + defaultMessage: 'Streams', + })} + /> + } + /> + <StreamsAppPageBody> + <EuiFlexGroup direction="column"> + <EuiSearchBar + query={query} + box={{ + incremental: true, + }} + onChange={(nextQuery) => { + setQuery(nextQuery.queryText); + }} + /> + <StreamsTable listFetch={streamsListFetch} query={query} /> + </EuiFlexGroup> + </StreamsAppPageBody> + </EuiFlexGroup> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/streams_app_context_provider/index.tsx b/x-pack/plugins/streams_app/public/components/streams_app_context_provider/index.tsx new file mode 100644 index 000000000000..fd5886c08939 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/streams_app_context_provider/index.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useMemo } from 'react'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import type { StreamsAppKibanaContext } from '../../hooks/use_kibana'; + +export function StreamsAppContextProvider({ + context, + children, +}: { + context: StreamsAppKibanaContext; + children: React.ReactNode; +}) { + const servicesForContext = useMemo(() => { + const { core, ...services } = context; + return { + ...core, + ...services, + }; + }, [context]); + + return <KibanaContextProvider services={servicesForContext}>{children}</KibanaContextProvider>; +} diff --git a/x-pack/plugins/streams_app/public/components/streams_app_page_body/index.tsx b/x-pack/plugins/streams_app/public/components/streams_app_page_body/index.tsx new file mode 100644 index 000000000000..0f13dc31e277 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/streams_app_page_body/index.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiPanel, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/css'; + +export function StreamsAppPageBody({ children }: { children: React.ReactNode }) { + const theme = useEuiTheme().euiTheme; + return ( + <EuiPanel + hasBorder={false} + hasShadow={false} + className={css` + border-top: 1px solid ${theme.colors.lightShade}; + border-radius: 0px; + `} + paddingSize="l" + > + {children} + </EuiPanel> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/streams_app_page_header/index.tsx b/x-pack/plugins/streams_app/public/components/streams_app_page_header/index.tsx new file mode 100644 index 000000000000..1171772116f2 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/streams_app_page_header/index.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexGroup, EuiPageHeader, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/css'; +import React from 'react'; + +export function StreamsAppPageHeader({ + title, + children, + verticalPaddingSize = 'l', +}: { + title: React.ReactNode; + children?: React.ReactNode; + verticalPaddingSize?: 'none' | 'l'; +}) { + const theme = useEuiTheme().euiTheme; + + return ( + <EuiFlexGroup direction="column" gutterSize="s"> + <EuiPageHeader + className={css` + padding: ${verticalPaddingSize === 'none' ? 0 : theme.size[verticalPaddingSize]} + ${theme.size.l}; + `} + > + {title} + </EuiPageHeader> + {children} + </EuiFlexGroup> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/streams_app_page_header/streams_app_page_header_title.tsx b/x-pack/plugins/streams_app/public/components/streams_app_page_header/streams_app_page_header_title.tsx new file mode 100644 index 000000000000..ff7d6581dea4 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/streams_app_page_header/streams_app_page_header_title.tsx @@ -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 { EuiTitle } from '@elastic/eui'; +import React from 'react'; + +export function StreamsAppPageHeaderTitle({ title }: { title: string }) { + return ( + <EuiTitle size="l"> + <h1>{title}</h1> + </EuiTitle> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/streams_app_page_template/index.tsx b/x-pack/plugins/streams_app/public/components/streams_app_page_template/index.tsx new file mode 100644 index 000000000000..c474f54c2274 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/streams_app_page_template/index.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { css } from '@emotion/css'; +import React from 'react'; +import { EuiPanel, EuiSpacer } from '@elastic/eui'; +import { useKibana } from '../../hooks/use_kibana'; + +export function StreamsAppPageTemplate({ children }: { children: React.ReactNode }) { + const { + dependencies: { + start: { observabilityShared }, + }, + } = useKibana(); + + const { PageTemplate } = observabilityShared.navigation; + + return ( + <PageTemplate + pageSectionProps={{ + className: css` + max-height: calc(100vh - var(--euiFixedHeadersOffset, 0)); + overflow: auto; + padding-inline: 0px; + `, + contentProps: { + className: css` + padding-block: 0px; + display: flex; + height: 100%; + `, + }, + }} + > + <EuiPanel paddingSize="none" color="subdued" hasShadow={false} hasBorder={false}> + <EuiSpacer size="m" /> + {children} + </EuiPanel> + </PageTemplate> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/streams_app_router_breadcrumb/index.tsx b/x-pack/plugins/streams_app/public/components/streams_app_router_breadcrumb/index.tsx new file mode 100644 index 000000000000..88aab4662de6 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/streams_app_router_breadcrumb/index.tsx @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createRouterBreadcrumbComponent } from '@kbn/typed-react-router-config'; +import type { StreamsAppRoutes } from '../../routes/config'; + +export const StreamsAppRouterBreadcrumb = createRouterBreadcrumbComponent<StreamsAppRoutes>(); diff --git a/x-pack/plugins/streams_app/public/components/streams_app_search_bar/index.tsx b/x-pack/plugins/streams_app/public/components/streams_app_search_bar/index.tsx new file mode 100644 index 000000000000..563fb752efbd --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/streams_app_search_bar/index.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { css } from '@emotion/css'; +import type { TimeRange } from '@kbn/es-query'; +import { SearchBar } from '@kbn/unified-search-plugin/public'; +import React, { useMemo } from 'react'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { useKibana } from '../../hooks/use_kibana'; + +const parentClassName = css` + width: 100%; +`; + +interface Props { + query?: string; + dateRangeFrom?: string; + dateRangeTo?: string; + onQueryChange?: (payload: { dateRange?: TimeRange; query: string }) => void; + onQuerySubmit?: (payload: { dateRange?: TimeRange; query: string }, isUpdate?: boolean) => void; + onRefresh?: Required<React.ComponentProps<typeof SearchBar>>['onRefresh']; + placeholder?: string; + dataViews?: DataView[]; +} + +export function StreamsAppSearchBar({ + dateRangeFrom, + dateRangeTo, + onQueryChange, + onQuerySubmit, + onRefresh, + query, + placeholder, + dataViews, +}: Props) { + const { + dependencies: { + start: { unifiedSearch }, + }, + } = useKibana(); + + const queryObj = useMemo(() => (query ? { query, language: 'kuery' } : undefined), [query]); + + const showQueryInput = query === undefined; + + return ( + <div className={parentClassName}> + <unifiedSearch.ui.SearchBar + appName="streamsApp" + onQuerySubmit={({ dateRange, query: nextQuery }, isUpdate) => { + onQuerySubmit?.( + { dateRange, query: (nextQuery?.query as string | undefined) ?? '' }, + isUpdate + ); + }} + onQueryChange={({ dateRange, query: nextQuery }) => { + onQueryChange?.({ dateRange, query: (nextQuery?.query as string | undefined) ?? '' }); + }} + query={queryObj} + showQueryInput={showQueryInput} + showFilterBar={false} + showQueryMenu={false} + showDatePicker={Boolean(dateRangeFrom && dateRangeTo)} + showSubmitButton={true} + dateRangeFrom={dateRangeFrom} + dateRangeTo={dateRangeTo} + onRefresh={onRefresh} + displayStyle="inPage" + disableQueryLanguageSwitcher + placeholder={placeholder} + indexPatterns={dataViews} + /> + </div> + ); +} diff --git a/x-pack/plugins/streams_app/public/components/streams_table/index.tsx b/x-pack/plugins/streams_app/public/components/streams_table/index.tsx new file mode 100644 index 000000000000..f92c94f115e9 --- /dev/null +++ b/x-pack/plugins/streams_app/public/components/streams_table/index.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiFlexGroup, + EuiIcon, + EuiLink, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { AbortableAsyncState } from '@kbn/observability-utils-browser/hooks/use_abortable_async'; +import { StreamDefinition } from '@kbn/streams-plugin/common'; +import React, { useMemo } from 'react'; +import { useStreamsAppRouter } from '../../hooks/use_streams_app_router'; + +export function StreamsTable({ + listFetch, + query, +}: { + listFetch: AbortableAsyncState<{ definitions: StreamDefinition[] }>; + query: string; +}) { + const router = useStreamsAppRouter(); + + const items = useMemo(() => { + return listFetch.value?.definitions ?? []; + }, [listFetch.value?.definitions]); + + const filteredItems = useMemo(() => { + if (!query) { + return items; + } + + return items.filter((item) => item.id.toLowerCase().includes(query.toLowerCase())); + }, [query, items]); + + const columns = useMemo<Array<EuiBasicTableColumn<StreamDefinition>>>(() => { + return [ + { + field: 'id', + name: i18n.translate('xpack.streams.streamsTable.nameColumnTitle', { + defaultMessage: 'Name', + }), + render: (_, { id }) => { + return ( + <EuiFlexGroup direction="row" gutterSize="s" alignItems="center"> + <EuiIcon type="branch" /> + <EuiLink + data-test-subj="logsaiColumnsLink" + href={router.link('/{key}', { path: { key: id } })} + > + {id} + </EuiLink> + </EuiFlexGroup> + ); + }, + }, + ]; + }, [router]); + + return ( + <EuiFlexGroup direction="column" gutterSize="m"> + <EuiTitle size="xxs"> + <h2> + {i18n.translate('xpack.streams.streamsTable.tableTitle', { + defaultMessage: 'Streams', + })} + </h2> + </EuiTitle> + <EuiBasicTable columns={columns} items={filteredItems} loading={listFetch.loading} /> + </EuiFlexGroup> + ); +} diff --git a/x-pack/plugins/streams_app/public/hooks/use_kibana.tsx b/x-pack/plugins/streams_app/public/hooks/use_kibana.tsx new file mode 100644 index 000000000000..9c6b23465fb1 --- /dev/null +++ b/x-pack/plugins/streams_app/public/hooks/use_kibana.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart } from '@kbn/core/public'; +import { useMemo } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { StreamsAppStartDependencies } from '../types'; +import type { StreamsAppServices } from '../services/types'; + +export interface StreamsAppKibanaContext { + core: CoreStart; + dependencies: { + start: StreamsAppStartDependencies; + }; + services: StreamsAppServices; +} + +const useTypedKibana = (): StreamsAppKibanaContext => { + const context = useKibana<CoreStart & Omit<StreamsAppKibanaContext, 'core'>>(); + + return useMemo(() => { + const { dependencies, services, ...core } = context.services; + + return { + core, + dependencies, + services, + }; + }, [context.services]); +}; + +export { useTypedKibana as useKibana }; diff --git a/x-pack/plugins/streams_app/public/hooks/use_streams_app_breadcrumbs.ts b/x-pack/plugins/streams_app/public/hooks/use_streams_app_breadcrumbs.ts new file mode 100644 index 000000000000..e3ac760e3b77 --- /dev/null +++ b/x-pack/plugins/streams_app/public/hooks/use_streams_app_breadcrumbs.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createUseBreadcrumbs } from '@kbn/typed-react-router-config'; +import { StreamsAppRoutes } from '../routes/config'; + +export const useStreamsAppBreadcrumbs = createUseBreadcrumbs<StreamsAppRoutes>(); diff --git a/x-pack/plugins/streams_app/public/hooks/use_streams_app_fetch.ts b/x-pack/plugins/streams_app/public/hooks/use_streams_app_fetch.ts new file mode 100644 index 000000000000..08b112d4f207 --- /dev/null +++ b/x-pack/plugins/streams_app/public/hooks/use_streams_app_fetch.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { + UseAbortableAsync, + useAbortableAsync, +} from '@kbn/observability-utils-browser/hooks/use_abortable_async'; +import { omit } from 'lodash'; +import { useKibana } from './use_kibana'; + +export const useStreamsAppFetch: UseAbortableAsync<{}, { disableToastOnError?: boolean }> = ( + callback, + deps, + options +) => { + const { + core: { notifications }, + } = useKibana(); + + const onError = (error: Error) => { + let requestUrl: string | undefined; + + if (!options?.disableToastOnError) { + if ( + 'body' in error && + typeof error.body === 'object' && + !!error.body && + 'message' in error.body && + typeof error.body.message === 'string' + ) { + error.message = error.body.message; + } + + if ( + 'request' in error && + typeof error.request === 'object' && + !!error.request && + 'url' in error.request && + typeof error.request.url === 'string' + ) { + requestUrl = error.request.url; + } + + notifications.toasts.addError(error, { + title: i18n.translate('xpack.streams.failedToFetchError', { + defaultMessage: 'Failed to fetch data{requestUrlSuffix}', + values: { + requestUrlSuffix: requestUrl ? ` (${requestUrl})` : '', + }, + }), + }); + } + }; + + const optionsForHook = { + ...omit(options, 'disableToastOnError'), + onError, + }; + + return useAbortableAsync( + ({ signal }) => { + return callback({ signal }); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + deps, + optionsForHook + ); +}; diff --git a/x-pack/plugins/streams_app/public/hooks/use_streams_app_params.ts b/x-pack/plugins/streams_app/public/hooks/use_streams_app_params.ts new file mode 100644 index 000000000000..2931a6fa64f8 --- /dev/null +++ b/x-pack/plugins/streams_app/public/hooks/use_streams_app_params.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { type PathsOf, type TypeOf, useParams } from '@kbn/typed-react-router-config'; +import type { StreamsAppRoutes } from '../routes/config'; + +export function useStreamsAppParams<TPath extends PathsOf<StreamsAppRoutes>>( + path: TPath +): TypeOf<StreamsAppRoutes, TPath> { + return useParams(path)! as TypeOf<StreamsAppRoutes, TPath>; +} diff --git a/x-pack/plugins/entity_manager/server/lib/errors.ts b/x-pack/plugins/streams_app/public/hooks/use_streams_app_route_path.ts similarity index 50% rename from x-pack/plugins/entity_manager/server/lib/errors.ts rename to x-pack/plugins/streams_app/public/hooks/use_streams_app_route_path.ts index e0d341f87a9f..78e63ead57da 100644 --- a/x-pack/plugins/entity_manager/server/lib/errors.ts +++ b/x-pack/plugins/streams_app/public/hooks/use_streams_app_route_path.ts @@ -5,10 +5,11 @@ * 2.0. */ -export class AssetNotFoundError extends Error { - constructor(ean: string) { - super(`Asset with ean (${ean}) not found in the provided time range`); - Object.setPrototypeOf(this, new.target.prototype); - this.name = 'AssetNotFoundError'; - } +import { PathsOf, useRoutePath } from '@kbn/typed-react-router-config'; +import type { StreamsAppRoutes } from '../routes/config'; + +export function useStreamsAppRoutePath() { + const path = useRoutePath(); + + return path as PathsOf<StreamsAppRoutes>; } diff --git a/x-pack/plugins/streams_app/public/hooks/use_streams_app_router.ts b/x-pack/plugins/streams_app/public/hooks/use_streams_app_router.ts new file mode 100644 index 000000000000..17472044b7b4 --- /dev/null +++ b/x-pack/plugins/streams_app/public/hooks/use_streams_app_router.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PathsOf, TypeAsArgs, TypeOf } from '@kbn/typed-react-router-config'; +import { useMemo } from 'react'; +import type { StreamsAppRouter, StreamsAppRoutes } from '../routes/config'; +import { streamsAppRouter } from '../routes/config'; +import { useKibana } from './use_kibana'; + +interface StatefulStreamsAppRouter extends StreamsAppRouter { + push<T extends PathsOf<StreamsAppRoutes>>( + path: T, + ...params: TypeAsArgs<TypeOf<StreamsAppRoutes, T>> + ): void; + replace<T extends PathsOf<StreamsAppRoutes>>( + path: T, + ...params: TypeAsArgs<TypeOf<StreamsAppRoutes, T>> + ): void; +} + +export function useStreamsAppRouter(): StatefulStreamsAppRouter { + const { + core: { + http, + application: { navigateToApp }, + }, + } = useKibana(); + + const link = (...args: any[]) => { + // @ts-expect-error + return streamsAppRouter.link(...args); + }; + + return useMemo<StatefulStreamsAppRouter>( + () => ({ + ...streamsAppRouter, + push: (...args) => { + const next = link(...args); + navigateToApp('streams', { path: next, replace: false }); + }, + replace: (path, ...args) => { + const next = link(path, ...args); + navigateToApp('streams', { path: next, replace: true }); + }, + link: (path, ...args) => { + return http.basePath.prepend('/app/streams' + link(path, ...args)); + }, + }), + [navigateToApp, http.basePath] + ); +} diff --git a/x-pack/plugins/streams_app/public/index.ts b/x-pack/plugins/streams_app/public/index.ts new file mode 100644 index 000000000000..eea2d8b7452a --- /dev/null +++ b/x-pack/plugins/streams_app/public/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; + +import { StreamsAppPlugin } from './plugin'; +import type { + StreamsAppPublicSetup, + StreamsAppPublicStart, + StreamsAppSetupDependencies, + StreamsAppStartDependencies, + ConfigSchema, +} from './types'; + +export type { StreamsAppPublicSetup, StreamsAppPublicStart }; + +export const plugin: PluginInitializer< + StreamsAppPublicSetup, + StreamsAppPublicStart, + StreamsAppSetupDependencies, + StreamsAppStartDependencies +> = (pluginInitializerContext: PluginInitializerContext<ConfigSchema>) => + new StreamsAppPlugin(pluginInitializerContext); diff --git a/x-pack/plugins/streams_app/public/plugin.ts b/x-pack/plugins/streams_app/public/plugin.ts new file mode 100644 index 000000000000..9df399693d02 --- /dev/null +++ b/x-pack/plugins/streams_app/public/plugin.ts @@ -0,0 +1,139 @@ +/* + * 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 { map } from 'rxjs'; +import { + AppMountParameters, + AppUpdater, + CoreSetup, + CoreStart, + DEFAULT_APP_CATEGORIES, + Plugin, + PluginInitializerContext, +} from '@kbn/core/public'; +import type { Logger } from '@kbn/logging'; +import { STREAMS_APP_ID } from '@kbn/deeplinks-observability/constants'; +import type { + ConfigSchema, + StreamsAppPublicSetup, + StreamsAppPublicStart, + StreamsAppSetupDependencies, + StreamsAppStartDependencies, +} from './types'; +import { StreamsAppServices } from './services/types'; + +export class StreamsAppPlugin + implements + Plugin< + StreamsAppPublicSetup, + StreamsAppPublicStart, + StreamsAppSetupDependencies, + StreamsAppStartDependencies + > +{ + logger: Logger; + + constructor(context: PluginInitializerContext<ConfigSchema>) { + this.logger = context.logger.get(); + } + setup( + coreSetup: CoreSetup<StreamsAppStartDependencies, StreamsAppPublicStart>, + pluginsSetup: StreamsAppSetupDependencies + ): StreamsAppPublicSetup { + pluginsSetup.observabilityShared.navigation.registerSections( + pluginsSetup.streams.status$.pipe( + map(({ status }) => { + if (status !== 'enabled') { + return []; + } + + return [ + { + label: '', + sortKey: 101, + entries: [ + { + label: i18n.translate('xpack.streams.streamsAppLinkTitle', { + defaultMessage: 'Streams', + }), + app: STREAMS_APP_ID, + path: '/', + isTechnicalPreview: true, + matchPath(currentPath: string) { + return ['/', ''].some((testPath) => currentPath.startsWith(testPath)); + }, + }, + ], + }, + ]; + }) + ) + ); + + coreSetup.application.register({ + id: STREAMS_APP_ID, + title: i18n.translate('xpack.streams.appTitle', { + defaultMessage: 'Streams', + }), + euiIconType: 'logoObservability', + appRoute: '/app/streams', + category: DEFAULT_APP_CATEGORIES.observability, + order: 8001, + updater$: pluginsSetup.streams.status$.pipe( + map(({ status }): AppUpdater => { + return (app) => { + if (status !== 'enabled') { + return { + visibleIn: [], + deepLinks: [], + }; + } + + return { + visibleIn: ['sideNav', 'globalSearch'], + deepLinks: + status === 'enabled' + ? [ + { + id: 'streams', + title: i18n.translate('xpack.streams.streamsAppDeepLinkTitle', { + defaultMessage: 'Streams', + }), + path: '/', + }, + ] + : [], + }; + }; + }) + ), + mount: async (appMountParameters: AppMountParameters<unknown>) => { + // Load application bundle and Get start services + const [{ renderApp }, [coreStart, pluginsStart]] = await Promise.all([ + import('./application'), + coreSetup.getStartServices(), + ]); + + const services: StreamsAppServices = {}; + + return renderApp({ + coreStart, + pluginsStart, + services, + appMountParameters, + }); + }, + }); + + return {}; + } + + start(coreStart: CoreStart, pluginsStart: StreamsAppStartDependencies): StreamsAppPublicStart { + return {}; + } +} diff --git a/x-pack/plugins/streams_app/public/routes/config.tsx b/x-pack/plugins/streams_app/public/routes/config.tsx new file mode 100644 index 000000000000..e3efdc6d871e --- /dev/null +++ b/x-pack/plugins/streams_app/public/routes/config.tsx @@ -0,0 +1,68 @@ +/* + * 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 { createRouter, Outlet, RouteMap } from '@kbn/typed-react-router-config'; +import * as t from 'io-ts'; +import React from 'react'; +import { StreamDetailView } from '../components/stream_detail_view'; +import { StreamsAppPageTemplate } from '../components/streams_app_page_template'; +import { StreamsAppRouterBreadcrumb } from '../components/streams_app_router_breadcrumb'; +import { RedirectTo } from '../components/redirect_to'; +import { StreamListView } from '../components/stream_list_view'; + +/** + * The array of route definitions to be used when the application + * creates the routes. + */ +const streamsAppRoutes = { + '/': { + element: ( + <StreamsAppRouterBreadcrumb + title={i18n.translate('xpack.streams.appBreadcrumbTitle', { + defaultMessage: 'Streams', + })} + path="/" + > + <StreamsAppPageTemplate> + <Outlet /> + </StreamsAppPageTemplate> + </StreamsAppRouterBreadcrumb> + ), + children: { + '/{key}': { + element: <Outlet />, + params: t.type({ + path: t.type({ + key: t.string, + }), + }), + children: { + '/{key}': { + element: <RedirectTo path="/{key}/{tab}" params={{ path: { tab: 'overview' } }} />, + }, + '/{key}/{tab}': { + element: <StreamDetailView />, + params: t.type({ + path: t.type({ + tab: t.string, + }), + }), + }, + }, + }, + '/': { + element: <StreamListView />, + }, + }, + }, +} satisfies RouteMap; + +export type StreamsAppRoutes = typeof streamsAppRoutes; + +export const streamsAppRouter = createRouter(streamsAppRoutes); + +export type StreamsAppRouter = typeof streamsAppRouter; diff --git a/x-pack/plugins/streams_app/public/services/types.ts b/x-pack/plugins/streams_app/public/services/types.ts new file mode 100644 index 000000000000..7f75493d2525 --- /dev/null +++ b/x-pack/plugins/streams_app/public/services/types.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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface StreamsAppServices {} diff --git a/x-pack/plugins/streams_app/public/types.ts b/x-pack/plugins/streams_app/public/types.ts new file mode 100644 index 000000000000..58d44784fe03 --- /dev/null +++ b/x-pack/plugins/streams_app/public/types.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { + DataViewsPublicPluginSetup, + DataViewsPublicPluginStart, +} from '@kbn/data-views-plugin/public'; +import type { + ObservabilitySharedPluginSetup, + ObservabilitySharedPluginStart, +} from '@kbn/observability-shared-plugin/public'; +import type { StreamsPluginSetup, StreamsPluginStart } from '@kbn/streams-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { SharePublicSetup, SharePublicStart } from '@kbn/share-plugin/public/plugin'; +/* eslint-disable @typescript-eslint/no-empty-interface*/ + +export interface ConfigSchema {} + +export interface StreamsAppSetupDependencies { + streams: StreamsPluginSetup; + data: DataPublicPluginSetup; + dataViews: DataViewsPublicPluginSetup; + observabilityShared: ObservabilitySharedPluginSetup; + unifiedSearch: {}; + share: SharePublicSetup; +} + +export interface StreamsAppStartDependencies { + streams: StreamsPluginStart; + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + observabilityShared: ObservabilitySharedPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; + share: SharePublicStart; +} + +export interface StreamsAppPublicSetup {} + +export interface StreamsAppPublicStart {} diff --git a/x-pack/plugins/streams_app/public/util/esql_result_to_timeseries.ts b/x-pack/plugins/streams_app/public/util/esql_result_to_timeseries.ts new file mode 100644 index 000000000000..c32bbf89135b --- /dev/null +++ b/x-pack/plugins/streams_app/public/util/esql_result_to_timeseries.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 type { AbortableAsyncState } from '@kbn/observability-utils-browser/hooks/use_abortable_async'; +import type { UnparsedEsqlResponse } from '@kbn/observability-utils-server/es/client/create_observability_es_client'; +import { orderBy } from 'lodash'; + +interface Timeseries<T extends string> { + id: string; + label: string; + metricNames: T[]; + data: Array<{ x: number } & Record<T, number | null>>; +} + +export function esqlResultToTimeseries<T extends string>({ + result, + metricNames, +}: { + result: AbortableAsyncState<UnparsedEsqlResponse>; + metricNames: T[]; +}): Array<Timeseries<T>> { + const columns = result.value?.columns; + + const rows = result.value?.values; + + if (!columns?.length || !rows?.length) { + return []; + } + + const timestampColumn = columns.find((col) => col.name === '@timestamp'); + + if (!timestampColumn) { + return []; + } + + const collectedSeries: Map<string, Timeseries<T>> = new Map(); + + rows.forEach((columnsInRow) => { + const values = new Map<string, number | null>(); + const labels = new Map<string, string>(); + let timestamp: number; + + columnsInRow.forEach((value, index) => { + const column = columns[index]; + const isTimestamp = column.name === '@timestamp'; + const isMetric = metricNames.indexOf(column.name as T) !== -1; + + if (isTimestamp) { + timestamp = new Date(value as string | number).getTime(); + } else if (isMetric) { + values.set(column.name, value as number | null); + } else { + labels.set(column.name, String(value)); + } + }); + + const seriesKey = + Array.from(labels.entries()) + .map(([key, value]) => [key, value].join(':')) + .sort() + .join(',') || '-'; + + if (!collectedSeries.has(seriesKey)) { + collectedSeries.set(seriesKey, { + id: seriesKey, + data: [], + label: seriesKey, + metricNames, + }); + } + + const series = collectedSeries.get(seriesKey)!; + + const coordinate = { + x: timestamp!, + } as { x: number } & Record<T, number | null>; + + values.forEach((value, key) => { + if (key !== 'x') { + // @ts-expect-error + coordinate[key as T] = value; + } + }); + + series.data.push(coordinate); + + return collectedSeries; + }); + + return Array.from(collectedSeries.entries()).map(([id, timeseries]) => { + return { + ...timeseries, + data: orderBy(timeseries.data, 'x', 'asc'), + }; + }); +} diff --git a/x-pack/plugins/streams_app/server/config.ts b/x-pack/plugins/streams_app/server/config.ts new file mode 100644 index 000000000000..73e631c7f6d9 --- /dev/null +++ b/x-pack/plugins/streams_app/server/config.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, type TypeOf } from '@kbn/config-schema'; + +export const config = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type StreamsAppConfig = TypeOf<typeof config>; diff --git a/x-pack/plugins/streams_app/server/index.ts b/x-pack/plugins/streams_app/server/index.ts new file mode 100644 index 000000000000..1ca3b3c6e3de --- /dev/null +++ b/x-pack/plugins/streams_app/server/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { + PluginConfigDescriptor, + PluginInitializer, + PluginInitializerContext, +} from '@kbn/core/server'; +import type { StreamsAppConfig } from './config'; +import { StreamsAppPlugin } from './plugin'; +import type { + StreamsAppServerSetup, + StreamsAppServerStart, + StreamsAppSetupDependencies, + StreamsAppStartDependencies, +} from './types'; + +export type { StreamsAppServerSetup, StreamsAppServerStart }; + +import { config as configSchema } from './config'; + +export const config: PluginConfigDescriptor<StreamsAppConfig> = { + schema: configSchema, +}; + +export const plugin: PluginInitializer< + StreamsAppServerSetup, + StreamsAppServerStart, + StreamsAppSetupDependencies, + StreamsAppStartDependencies +> = async (pluginInitializerContext: PluginInitializerContext<StreamsAppConfig>) => + new StreamsAppPlugin(pluginInitializerContext); diff --git a/x-pack/plugins/streams_app/server/plugin.ts b/x-pack/plugins/streams_app/server/plugin.ts new file mode 100644 index 000000000000..00fb61c1a9cc --- /dev/null +++ b/x-pack/plugins/streams_app/server/plugin.ts @@ -0,0 +1,42 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; +import type { + ConfigSchema, + StreamsAppServerSetup, + StreamsAppServerStart, + StreamsAppSetupDependencies, + StreamsAppStartDependencies, +} from './types'; + +export class StreamsAppPlugin + implements + Plugin< + StreamsAppServerSetup, + StreamsAppServerStart, + StreamsAppSetupDependencies, + StreamsAppStartDependencies + > +{ + logger: Logger; + + constructor(context: PluginInitializerContext<ConfigSchema>) { + this.logger = context.logger.get(); + } + setup( + coreSetup: CoreSetup<StreamsAppStartDependencies, StreamsAppServerStart>, + pluginsSetup: StreamsAppSetupDependencies + ): StreamsAppServerSetup { + return {}; + } + + start(core: CoreStart, pluginsStart: StreamsAppStartDependencies): StreamsAppServerStart { + return {}; + } +} diff --git a/x-pack/plugins/streams_app/server/types.ts b/x-pack/plugins/streams_app/server/types.ts new file mode 100644 index 000000000000..e425ae7422d7 --- /dev/null +++ b/x-pack/plugins/streams_app/server/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { StreamsPluginStart, StreamsPluginSetup } from '@kbn/streams-plugin/server'; + +/* eslint-disable @typescript-eslint/no-empty-interface*/ + +export interface ConfigSchema {} + +export interface StreamsAppSetupDependencies { + streams: StreamsPluginSetup; +} + +export interface StreamsAppStartDependencies { + streams: StreamsPluginStart; +} + +export interface StreamsAppServerSetup {} + +export interface StreamsAppServerStart {} diff --git a/x-pack/plugins/streams_app/tsconfig.json b/x-pack/plugins/streams_app/tsconfig.json new file mode 100644 index 000000000000..39acb94665ae --- /dev/null +++ b/x-pack/plugins/streams_app/tsconfig.json @@ -0,0 +1,37 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "../../../typings/**/*", + "common/**/*", + "public/**/*", + "typings/**/*", + "public/**/*.json", + "server/**/*", + ".storybook/**/*" + ], + "exclude": ["target/**/*", ".storybook/**/*.js"], + "kbn_references": [ + "@kbn/core", + "@kbn/data-plugin", + "@kbn/data-views-plugin", + "@kbn/observability-shared-plugin", + "@kbn/unified-search-plugin", + "@kbn/react-kibana-context-render", + "@kbn/shared-ux-link-redirect-app", + "@kbn/typed-react-router-config", + "@kbn/i18n", + "@kbn/observability-utils-browser", + "@kbn/kibana-react-plugin", + "@kbn/es-query", + "@kbn/logging", + "@kbn/deeplinks-observability", + "@kbn/config-schema", + "@kbn/calculate-auto", + "@kbn/streams-plugin", + "@kbn/share-plugin", + "@kbn/observability-utils-server", + ] +} diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts index 23ef344c197f..f5b3912eabd3 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts @@ -644,7 +644,7 @@ describe('estimateCapacity', () => { value: expect.any(Object), }); expect(logger.warn).toHaveBeenCalledWith( - 'Task Manager is unhealthy, the assumedAverageRecurringRequiredThroughputPerMinutePerKibana (175) < capacityPerMinutePerKibana (200)' + 'Task Manager is unhealthy, the assumedRequiredThroughputPerMinutePerKibana (215) >= capacityPerMinutePerKibana (200)' ); }); @@ -710,7 +710,7 @@ describe('estimateCapacity', () => { value: expect.any(Object), }); expect(logger.warn).toHaveBeenCalledWith( - 'Task Manager is unhealthy, the assumedRequiredThroughputPerMinutePerKibana (250) >= capacityPerMinutePerKibana (200) AND assumedAverageRecurringRequiredThroughputPerMinutePerKibana (210) >= capacityPerMinutePerKibana (200)' + 'Task Manager is unhealthy, the assumedAverageRecurringRequiredThroughputPerMinutePerKibana (210) > capacityPerMinutePerKibana (200)' ); }); diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts index acbf1284b21b..a4a9c963e8ee 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts @@ -248,13 +248,13 @@ function getHealthStatus( return { status: HealthStatus.OK, reason }; } - if (assumedAverageRecurringRequiredThroughputPerMinutePerKibana < capacityPerMinutePerKibana) { - const reason = `Task Manager is unhealthy, the assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) < capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`; + if (assumedAverageRecurringRequiredThroughputPerMinutePerKibana > capacityPerMinutePerKibana) { + const reason = `Task Manager is unhealthy, the assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) > capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`; logger.warn(reason); return { status: HealthStatus.OK, reason }; } - const reason = `Task Manager is unhealthy, the assumedRequiredThroughputPerMinutePerKibana (${assumedRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana}) AND assumedAverageRecurringRequiredThroughputPerMinutePerKibana (${assumedAverageRecurringRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`; + const reason = `Task Manager is unhealthy, the assumedRequiredThroughputPerMinutePerKibana (${assumedRequiredThroughputPerMinutePerKibana}) >= capacityPerMinutePerKibana (${capacityPerMinutePerKibana})`; logger.warn(reason); return { status: HealthStatus.OK, reason }; } diff --git a/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx index 2df9bea337ba..e13fb396808d 100644 --- a/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'; import { INSTALLATION_STATUS, THREAT_INTELLIGENCE_CATEGORY } from '../utils/filter_integrations'; @@ -25,9 +25,7 @@ const renderUseQuery = (result: { items: any[] }) => describe('useIntegrations', () => { it('should have undefined data during loading state', async () => { const mockIntegrations = { items: [] }; - const { result, waitFor } = renderUseQuery(mockIntegrations); - - await waitFor(() => result.current.isLoading); + const { result } = renderUseQuery(mockIntegrations); expect(result.current.isLoading).toBeTruthy(); expect(result.current.data).toBeUndefined(); @@ -43,9 +41,9 @@ describe('useIntegrations', () => { }, ], }; - const { result, waitFor } = renderUseQuery(mockIntegrations); + const { result } = renderUseQuery(mockIntegrations); - await waitFor(() => result.current.isSuccess); + await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.isLoading).toBeFalsy(); expect(result.current.data).toEqual(mockIntegrations); diff --git a/x-pack/plugins/threat_intelligence/public/modules/block_list/hooks/use_policies.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/block_list/hooks/use_policies.test.tsx index a01b7da64b68..4a1af74a100a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/block_list/hooks/use_policies.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/block_list/hooks/use_policies.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'; const createWrapper = () => { @@ -24,9 +24,7 @@ const renderUseQuery = (result: { items: any[] }) => describe('usePolicies', () => { it('should have undefined data during loading state', async () => { const mockPolicies = { items: [] }; - const { result, waitFor } = renderUseQuery(mockPolicies); - - await waitFor(() => result.current.isLoading); + const { result } = renderUseQuery(mockPolicies); expect(result.current.isLoading).toBeTruthy(); expect(result.current.data).toBeUndefined(); @@ -41,9 +39,9 @@ describe('usePolicies', () => { }, ], }; - const { result, waitFor } = renderUseQuery(mockPolicies); + const { result } = renderUseQuery(mockPolicies); - await waitFor(() => result.current.isSuccess); + await waitFor(() => expect(result.current.isSuccess).toBeTruthy()); expect(result.current.isLoading).toBeFalsy(); expect(result.current.data).toEqual(mockPolicies); diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.test.tsx index 8e2f5d3d96a2..a38b25fce0f6 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.test.tsx @@ -6,7 +6,7 @@ */ import React, { FC, ReactNode } from 'react'; -import { Renderer, renderHook, RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook, RenderHookResult } from '@testing-library/react'; import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; import { KibanaContext } from '../../../hooks/use_kibana'; import { useCaseDisabled } from './use_case_permission'; @@ -27,7 +27,7 @@ const getProviderComponent = ); describe('useCasePermission', () => { - let hookResult: RenderHookResult<{}, boolean, Renderer<unknown>>; + let hookResult: RenderHookResult<boolean, unknown>; it('should return false if user has correct permissions and indicator has a name', () => { const mockedServices = { diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_indicator_by_id.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_indicator_by_id.test.tsx index 0c23962ccaa4..e9f43c316190 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_indicator_by_id.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_indicator_by_id.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import { useIndicatorById, UseIndicatorByIdValue } from './use_indicator_by_id'; +import { waitFor, renderHook } from '@testing-library/react'; +import { useIndicatorById } from './use_indicator_by_id'; import { TestProvidersComponent } from '../../../mocks/test_providers'; import { createFetchIndicatorById } from '../services/fetch_indicator_by_id'; import { Indicator } from '../../../../common/types/indicator'; @@ -16,13 +16,10 @@ jest.mock('../services/fetch_indicator_by_id'); const indicatorByIdQueryResult = { _id: 'testId' } as unknown as Indicator; const renderUseIndicatorById = (initialProps = { indicatorId: 'testId' }) => - renderHook<{ indicatorId: string }, UseIndicatorByIdValue>( - (props) => useIndicatorById(props.indicatorId), - { - initialProps, - wrapper: TestProvidersComponent, - } - ); + renderHook((props) => useIndicatorById(props.indicatorId), { + initialProps, + wrapper: TestProvidersComponent, + }); describe('useIndicatorById()', () => { type MockedCreateFetchIndicators = jest.MockedFunction<typeof createFetchIndicatorById>; @@ -49,8 +46,7 @@ describe('useIndicatorById()', () => { expect(indicatorsQuery).toHaveBeenCalledTimes(1); // isLoading should turn to false eventually - await hookResult.waitFor(() => !hookResult.result.current.isLoading); - expect(hookResult.result.current.isLoading).toEqual(false); + await waitFor(() => expect(hookResult.result.current.isLoading).toBe(false)); }); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx index 11e1d03a32b5..72935990ef71 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_aggregated_indicators.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useAggregatedIndicators, UseAggregatedIndicatorsParam } from './use_aggregated_indicators'; import { mockedTimefilterService, TestProvidersComponent } from '../../../mocks/test_providers'; import { createFetchAggregatedIndicators } from '../services/fetch_aggregated_indicators'; @@ -47,7 +47,7 @@ describe('useAggregatedIndicators()', () => { it('should create and call the aggregatedIndicatorsQuery correctly', async () => { aggregatedIndicatorsQuery.mockResolvedValue([]); - const { result, rerender, waitFor } = renderUseAggregatedIndicators(); + const { result, rerender } = renderUseAggregatedIndicators(); // indicators service and the query should be called just once expect( @@ -81,7 +81,7 @@ describe('useAggregatedIndicators()', () => { expect.any(AbortSignal) ); - await waitFor(() => !result.current.isLoading); + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_column_settings.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_column_settings.test.ts index 74b76030bca3..cbba1c3043f3 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_column_settings.test.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_column_settings.test.ts @@ -6,7 +6,7 @@ */ import { mockedServices, TestProvidersComponent } from '../../../mocks/test_providers'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useColumnSettings } from './use_column_settings'; const renderUseColumnSettings = () => diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx index be710e771b04..2276d1cfcf63 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; -import { useIndicators, UseIndicatorsParams, UseIndicatorsValue } from './use_indicators'; +import { act, waitFor, renderHook } from '@testing-library/react'; +import { useIndicators, UseIndicatorsParams } from './use_indicators'; import { TestProvidersComponent } from '../../../mocks/test_providers'; import { createFetchIndicators } from '../services/fetch_indicators'; import { mockTimeRange } from '../../../mocks/mock_indicators_filters_context'; @@ -23,7 +23,7 @@ const useIndicatorsParams: UseIndicatorsParams = { const indicatorsQueryResult = { indicators: [], total: 0 }; const renderUseIndicators = (initialProps = useIndicatorsParams) => - renderHook<UseIndicatorsParams, UseIndicatorsValue>((props) => useIndicators(props), { + renderHook(useIndicators, { initialProps, wrapper: TestProvidersComponent, }); @@ -53,7 +53,7 @@ describe('useIndicators()', () => { expect(indicatorsQuery).toHaveBeenCalledTimes(1); // isLoading should turn to false eventually - await hookResult.waitFor(() => !hookResult.result.current.isLoading); + await waitFor(() => expect(hookResult.result.current.isLoading).toBe(false)); expect(hookResult.result.current.isLoading).toEqual(false); }); }); @@ -77,7 +77,7 @@ describe('useIndicators()', () => { // Change page size await act(async () => hookResult.result.current.onChangeItemsPerPage(50)); - expect(indicatorsQuery).toHaveBeenCalledTimes(3); + await waitFor(() => expect(indicatorsQuery).toHaveBeenCalledTimes(3)); expect(indicatorsQuery).toHaveBeenLastCalledWith( expect.objectContaining({ pagination: expect.objectContaining({ pageIndex: 0, pageSize: 50 }), @@ -101,7 +101,7 @@ describe('useIndicators()', () => { expect.any(AbortSignal) ); - await hookResult.waitFor(() => !hookResult.result.current.isLoading); + await waitFor(() => expect(hookResult.result.current.isLoading).toBe(false)); expect(hookResult.result.current).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_toolbar_options.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_toolbar_options.test.tsx index 7af7320af098..9a69b71fd3f7 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_toolbar_options.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_toolbar_options.test.tsx @@ -6,7 +6,7 @@ */ import { TestProvidersComponent } from '../../../mocks/test_providers'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useToolbarOptions } from './use_toolbar_options'; describe('useToolbarOptions()', () => { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.test.tsx index 893367b42dbd..8735817747f1 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.test.tsx @@ -6,7 +6,7 @@ */ import { mockedSearchService, TestProvidersComponent } from '../../../mocks/test_providers'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { BehaviorSubject } from 'rxjs'; import { useIndicatorsTotalCount } from './use_total_count'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filter_in_out.test.ts b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filter_in_out.test.ts index 7e435d944145..18c8fccd573f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filter_in_out.test.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filter_in_out.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Renderer, renderHook, RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook, RenderHookResult } from '@testing-library/react'; import { generateMockIndicator, generateMockUrlIndicator, @@ -19,7 +19,7 @@ import { updateFiltersArray } from '../utils/filter'; jest.mock('../utils/filter', () => ({ updateFiltersArray: jest.fn() })); describe('useFilterInOut()', () => { - let hookResult: RenderHookResult<{}, UseFilterInValue, Renderer<unknown>>; + let hookResult: RenderHookResult<UseFilterInValue, unknown>; it('should return empty object if Indicator is incorrect', () => { const indicator: Indicator = generateMockIndicator(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_add_to_timeline.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_add_to_timeline.test.tsx index 18d96282d98b..0b5cc302ef0e 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_add_to_timeline.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_add_to_timeline.test.tsx @@ -6,7 +6,7 @@ */ import { EMPTY_VALUE } from '../../../constants/common'; -import { Renderer, renderHook, RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook, RenderHookResult } from '@testing-library/react'; import { generateMockIndicator, generateMockUrlIndicator, @@ -16,7 +16,7 @@ import { TestProvidersComponent } from '../../../mocks/test_providers'; import { useAddToTimeline, UseAddToTimelineValue } from './use_add_to_timeline'; describe('useInvestigateInTimeline()', () => { - let hookResult: RenderHookResult<{}, UseAddToTimelineValue, Renderer<unknown>>; + let hookResult: RenderHookResult<UseAddToTimelineValue, unknown>; xit('should return empty object if Indicator is incorrect', () => { const indicator: Indicator = generateMockIndicator(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.test.tsx index 5d416c5ff56c..dc9b3a4a0029 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { Renderer, renderHook, RenderHookResult } from '@testing-library/react-hooks'; +import { renderHook, RenderHookResult } from '@testing-library/react'; import { useInvestigateInTimeline, UseInvestigateInTimelineValue, @@ -18,7 +18,7 @@ import { import { TestProvidersComponent } from '../../../mocks/test_providers'; describe('useInvestigateInTimeline()', () => { - let hookResult: RenderHookResult<{}, UseInvestigateInTimelineValue, Renderer<unknown>>; + let hookResult: RenderHookResult<UseInvestigateInTimelineValue, unknown>; it('should return empty object if Indicator is incorrect', () => { const indicator: Indicator = generateMockIndicator(); diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts index 9b289f481a2d..3e335a7edd27 100644 --- a/x-pack/plugins/timelines/common/index.ts +++ b/x-pack/plugins/timelines/common/index.ts @@ -42,8 +42,8 @@ export type { BeatFields, BrowserFields, CursorType, - EqlOptionsData, - EqlOptionsSelected, + EqlFieldsComboBoxOptions, + EqlOptions, FieldsEqlOptions, FieldInfo, IndexField, @@ -67,3 +67,5 @@ export type { } from './search_strategy'; export { Direction, EntityType, EMPTY_BROWSER_FIELDS } from './search_strategy'; + +export { getDataFromFieldsHits, toArray, isGeoField, toObjectArrayOfStrings } from './utils'; diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts index 66758bbcb94d..8636a5594104 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts @@ -22,13 +22,13 @@ export interface TimelineEqlResponse extends EqlSearchStrategyResponse<EqlSearch inspect: Maybe<Inspect>; } -export interface EqlOptionsData { +export interface EqlFieldsComboBoxOptions { keywordFields: EuiComboBoxOptionOption[]; dateFields: EuiComboBoxOptionOption[]; nonDateFields: EuiComboBoxOptionOption[]; } -export interface EqlOptionsSelected { +export interface EqlOptions { eventCategoryField?: string; tiebreakerField?: string; timestampField?: string; @@ -36,4 +36,4 @@ export interface EqlOptionsSelected { size?: number; } -export type FieldsEqlOptions = keyof EqlOptionsSelected; +export type FieldsEqlOptions = keyof EqlOptions; diff --git a/x-pack/plugins/timelines/common/utils/field_formatters.test.ts b/x-pack/plugins/timelines/common/utils/field_formatters.test.ts index 43babd374d99..46eb77c8f7f3 100644 --- a/x-pack/plugins/timelines/common/utils/field_formatters.test.ts +++ b/x-pack/plugins/timelines/common/utils/field_formatters.test.ts @@ -7,7 +7,7 @@ import { eventDetailsFormattedFields, eventHit } from '@kbn/securitysolution-t-grid'; import { EventHit } from '../search_strategy'; -import { getDataFromFieldsHits, getDataSafety } from './field_formatters'; +import { getDataFromFieldsHits } from './field_formatters'; describe('Events Details Helpers', () => { const fields: EventHit['fields'] = eventHit.fields; @@ -84,7 +84,7 @@ describe('Events Details Helpers', () => { }, ]; const result = getDataFromFieldsHits(whackFields); - expect(result).toEqual(whackResultFields); + expect(result).toMatchObject(whackResultFields); }); it('flattens alert parameters', () => { const ruleParameterFields = { @@ -191,7 +191,7 @@ describe('Events Details Helpers', () => { ]; const result = getDataFromFieldsHits(ruleParameterFields); - expect(result).toEqual(ruleParametersResultFields); + expect(result).toMatchObject(ruleParametersResultFields); }); it('get data from threat enrichments', () => { @@ -546,6 +546,17 @@ describe('Events Details Helpers', () => { originalValue: ['495ad7a7-316e-4544-8a0f-9c098daee76e'], values: ['495ad7a7-316e-4544-8a0f-9c098daee76e'], }, + { + category: 'threat', + field: 'threat.enrichments', + isObjectArray: true, + originalValue: [ + '{"matched.field":["myhash.mysha256"],"matched.index":["logs-ti_abusech.malware"],"matched.type":["indicator_match_rule"],"feed.name":["AbuseCH malware"],"matched.atomic":["a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3"]}', + ], + values: [ + '{"matched.field":["myhash.mysha256"],"matched.index":["logs-ti_abusech.malware"],"matched.type":["indicator_match_rule"],"feed.name":["AbuseCH malware"],"matched.atomic":["a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3"]}', + ], + }, { category: 'threat', field: 'threat.enrichments.matched.field', @@ -581,25 +592,9 @@ describe('Events Details Helpers', () => { originalValue: ['a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3'], values: ['a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3'], }, - { - category: 'threat', - field: 'threat.enrichments', - isObjectArray: true, - originalValue: [ - '{"matched.field":["myhash.mysha256"],"matched.index":["logs-ti_abusech.malware"],"matched.type":["indicator_match_rule"],"feed.name":["AbuseCH malware"],"matched.atomic":["a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3"]}', - ], - values: [ - '{"matched.field":["myhash.mysha256"],"matched.index":["logs-ti_abusech.malware"],"matched.type":["indicator_match_rule"],"feed.name":["AbuseCH malware"],"matched.atomic":["a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3"]}', - ], - }, ]; const result = getDataFromFieldsHits(data); - expect(result).toEqual(ruleParametersResultFields); + expect(result).toMatchObject(ruleParametersResultFields); }); }); - - it('#getDataSafety', async () => { - const result = await getDataSafety(getDataFromFieldsHits, fields); - expect(result).toEqual(resultFields); - }); }); diff --git a/x-pack/plugins/timelines/common/utils/field_formatters.ts b/x-pack/plugins/timelines/common/utils/field_formatters.ts index c9292987f59b..2e3785633bc3 100644 --- a/x-pack/plugins/timelines/common/utils/field_formatters.ts +++ b/x-pack/plugins/timelines/common/utils/field_formatters.ts @@ -8,9 +8,15 @@ import { isEmpty } from 'lodash/fp'; import { ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils'; -import { ecsFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/ecs_field_map'; -import { technicalRuleFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/technical_rule_field_map'; -import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; +import { + ecsFieldMap, + EcsFieldMap, +} from '@kbn/rule-registry-plugin/common/assets/field_maps/ecs_field_map'; +import { + technicalRuleFieldMap, + TechnicalRuleFieldMap, +} from '@kbn/rule-registry-plugin/common/assets/field_maps/technical_rule_field_map'; +import { legacyExperimentalFieldMap, ExperimentalRuleFieldMap } from '@kbn/alerts-as-data-utils'; import { Fields, TimelineEventsDetailsItem } from '../search_strategy'; import { toObjectArrayOfStrings, toStringArray } from './to_array'; import { ENRICHMENT_DESTINATION_PATH } from '../constants'; @@ -51,117 +57,141 @@ export const isRuleParametersFieldOrSubfield = (field: string, prependField?: st export const isThreatEnrichmentFieldOrSubfield = (field: string, prependField?: string) => prependField?.includes(ENRICHMENT_DESTINATION_PATH) || field === ENRICHMENT_DESTINATION_PATH; +// Helper functions +const createFieldItem = ( + fieldCategory: string, + field: string, + values: string[], + isObjectArray: boolean +): TimelineEventsDetailsItem => ({ + category: fieldCategory, + field, + values, + originalValue: values, + isObjectArray, +}); + +const processGeoField = ( + field: string, + item: unknown[], + fieldCategory: string +): TimelineEventsDetailsItem => { + const formattedLocation = formatGeoLocation(item); + return createFieldItem(fieldCategory, field, formattedLocation, true); +}; + +const processSimpleField = ( + dotField: string, + strArr: string[], + isObjectArray: boolean, + fieldCategory: string +): TimelineEventsDetailsItem => createFieldItem(fieldCategory, dotField, strArr, isObjectArray); + +const processNestedFields = ( + item: unknown, + dotField: string, + fieldCategory: string, + prependDotField: boolean +): TimelineEventsDetailsItem[] => { + if (Array.isArray(item)) { + return item.flatMap((curr) => + getDataFromFieldsHits(curr as Fields, prependDotField ? dotField : undefined, fieldCategory) + ); + } + + return getDataFromFieldsHits( + item as Fields, + prependDotField ? dotField : undefined, + fieldCategory + ); +}; + +type DisjointFieldNames = 'ecs.version' | 'event.action' | 'event.kind' | 'event.original'; + +// Memoized field maps +const fieldMaps: EcsFieldMap & + Omit<TechnicalRuleFieldMap, DisjointFieldNames> & + ExperimentalRuleFieldMap = { + ...technicalRuleFieldMap, + ...ecsFieldMap, + ...legacyExperimentalFieldMap, +}; + export const getDataFromFieldsHits = ( fields: Fields, prependField?: string, prependFieldCategory?: string -): TimelineEventsDetailsItem[] => - Object.keys(fields).reduce<TimelineEventsDetailsItem[]>((accumulator, field) => { +): TimelineEventsDetailsItem[] => { + const resultMap = new Map<string, TimelineEventsDetailsItem>(); + const fieldNames = Object.keys(fields); + for (let i = 0; i < fieldNames.length; i++) { + const field = fieldNames[i]; const item: unknown[] = fields[field]; - const fieldCategory = - prependFieldCategory != null ? prependFieldCategory : getFieldCategory(field); + const fieldCategory = prependFieldCategory ?? getFieldCategory(field); + const dotField = prependField ? `${prependField}.${field}` : field; + + // Handle geo fields if (isGeoField(field)) { - return [ - ...accumulator, - { - category: fieldCategory, - field, - values: formatGeoLocation(item), - originalValue: formatGeoLocation(item), - isObjectArray: true, // important for UI - }, - ]; + const geoItem = processGeoField(field, item, fieldCategory); + resultMap.set(field, geoItem); + // eslint-disable-next-line no-continue + continue; } + const objArrStr = toObjectArrayOfStrings(item); const strArr = objArrStr.map(({ str }) => str); const isObjectArray = objArrStr.some((o) => o.isObjectArray); - const dotField = prependField ? `${prependField}.${field}` : field; - // return simple field value (non-ecs object, non-array) - if ( - !isObjectArray || - (Object.keys({ - ...ecsFieldMap, - ...technicalRuleFieldMap, - ...legacyExperimentalFieldMap, - }).find((ecsField) => ecsField === field) === undefined && - !isRuleParametersFieldOrSubfield(field, prependField)) - ) { - return [ - ...accumulator, - { - category: fieldCategory, - field: dotField, - values: strArr, - originalValue: strArr, - isObjectArray, - }, - ]; + const isEcsField = fieldMaps[field as keyof typeof fieldMaps] !== undefined; + const isRuleParameters = isRuleParametersFieldOrSubfield(field, prependField); + + // Handle simple fields + if (!isObjectArray || (!isEcsField && !isRuleParameters)) { + const simpleItem = processSimpleField(dotField, strArr, isObjectArray, fieldCategory); + resultMap.set(dotField, simpleItem); + // eslint-disable-next-line no-continue + continue; } - const threatEnrichmentObject = isThreatEnrichmentFieldOrSubfield(field, prependField) - ? [ - { - category: fieldCategory, - field: dotField, - values: strArr, - originalValue: strArr, - isObjectArray, - }, - ] - : []; - - // format nested fields - let nestedFields: TimelineEventsDetailsItem[] = []; - if (isRuleParametersFieldOrSubfield(field, prependField)) { - nestedFields = Array.isArray(item) - ? item - .reduce<TimelineEventsDetailsItem[][]>((acc, curr) => { - acc.push(getDataFromFieldsHits(curr as Fields, dotField, fieldCategory)); - return acc; - }, []) - .flat() - : getDataFromFieldsHits(item, dotField, fieldCategory); - } else { - nestedFields = Array.isArray(item) - ? item - .reduce<TimelineEventsDetailsItem[][]>((acc, curr) => { - acc.push(getDataFromFieldsHits(curr as Fields, dotField, fieldCategory)); - return acc; - }, []) - .flat() - : getDataFromFieldsHits(item, prependField, fieldCategory); + // Handle threat enrichment + if (isThreatEnrichmentFieldOrSubfield(field, prependField)) { + const enrichmentItem = createFieldItem(fieldCategory, dotField, strArr, isObjectArray); + resultMap.set(dotField, enrichmentItem); } - // combine duplicate fields - const flat: Record<string, TimelineEventsDetailsItem> = [ - ...accumulator, - ...nestedFields, - ...threatEnrichmentObject, - ].reduce( - (acc, f) => ({ - ...acc, - // acc/flat is hashmap to determine if we already have the field or not without an array iteration - // its converted back to array in return with Object.values - ...(acc[f.field] != null - ? { - [f.field]: { - ...f, - originalValue: acc[f.field].originalValue.includes(f.originalValue[0]) - ? acc[f.field].originalValue - : [...acc[f.field].originalValue, ...f.originalValue], - values: acc[f.field].values?.includes(f.values?.[0] || '') - ? acc[f.field].values - : [...(acc[f.field].values || []), ...(f.values || [])], - }, - } - : { [f.field]: f }), - }), - {} as Record<string, TimelineEventsDetailsItem> + // Process nested fields + const nestedFields = processNestedFields( + item, + dotField, + fieldCategory, + isRuleParameters || isThreatEnrichmentFieldOrSubfield(field, prependField) ); + // Merge results + for (const nestedItem of nestedFields) { + const existing = resultMap.get(nestedItem.field); + + if (!existing) { + resultMap.set(nestedItem.field, nestedItem); + // eslint-disable-next-line no-continue + continue; + } - return Object.values(flat); - }, []); + // Merge values and originalValue arrays + const mergedValues = existing.values?.includes(nestedItem.values?.[0] || '') + ? existing.values + : [...(existing.values || []), ...(nestedItem.values || [])]; -export const getDataSafety = <A, T>(fn: (args: A) => T, args: A): Promise<T> => - new Promise((resolve) => setTimeout(() => resolve(fn(args)))); + const mergedOriginal = existing.originalValue.includes(nestedItem.originalValue[0]) + ? existing.originalValue + : [...existing.originalValue, ...nestedItem.originalValue]; + + resultMap.set(nestedItem.field, { + ...nestedItem, + values: mergedValues, + originalValue: mergedOriginal, + }); + } + } + + return Array.from(resultMap.values()); +}; diff --git a/x-pack/plugins/timelines/common/utils/index.ts b/x-pack/plugins/timelines/common/utils/index.ts new file mode 100644 index 000000000000..e4b7eefec454 --- /dev/null +++ b/x-pack/plugins/timelines/common/utils/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 { getDataFromFieldsHits, isGeoField } from './field_formatters'; +export { toArray, toObjectArrayOfStrings } from './to_array'; diff --git a/x-pack/plugins/timelines/common/utils/to_array.ts b/x-pack/plugins/timelines/common/utils/to_array.ts index fbb2b8d48a25..d13eb0578008 100644 --- a/x-pack/plugins/timelines/common/utils/to_array.ts +++ b/x-pack/plugins/timelines/common/utils/to_array.ts @@ -5,83 +5,55 @@ * 2.0. */ -export const toArray = <T = string>(value: T | T[] | null): T[] => - Array.isArray(value) ? value : value == null ? [] : [value]; -export const toStringArray = <T = string>(value: T | T[] | null): string[] => { - if (Array.isArray(value)) { - return value.reduce<string[]>((acc, v) => { - if (v != null) { - switch (typeof v) { - case 'number': - case 'boolean': - return [...acc, v.toString()]; - case 'object': - try { - return [...acc, JSON.stringify(v)]; - } catch { - return [...acc, 'Invalid Object']; - } - case 'string': - return [...acc, v]; - default: - return [...acc, `${v}`]; - } +export const toArray = <T>(value: T | T[] | null | undefined): T[] => + value == null ? [] : Array.isArray(value) ? value : [value]; + +export const toStringArray = <T>(value: T | T[] | null): string[] => { + if (value == null) return []; + + const arr = Array.isArray(value) ? value : [value]; + return arr.reduce<string[]>((acc, v) => { + if (v == null) return acc; + + if (typeof v === 'object') { + try { + acc.push(JSON.stringify(v)); + } catch { + acc.push('Invalid Object'); } return acc; - }, []); - } else if (value == null) { - return []; - } else if (!Array.isArray(value) && typeof value === 'object') { - try { - return [JSON.stringify(value)]; - } catch { - return ['Invalid Object']; } - } else { - return [`${value}`]; - } + + acc.push(String(v)); + return acc; + }, []); }; -export const toObjectArrayOfStrings = <T = string>( + +export const toObjectArrayOfStrings = <T>( value: T | T[] | null ): Array<{ str: string; isObjectArray?: boolean; }> => { - if (Array.isArray(value)) { - return value.reduce< - Array<{ - str: string; - isObjectArray?: boolean; - }> - >((acc, v) => { - if (v != null) { - switch (typeof v) { - case 'number': - case 'boolean': - return [...acc, { str: v.toString() }]; - case 'object': - try { - return [...acc, { str: JSON.stringify(v), isObjectArray: true }]; // need to track when string is not a simple value - } catch { - return [...acc, { str: 'Invalid Object' }]; - } - case 'string': - return [...acc, { str: v }]; - default: - return [...acc, { str: `${v}` }]; - } + if (value == null) return []; + + const arr = Array.isArray(value) ? value : [value]; + return arr.reduce<Array<{ str: string; isObjectArray?: boolean }>>((acc, v) => { + if (v == null) return acc; + + if (typeof v === 'object') { + try { + acc.push({ + str: JSON.stringify(v), + isObjectArray: true, + }); + } catch { + acc.push({ str: 'Invalid Object' }); } return acc; - }, []); - } else if (value == null) { - return []; - } else if (!Array.isArray(value) && typeof value === 'object') { - try { - return [{ str: JSON.stringify(value), isObjectArray: true }]; - } catch { - return [{ str: 'Invalid Object' }]; } - } else { - return [{ str: `${value}` }]; - } + + acc.push({ str: String(v) }); + return acc; + }, []); }; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts index 2b4f562954df..645f6daa5727 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts @@ -17,7 +17,6 @@ import { } from '../../../../common/search_strategy'; import { TimelineEqlResponse } from '../../../../common/search_strategy/timeline/events/eql'; import { inspectStringifyObject } from '../../../utils/build_query'; -import { TIMELINE_EVENTS_FIELDS } from '../factory/helpers/constants'; import { formatTimelineData } from '../factory/helpers/format_timeline_data'; export const buildEqlDsl = (options: TimelineEqlRequestOptions): Record<string, unknown> => { @@ -68,38 +67,38 @@ export const buildEqlDsl = (options: TimelineEqlRequestOptions): Record<string, }, }; }; -const parseSequences = async (sequences: Array<EqlSequence<unknown>>, fieldRequested: string[]) => - sequences.reduce<Promise<TimelineEdges[]>>(async (acc, sequence, sequenceIndex) => { +const parseSequences = async (sequences: Array<EqlSequence<unknown>>, fieldRequested: string[]) => { + let result: TimelineEdges[] = []; + + for (const [sequenceIndex, sequence] of sequences.entries()) { const sequenceParentId = sequence.events[0]?._id ?? null; - const data = await acc; - const allData = await Promise.all( - sequence.events.map(async (event, eventIndex) => { - const item = await formatTimelineData( - fieldRequested, - TIMELINE_EVENTS_FIELDS, - event as EventHit - ); - return Promise.resolve({ - ...item, - node: { - ...item.node, - ecs: { - ...item.node.ecs, - ...(sequenceParentId != null - ? { - eql: { - parentId: sequenceParentId, - sequenceNumber: `${sequenceIndex}-${eventIndex}`, - }, - } - : {}), - }, - }, - }); - }) + const formattedEvents = await formatTimelineData( + sequence.events as EventHit[], + fieldRequested, + false ); - return Promise.resolve([...data, ...allData]); - }, Promise.resolve([])); + + const eventsWithEql = formattedEvents.map((item, eventIndex) => ({ + ...item, + node: { + ...item.node, + ecs: { + ...item.node.ecs, + ...(sequenceParentId && { + eql: { + parentId: sequenceParentId, + sequenceNumber: `${sequenceIndex}-${eventIndex}`, + }, + }), + }, + }, + })); + + result = result.concat(eventsWithEql); + } + + return result; +}; export const parseEqlResponse = async ( options: TimelineEqlRequestOptions, @@ -116,10 +115,10 @@ export const parseEqlResponse = async ( if (response.rawResponse.hits.sequences !== undefined) { edges = await parseSequences(response.rawResponse.hits.sequences, options.fieldRequested); } else if (response.rawResponse.hits.events !== undefined) { - edges = await Promise.all( - response.rawResponse.hits.events.map(async (event) => - formatTimelineData(options.fieldRequested, TIMELINE_EVENTS_FIELDS, event as EventHit) - ) + edges = await formatTimelineData( + response.rawResponse.hits.events as EventHit[], + options.fieldRequested, + false ); } diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts index 4ed857b4885e..2ee2b64162c1 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/index.ts @@ -14,13 +14,11 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants import { EventHit, TimelineEventsAllStrategyResponse, - TimelineEdges, } from '../../../../../../common/search_strategy'; import { TimelineFactory } from '../../types'; import { buildTimelineEventsAllQuery } from './query.events_all.dsl'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { formatTimelineData } from '../../helpers/format_timeline_data'; -import { TIMELINE_EVENTS_FIELDS } from '../../helpers/constants'; export const timelineEventsAll: TimelineFactory<TimelineEventsQueries.all> = { buildDsl: ({ authFilter, ...options }) => { @@ -54,14 +52,10 @@ export const timelineEventsAll: TimelineFactory<TimelineEventsQueries.all> = { fieldRequested = [...new Set(fieldsReturned)]; } - const edges: TimelineEdges[] = await Promise.all( - hits.map((hit) => - formatTimelineData( - fieldRequested, - options.excludeEcsData ? [] : TIMELINE_EVENTS_FIELDS, - hit as EventHit - ) - ) + const edges = await formatTimelineData( + hits as EventHit[], + fieldRequested, + options.excludeEcsData ?? false ); const consumers = producerBuckets.reduce( diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts index 46edcf926d06..fcb90b73c241 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/details/index.ts @@ -12,15 +12,11 @@ import { TimelineEventsQueries } from '../../../../../../common/api/search_strat import { EventHit, TimelineEventsDetailsStrategyResponse, - TimelineEventsDetailsItem, } from '../../../../../../common/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { TimelineFactory } from '../../types'; import { buildTimelineDetailsQuery } from './query.events_details.dsl'; -import { - getDataFromFieldsHits, - getDataSafety, -} from '../../../../../../common/utils/field_formatters'; +import { getDataFromFieldsHits } from '../../../../../../common/utils/field_formatters'; import { buildEcsObjects } from '../../helpers/build_ecs_objects'; export const timelineEventsDetails: TimelineFactory<TimelineEventsQueries.details> = { @@ -57,10 +53,7 @@ export const timelineEventsDetails: TimelineFactory<TimelineEventsQueries.detail }; } - const fieldsData = await getDataSafety<EventHit['fields'], TimelineEventsDetailsItem[]>( - getDataFromFieldsHits, - merge(fields, hitsData) - ); + const fieldsData = getDataFromFieldsHits(merge(fields, hitsData)); const rawEventData = response.rawResponse.hits.hits[0]; const ecs = buildEcsObjects(rawEventData as EventHit); diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.test.ts index 5117f8dc889e..3e96494c8831 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.test.ts @@ -7,12 +7,12 @@ import { eventHit } from '@kbn/securitysolution-t-grid'; import { EventHit } from '../../../../../common/search_strategy'; -import { TIMELINE_EVENTS_FIELDS } from './constants'; import { formatTimelineData } from './format_timeline_data'; describe('formatTimelineData', () => { it('should properly format the timeline data', async () => { const res = await formatTimelineData( + [eventHit], [ '@timestamp', 'host.name', @@ -21,187 +21,188 @@ describe('formatTimelineData', () => { 'source.geo.location', 'threat.enrichments.matched.field', ], - TIMELINE_EVENTS_FIELDS, - eventHit + false ); - expect(res).toEqual({ - cursor: { - tiebreaker: 'beats-ci-immutable-ubuntu-1804-1605624279743236239', - value: '1605624488922', - }, - node: { - _id: 'tkCt1nUBaEgqnrVSZ8R_', - _index: 'auditbeat-7.8.0-2020.11.05-000003', - data: [ - { - field: '@timestamp', - value: ['2020-11-17T14:48:08.922Z'], - }, - { - field: 'host.name', - value: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'], - }, - { - field: 'threat.enrichments.matched.field', - value: [ - 'matched_field', - 'other_matched_field', - 'matched_field_2', - 'host.name', - 'host.hostname', - 'host.architecture', - ], - }, - { - field: 'source.geo.location', - value: [`{"lon":118.7778,"lat":32.0617}`], - }, - ], - ecs: { - '@timestamp': ['2020-11-17T14:48:08.922Z'], + expect(res).toEqual([ + { + cursor: { + tiebreaker: 'beats-ci-immutable-ubuntu-1804-1605624279743236239', + value: '1605624488922', + }, + node: { _id: 'tkCt1nUBaEgqnrVSZ8R_', _index: 'auditbeat-7.8.0-2020.11.05-000003', - agent: { - type: ['auditbeat'], - }, - event: { - action: ['process_started'], - category: ['process'], - dataset: ['process'], - kind: ['event'], - module: ['system'], - type: ['start'], - }, - host: { - id: ['e59991e835905c65ed3e455b33e13bd6'], - ip: ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'], - name: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'], - os: { - family: ['debian'], + data: [ + { + field: '@timestamp', + value: ['2020-11-17T14:48:08.922Z'], }, - }, - message: ['Process go (PID: 4313) by user jenkins STARTED'], - process: { - args: ['go', 'vet', './...'], - entity_id: ['Z59cIkAAIw8ZoK0H'], - executable: [ - '/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go', - ], - hash: { - sha1: ['1eac22336a41e0660fb302add9d97daa2bcc7040'], - }, - name: ['go'], - pid: ['4313'], - ppid: ['3977'], - working_directory: [ - '/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat', - ], - }, - timestamp: '2020-11-17T14:48:08.922Z', - user: { - name: ['jenkins'], - }, - threat: { - enrichments: [ - { - feed: { name: [] }, - indicator: { - provider: ['yourself'], - reference: [], - }, - matched: { - atomic: ['matched_atomic'], - field: ['matched_field', 'other_matched_field'], - type: [], - }, - }, - { - feed: { name: [] }, - indicator: { - provider: ['other_you'], - reference: [], - }, - matched: { - atomic: ['matched_atomic_2'], - field: ['matched_field_2'], - type: [], - }, - }, - { - feed: { - name: [], - }, - indicator: { - provider: [], - reference: [], - }, - matched: { - atomic: ['MacBook-Pro-de-Gloria.local'], - field: ['host.name'], - type: ['indicator_match_rule'], - }, + { + field: 'host.name', + value: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'], + }, + { + field: 'threat.enrichments.matched.field', + value: [ + 'matched_field', + 'other_matched_field', + 'matched_field_2', + 'host.name', + 'host.hostname', + 'host.architecture', + ], + }, + { + field: 'source.geo.location', + value: [`{"lon":118.7778,"lat":32.0617}`], + }, + ], + ecs: { + '@timestamp': ['2020-11-17T14:48:08.922Z'], + _id: 'tkCt1nUBaEgqnrVSZ8R_', + _index: 'auditbeat-7.8.0-2020.11.05-000003', + agent: { + type: ['auditbeat'], + }, + event: { + action: ['process_started'], + category: ['process'], + dataset: ['process'], + kind: ['event'], + module: ['system'], + type: ['start'], + }, + host: { + id: ['e59991e835905c65ed3e455b33e13bd6'], + ip: ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'], + name: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'], + os: { + family: ['debian'], }, - { - feed: { - name: [], - }, - indicator: { - provider: [], - reference: [], - }, - matched: { - atomic: ['MacBook-Pro-de-Gloria.local'], - field: ['host.hostname'], - type: ['indicator_match_rule'], - }, + }, + message: ['Process go (PID: 4313) by user jenkins STARTED'], + process: { + args: ['go', 'vet', './...'], + entity_id: ['Z59cIkAAIw8ZoK0H'], + executable: [ + '/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go', + ], + hash: { + sha1: ['1eac22336a41e0660fb302add9d97daa2bcc7040'], }, - { - feed: { - name: [], - }, - indicator: { - provider: [], - reference: [], + name: ['go'], + pid: ['4313'], + ppid: ['3977'], + working_directory: [ + '/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat', + ], + }, + timestamp: '2020-11-17T14:48:08.922Z', + user: { + name: ['jenkins'], + }, + threat: { + enrichments: [ + { + feed: { name: [] }, + indicator: { + provider: ['yourself'], + reference: [], + }, + matched: { + atomic: ['matched_atomic'], + field: ['matched_field', 'other_matched_field'], + type: [], + }, }, - matched: { - atomic: ['x86_64'], - field: ['host.architecture'], - type: ['indicator_match_rule'], + { + feed: { name: [] }, + indicator: { + provider: ['other_you'], + reference: [], + }, + matched: { + atomic: ['matched_atomic_2'], + field: ['matched_field_2'], + type: [], + }, }, - }, - { - feed: { - name: [], + { + feed: { + name: [], + }, + indicator: { + provider: [], + reference: [], + }, + matched: { + atomic: ['MacBook-Pro-de-Gloria.local'], + field: ['host.name'], + type: ['indicator_match_rule'], + }, }, - indicator: { - provider: [], - reference: [], + { + feed: { + name: [], + }, + indicator: { + provider: [], + reference: [], + }, + matched: { + atomic: ['MacBook-Pro-de-Gloria.local'], + field: ['host.hostname'], + type: ['indicator_match_rule'], + }, }, - matched: { - atomic: ['MacBook-Pro-de-Gloria.local'], - field: ['host.name'], - type: ['indicator_match_rule'], + { + feed: { + name: [], + }, + indicator: { + provider: [], + reference: [], + }, + matched: { + atomic: ['x86_64'], + field: ['host.architecture'], + type: ['indicator_match_rule'], + }, }, - }, - { - feed: { - name: [], + { + feed: { + name: [], + }, + indicator: { + provider: [], + reference: [], + }, + matched: { + atomic: ['MacBook-Pro-de-Gloria.local'], + field: ['host.name'], + type: ['indicator_match_rule'], + }, }, - indicator: { - provider: [], - reference: [], + { + feed: { + name: [], + }, + indicator: { + provider: [], + reference: [], + }, + matched: { + atomic: ['MacBook-Pro-de-Gloria.local'], + field: ['host.hostname'], + type: ['indicator_match_rule'], + }, }, - matched: { - atomic: ['MacBook-Pro-de-Gloria.local'], - field: ['host.hostname'], - type: ['indicator_match_rule'], - }, - }, - ], + ], + }, }, }, }, - }); + ]); }); it('should properly format the rule signal results', async () => { @@ -240,57 +241,61 @@ describe('formatTimelineData', () => { expect( await formatTimelineData( + [response], ['@timestamp', 'host.name', 'destination.ip', 'source.ip'], - TIMELINE_EVENTS_FIELDS, - response + false ) - ).toEqual({ - cursor: { - tiebreaker: null, - value: '', - }, - node: { - _id: 'a77040f198355793c35bf22b900902371309be615381f0a2ec92c208b6132562', - _index: '.siem-signals-patrykkopycinski-default-000007', - data: [ - { - field: '@timestamp', - value: ['2021-01-09T13:41:40.517Z'], - }, - ], - ecs: { - '@timestamp': ['2021-01-09T13:41:40.517Z'], - timestamp: '2021-01-09T13:41:40.517Z', + ).toEqual([ + { + cursor: { + tiebreaker: null, + value: '', + }, + node: { _id: 'a77040f198355793c35bf22b900902371309be615381f0a2ec92c208b6132562', _index: '.siem-signals-patrykkopycinski-default-000007', - event: { - kind: ['signal'], - }, - kibana: { - alert: { - original_time: ['2021-01-09T13:39:32.595Z'], - workflow_status: ['open'], - threshold_result: ['{"count":10000,"value":"2a990c11-f61b-4c8e-b210-da2574e9f9db"}'], - severity: ['low'], - risk_score: ['21'], - rule: { - building_block_type: [], - exceptions_list: [], - from: ['now-360s'], - uuid: ['696c24e0-526d-11eb-836c-e1620268b945'], - name: ['Threshold test'], - to: ['now'], - type: ['threshold'], - version: ['1'], - timeline_id: [], - timeline_title: [], - note: [], + data: [ + { + field: '@timestamp', + value: ['2021-01-09T13:41:40.517Z'], + }, + ], + ecs: { + '@timestamp': ['2021-01-09T13:41:40.517Z'], + timestamp: '2021-01-09T13:41:40.517Z', + _id: 'a77040f198355793c35bf22b900902371309be615381f0a2ec92c208b6132562', + _index: '.siem-signals-patrykkopycinski-default-000007', + event: { + kind: ['signal'], + }, + kibana: { + alert: { + original_time: ['2021-01-09T13:39:32.595Z'], + workflow_status: ['open'], + threshold_result: [ + '{"count":10000,"value":"2a990c11-f61b-4c8e-b210-da2574e9f9db"}', + ], + severity: ['low'], + risk_score: ['21'], + rule: { + building_block_type: [], + exceptions_list: [], + from: ['now-360s'], + uuid: ['696c24e0-526d-11eb-836c-e1620268b945'], + name: ['Threshold test'], + to: ['now'], + type: ['threshold'], + version: ['1'], + timeline_id: [], + timeline_title: [], + note: [], + }, }, }, }, }, }, - }); + ]); }); it('should properly format the inventory rule signal results', async () => { @@ -347,6 +352,7 @@ describe('formatTimelineData', () => { expect( await formatTimelineData( + [response], [ 'kibana.alert.status', '@timestamp', @@ -376,168 +382,169 @@ describe('formatTimelineData', () => { 'event.kind', 'kibana.alert.rule.parameters', ], - TIMELINE_EVENTS_FIELDS, - response + false ) - ).toEqual({ - cursor: { - tiebreaker: null, - value: '', - }, - node: { - _id: '3fef4a4c-3d96-4e79-b4e5-158a0461d577', - _index: '.internal.alerts-observability.metrics.alerts-default-000001', - data: [ - { - field: 'kibana.alert.rule.consumer', - value: ['infrastructure'], - }, - { - field: '@timestamp', - value: ['2022-07-21T22:38:57.888Z'], - }, - { - field: 'kibana.alert.workflow_status', - value: ['open'], - }, - { - field: 'kibana.alert.reason', - value: [ - 'CPU usage is 37.8% in the last 1 day for gke-edge-oblt-pool-1-9a60016d-7dvq. Alert when > 10%.', - ], - }, - { - field: 'kibana.alert.rule.name', - value: ['test 1212'], - }, - { - field: 'kibana.alert.rule.uuid', - value: ['15d82f10-0926-11ed-bece-6b0c033d0075'], - }, - { - field: 'kibana.alert.rule.parameters.sourceId', - value: ['default'], - }, - { - field: 'kibana.alert.rule.parameters.nodeType', - value: ['host'], - }, - { - field: 'kibana.alert.rule.parameters.criteria.comparator', - value: ['>'], - }, - { - field: 'kibana.alert.rule.parameters.criteria.timeSize', - value: ['1'], - }, - { - field: 'kibana.alert.rule.parameters.criteria.metric', - value: ['cpu'], - }, - { - field: 'kibana.alert.rule.parameters.criteria.threshold', - value: ['10'], - }, - { - field: 'kibana.alert.rule.parameters.criteria.customMetric.aggregation', - value: ['avg'], - }, - { - field: 'kibana.alert.rule.parameters.criteria.customMetric.id', - value: ['alert-custom-metric'], - }, - { - field: 'kibana.alert.rule.parameters.criteria.customMetric.field', - value: [''], - }, - { - field: 'kibana.alert.rule.parameters.criteria.customMetric.type', - value: ['custom'], - }, - { - field: 'kibana.alert.rule.parameters.criteria.timeUnit', - value: ['d'], - }, - { - field: 'event.action', - value: ['active'], - }, - { - field: 'event.kind', - value: ['signal'], - }, - { - field: 'kibana.alert.status', - value: ['active'], - }, - { - field: 'kibana.alert.duration.us', - value: ['9502040000'], - }, - { - field: 'kibana.alert.rule.category', - value: ['Inventory'], - }, - { - field: 'kibana.alert.uuid', - value: ['3fef4a4c-3d96-4e79-b4e5-158a0461d577'], - }, - { - field: 'kibana.alert.start', - value: ['2022-07-21T20:00:35.848Z'], - }, - { - field: 'kibana.alert.rule.producer', - value: ['infrastructure'], - }, - { - field: 'kibana.alert.rule.rule_type_id', - value: ['metrics.alert.inventory.threshold'], - }, - { - field: 'kibana.alert.instance.id', - value: ['gke-edge-oblt-pool-1-9a60016d-7dvq'], - }, - { - field: 'kibana.alert.rule.execution.uuid', - value: ['37498c42-0190-4a83-adfa-c7e5f817f977'], - }, - { - field: 'kibana.space_ids', - value: ['default'], - }, - { - field: 'kibana.version', - value: ['8.4.0'], - }, - ], - ecs: { - '@timestamp': ['2022-07-21T22:38:57.888Z'], + ).toEqual([ + { + cursor: { + tiebreaker: null, + value: '', + }, + node: { _id: '3fef4a4c-3d96-4e79-b4e5-158a0461d577', _index: '.internal.alerts-observability.metrics.alerts-default-000001', - event: { - action: ['active'], - kind: ['signal'], - }, - kibana: { - alert: { - reason: [ + data: [ + { + field: 'kibana.alert.rule.consumer', + value: ['infrastructure'], + }, + { + field: '@timestamp', + value: ['2022-07-21T22:38:57.888Z'], + }, + { + field: 'kibana.alert.workflow_status', + value: ['open'], + }, + { + field: 'kibana.alert.reason', + value: [ 'CPU usage is 37.8% in the last 1 day for gke-edge-oblt-pool-1-9a60016d-7dvq. Alert when > 10%.', ], - rule: { - consumer: ['infrastructure'], - name: ['test 1212'], - uuid: ['15d82f10-0926-11ed-bece-6b0c033d0075'], - parameters: [ - '{"sourceId":"default","nodeType":"host","criteria":[{"comparator":">","timeSize":1,"metric":"cpu","threshold":[10],"customMetric":{"aggregation":"avg","id":"alert-custom-metric","field":"","type":"custom"},"timeUnit":"d"}]}', + }, + { + field: 'kibana.alert.rule.name', + value: ['test 1212'], + }, + { + field: 'kibana.alert.rule.uuid', + value: ['15d82f10-0926-11ed-bece-6b0c033d0075'], + }, + { + field: 'kibana.alert.rule.parameters.sourceId', + value: ['default'], + }, + { + field: 'kibana.alert.rule.parameters.nodeType', + value: ['host'], + }, + { + field: 'kibana.alert.rule.parameters.criteria.comparator', + value: ['>'], + }, + { + field: 'kibana.alert.rule.parameters.criteria.timeSize', + value: ['1'], + }, + { + field: 'kibana.alert.rule.parameters.criteria.metric', + value: ['cpu'], + }, + { + field: 'kibana.alert.rule.parameters.criteria.threshold', + value: ['10'], + }, + { + field: 'kibana.alert.rule.parameters.criteria.customMetric.aggregation', + value: ['avg'], + }, + { + field: 'kibana.alert.rule.parameters.criteria.customMetric.id', + value: ['alert-custom-metric'], + }, + { + field: 'kibana.alert.rule.parameters.criteria.customMetric.field', + value: [''], + }, + { + field: 'kibana.alert.rule.parameters.criteria.customMetric.type', + value: ['custom'], + }, + { + field: 'kibana.alert.rule.parameters.criteria.timeUnit', + value: ['d'], + }, + { + field: 'event.action', + value: ['active'], + }, + { + field: 'event.kind', + value: ['signal'], + }, + { + field: 'kibana.alert.status', + value: ['active'], + }, + { + field: 'kibana.alert.duration.us', + value: ['9502040000'], + }, + { + field: 'kibana.alert.rule.category', + value: ['Inventory'], + }, + { + field: 'kibana.alert.uuid', + value: ['3fef4a4c-3d96-4e79-b4e5-158a0461d577'], + }, + { + field: 'kibana.alert.start', + value: ['2022-07-21T20:00:35.848Z'], + }, + { + field: 'kibana.alert.rule.producer', + value: ['infrastructure'], + }, + { + field: 'kibana.alert.rule.rule_type_id', + value: ['metrics.alert.inventory.threshold'], + }, + { + field: 'kibana.alert.instance.id', + value: ['gke-edge-oblt-pool-1-9a60016d-7dvq'], + }, + { + field: 'kibana.alert.rule.execution.uuid', + value: ['37498c42-0190-4a83-adfa-c7e5f817f977'], + }, + { + field: 'kibana.space_ids', + value: ['default'], + }, + { + field: 'kibana.version', + value: ['8.4.0'], + }, + ], + ecs: { + '@timestamp': ['2022-07-21T22:38:57.888Z'], + _id: '3fef4a4c-3d96-4e79-b4e5-158a0461d577', + _index: '.internal.alerts-observability.metrics.alerts-default-000001', + event: { + action: ['active'], + kind: ['signal'], + }, + kibana: { + alert: { + reason: [ + 'CPU usage is 37.8% in the last 1 day for gke-edge-oblt-pool-1-9a60016d-7dvq. Alert when > 10%.', ], + rule: { + consumer: ['infrastructure'], + name: ['test 1212'], + uuid: ['15d82f10-0926-11ed-bece-6b0c033d0075'], + parameters: [ + '{"sourceId":"default","nodeType":"host","criteria":[{"comparator":">","timeSize":1,"metric":"cpu","threshold":[10],"customMetric":{"aggregation":"avg","id":"alert-custom-metric","field":"","type":"custom"},"timeUnit":"d"}]}', + ], + }, + workflow_status: ['open'], }, - workflow_status: ['open'], }, + timestamp: '2022-07-21T22:38:57.888Z', }, - timestamp: '2022-07-21T22:38:57.888Z', }, }, - }); + ]); }); }); diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.ts index f56cfd32391d..481b74a802fe 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/format_timeline_data.ts @@ -5,118 +5,130 @@ * 2.0. */ -import { get, has, merge, uniq } from 'lodash/fp'; -import { EventHit, TimelineEdges, TimelineNonEcsData } from '../../../../../common/search_strategy'; +import { get, has } from 'lodash/fp'; +import { + EventHit, + TimelineEdges, + TimelineNonEcsData, + EventSource, +} from '../../../../../common/search_strategy'; import { toStringArray } from '../../../../../common/utils/to_array'; -import { getDataFromFieldsHits, getDataSafety } from '../../../../../common/utils/field_formatters'; +import { getDataFromFieldsHits } from '../../../../../common/utils/field_formatters'; import { getTimestamp } from './get_timestamp'; import { getNestedParentPath } from './get_nested_parent_path'; import { buildObjectRecursive } from './build_object_recursive'; -import { ECS_METADATA_FIELDS } from './constants'; +import { ECS_METADATA_FIELDS, TIMELINE_EVENTS_FIELDS } from './constants'; -export const formatTimelineData = async ( - dataFields: readonly string[], - ecsFields: readonly string[], - hit: EventHit -) => - uniq([...ecsFields, ...dataFields]).reduce<Promise<TimelineEdges>>( - async (acc, fieldName) => { - const flattenedFields: TimelineEdges = await acc; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - flattenedFields.node._id = hit._id!; - flattenedFields.node._index = hit._index; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - flattenedFields.node.ecs._id = hit._id!; - flattenedFields.node.ecs.timestamp = getTimestamp(hit); - flattenedFields.node.ecs._index = hit._index; - if (hit.sort && hit.sort.length > 1) { - flattenedFields.cursor.value = hit.sort[0]; - flattenedFields.cursor.tiebreaker = hit.sort[1]; - } - const waitForIt = await mergeTimelineFieldsWithHit( - fieldName, - flattenedFields, - hit, - dataFields, - ecsFields - ); - return Promise.resolve(waitForIt); - }, - Promise.resolve({ - node: { ecs: { _id: '' }, data: [], _id: '', _index: '' }, - cursor: { - value: '', - tiebreaker: null, - }, - }) - ); - -const getValuesFromFields = async ( +const createBaseTimelineEdges = (): TimelineEdges => ({ + node: { + ecs: { _id: '' }, + data: [], + _id: '', + _index: '', + }, + cursor: { + value: '', + tiebreaker: null, + }, +}); + +function deepMerge(target: EventSource, source: EventSource) { + for (const key in source) { + if (source && source[key] instanceof Object && target && target[key] instanceof Object) { + deepMerge(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + return target; +} + +const processMetadataField = (fieldName: string, hit: EventHit): TimelineNonEcsData[] => [ + { + field: fieldName, + value: toStringArray(get(fieldName, hit)), + }, +]; + +const processFieldData = ( fieldName: string, hit: EventHit, nestedParentFieldName?: string -): Promise<TimelineNonEcsData[]> => { - if (ECS_METADATA_FIELDS.includes(fieldName)) { - return [{ field: fieldName, value: toStringArray(get(fieldName, hit)) }]; - } +): TimelineNonEcsData[] => { + const fieldToEval = nestedParentFieldName + ? { [nestedParentFieldName]: hit.fields[nestedParentFieldName] } + : { [fieldName]: hit.fields[fieldName] }; - let fieldToEval; - - if (nestedParentFieldName == null) { - fieldToEval = { - [fieldName]: hit.fields[fieldName], - }; - } else { - fieldToEval = { - [nestedParentFieldName]: hit.fields[nestedParentFieldName], - }; - } - const formattedData = await getDataSafety(getDataFromFieldsHits, fieldToEval); - return formattedData.reduce((acc: TimelineNonEcsData[], { field, values }) => { - // nested fields return all field values, pick only the one we asked for + const formattedData = getDataFromFieldsHits(fieldToEval); + const fieldsData: TimelineNonEcsData[] = []; + return formattedData.reduce((agg, { field, values }) => { if (field.includes(fieldName)) { - acc.push({ field, value: values }); + agg.push({ + field, + value: values, + }); } - return acc; - }, []); + return agg; + }, fieldsData); }; -const mergeTimelineFieldsWithHit = async <T>( - fieldName: string, - flattenedFields: T, - hit: EventHit, - dataFields: readonly string[], - ecsFields: readonly string[] -) => { - if (fieldName != null) { - const nestedParentPath = getNestedParentPath(fieldName, hit.fields); - if ( - nestedParentPath != null || - has(fieldName, hit.fields) || - ECS_METADATA_FIELDS.includes(fieldName) - ) { - const objectWithProperty = { - node: { - ...get('node', flattenedFields), - data: dataFields.includes(fieldName) - ? [ - ...get('node.data', flattenedFields), - ...(await getValuesFromFields(fieldName, hit, nestedParentPath)), - ] - : get('node.data', flattenedFields), - ecs: ecsFields.includes(fieldName) - ? { - ...get('node.ecs', flattenedFields), - ...buildObjectRecursive(fieldName, hit.fields), - } - : get('node.ecs', flattenedFields), - }, - }; - return merge(flattenedFields, objectWithProperty); +export const formatTimelineData = async ( + hits: EventHit[], + fieldRequested: readonly string[], + excludeEcsData: boolean +): Promise<TimelineEdges[]> => { + const ecsFields = excludeEcsData ? [] : TIMELINE_EVENTS_FIELDS; + + const uniqueFields = new Set([...ecsFields, ...fieldRequested]); + const dataFieldSet = new Set(fieldRequested); + const ecsFieldSet = new Set(ecsFields); + + const results: TimelineEdges[] = new Array(hits.length); + + for (let i = 0; i < hits.length; i++) { + const hit = hits[i]; + if (hit._id) { + const result = createBaseTimelineEdges(); + + result.node._id = hit._id; + result.node._index = hit._index; + result.node.ecs._id = hit._id; + result.node.ecs.timestamp = getTimestamp(hit); + result.node.ecs._index = hit._index; + + if (hit.sort?.length > 1) { + result.cursor.value = hit.sort[0]; + result.cursor.tiebreaker = hit.sort[1]; + } + + result.node.data = []; + + for (const fieldName of uniqueFields) { + const nestedParentPath = getNestedParentPath(fieldName, hit.fields); + const isEcs = ECS_METADATA_FIELDS.includes(fieldName); + if (!nestedParentPath && !has(fieldName, hit.fields) && !isEcs) { + // eslint-disable-next-line no-continue + continue; + } + + if (dataFieldSet.has(fieldName)) { + const values = isEcs + ? processMetadataField(fieldName, hit) + : processFieldData(fieldName, hit, nestedParentPath); + + result.node.data.push(...values); + } + + if (ecsFieldSet.has(fieldName)) { + deepMerge(result.node.ecs, buildObjectRecursive(fieldName, hit.fields)); + } + } + + results[i] = result; } else { - return flattenedFields; + results[i] = createBaseTimelineEdges(); } - } else { - return flattenedFields; } + + return results; }; diff --git a/x-pack/plugins/transform/public/app/common/pivot_aggs.test.ts b/x-pack/plugins/transform/public/app/common/pivot_aggs.test.ts index 61945773faec..5f0bb251a6cb 100644 --- a/x-pack/plugins/transform/public/app/common/pivot_aggs.test.ts +++ b/x-pack/plugins/transform/public/app/common/pivot_aggs.test.ts @@ -100,7 +100,7 @@ describe('getAggConfigFromEsAgg', () => { field: 'products.base_price', parentAgg: result, aggConfig: { - percents: '1,5,25,50,75,95,99', + percents: [1, 5, 25, 50, 75, 95, 99], }, }); 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 19a432664473..eedaa75cb3c9 100644 --- a/x-pack/plugins/transform/public/app/common/pivot_aggs.ts +++ b/x-pack/plugins/transform/public/app/common/pivot_aggs.ts @@ -225,6 +225,7 @@ export interface PivotAggsConfigWithExtra<T, ESConfig extends { [key: string]: a onChange: (arg: Partial<T>) => void; selectedField: string; isValid?: boolean; + errorMessages?: string[]; }>; /** Aggregation specific configuration */ aggConfig: Partial<T>; @@ -238,6 +239,8 @@ export interface PivotAggsConfigWithExtra<T, ESConfig extends { [key: string]: a getAggName?: () => string | undefined; /** Helper text for the aggregation reflecting some configuration info */ helperText?: () => string | undefined; + /** Returns validation error messages */ + getErrorMessages?: () => string[] | undefined; } interface PivotAggsConfigPercentiles extends PivotAggsConfigWithUiBase { diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx index 37e8096f355e..7e9c1b312b99 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.test.tsx @@ -7,8 +7,7 @@ import React, { type FC, type PropsWithChildren } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { render, screen, waitFor } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, screen, waitFor, renderHook } from '@testing-library/react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import type { CoreSetup } from '@kbn/core/public'; @@ -47,7 +46,7 @@ describe('Transform: useIndexData()', () => { </QueryClientProvider> ); - const { result, waitForNextUpdate } = renderHook( + const { result } = renderHook( () => useIndexData({ dataView: { @@ -62,13 +61,13 @@ describe('Transform: useIndexData()', () => { { wrapper } ); - const IndexObj: UseIndexDataReturnType = result.current; - - await waitForNextUpdate(); + await waitFor(() => { + const IndexObj: UseIndexDataReturnType = result.current; - expect(IndexObj.errorMessage).toBe(''); - expect(IndexObj.status).toBe(INDEX_STATUS.UNUSED); - expect(IndexObj.tableItems).toEqual([]); + expect(IndexObj.errorMessage).toBe(''); + expect(IndexObj.status).toBe(INDEX_STATUS.UNUSED); + expect(IndexObj.tableItems).toEqual([]); + }); }); test('dataView set triggers loading', async () => { @@ -78,7 +77,7 @@ describe('Transform: useIndexData()', () => { </QueryClientProvider> ); - const { result, waitForNextUpdate } = renderHook( + const { result } = renderHook( () => useIndexData({ dataView: { @@ -108,11 +107,11 @@ describe('Transform: useIndexData()', () => { const IndexObj: UseIndexDataReturnType = result.current; - await waitForNextUpdate(); - - expect(IndexObj.errorMessage).toBe(''); - expect(IndexObj.status).toBe(INDEX_STATUS.LOADING); - expect(IndexObj.tableItems).toEqual([]); + await waitFor(() => { + expect(IndexObj.errorMessage).toBe(''); + expect(IndexObj.status).toBe(INDEX_STATUS.LOADING); + expect(IndexObj.tableItems).toEqual([]); + }); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx index b24adddf8f15..f08746893a2b 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx @@ -242,6 +242,7 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha }); }} isValid={aggConfigDef.isValid()} + errorMessages={aggConfigDef.getErrorMessages?.()} /> ) : null} {isUnsupportedAgg && ( diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts index 783d5d68a743..46c71c5e149d 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts @@ -82,7 +82,7 @@ describe('Transform: Define Pivot Common', () => { aggName: 'the-field.percentiles', dropDownName: 'percentiles( the-f[i]e>ld )', AggFormComponent: PercentilesAggForm, - aggConfig: { percents: '1,5,25,50,75,95,99' }, + aggConfig: { percents: [1, 5, 25, 50, 75, 95, 99] }, }, 'filter( the-f[i]e>ld )': { agg: 'filter', @@ -222,7 +222,7 @@ describe('Transform: Define Pivot Common', () => { dropDownName: 'percentiles( the-f[i]e>ld )', field: ' the-f[i]e>ld ', AggFormComponent: PercentilesAggForm, - aggConfig: { percents: '1,5,25,50,75,95,99' }, + aggConfig: { percents: [1, 5, 25, 50, 75, 95, 99] }, }, 'sum( the-f[i]e>ld )': { agg: 'sum', @@ -292,7 +292,7 @@ describe('Transform: Define Pivot Common', () => { dropDownName: 'percentiles(rt_bytes_bigger)', field: 'rt_bytes_bigger', AggFormComponent: PercentilesAggForm, - aggConfig: { percents: '1,5,25,50,75,95,99' }, + aggConfig: { percents: [1, 5, 25, 50, 75, 95, 99] }, }, 'sum(rt_bytes_bigger)': { agg: 'sum', diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/config.test.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/config.test.ts new file mode 100644 index 000000000000..4b97357b5e03 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/config.test.ts @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getPercentilesAggConfig } from './config'; +import type { IPivotAggsConfigPercentiles } from './types'; +import { PERCENTILES_AGG_DEFAULT_PERCENTS } from '../../../../../../common'; + +describe('percentiles agg config', () => { + let config: IPivotAggsConfigPercentiles; + + beforeEach(() => { + config = getPercentilesAggConfig({ + agg: 'percentiles', + aggName: 'test-agg', + field: ['test-field'], + dropDownName: 'test-agg', + }); + }); + + describe('#setUiConfigFromEs', () => { + test('sets field and percents from ES config', () => { + // act + config.setUiConfigFromEs({ + field: 'test-field', + percents: [10, 20, 30], + }); + + // assert + expect(config.field).toEqual('test-field'); + expect(config.aggConfig).toEqual({ percents: [10, 20, 30] }); + }); + }); + + describe('#getEsAggConfig', () => { + test('returns null for invalid config', () => { + // arrange + config.aggConfig.percents = [150]; // invalid percentile value + + // act and assert + expect(config.getEsAggConfig()).toBeNull(); + }); + + test('returns valid config', () => { + // arrange + config.field = 'test-field'; + config.aggConfig.percents = [10, 20, 30]; + + // act and assert + expect(config.getEsAggConfig()).toEqual({ + field: 'test-field', + percents: [10, 20, 30], + }); + }); + + test('returns default percents if none specified', () => { + // arrange + config.field = 'test-field'; + + // act and assert + expect(config.getEsAggConfig()).toEqual({ + field: 'test-field', + percents: PERCENTILES_AGG_DEFAULT_PERCENTS, + }); + }); + }); + + describe('#isValid', () => { + test('returns false for percentiles out of range', () => { + // arrange + config.aggConfig.percents = [150]; + + // act and assert + expect(config.isValid()).toBeFalsy(); + expect(config.aggConfig.errors).toContain('PERCENTILE_OUT_OF_RANGE'); + }); + + test('returns false for invalid number format', () => { + // arrrange + config.aggConfig.pendingPercentileInput = 'invalid'; + + // act and assert + expect(config.isValid()).toBeFalsy(); + expect(config.aggConfig.errors).toContain('INVALID_FORMAT'); + }); + + test('returns true for valid percents', () => { + // arrange + config.aggConfig.percents = [10, 20, 30]; + + // act and assert + expect(config.isValid()).toBeTruthy(); + expect(config.aggConfig.errors).toBeUndefined(); + }); + + test('validates pending input along with existing percents', () => { + // arrange + config.aggConfig.percents = [10, 20, 30]; + config.aggConfig.pendingPercentileInput = '50'; + + // act and assert + expect(config.isValid()).toBeTruthy(); + expect(config.aggConfig.errors).toBeUndefined(); + }); + }); + + describe('#getErrorMessages', () => { + test('returns undefined when there are no errors', () => { + // arrange + config.aggConfig.errors = undefined; + + // act and assert + expect(config.getErrorMessages?.()).toBeUndefined(); + }); + + test('returns undefined when errors array is empty', () => { + // arrange + config.aggConfig.errors = []; + + // act and assert + expect(config.getErrorMessages?.()).toBeUndefined(); + }); + + test('returns translated messages for single error', () => { + // arrange + config.aggConfig.errors = ['PERCENTILE_OUT_OF_RANGE']; + + // act and assert + expect(config.getErrorMessages?.()).toEqual(['Percentiles must be between 0 and 100']); + }); + + test('returns translated messages for multiple errors', () => { + // arrange + config.aggConfig.errors = ['PERCENTILE_OUT_OF_RANGE', 'INVALID_FORMAT']; + + // act and assert + expect(config.getErrorMessages?.()).toEqual([ + 'Percentiles must be between 0 and 100', + 'Percentile must be a valid number', + ]); + }); + }); +}); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/config.ts index 34a160225951..85260e18463e 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/config.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/config.ts @@ -5,43 +5,60 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { PercentilesAggForm } from './percentiles_form_component'; -import type { IPivotAggsConfigPercentiles } from './types'; +import type { + IPivotAggsConfigPercentiles, + PercentilesAggConfig, + ValidationResult, + ValidationResultErrorType, +} from './types'; import type { PivotAggsConfigBase } from '../../../../../../common'; import { isPivotAggsConfigWithUiBase, PERCENTILES_AGG_DEFAULT_PERCENTS, } from '../../../../../../common'; import type { PivotAggsConfigWithUiBase } from '../../../../../../common/pivot_aggs'; +import { MAX_PERCENTILE_PRECISION, MAX_PERCENTILE_VALUE, MIN_PERCENTILE_VALUE } from './constants'; -/** - * TODO this callback has been moved. - * The logic of parsing the string should be improved. - */ -function parsePercentsInput(inputValue: string | undefined) { - if (inputValue !== undefined) { - const strVals: string[] = inputValue.split(','); - const percents: number[] = []; - for (const str of strVals) { - if (str.trim().length > 0 && isNaN(str as any) === false) { - const val = Number(str); - if (val >= 0 && val <= 100) { - percents.push(val); - } else { - return []; - } - } +function validatePercentsInput(config: Partial<PercentilesAggConfig>): ValidationResult { + const allValues = [...(config.percents ?? [])]; + const errors: ValidationResultErrorType[] = []; + // Combine existing percents with pending input for validation + if (config.pendingPercentileInput) { + // Replace comma with dot before converting to number + const normalizedInput = config.pendingPercentileInput.replace(',', '.'); + const pendingValue = Number(normalizedInput); + + if (allValues.includes(pendingValue)) { + errors.push('DUPLICATE_VALUE'); + } + + if (normalizedInput.replace('.', '').length > MAX_PERCENTILE_PRECISION) { + errors.push('NUMBER_TOO_PRECISE'); } - return percents; + allValues.push(pendingValue); } - return []; -} + if (allValues.length === 0) { + return { + isValid: false, + errors: [], + }; + } + + if (allValues.some((value) => isNaN(value))) { + errors.push('INVALID_FORMAT'); + } + if (allValues.some((value) => value < MIN_PERCENTILE_VALUE || value > MAX_PERCENTILE_VALUE)) { + errors.push('PERCENTILE_OUT_OF_RANGE'); + } -// Input string should only include comma separated numbers -function isValidPercentsInput(inputValue: string) { - return /^[0-9]+(,[0-9]+)*$/.test(inputValue); + return { + isValid: errors.length === 0, + errors: errors.length > 0 ? errors : undefined, + }; } export function getPercentilesAggConfig( @@ -56,13 +73,13 @@ export function getPercentilesAggConfig( AggFormComponent: PercentilesAggForm, field, aggConfig: { - percents: PERCENTILES_AGG_DEFAULT_PERCENTS.toString(), + percents: PERCENTILES_AGG_DEFAULT_PERCENTS, }, setUiConfigFromEs(esAggDefinition) { const { field: esField, percents } = esAggDefinition; this.field = esField; - this.aggConfig.percents = percents.join(','); + this.aggConfig.percents = percents; }, getEsAggConfig() { if (!this.isValid()) { @@ -71,13 +88,36 @@ export function getPercentilesAggConfig( return { field: this.field as string, - percents: parsePercentsInput(this.aggConfig.percents), + percents: this.aggConfig.percents ?? [], }; }, isValid() { - return ( - typeof this.aggConfig.percents === 'string' && isValidPercentsInput(this.aggConfig.percents) - ); + const validationResult = validatePercentsInput(this.aggConfig); + this.aggConfig.errors = validationResult.errors; + return validationResult.isValid; + }, + getErrorMessages() { + if (!this.aggConfig.errors?.length) return; + + return this.aggConfig.errors.map((error) => ERROR_MESSAGES[error]); }, }; } + +const ERROR_MESSAGES: Record<ValidationResultErrorType, string> = { + INVALID_FORMAT: i18n.translate('xpack.transform.agg.popoverForm.invalidFormatError', { + defaultMessage: 'Percentile must be a valid number', + }), + PERCENTILE_OUT_OF_RANGE: i18n.translate( + 'xpack.transform.agg.popoverForm.percentileOutOfRangeError', + { + defaultMessage: 'Percentiles must be between 0 and 100', + } + ), + NUMBER_TOO_PRECISE: i18n.translate('xpack.transform.agg.popoverForm.numberTooPreciseError', { + defaultMessage: 'Value is too precise. Use fewer decimal places.', + }), + DUPLICATE_VALUE: i18n.translate('xpack.transform.agg.popoverForm.duplicateValueError', { + defaultMessage: 'Value already exists', + }), +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/constants.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/constants.ts new file mode 100644 index 000000000000..4942d3b2f2d1 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/constants.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const MAX_PERCENTILE_PRECISION = 17; +export const MAX_PERCENTILE_VALUE = 100; +export const MIN_PERCENTILE_VALUE = 0; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/percentiles_form_component.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/percentiles_form_component.tsx index 18f619351c46..d8b32cd8311f 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/percentiles_form_component.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/percentiles_form_component.tsx @@ -5,38 +5,84 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFieldText, EuiFormRow } from '@elastic/eui'; +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { EuiComboBox, EuiFormRow } from '@elastic/eui'; import type { IPivotAggsConfigPercentiles } from './types'; export const PercentilesAggForm: IPivotAggsConfigPercentiles['AggFormComponent'] = ({ aggConfig, onChange, isValid, + errorMessages, }) => { + const selectedOptions = useMemo( + () => aggConfig.percents?.map((p) => ({ label: p.toString() })) ?? [], + [aggConfig.percents] + ); + + const handleCreateOption = useCallback( + (inputValue: string) => { + if (!isValid) return false; + + const newValue = Number(inputValue.replace(',', '.')); + + const newOption = { + label: newValue.toString(), + }; + const updatedOptions = [...selectedOptions, newOption]; + + onChange({ + percents: updatedOptions.map((option) => Number(option.label)), + }); + }, + [isValid, onChange, selectedOptions] + ); + + const handleOptionsChange = useCallback( + (newOptions: Array<EuiComboBoxOptionOption<string>>) => { + onChange({ percents: newOptions.map((option) => Number(option.label)) }); + }, + [onChange] + ); + + const handleSearchChange = useCallback( + (searchValue: string) => { + // If we're clearing the input after a valid creation, + // this is the post-creation cleanup + if (searchValue === '' && aggConfig.pendingPercentileInput && isValid) return; + + onChange({ + ...aggConfig, + pendingPercentileInput: searchValue, + }); + }, + [aggConfig, onChange, isValid] + ); + + // Get the last error message if there are any + const lastErrorMessage = errorMessages?.length + ? errorMessages[errorMessages.length - 1] + : undefined; + return ( <> <EuiFormRow label={i18n.translate('xpack.transform.agg.popoverForm.percentsLabel', { defaultMessage: 'Percents', })} - error={ - !isValid && [ - i18n.translate('xpack.transform.groupBy.popoverForm.intervalPercents', { - defaultMessage: 'Enter a comma-separated list of percentiles', - }), - ] - } + error={lastErrorMessage} isInvalid={!isValid} > - <EuiFieldText - value={aggConfig.percents} - onChange={(e) => { - onChange({ - percents: e.target.value, - }); - }} + <EuiComboBox + noSuggestions + selectedOptions={selectedOptions} + onCreateOption={handleCreateOption} + onChange={handleOptionsChange} + onSearchChange={handleSearchChange} + isInvalid={!isValid} + data-test-subj="transformPercentilesAggPercentsSelector" /> </EuiFormRow> </> diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/types.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/types.ts index fc16ba89a88c..d4dbe83425cd 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/types.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/percentiles_agg/types.ts @@ -8,10 +8,23 @@ import type { PivotAggsConfigWithExtra } from '../../../../../../common/pivot_aggs'; export interface PercentilesAggConfig { - /** Comma separated list */ - percents: string; + percents: number[]; + pendingPercentileInput?: string; + errors?: ValidationResultErrorType[]; } + +export type ValidationResultErrorType = + | 'INVALID_FORMAT' + | 'PERCENTILE_OUT_OF_RANGE' + | 'NUMBER_TOO_PRECISE' + | 'DUPLICATE_VALUE'; + export type IPivotAggsConfigPercentiles = PivotAggsConfigWithExtra< PercentilesAggConfig, { field: string; percents: number[] } >; + +export interface ValidationResult { + isValid: boolean; + errors?: ValidationResultErrorType[]; +} diff --git a/x-pack/plugins/transform/public/app/sections/edit_transform/state_management/edit_transform_flyout_state.test.tsx b/x-pack/plugins/transform/public/app/sections/edit_transform/state_management/edit_transform_flyout_state.test.tsx index 7e5b38294b66..c70610017b7d 100644 --- a/x-pack/plugins/transform/public/app/sections/edit_transform/state_management/edit_transform_flyout_state.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/edit_transform/state_management/edit_transform_flyout_state.test.tsx @@ -6,7 +6,8 @@ */ import React, { type FC, type PropsWithChildren } from 'react'; -import { act, renderHook } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; import { getTransformConfigMock } from './__mocks__/transform_config'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx index 500706511378..2805d2d0b3c6 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.test.tsx @@ -7,7 +7,7 @@ import React, { type FC, type PropsWithChildren } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; jest.mock('../../../../app_dependencies'); @@ -19,12 +19,11 @@ describe('Transform: Transform List Actions', () => { const wrapper: FC<PropsWithChildren<unknown>> = ({ children }) => ( <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> ); - const { result, waitForNextUpdate } = renderHook( - () => useActions({ forceDisable: false, transformNodes: 1 }), - { wrapper } - ); + const { result } = renderHook(() => useActions({ forceDisable: false, transformNodes: 1 }), { + wrapper, + }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const actions = result.current.actions; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx index dcab70822ecd..04673a386d06 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_columns.test.tsx @@ -7,7 +7,7 @@ import React, { type FC, type PropsWithChildren } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { useColumns } from './use_columns'; @@ -19,11 +19,11 @@ describe('Transform: Job List Columns', () => { const wrapper: FC<PropsWithChildren<unknown>> = ({ children }) => ( <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> ); - const { result, waitForNextUpdate } = renderHook(() => useColumns([], () => {}, 1, [], false), { + const { result } = renderHook(() => useColumns([], () => {}, 1, [], false), { wrapper, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const columns: ReturnType<typeof useColumns>['columns'] = result.current.columns; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index e76fac3cb6c0..89faeb25d889 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -406,9 +406,6 @@ "cellActions.extraActionsAriaLabel": "Actions supplémentaires", "cellActions.showMoreActionsLabel": "Plus d'actions", "cellActions.youAreInADialogContainingOptionsScreenReaderOnly": "Vous êtes dans une boîte de dialogue contenant des options pour le champ {fieldName}. Appuyez sur Tab pour naviguer entre les options. Appuyez sur Échap pour quitter.", - "charts.advancedSettings.visualization.colorMappingText": "Mappe des valeurs à des couleurs spécifiques dans les graphiques avec la palette <strong>Compatibilité</strong>.", - "charts.advancedSettings.visualization.colorMappingTextDeprecation": "Ce paramètre est déclassé et ne sera plus compatible avec les futures versions.", - "charts.advancedSettings.visualization.colorMappingTitle": "Mapping des couleurs", "charts.advancedSettings.visualization.useLegacyTimeAxis.deprecation": "Ce paramètre est déclassé et ne sera plus compatible avec les futures versions.", "charts.advancedSettings.visualization.useLegacyTimeAxis.description": "Active l'axe de temps hérité pour les graphiques dans Lens, Discover, Visualize et TSVB", "charts.advancedSettings.visualization.useLegacyTimeAxis.name": "Axe de temps hérité pour les graphiques", @@ -11530,7 +11527,6 @@ "xpack.apm.profiling.callout.dismiss": "Rejeter", "xpack.apm.profiling.callout.learnMore": "En savoir plus", "xpack.apm.profiling.callout.title": "Affichage des informations de profilage de l'hôte ou des hôtes exécutant des services {serviceName}", - "xpack.apm.profiling.flamegraph.filteredLabel": "Affichage des informations de profilage du ou des hôtes des services", "xpack.apm.profiling.flamegraph.link": "Accéder au flame-graph d'Universal Profiling", "xpack.apm.profiling.flamegraph.noDataFound": "Aucune donnée trouvée", "xpack.apm.profiling.tabs.flamegraph": "Flame-graph", @@ -12501,7 +12497,6 @@ "xpack.banners.settings.textContent.title": "Texte de la bannière", "xpack.canvas.addCanvasElementTrigger.description": "Une nouvelle action apparaît dans le menu du panneau d'ajout Canvas", "xpack.canvas.addCanvasElementTrigger.title": "Menu Ajouter un panneau", - "xpack.canvas.appDescription": "Vos données méritent une présentation irréprochable.", "xpack.canvas.argAddPopover.addAriaLabel": "Ajouter un argument", "xpack.canvas.argFormAdvancedFailure.applyButtonLabel": "Appliquer", "xpack.canvas.argFormAdvancedFailure.resetButtonLabel": "Réinitialiser", @@ -12690,7 +12685,6 @@ "xpack.canvas.expressionTypes.argTypes.seriesStyle.styleLabel": "Style", "xpack.canvas.expressionTypes.argTypes.seriesStyleLabel": "Définir le style d'une série nommée sélectionnée", "xpack.canvas.expressionTypes.argTypes.seriesStyleTitle": "Style de la série", - "xpack.canvas.featureCatalogue.canvasSubtitle": "Concevez des présentations irréprochables.", "xpack.canvas.features.reporting.pdf": "Générer des rapports PDF", "xpack.canvas.features.reporting.pdfFeatureName": "Reporting", "xpack.canvas.formatMsg.toaster.errorStatusMessage": "Erreur {errStatus} {errStatusText} : {errMessage}.", @@ -15110,15 +15104,9 @@ "xpack.csp.grouping.nullGroupTooltip.groupingTitle": "regrouper par", "xpack.csp.integrationDashboard.noFindings.promptTitle": "Statut d'évaluation des résultats", "xpack.csp.integrationDashboard.noFindingsPrompt.promptDescription": "En attente de la collecte et de l'indexation des données. Si ce processus prend plus de temps que prévu, veuillez contacter notre support technique", - "xpack.csp.kspmIntegration.aksOption.benchmarkTitle": "CIS AKS", - "xpack.csp.kspmIntegration.aksOption.nameTitle": "AKS", - "xpack.csp.kspmIntegration.aksOption.tooltipContent": "Azure Kubernetes Service - Bientôt disponible", "xpack.csp.kspmIntegration.eksOption.benchmarkTitle": "CIS EKS", "xpack.csp.kspmIntegration.eksOption.nameTitle": "EKS", "xpack.csp.kspmIntegration.eksOption.tooltipContent": "EKS (Elastic Kubernetes Service)", - "xpack.csp.kspmIntegration.gkeOption.benchmarkTitle": "CIS GKE", - "xpack.csp.kspmIntegration.gkeOption.nameTitle": "GKE", - "xpack.csp.kspmIntegration.gkeOption.tooltipContent": "Google Kubernetes Engine - Bientôt disponible", "xpack.csp.kspmIntegration.integration.nameTitle": "Gestion du niveau de sécurité Kubernetes", "xpack.csp.kspmIntegration.integration.shortNameTitle": "KSPM", "xpack.csp.kspmIntegration.vanillaOption.benchmarkTitle": "CIS Kubernetes", @@ -15436,7 +15424,6 @@ "xpack.datasetQuality.types.label": "Types", "xpack.dataUsage.charts.ingestedMax": "Données ingérées", "xpack.dataUsage.charts.retainedMax": "Données conservées dans le stockage", - "xpack.dataUsage.metrics.filter.clearAll": "Tout effacer", "xpack.dataUsage.metrics.filter.dataStreams": "Flux de données", "xpack.dataUsage.metrics.filter.emptyMessage": "Aucun {filterName} disponible", "xpack.dataUsage.metrics.filter.metricTypes": "Types d'indicateurs", @@ -18315,10 +18302,7 @@ "xpack.enterpriseSearch.createConnector..title": "Créer un connecteur", "xpack.enterpriseSearch.createConnector.badgeType.ElasticManaged": "Géré par Elastic", "xpack.enterpriseSearch.createConnector.badgeType.selfManaged": "Autogéré", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.BetaBadgeLabel": "Bêta", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.OnlySelfManagedBadgeLabel": "Autogéré", "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.placeholder.text": "Choisir une source de données", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.thechPreviewBadgeLabel": "Préversion technique", "xpack.enterpriseSearch.createConnector.configurationStep.configurationLabel": "Configuration", "xpack.enterpriseSearch.createConnector.configurationStep.h4.finishUpLabel": "Terminer", "xpack.enterpriseSearch.createConnector.configurationStep.p.description": "Vous pouvez synchroniser manuellement vos données, planifier une synchronisation récurrente ou gérer vos domaines.", @@ -18593,7 +18577,6 @@ "xpack.enterpriseSearch.overview.gettingStarted.testConnection.description": "Envoyez une requête de test pour confirmer que votre client de langage et votre instance Elasticsearch sont opérationnels.", "xpack.enterpriseSearch.overview.gettingStarted.testConnection.title": "Tester votre connexion", "xpack.enterpriseSearch.overview.navTitle": "Aperçu", - "xpack.enterpriseSearch.overview.setupCta.description": "Ajoutez des fonctions de recherche à votre application ou à votre organisation interne avec Elastic App Search et Workplace Search. Regardez la vidéo pour savoir ce qu'il est possible de faire lorsque la recherche est facilitée.", "xpack.enterpriseSearch.pageTemplate.endpointsButtonLabel": "Points de terminaison et clés d'API", "xpack.enterpriseSearch.passwordLabel": "Mot de passe", "xpack.enterpriseSearch.pipeline.title": "Transformer et enrichir vos données", @@ -18916,7 +18899,6 @@ "xpack.enterpriseSearch.searchNav.otherTools": "Autres outils", "xpack.enterpriseSearch.searchNav.relevance": "Pertinence", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "Intelligence artificielle de recherche", - "xpack.enterpriseSearch.searchProvider.type.name": "Recherche", "xpack.enterpriseSearch.searchProvider.webCrawler.name": "Robot d'indexation d'Elastic", "xpack.enterpriseSearch.selectConnector.badgeOnClick.ariaLabel": "Cliquer pour ouvrir la fenêtre contextuelle d'explication du connecteur", "xpack.enterpriseSearch.selectConnector.connectorClientBadgeLabel": "Autogéré", @@ -20502,7 +20484,6 @@ "xpack.fleet.agentPolicyForm.newAgentPolicyFieldLabel": "Nouveau nom de la stratégie d'agent", "xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText": "La sortie {outputType} pour l'intégration des agents n'est pas prise en charge pour Fleet Server, Synthetics ou APM.", "xpack.fleet.agentPolicyForm.outputOptionDisableOutputTypeText": "La sortie {outputType} pour l'intégration des agents n'est pas prise en charge pour Fleet Server, Synthetics ou APM.", - "xpack.fleet.agentPolicyForm.spaceDescription": "Sélectionnez un ou plusieurs espaces pour cette politique ou créez un nouvel espace. {link}", "xpack.fleet.agentPolicyForm.spaceFieldLabel": "Espaces", "xpack.fleet.agentPolicyForm.systemMonitoringText": "Collecte des logs et des mesures du système", "xpack.fleet.agentPolicyForm.systemMonitoringTooltipText": "Cela ajoutera également une intégration {system} pour collecter les logs et les indicateurs du système.", @@ -26183,8 +26164,8 @@ "xpack.inventory.entitiesGrid.euiDataGrid.alertsTooltip": "Le nombre d'alertes actives", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameLabel": "Nom de l'entité", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameTooltip": "Nom de l'entité (entity.displayName)", - "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft": "Affichage de {currentItems} sur {total} {boldEntites}", - "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft.entites": "Entités", + "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft": "Affichage de {currentItems} sur {total} {boldEntities}", + "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft.entities": "Entités", "xpack.inventory.entitiesGrid.euiDataGrid.inventoryEntitiesGridLabel": "Grille des entités d'inventaire", "xpack.inventory.entitiesGrid.euiDataGrid.lastSeen": "{date} à {time}", "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenLabel": "Vu en dernier", @@ -26373,7 +26354,6 @@ "xpack.lens.app.createVisualizationLabel": "ES|QL", "xpack.lens.app.docLoadingError": "Erreur lors du chargement du document enregistré", "xpack.lens.app.editLensEmbeddableLabel": "Modifier la visualisation", - "xpack.lens.app.editVisualizationLabel": "Modifier la visualisation {lang}", "xpack.lens.app.exploreDataInDiscover": "Explorer dans Discover", "xpack.lens.app.exploreDataInDiscoverDrilldown": "Ouvrir dans Discover", "xpack.lens.app.exploreDataInDiscoverDrilldown.newTabConfig": "Ouvrir dans un nouvel onglet", @@ -26531,14 +26511,9 @@ "xpack.lens.editorFrame.suggestionPanelTitle": "Suggestions", "xpack.lens.editorFrame.tooManyDimensionsSingularWarningLabel": "Veuillez retirer {dimensionsTooMany, plural, one {une dimension} other {{dimensionsTooMany} dimensions}}", "xpack.lens.editorFrame.workspaceLabel": "Espace de travail", - "xpack.lens.embeddable.failure": "Impossible d'afficher la visualisation", - "xpack.lens.embeddable.featureBadge.iconDescription": "{count} {count, plural, one {modificateur} other {modificateurs}} de visualisation", - "xpack.lens.embeddable.fixErrors": "Effectuez des modifications dans l'éditeur Lens pour corriger l'erreur", - "xpack.lens.embeddable.legacyURLConflict.shortMessage": "Vous avez rencontré un conflit d’URL.", - "xpack.lens.embeddable.missingTimeRangeParam.longMessage": "La propriété timeRange est requise pour cette configuration.", - "xpack.lens.embeddable.missingTimeRangeParam.shortMessage": "Propriété timeRange manquante", - "xpack.lens.embeddable.moreErrors": "Effectuez des modifications dans l'éditeur Lens pour afficher plus d'erreurs", - "xpack.lens.embeddableDisplayName": "Lens", + "xpack.lens.featureBadge.iconDescription": "{count} {count, plural, one {modificateur} other {modificateurs}} de visualisation", + "xpack.lens.fixErrors": "Effectuez des modifications dans l'éditeur Lens pour corriger l'erreur", + "xpack.lens.moreErrors": "Effectuez des modifications dans l'éditeur Lens pour afficher plus d'erreurs", "xpack.lens.endValue.nearest": "La plus proche", "xpack.lens.endValue.none": "Masquer", "xpack.lens.endValue.zero": "Zéro", @@ -27048,7 +27023,6 @@ "xpack.lens.legacyMetric.titlePositions.bottom": "Bas", "xpack.lens.legacyMetric.titlePositions.top": "Haut", "xpack.lens.legacyUrlConflict.objectNoun": "Visualisation Lens", - "xpack.lens.lensSavedObjectLabel": "Visualisation Lens", "xpack.lens.lineCurve.smooth": "Lisser", "xpack.lens.lineCurve.step": "Étape", "xpack.lens.lineCurve.straight": "Droit", @@ -35469,10 +35443,6 @@ "xpack.remoteClusters.updateRemoteCluster.noRemoteClusterErrorMessage": "Aucun cluster distant ne porte ce nom.", "xpack.remoteClusters.updateRemoteCluster.unknownRemoteClusterErrorMessage": "Impossible de modifier le cluster, aucune réponse renvoyée d'ES.", "xpack.reporting.breadcrumb": "Reporting", - "xpack.reporting.deprecations.csvPanelActionDownload.manualStep1": "Supprimez \"{enablePanelActionDownload}\" de `kibana.yml` ou modifiez le paramètre sur `false`.", - "xpack.reporting.deprecations.csvPanelActionDownload.manualStep2": "Utilisez le panneau de remplacement pour générer des rapports CSV à partir des panneaux de recherche enregistrés dans l'application Tableau de bord.", - "xpack.reporting.deprecations.csvPanelActionDownload.message": "Le paramètre \"{enablePanelActionDownload}\" est déclassé.", - "xpack.reporting.deprecations.csvPanelActionDownload.title": "Le paramètre permettant d'activer le téléchargement CSV à partir des panneaux de recherche enregistrés dans les tableaux de bord est déclassé.", "xpack.reporting.deprecations.migrateIndexIlmPolicy.manualStepOneMessage": "Mettez à jour tous les index de reporting de façon à ce qu'ils utilisent la politique \"{reportingIlmPolicy}\" à l'aide de l'API de paramètres des index.", "xpack.reporting.deprecations.migrateIndexIlmPolicyActionMessage": "Les nouveaux index de reporting seront gérés par la politique ILM provisionnée \"{reportingIlmPolicy}\". Vous devez modifier cette politique pour gérer le cycle de vie des rapports. Cette modification cible le modèle d'indexation du système caché \"{indexPattern}\".", "xpack.reporting.deprecations.migrateIndexIlmPolicyActionTitle": "Des index de reporting gérés par une politique ILM personnalisée ont été détectés.", @@ -36035,15 +36005,6 @@ "xpack.searchInferenceEndpoints.actions.endpointAddedSuccess": "Point de terminaison ajouté", "xpack.searchInferenceEndpoints.actions.endpointAddedSuccessDescription": "Le point de terminaison d'inférence \"{endpointId}\" a été ajouté.", "xpack.searchInferenceEndpoints.actions.trainedModelsStatGatherFailed": "Échec de la récupération des statistiques du modèle entraîné", - "xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription": "Les points de terminaison d'inférence vous permettent d'effectuer des tâches d'inférence à l'aide de modèles NLP fournis par des services tiers ou de modèles intégrés d'Elastic comme ELSER et E5. Configurez des tâches telles que l'incorporation de texte, les complétions, le reclassement et bien plus encore à l'aide de la fonction Créer une API d'inférence.", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description": "E5 est un modèle NLP tiers qui vous permet de réaliser des recherches sémantiques multilingues en utilisant des représentations vectorielles denses.", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title": "E5 multilingue", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription": "ELSER est le modèle NLP vectoriel creux d'Elastic pour la recherche sémantique en anglais.", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle": "ELSER", - "xpack.searchInferenceEndpoints.addEmptyPrompt.learnHowToCreateInferenceEndpoints": "Découvrez comment créer des points de terminaison d'inférence", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithE5": "Recherche sémantique avec E5 multilingue", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithElser": "Recherche sémantique avec ELSER", - "xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel": "En savoir plus sur les modèles NLP intégrés :", "xpack.searchInferenceEndpoints.allInferenceEndpoints.description": "Les points de terminaison d'inférence rationalisent le déploiement et la gestion des modèles d'apprentissage automatique dans Elasticsearch. Configurez et gérez des tâches NLP à l'aide de points de terminaison uniques afin de créer une recherche basée sur l'IA.", "xpack.searchInferenceEndpoints.apiDocumentationLink": "Documentation sur les API", "xpack.searchInferenceEndpoints.cancel": "Annuler", @@ -36549,7 +36510,6 @@ "xpack.security.management.editRole.featureTable.cannotCustomizeSubFeaturesTooltip": "La personnalisation des privilèges de sous-fonctionnalité est une fonctionnalité soumise à abonnement.", "xpack.security.management.editRole.featureTable.customizeSubFeaturePrivilegesSwitchLabel": "Personnaliser les privilèges des sous-fonctionnalités", "xpack.security.management.editRole.featureTable.featureAccordionSwitchLabel": "{grantedCount}/{featureCount} {featureCount, plural, one {fonctionnalité accordée} other {fonctionnalités accordées}}", - "xpack.security.management.editRole.featureTable.featureVisibilityTitle": "Personnaliser les privilèges des fonctionnalités", "xpack.security.management.editRole.featureTable.managementCategoryHelpText": "Des autorisations de gestion de suite supplémentaires sont disponibles en dehors de ce menu, dans les privilèges d'index et de cluster.", "xpack.security.management.editRole.featureTable.privilegeCustomizationTooltip": "La fonctionnalité possède des privilèges de sous-fonctionnalités personnalisés. Développez cette ligne pour en savoir plus.", "xpack.security.management.editRole.indexPrivilegeForm.clustersFormRowLabel": "Clusters distants", @@ -36609,18 +36569,10 @@ "xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaAdminTitle": "kibana_admin", "xpack.security.management.editRole.spacePrivilegeForm.basePrivilegeControlLegend": "Privilèges pour toutes les fonctionnalités", "xpack.security.management.editRole.spacePrivilegeForm.cancelButton": "Annuler", - "xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivilegeDescription": "Augmentez les niveaux de privilèges sur la base de chaque fonctionnalité. Certaines fonctionnalités peuvent être masquées par l'espace ou concernées par un privilège d'espace global.", - "xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivileges": "Personnaliser par fonctionnalité", - "xpack.security.management.editRole.spacePrivilegeForm.featurePrivilegeSummaryDescription": "Certaines fonctionnalités peuvent être masquées par l'espace ou concernées par un privilège d'espace global.", - "xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeNotice": "Ces privilèges s'appliqueront à tous les espaces, actuels et futurs.", - "xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeWarning": "La création d'un privilège global peut impacter vos autres privilèges liés aux espaces.", - "xpack.security.management.editRole.spacePrivilegeForm.modalHeadline": "Ce rôle aura accès aux espaces suivants", - "xpack.security.management.editRole.spacePrivilegeForm.modalTitle": "Affecter un rôle à l'espace", "xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormHelpText": "Affectez le niveau de privilège que vous souhaitez accorder à toutes les fonctionnalités présentes et futures de cet espace.", "xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormLabel": "Privilèges pour toutes les fonctionnalités", "xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormHelpText": "Sélectionnez un ou plusieurs espaces Kibana auxquels vous souhaitez affecter des privilèges.", "xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormLabel": "Espaces", - "xpack.security.management.editRole.spacePrivilegeForm.summaryOfFeaturePrivileges": "Résumé des privilèges des fonctionnalités", "xpack.security.management.editRole.spacePrivilegeForm.supersededWarning": "Les privilèges déclarés sont moins flexibles que les privilèges globaux configurés. Affichez le résumé des privilèges pour voir les privilèges effectifs.", "xpack.security.management.editRole.spacePrivilegeForm.supersededWarningTitle": "Remplacé par les privilèges globaux", "xpack.security.management.editRole.spacePrivilegeMatrix.globalSpaceName": "Tous les espaces", @@ -36753,9 +36705,7 @@ "xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowHelpText": "Si aucun champ n'est accordé, les utilisateurs affectés à ce rôle ne pourront voir aucune donnée pour cet index.", "xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowLabel": "Champs accordés", "xpack.security.management.editRoles.indexPrivilegeForm.grantFieldPrivilegesLabel": "Accorder l'accès aux champs spécifiques", - "xpack.security.management.editRolespacePrivilegeForm.createGlobalPrivilegeButton": "Créer un privilège global", "xpack.security.management.editRolespacePrivilegeForm.createPrivilegeButton": "Ajouter un privilège Kibana", - "xpack.security.management.editRolespacePrivilegeForm.updateGlobalPrivilegeButton": "Mettre à jour le privilège global", "xpack.security.management.editRolespacePrivilegeForm.updatePrivilegeButton": "Mettre à jour le privilège d'espace", "xpack.security.management.enabledBadge": "Activé", "xpack.security.management.readonlyBadge.text": "Lecture seule", @@ -38091,7 +38041,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoAriaLabel": "Ouvrir une fenêtre contextuelle d'aide", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipContent": "Consultez {createEsqlRuleTypeLink} pour commencer à utiliser les règles ES|QL.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipLink": "documentation", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryFieldRequiredError": "Une requête ES|QL est requise.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryLabel": "Requête ES|QL", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldAnomalyThresholdLabel": "Seuil de score d'anomalie", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldMachineLearningJobIdLabel": "Tâche de Machine Learning", @@ -44868,7 +44817,6 @@ "xpack.spaces.management.spaceDetails.footerActions.updateSpace": "Appliquer les modifications", "xpack.spaces.management.spaceDetails.keepEditingButton": "Enregistrer avant de quitter", "xpack.spaces.management.spaceDetails.leavePageButton": "Quitter", - "xpack.spaces.management.spaceDetails.privilegeForm.heading": "Définissez les privilèges qu'un rôle donné devrait avoir dans cet espace.", "xpack.spaces.management.spaceDetails.roles.assign": "Attribuer de nouveaux rôles", "xpack.spaces.management.spaceDetails.roles.assign.privilegeConflictMsg.description": "La mise à jour en groupe des paramètres ici remplacera les paramètres individuels actuels.", "xpack.spaces.management.spaceDetails.roles.assign.privilegeConflictMsg.title": "Les privilèges s'appliqueront uniquement à cet espace.", @@ -44919,7 +44867,6 @@ "xpack.spaces.management.spaceIdentifier.kibanaURLForSpaceIdentifierDescription": "Vous ne pouvez pas modifier l'identifiant d'URL après sa création.", "xpack.spaces.management.spaceIdentifier.urlIdentifierTitle": "Identifiant d'URL", "xpack.spaces.management.spacesGridPage.actionsColumnName": "Actions", - "xpack.spaces.management.spacesGridPage.allFeaturesEnabled": "Toutes les fonctionnalités", "xpack.spaces.management.spacesGridPage.createSpaceButtonLabel": "Créer l'espace", "xpack.spaces.management.spacesGridPage.currentSpaceMarkerText": "actuel", "xpack.spaces.management.spacesGridPage.deleteActionDescription": "Supprimer {spaceName}", @@ -44929,13 +44876,10 @@ "xpack.spaces.management.spacesGridPage.editSpaceActionDescription": "Modifier {spaceName}.", "xpack.spaces.management.spacesGridPage.editSpaceActionName": "Modifier", "xpack.spaces.management.spacesGridPage.errorTitle": "Erreur lors du chargement des espaces", - "xpack.spaces.management.spacesGridPage.featuresColumnName": "Fonctionnalités visibles", "xpack.spaces.management.spacesGridPage.identifierColumnName": "Identificateur", "xpack.spaces.management.spacesGridPage.loadingTitle": "chargement…", - "xpack.spaces.management.spacesGridPage.noFeaturesEnabled": "Aucune fonctionnalité visible", "xpack.spaces.management.spacesGridPage.searchPlaceholder": "Recherche", "xpack.spaces.management.spacesGridPage.solutionColumnName": "Afficher la solution", - "xpack.spaces.management.spacesGridPage.someFeaturesEnabled": "{enabledFeatureCount}/{totalFeatureCount}", "xpack.spaces.management.spacesGridPage.spaceColumnName": "Espace", "xpack.spaces.management.spacesGridPage.spacesTitle": "Espaces", "xpack.spaces.management.spacesGridPage.switchSpaceActionDescription": "Basculer vers {spaceName}", @@ -47505,7 +47449,6 @@ "xpack.transform.groupBy.popoverForm.fieldLabel": "Champ", "xpack.transform.groupBy.popoverForm.intervalError": "Intervalle non valide.", "xpack.transform.groupBy.popoverForm.intervalLabel": "Intervalle", - "xpack.transform.groupBy.popoverForm.intervalPercents": "Entrer une liste de centiles séparés par des virgules", "xpack.transform.groupBy.popoverForm.invalidSizeErrorMessage": "Entrer un nombre positif valide", "xpack.transform.groupBy.popoverForm.missingBucketCheckboxHelpText": "Cochez cette case pour inclure les documents sans valeur.", "xpack.transform.groupby.popoverForm.missingBucketCheckboxLabel": "Inclure les compartiments manquants", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2be0cd2fb4c4..2c6097e91134 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -406,9 +406,6 @@ "cellActions.extraActionsAriaLabel": "追加のアクション", "cellActions.showMoreActionsLabel": "さらにアクションを表示", "cellActions.youAreInADialogContainingOptionsScreenReaderOnly": "フィールド {fieldName} のオプションを含む、ダイアログを表示しています。Tab を押すと、オプションを操作します。Escapeを押すと、終了します。", - "charts.advancedSettings.visualization.colorMappingText": "<strong>互換性</strong>パレットを使用して、グラフで値を特定の色にマッピングします。", - "charts.advancedSettings.visualization.colorMappingTextDeprecation": "この設定はサポートが終了し、将来のバージョンではサポートされません。", - "charts.advancedSettings.visualization.colorMappingTitle": "カラーマッピング", "charts.advancedSettings.visualization.useLegacyTimeAxis.deprecation": "この設定はサポートが終了し、将来のバージョンではサポートされません。", "charts.advancedSettings.visualization.useLegacyTimeAxis.description": "Lens、Discover、Visualize、およびTSVBでグラフのレガシー時間軸を有効にします", "charts.advancedSettings.visualization.useLegacyTimeAxis.name": "レガシーグラフ時間軸", @@ -11513,7 +11510,6 @@ "xpack.apm.profiling.callout.dismiss": "閉じる", "xpack.apm.profiling.callout.learnMore": "詳細", "xpack.apm.profiling.callout.title": "{serviceName}サービスを実行しているホストからプロファイリングインサイトを表示しています", - "xpack.apm.profiling.flamegraph.filteredLabel": "サービスのホストからプロファイリングインサイトを表示しています", "xpack.apm.profiling.flamegraph.link": "ユニバーサルプロファイリングFlamegraphに移動", "xpack.apm.profiling.flamegraph.noDataFound": "データが見つかりません", "xpack.apm.profiling.tabs.flamegraph": "Flamegraph", @@ -12485,7 +12481,6 @@ "xpack.banners.settings.textContent.title": "バナーテキスト", "xpack.canvas.addCanvasElementTrigger.description": "新しいアクションは、キャンバスのパネルの追加メニューに表示されます", "xpack.canvas.addCanvasElementTrigger.title": "パネルの追加メニュー", - "xpack.canvas.appDescription": "データを完璧に美しく表現します。", "xpack.canvas.argAddPopover.addAriaLabel": "引数を追加", "xpack.canvas.argFormAdvancedFailure.applyButtonLabel": "適用", "xpack.canvas.argFormAdvancedFailure.resetButtonLabel": "リセット", @@ -12674,7 +12669,6 @@ "xpack.canvas.expressionTypes.argTypes.seriesStyle.styleLabel": "スタイル", "xpack.canvas.expressionTypes.argTypes.seriesStyleLabel": "選択された名前付きの数列のスタイルを設定", "xpack.canvas.expressionTypes.argTypes.seriesStyleTitle": "数列スタイル", - "xpack.canvas.featureCatalogue.canvasSubtitle": "詳細まで正確な表示を設計します。", "xpack.canvas.features.reporting.pdf": "PDFレポートを生成", "xpack.canvas.features.reporting.pdfFeatureName": "レポート", "xpack.canvas.formatMsg.toaster.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", @@ -15089,15 +15083,9 @@ "xpack.csp.grouping.nullGroupTooltip.groupingTitle": "グループ分けの条件", "xpack.csp.integrationDashboard.noFindings.promptTitle": "調査結果評価ステータス", "xpack.csp.integrationDashboard.noFindingsPrompt.promptDescription": "データの収集とインデックス作成を待機しています。このプロセスに想定よりも時間がかかる場合は、サポートまでお問い合わせください", - "xpack.csp.kspmIntegration.aksOption.benchmarkTitle": "CIS AKS", - "xpack.csp.kspmIntegration.aksOption.nameTitle": "AKS", - "xpack.csp.kspmIntegration.aksOption.tooltipContent": "Azure Kubernetes Service - まもなくリリース", "xpack.csp.kspmIntegration.eksOption.benchmarkTitle": "CIS EKS", "xpack.csp.kspmIntegration.eksOption.nameTitle": "EKS", "xpack.csp.kspmIntegration.eksOption.tooltipContent": "Elastic Kubernetes Service", - "xpack.csp.kspmIntegration.gkeOption.benchmarkTitle": "CIS GKE", - "xpack.csp.kspmIntegration.gkeOption.nameTitle": "GKE", - "xpack.csp.kspmIntegration.gkeOption.tooltipContent": "Google Kubernetes Engine - まもなくリリース", "xpack.csp.kspmIntegration.integration.nameTitle": "Kubernetesセキュリティ態勢管理", "xpack.csp.kspmIntegration.integration.shortNameTitle": "KSPM", "xpack.csp.kspmIntegration.vanillaOption.benchmarkTitle": "CIS Kubernetes", @@ -15415,7 +15403,6 @@ "xpack.datasetQuality.types.label": "タイプ", "xpack.dataUsage.charts.ingestedMax": "インジェストされたデータ", "xpack.dataUsage.charts.retainedMax": "ストレージに保持されたデータ", - "xpack.dataUsage.metrics.filter.clearAll": "すべて消去", "xpack.dataUsage.metrics.filter.dataStreams": "データストリーム", "xpack.dataUsage.metrics.filter.emptyMessage": "{filterName}がありません", "xpack.dataUsage.metrics.filter.metricTypes": "メトリックタイプ", @@ -18289,10 +18276,7 @@ "xpack.enterpriseSearch.createConnector..title": "コネクターを作成する", "xpack.enterpriseSearch.createConnector.badgeType.ElasticManaged": "Elasticマネージド", "xpack.enterpriseSearch.createConnector.badgeType.selfManaged": "自己管理", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.BetaBadgeLabel": "ベータ", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.OnlySelfManagedBadgeLabel": "自己管理", "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.placeholder.text": "データソースを選択", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.thechPreviewBadgeLabel": "テクニカルプレビュー", "xpack.enterpriseSearch.createConnector.configurationStep.configurationLabel": "構成", "xpack.enterpriseSearch.createConnector.configurationStep.h4.finishUpLabel": "終了", "xpack.enterpriseSearch.createConnector.configurationStep.p.description": "データを手動で同期したり、繰り返し同期をスケジュールしたり、ドメインを管理したりできます。", @@ -18565,7 +18549,6 @@ "xpack.enterpriseSearch.overview.gettingStarted.testConnection.description": "テストリクエストを送信して、言語クライアントとElasticsearchインスタンスが起動し、実行中であることを確認してください。", "xpack.enterpriseSearch.overview.gettingStarted.testConnection.title": "接続をテスト", "xpack.enterpriseSearch.overview.navTitle": "概要", - "xpack.enterpriseSearch.overview.setupCta.description": "Elastic App Search および Workplace Search を使用して、アプリまたは社内組織に検索を追加できます。検索が簡単になるとどのような利点があるのかについては、動画をご覧ください。", "xpack.enterpriseSearch.pageTemplate.endpointsButtonLabel": "エンドポイントとAPIキー", "xpack.enterpriseSearch.passwordLabel": "パスワード", "xpack.enterpriseSearch.pipeline.title": "データの変換とエンリッチ", @@ -18886,7 +18869,6 @@ "xpack.enterpriseSearch.searchNav.mngt": "スタック管理", "xpack.enterpriseSearch.searchNav.otherTools": "その他のツール", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "検索AI", - "xpack.enterpriseSearch.searchProvider.type.name": "検索", "xpack.enterpriseSearch.searchProvider.webCrawler.name": "Elastic Webクローラー", "xpack.enterpriseSearch.selectConnector.badgeOnClick.ariaLabel": "クリックすると、コネクター説明ポップオーバーが開きます", "xpack.enterpriseSearch.selectConnector.connectorClientBadgeLabel": "セルフマネージド", @@ -20471,7 +20453,6 @@ "xpack.fleet.agentPolicyForm.newAgentPolicyFieldLabel": "新しいエージェントポリシー名", "xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText": "Fleet Server、Synthetics、APMではエージェント統合の{outputType}出力はサポートされていません。", "xpack.fleet.agentPolicyForm.outputOptionDisableOutputTypeText": "Fleet Server、Synthetics、APMではエージェント統合の{outputType}出力はサポートされていません。", - "xpack.fleet.agentPolicyForm.spaceDescription": "このポリシーに1つ以上のスペースを選択するか、新しいスペースを作成します。{link}", "xpack.fleet.agentPolicyForm.spaceFieldLabel": "スペース", "xpack.fleet.agentPolicyForm.systemMonitoringText": "システムログとメトリックの収集", "xpack.fleet.agentPolicyForm.systemMonitoringTooltipText": "これにより、{system}統合も追加され、システムログとメトリックを収集します。", @@ -26155,8 +26136,8 @@ "xpack.inventory.entitiesGrid.euiDataGrid.alertsTooltip": "アクティブなアラートの件数", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameLabel": "エンティティ名", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameTooltip": "エンティティの名前(entity.displayName)", - "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft": "{currentItems}/{total}個の{boldEntites}を表示中", - "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft.entites": "エンティティ", + "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft": "{currentItems}/{total}個の{boldEntities}を表示中", + "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft.entities": "エンティティ", "xpack.inventory.entitiesGrid.euiDataGrid.inventoryEntitiesGridLabel": "インベントリエンティティグリッド", "xpack.inventory.entitiesGrid.euiDataGrid.lastSeen": "{date} @ {time}", "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenLabel": "前回の認識", @@ -26344,7 +26325,6 @@ "xpack.lens.app.createVisualizationLabel": "ES|QL", "xpack.lens.app.docLoadingError": "保存されたドキュメントの保存中にエラーが発生", "xpack.lens.app.editLensEmbeddableLabel": "ビジュアライゼーションを編集", - "xpack.lens.app.editVisualizationLabel": "{lang}ビジュアライゼーションを編集", "xpack.lens.app.exploreDataInDiscover": "Discoverで探索", "xpack.lens.app.exploreDataInDiscoverDrilldown": "Discoverで開く", "xpack.lens.app.exploreDataInDiscoverDrilldown.newTabConfig": "新しいタブで開く", @@ -26503,14 +26483,13 @@ "xpack.lens.editorFrame.suggestionPanelTitle": "提案", "xpack.lens.editorFrame.tooManyDimensionsSingularWarningLabel": "{dimensionsTooMany, plural, other {{dimensionsTooMany} ディメンション}}を削除してください", "xpack.lens.editorFrame.workspaceLabel": "ワークスペース", - "xpack.lens.embeddable.failure": "ビジュアライゼーションを表示できませんでした", - "xpack.lens.embeddable.featureBadge.iconDescription": "{count}個のビジュアライゼーション{count, plural, other {修飾子}}", - "xpack.lens.embeddable.fixErrors": "Lensエディターで編集し、エラーを修正", - "xpack.lens.embeddable.legacyURLConflict.shortMessage": "URLの競合が発生しました", - "xpack.lens.embeddable.missingTimeRangeParam.longMessage": "指定された構成にはtimeRangeプロパティが必須です", - "xpack.lens.embeddable.missingTimeRangeParam.shortMessage": "timeRangeプロパティがありません", - "xpack.lens.embeddable.moreErrors": "Lensエディターで編集すると、エラーの詳細が表示されます", - "xpack.lens.embeddableDisplayName": "Lens", + "xpack.lens.failure": "ビジュアライゼーションを表示できませんでした", + "xpack.lens.featureBadge.iconDescription": "{count}個のビジュアライゼーション{count, plural, other {修飾子}}", + "xpack.lens.fixErrors": "Lensエディターで編集し、エラーを修正", + "xpack.lens.legacyURLConflict.shortMessage": "URLの競合が発生しました", + "xpack.lens.missingTimeRangeParam.longMessage": "指定された構成にはtimeRangeプロパティが必須です", + "xpack.lens.missingTimeRangeParam.shortMessage": "timeRangeプロパティがありません", + "xpack.lens.moreErrors": "Lensエディターで編集すると、エラーの詳細が表示されます", "xpack.lens.endValue.nearest": "最も近い", "xpack.lens.endValue.none": "非表示", "xpack.lens.endValue.zero": "ゼロ", @@ -27019,7 +26998,6 @@ "xpack.lens.legacyMetric.titlePositions.bottom": "一番下", "xpack.lens.legacyMetric.titlePositions.top": "トップ", "xpack.lens.legacyUrlConflict.objectNoun": "Lensビジュアライゼーション", - "xpack.lens.lensSavedObjectLabel": "Lensビジュアライゼーション", "xpack.lens.lineCurve.smooth": "平滑化", "xpack.lens.lineCurve.step": "手順", "xpack.lens.lineCurve.straight": "直線", @@ -35438,10 +35416,6 @@ "xpack.remoteClusters.updateRemoteCluster.noRemoteClusterErrorMessage": "その名前のリモートクラスターはありません。", "xpack.remoteClusters.updateRemoteCluster.unknownRemoteClusterErrorMessage": "ES からレスポンスが返らず、クラスターを編集できません。", "xpack.reporting.breadcrumb": "レポート", - "xpack.reporting.deprecations.csvPanelActionDownload.manualStep1": "kibana.ymlから“{enablePanelActionDownload}”を削除するか、設定をfalseに変更します。", - "xpack.reporting.deprecations.csvPanelActionDownload.manualStep2": "置換パネルアクションを使用して、ダッシュボードアプリケーションに保存された検索パネルからCSV形式のレポートを生成します。", - "xpack.reporting.deprecations.csvPanelActionDownload.message": "\"{enablePanelActionDownload}\"設定は廃止予定です。", - "xpack.reporting.deprecations.csvPanelActionDownload.title": "ダッシュボードの保存された検索パネルからCSVダウンロードを有効にする設定は廃止予定です。", "xpack.reporting.deprecations.migrateIndexIlmPolicy.manualStepOneMessage": "インデックス設定APIを使用して、すべてのレポートインデックスを更新し、\"{reportingIlmPolicy}\"ポリシーを使用します。", "xpack.reporting.deprecations.migrateIndexIlmPolicyActionMessage": "新しいレポートインデックスは\"{reportingIlmPolicy}\"がプロビジョニングしたILMポリシーによって管理されます。レポートライフサイクルを管理するには、このポリシーを編集する必要があります。この変更は、非表示のシステムインデックスパターン\"{indexPattern}\"を対象としています。", "xpack.reporting.deprecations.migrateIndexIlmPolicyActionTitle": "カスタムILMポリシーで管理されたレポートインデックスが見つかりました。", @@ -36004,15 +35978,6 @@ "xpack.searchInferenceEndpoints.actions.endpointAddedSuccess": "エンドポイントが追加されました", "xpack.searchInferenceEndpoints.actions.endpointAddedSuccessDescription": "インターフェースエンドポイント\"{endpointId}\"が追加されました。", "xpack.searchInferenceEndpoints.actions.trainedModelsStatGatherFailed": "学習済みモデル統計情報を取得できませんでした", - "xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription": "推論エンドポイントを使用すると、サードパーティサービスが提供するNLPモデルや、ELSERやE5などのElasticの組み込みモデルを使用して推論タスクを実行できます。Create Inference APIを使用して、テキスト埋め込み、入力、再ランク付けなどのタスクを設定します。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description": "E5は、密ベクトル表現を使用して、多言語のセマンティック検索を可能にするサードパーティNLPモデルです。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title": "E5 Multilingual", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription": "ELSERは、英語でのセマンティック検索向けにElasticの疎ベクトルNLPモデルです。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle": "ELSER", - "xpack.searchInferenceEndpoints.addEmptyPrompt.learnHowToCreateInferenceEndpoints": "推論エンドポイントを作成する方法", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithE5": "E5 Multilingualを使用したセマンティック検索", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithElser": "ELSERを使用したセマンティック検索", - "xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel": "組み込まれたNLPモデルの詳細:", "xpack.searchInferenceEndpoints.allInferenceEndpoints.description": "推論エンドポイントは、Elasticsearchにおける機械学習モデルのデプロイと管理を合理化します。独自のエンドポイントを使用してNLPタスクを設定および管理し、AIを活用した検索を構築します。", "xpack.searchInferenceEndpoints.apiDocumentationLink": "APIドキュメント", "xpack.searchInferenceEndpoints.cancel": "キャンセル", @@ -36517,7 +36482,6 @@ "xpack.security.management.editRole.featureTable.cannotCustomizeSubFeaturesTooltip": "サブ機能権限のカスタマイズはサブスクリプション機能です。", "xpack.security.management.editRole.featureTable.customizeSubFeaturePrivilegesSwitchLabel": "サブ機能権限をカスタマイズする", "xpack.security.management.editRole.featureTable.featureAccordionSwitchLabel": "{grantedCount} / {featureCount} {featureCount, plural, other {機能}}が付与されました", - "xpack.security.management.editRole.featureTable.featureVisibilityTitle": "機能権限をカスタマイズ", "xpack.security.management.editRole.featureTable.managementCategoryHelpText": "追加のスタック管理権限は、このメニューの外にあるインデックス権限とクラスター権限をご覧ください。", "xpack.security.management.editRole.featureTable.privilegeCustomizationTooltip": "機能でサブ機能の権限がカスタマイズされています。この行を展開すると詳細が表示されます。", "xpack.security.management.editRole.indexPrivilegeForm.clustersFormRowLabel": "リモートクラスター", @@ -36577,18 +36541,10 @@ "xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaAdminTitle": "kibana_admin", "xpack.security.management.editRole.spacePrivilegeForm.basePrivilegeControlLegend": "すべての機能の権限", "xpack.security.management.editRole.spacePrivilegeForm.cancelButton": "キャンセル", - "xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivilegeDescription": "機能ごとに権限のレベルを上げます。機能によってはスペースごとに非表示になっているか、グローバルスペース権限による影響を受けているものもあります。", - "xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivileges": "機能ごとにカスタマイズ", - "xpack.security.management.editRole.spacePrivilegeForm.featurePrivilegeSummaryDescription": "機能によってはスペースごとに非表示になっているか、グローバルスペース権限による影響を受けているものもあります。", - "xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeNotice": "これらの権限はすべての現在および未来のスペースに適用されます。", - "xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeWarning": "グローバル権限の作成は他のスペース権限に影響を与える可能性があります。", - "xpack.security.management.editRole.spacePrivilegeForm.modalHeadline": "このロールには、次のスペースへのアクセス権が付与されます", - "xpack.security.management.editRole.spacePrivilegeForm.modalTitle": "ロールをスペースに割り当て", "xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormHelpText": "このスペース全体の現在と将来のすべての機能に対して、付与する権限レベルを割り当てます。", "xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormLabel": "すべての機能の権限", "xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormHelpText": "権限を割り当てる1つ以上のKibanaスペースを選択します。", "xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormLabel": "スペース", - "xpack.security.management.editRole.spacePrivilegeForm.summaryOfFeaturePrivileges": "機能権限のサマリー", "xpack.security.management.editRole.spacePrivilegeForm.supersededWarning": "宣言された権限は、構成済みグローバル権限よりも許容度が低くなります。権限サマリーを表示すると有効な権限がわかります。", "xpack.security.management.editRole.spacePrivilegeForm.supersededWarningTitle": "グローバル権限に置き換え", "xpack.security.management.editRole.spacePrivilegeMatrix.globalSpaceName": "すべてのスペース", @@ -36721,9 +36677,7 @@ "xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowHelpText": "フィールドが提供されていない場合、このロールのユーザーはこのインデックスのデータを表示できません。", "xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowLabel": "許可されたフィールド", "xpack.security.management.editRoles.indexPrivilegeForm.grantFieldPrivilegesLabel": "特定のフィールドへのアクセスを許可", - "xpack.security.management.editRolespacePrivilegeForm.createGlobalPrivilegeButton": "グローバル権限を作成", "xpack.security.management.editRolespacePrivilegeForm.createPrivilegeButton": "Kibanaの権限を追加", - "xpack.security.management.editRolespacePrivilegeForm.updateGlobalPrivilegeButton": "グローバル特権を更新", "xpack.security.management.editRolespacePrivilegeForm.updatePrivilegeButton": "スペース権限を更新", "xpack.security.management.enabledBadge": "有効", "xpack.security.management.readonlyBadge.text": "読み取り専用", @@ -38058,7 +38012,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoAriaLabel": "ヘルプポップオーバーを開く", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipContent": "ES|QL ルールの使用を開始するには、{createEsqlRuleTypeLink}を確認してください。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipLink": "ドキュメンテーション", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryFieldRequiredError": "ES|QLクエリは必須です。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryLabel": "ES|QLクエリ", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldAnomalyThresholdLabel": "異常スコアしきい値", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldMachineLearningJobIdLabel": "機械学習ジョブ", @@ -44828,7 +44781,6 @@ "xpack.spaces.management.spaceDetails.footerActions.updateSpace": "変更を適用", "xpack.spaces.management.spaceDetails.keepEditingButton": "移動する前に保存", "xpack.spaces.management.spaceDetails.leavePageButton": "移動", - "xpack.spaces.management.spaceDetails.privilegeForm.heading": "このスペースで特定のロールに割り当てる権限を定義します。", "xpack.spaces.management.spaceDetails.roles.assign": "新しいロールを割り当て", "xpack.spaces.management.spaceDetails.roles.assign.privilegeConflictMsg.description": "ここで設定を一括更新すると、現在の個別の設定が上書きされます。", "xpack.spaces.management.spaceDetails.roles.assign.privilegeConflictMsg.title": "権限はこのスペースにのみ適用されます。", @@ -44879,7 +44831,6 @@ "xpack.spaces.management.spaceIdentifier.kibanaURLForSpaceIdentifierDescription": "作成した後はURL識別子を変更できません。", "xpack.spaces.management.spaceIdentifier.urlIdentifierTitle": "URL 識別子", "xpack.spaces.management.spacesGridPage.actionsColumnName": "アクション", - "xpack.spaces.management.spacesGridPage.allFeaturesEnabled": "すべての機能", "xpack.spaces.management.spacesGridPage.createSpaceButtonLabel": "スペースを作成", "xpack.spaces.management.spacesGridPage.currentSpaceMarkerText": "現在", "xpack.spaces.management.spacesGridPage.deleteActionDescription": "{spaceName}を削除", @@ -44889,13 +44840,10 @@ "xpack.spaces.management.spacesGridPage.editSpaceActionDescription": "{spaceName} を編集。", "xpack.spaces.management.spacesGridPage.editSpaceActionName": "編集", "xpack.spaces.management.spacesGridPage.errorTitle": "スペースの読み込みエラー", - "xpack.spaces.management.spacesGridPage.featuresColumnName": "表示される機能", "xpack.spaces.management.spacesGridPage.identifierColumnName": "識別子", "xpack.spaces.management.spacesGridPage.loadingTitle": "読み込み中…", - "xpack.spaces.management.spacesGridPage.noFeaturesEnabled": "表示されている機能がありません", "xpack.spaces.management.spacesGridPage.searchPlaceholder": "検索", "xpack.spaces.management.spacesGridPage.solutionColumnName": "ソリューションビュー", - "xpack.spaces.management.spacesGridPage.someFeaturesEnabled": "{enabledFeatureCount} / {totalFeatureCount}", "xpack.spaces.management.spacesGridPage.spaceColumnName": "スペース", "xpack.spaces.management.spacesGridPage.spacesTitle": "スペース", "xpack.spaces.management.spacesGridPage.switchSpaceActionDescription": "{spaceName}に切り替える", @@ -47465,7 +47413,6 @@ "xpack.transform.groupBy.popoverForm.fieldLabel": "フィールド", "xpack.transform.groupBy.popoverForm.intervalError": "無効な間隔。", "xpack.transform.groupBy.popoverForm.intervalLabel": "間隔", - "xpack.transform.groupBy.popoverForm.intervalPercents": "パーセンタイルをコンマで区切って列記します。", "xpack.transform.groupBy.popoverForm.invalidSizeErrorMessage": "有効な正の数値を入力してください", "xpack.transform.groupBy.popoverForm.missingBucketCheckboxHelpText": "選択すると、値がないドキュメントを含めます。", "xpack.transform.groupby.popoverForm.missingBucketCheckboxLabel": "不足しているバケットを含める", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index eed25bd0c184..1158ffd26dde 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -402,9 +402,6 @@ "cellActions.extraActionsAriaLabel": "附加操作", "cellActions.showMoreActionsLabel": "更多操作", "cellActions.youAreInADialogContainingOptionsScreenReaderOnly": "您在对话框中,其中包含 {fieldName} 字段的选项。按 tab 键导航选项。按 escape 退出。", - "charts.advancedSettings.visualization.colorMappingText": "使用<strong>兼容性</strong>调色板将值映射到图表中的特定颜色。", - "charts.advancedSettings.visualization.colorMappingTextDeprecation": "此设置已过时,在未来版本中将不受支持。", - "charts.advancedSettings.visualization.colorMappingTitle": "颜色映射", "charts.advancedSettings.visualization.useLegacyTimeAxis.deprecation": "此设置已过时,在未来版本中将不受支持。", "charts.advancedSettings.visualization.useLegacyTimeAxis.description": "在 Lens、Discover、Visualize 和 TSVB 中为图表启用旧版时间轴", "charts.advancedSettings.visualization.useLegacyTimeAxis.name": "旧版图表时间轴", @@ -11297,7 +11294,6 @@ "xpack.apm.profiling.callout.dismiss": "关闭", "xpack.apm.profiling.callout.learnMore": "了解详情", "xpack.apm.profiling.callout.title": "正在显示运行 {serviceName} 服务的主机中的分析洞见", - "xpack.apm.profiling.flamegraph.filteredLabel": "正在显示服务主机中的分析洞见", "xpack.apm.profiling.flamegraph.link": "前往 Universal Profiling 火焰图", "xpack.apm.profiling.flamegraph.noDataFound": "未找到任何数据", "xpack.apm.profiling.tabs.flamegraph": "火焰图", @@ -12250,7 +12246,6 @@ "xpack.banners.settings.textContent.title": "横幅广告文本", "xpack.canvas.addCanvasElementTrigger.description": "一项新操作将在 Canvas 添加面板菜单中显示出来", "xpack.canvas.addCanvasElementTrigger.title": "添加面板菜单", - "xpack.canvas.appDescription": "以最佳像素展示您的数据。", "xpack.canvas.argAddPopover.addAriaLabel": "添加参数", "xpack.canvas.argFormAdvancedFailure.applyButtonLabel": "应用", "xpack.canvas.argFormAdvancedFailure.resetButtonLabel": "重置", @@ -12435,7 +12430,6 @@ "xpack.canvas.expressionTypes.argTypes.seriesStyle.styleLabel": "样式", "xpack.canvas.expressionTypes.argTypes.seriesStyleLabel": "设置选定已命名序列的样式", "xpack.canvas.expressionTypes.argTypes.seriesStyleTitle": "序列样式", - "xpack.canvas.featureCatalogue.canvasSubtitle": "设计像素级完美的演示文稿。", "xpack.canvas.features.reporting.pdf": "生成 PDF 报告", "xpack.canvas.features.reporting.pdfFeatureName": "Reporting", "xpack.canvas.formatMsg.toaster.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", @@ -14784,15 +14778,9 @@ "xpack.csp.grouping.nullGroupTooltip.groupingTitle": "分组依据", "xpack.csp.integrationDashboard.noFindings.promptTitle": "结果评估状态", "xpack.csp.integrationDashboard.noFindingsPrompt.promptDescription": "正在等待要收集和索引的数据。如果此进程花费的时间长于预期,请联系我们的支持人员", - "xpack.csp.kspmIntegration.aksOption.benchmarkTitle": "CIS AKS", - "xpack.csp.kspmIntegration.aksOption.nameTitle": "AKS", - "xpack.csp.kspmIntegration.aksOption.tooltipContent": "Azure Kubernetes 服务 - 即将推出", "xpack.csp.kspmIntegration.eksOption.benchmarkTitle": "CIS EKS", "xpack.csp.kspmIntegration.eksOption.nameTitle": "EKS", "xpack.csp.kspmIntegration.eksOption.tooltipContent": "Elastic Kubernetes 服务", - "xpack.csp.kspmIntegration.gkeOption.benchmarkTitle": "CIS GKE", - "xpack.csp.kspmIntegration.gkeOption.nameTitle": "GKE", - "xpack.csp.kspmIntegration.gkeOption.tooltipContent": "Google Kubernetes Engine - 即将推出", "xpack.csp.kspmIntegration.integration.nameTitle": "Kubernetes 安全态势管理", "xpack.csp.kspmIntegration.integration.shortNameTitle": "KSPM", "xpack.csp.kspmIntegration.vanillaOption.benchmarkTitle": "CIS Kubernetes", @@ -15109,7 +15097,6 @@ "xpack.datasetQuality.types.label": "类型", "xpack.dataUsage.charts.ingestedMax": "已采集的数据", "xpack.dataUsage.charts.retainedMax": "保留在存储中的数据", - "xpack.dataUsage.metrics.filter.clearAll": "全部清除", "xpack.dataUsage.metrics.filter.dataStreams": "数据流", "xpack.dataUsage.metrics.filter.emptyMessage": "无 {filterName} 可用", "xpack.dataUsage.metrics.filter.metricTypes": "指标类型", @@ -17952,10 +17939,7 @@ "xpack.enterpriseSearch.createConnector..title": "创建连接器", "xpack.enterpriseSearch.createConnector.badgeType.ElasticManaged": "Elastic 托管", "xpack.enterpriseSearch.createConnector.badgeType.selfManaged": "自管型", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.BetaBadgeLabel": "公测版", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.OnlySelfManagedBadgeLabel": "自管型", "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.placeholder.text": "选择数据源", - "xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.thechPreviewBadgeLabel": "技术预览", "xpack.enterpriseSearch.createConnector.configurationStep.configurationLabel": "配置", "xpack.enterpriseSearch.createConnector.configurationStep.h4.finishUpLabel": "结束", "xpack.enterpriseSearch.createConnector.configurationStep.p.description": "您可以手动同步数据,计划重复同步或管理您的域。", @@ -18229,7 +18213,6 @@ "xpack.enterpriseSearch.overview.gettingStarted.testConnection.description": "发送测试请求,以确认您的语言客户端和 Elasticsearch 实例已启动并正在运行。", "xpack.enterpriseSearch.overview.gettingStarted.testConnection.title": "测试您的连接", "xpack.enterpriseSearch.overview.navTitle": "概览", - "xpack.enterpriseSearch.overview.setupCta.description": "通过 Elastic App Search 和 Workplace Search,将搜索添加到您的应用或内部组织中。观看视频,了解方便易用的搜索功能可以帮您做些什么。", "xpack.enterpriseSearch.pageTemplate.endpointsButtonLabel": "终端和 API 密钥", "xpack.enterpriseSearch.passwordLabel": "密码", "xpack.enterpriseSearch.pipeline.title": "转换和扩充数据", @@ -18552,7 +18535,6 @@ "xpack.enterpriseSearch.searchNav.otherTools": "其他工具", "xpack.enterpriseSearch.searchNav.relevance": "相关性", "xpack.enterpriseSearch.searchProvider.aiSearch.name": "搜索 AI", - "xpack.enterpriseSearch.searchProvider.type.name": "搜索", "xpack.enterpriseSearch.searchProvider.webCrawler.name": "Elastic 网络爬虫", "xpack.enterpriseSearch.selectConnector.badgeOnClick.ariaLabel": "单击以打开连接器说明弹出框", "xpack.enterpriseSearch.selectConnector.connectorClientBadgeLabel": "自管型", @@ -20126,7 +20108,6 @@ "xpack.fleet.agentPolicyForm.newAgentPolicyFieldLabel": "新代理策略名称", "xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText": "Fleet 服务器、Synthetics 或 APM 不支持代理集成的 {outputType} 输出。", "xpack.fleet.agentPolicyForm.outputOptionDisableOutputTypeText": "Fleet 服务器、Synthetics 或 APM 不支持代理集成的 {outputType} 输出。", - "xpack.fleet.agentPolicyForm.spaceDescription": "为此策略选择一个或多个工作区,或创建新工作区。{link}", "xpack.fleet.agentPolicyForm.spaceFieldLabel": "工作区", "xpack.fleet.agentPolicyForm.systemMonitoringText": "收集系统日志和指标", "xpack.fleet.agentPolicyForm.systemMonitoringTooltipText": "这还会添加 {system} 集成以收集系统日志和指标。", @@ -25685,8 +25666,8 @@ "xpack.inventory.entitiesGrid.euiDataGrid.alertsTooltip": "活动告警计数", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameLabel": "实体名称", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameTooltip": "实体的名称 (entity.displayName)", - "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft": "正在显示 {currentItems} 个(共 {total} 个){boldEntites}", - "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft.entites": "实体", + "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft": "正在显示 {currentItems} 个(共 {total} 个){boldEntities}", + "xpack.inventory.entitiesGrid.euiDataGrid.headerLeft.entities": "实体", "xpack.inventory.entitiesGrid.euiDataGrid.inventoryEntitiesGridLabel": "库存实体网格", "xpack.inventory.entitiesGrid.euiDataGrid.lastSeen": "{date} @ {time}", "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenLabel": "最后看到时间", @@ -25873,7 +25854,6 @@ "xpack.lens.app.createVisualizationLabel": "ES|QL", "xpack.lens.app.docLoadingError": "加载已保存文档时出错", "xpack.lens.app.editLensEmbeddableLabel": "编辑可视化", - "xpack.lens.app.editVisualizationLabel": "编辑 {lang} 可视化", "xpack.lens.app.exploreDataInDiscover": "在 Discover 中浏览", "xpack.lens.app.exploreDataInDiscoverDrilldown": "在 Discover 中打开", "xpack.lens.app.exploreDataInDiscoverDrilldown.newTabConfig": "在新选项卡中打开", @@ -26032,14 +26012,9 @@ "xpack.lens.editorFrame.suggestionPanelTitle": "建议", "xpack.lens.editorFrame.tooManyDimensionsSingularWarningLabel": "请移除{dimensionsTooMany, plural, one {一个维度} other {{dimensionsTooMany} 个维度}}", "xpack.lens.editorFrame.workspaceLabel": "工作区", - "xpack.lens.embeddable.failure": "无法显示可视化", - "xpack.lens.embeddable.featureBadge.iconDescription": "{count} 个可视化{count, plural, other {修饰符}}", - "xpack.lens.embeddable.fixErrors": "在 Lens 编辑器中编辑以修复该错误", - "xpack.lens.embeddable.legacyURLConflict.shortMessage": "您遇到了 URL 冲突", - "xpack.lens.embeddable.missingTimeRangeParam.longMessage": "给定配置需要包含 timeRange 属性", - "xpack.lens.embeddable.missingTimeRangeParam.shortMessage": "缺少 timeRange 属性", - "xpack.lens.embeddable.moreErrors": "在 Lens 编辑器中编辑以查看更多错误", - "xpack.lens.embeddableDisplayName": "Lens", + "xpack.lens.featureBadge.iconDescription": "{count} 个可视化{count, plural, other {修饰符}}", + "xpack.lens.fixErrors": "在 Lens 编辑器中编辑以修复该错误", + "xpack.lens.moreErrors": "在 Lens 编辑器中编辑以查看更多错误", "xpack.lens.endValue.nearest": "最近", "xpack.lens.endValue.none": "隐藏", "xpack.lens.endValue.zero": "零", @@ -26549,7 +26524,6 @@ "xpack.lens.legacyMetric.titlePositions.bottom": "底部", "xpack.lens.legacyMetric.titlePositions.top": "顶部", "xpack.lens.legacyUrlConflict.objectNoun": "Lens 可视化", - "xpack.lens.lensSavedObjectLabel": "Lens 可视化", "xpack.lens.lineCurve.smooth": "平滑", "xpack.lens.lineCurve.step": "步骤", "xpack.lens.lineCurve.straight": "直线", @@ -34903,8 +34877,6 @@ "xpack.remoteClusters.updateRemoteCluster.noRemoteClusterErrorMessage": "没有该名称的远程集群。", "xpack.remoteClusters.updateRemoteCluster.unknownRemoteClusterErrorMessage": "无法编辑集群,ES 未返回任何响应。", "xpack.reporting.breadcrumb": "Reporting", - "xpack.reporting.deprecations.csvPanelActionDownload.manualStep2": "在 Dashboard 应用程序中使用替代面板操作从已保存搜索面板生成 CSV 报告。", - "xpack.reporting.deprecations.csvPanelActionDownload.title": "在仪表板中从已保存搜索面板启用 CSV 下载的设置已弃用。", "xpack.reporting.deprecations.migrateIndexIlmPolicyActionTitle": "找到由定制 ILM 策略管理的报告索引。", "xpack.reporting.deprecations.reportingRole.forbiddenErrorCorrectiveAction": "请确保您分配有'manage_security'集群权限。", "xpack.reporting.deprecations.reportingRole.forbiddenErrorMessage": "您没有足够的权限来修复此弃用。", @@ -35442,15 +35414,6 @@ "xpack.searchInferenceEndpoints.actions.endpointAddedFailure": "终端创建失败", "xpack.searchInferenceEndpoints.actions.endpointAddedSuccess": "已添加终端", "xpack.searchInferenceEndpoints.actions.trainedModelsStatGatherFailed": "无法检索已训练模型统计信息", - "xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription": "利用推理终端,您可以使用由第三方服务提供的 NLP 模型或 ELSER 和 E5 等 Elastic 内置模型来执行推理任务。通过使用创建推理 API 来设置任务,如文本嵌入、完成、重新排名等。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description": "E5 是一个第三方 NLP 模型,它允许您通过使用密集向量表示方法来执行多语言语义搜索。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title": "E5 多语言", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription": "ELSER 是 Elastic 的英文版稀疏向量语义搜索 NLP 模型。", - "xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle": "ELSER", - "xpack.searchInferenceEndpoints.addEmptyPrompt.learnHowToCreateInferenceEndpoints": "了解如何创建推理终端", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithE5": "利用 E5 多语言版进行语义搜索", - "xpack.searchInferenceEndpoints.addEmptyPrompt.semanticSearchWithElser": "利用 ELSER 进行语义搜索", - "xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel": "详细了解内置 NLP 模型:", "xpack.searchInferenceEndpoints.allInferenceEndpoints.description": "推理终端简化了 Elasticsearch 中的 Machine Learning 模型部署和管理。使用唯一的终端来设置和管理 NLP 任务,以构建 AI 驱动式搜索。", "xpack.searchInferenceEndpoints.apiDocumentationLink": "API 文档", "xpack.searchInferenceEndpoints.cancel": "取消", @@ -35943,7 +35906,6 @@ "xpack.security.management.editRole.featureTable.cannotCustomizeSubFeaturesTooltip": "定制子功能权限为订阅功能。", "xpack.security.management.editRole.featureTable.customizeSubFeaturePrivilegesSwitchLabel": "定制子功能权限", "xpack.security.management.editRole.featureTable.featureAccordionSwitchLabel": "{grantedCount} / {featureCount} 项{featureCount, plural, other {功能}}已授予", - "xpack.security.management.editRole.featureTable.featureVisibilityTitle": "定制功能权限", "xpack.security.management.editRole.featureTable.managementCategoryHelpText": "可以在此菜单以外、在索引和集群权限中找到其他堆栈管理权限。", "xpack.security.management.editRole.featureTable.privilegeCustomizationTooltip": "功能已定制子功能权限。展开此行以了解更多信息。", "xpack.security.management.editRole.indexPrivilegeForm.clustersFormRowLabel": "远程集群", @@ -36003,18 +35965,10 @@ "xpack.security.management.editRole.spaceAwarePrivilegeForm.kibanaAdminTitle": "kibana_admin", "xpack.security.management.editRole.spacePrivilegeForm.basePrivilegeControlLegend": "所有功能的权限", "xpack.security.management.editRole.spacePrivilegeForm.cancelButton": "取消", - "xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivilegeDescription": "按功能提高权限级别。某些功能可能被工作区隐藏或受全局工作区权限影响。", - "xpack.security.management.editRole.spacePrivilegeForm.customizeFeaturePrivileges": "按功能定制", - "xpack.security.management.editRole.spacePrivilegeForm.featurePrivilegeSummaryDescription": "某些功能可能被工作区隐藏或受全局工作区权限影响。", - "xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeNotice": "这些权限将应用到所有当前和未来工作区。", - "xpack.security.management.editRole.spacePrivilegeForm.globalPrivilegeWarning": "创建全局权限可能会影响您的其他工作区权限。", - "xpack.security.management.editRole.spacePrivilegeForm.modalHeadline": "必须向此角色授权以下工作区的访问权限", - "xpack.security.management.editRole.spacePrivilegeForm.modalTitle": "将角色分配给工作区", "xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormHelpText": "分配您希望向此工作区的所有现有和未来功能授予的权限级别。", "xpack.security.management.editRole.spacePrivilegeForm.privilegeSelectorFormLabel": "所有功能的权限", "xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormHelpText": "选择一个或多个希望分配权限的 Kibana 工作区。", "xpack.security.management.editRole.spacePrivilegeForm.spaceSelectorFormLabel": "工作区", - "xpack.security.management.editRole.spacePrivilegeForm.summaryOfFeaturePrivileges": "功能权限的摘要", "xpack.security.management.editRole.spacePrivilegeForm.supersededWarning": "声明的权限相对配置的全局权限有较小的宽容度。查看权限摘要以查看有效的权限。", "xpack.security.management.editRole.spacePrivilegeForm.supersededWarningTitle": "已由全局权限取代", "xpack.security.management.editRole.spacePrivilegeMatrix.globalSpaceName": "所有工作区", @@ -36146,9 +36100,7 @@ "xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowHelpText": "如果未授权任何字段,则分配到此角色的用户将无法查看此索引的任何数据。", "xpack.security.management.editRoles.indexPrivilegeForm.grantedFieldsFormRowLabel": "已授权字段", "xpack.security.management.editRoles.indexPrivilegeForm.grantFieldPrivilegesLabel": "授予对特定字段的访问权限", - "xpack.security.management.editRolespacePrivilegeForm.createGlobalPrivilegeButton": "创建全局权限", "xpack.security.management.editRolespacePrivilegeForm.createPrivilegeButton": "添加 Kibana 权限", - "xpack.security.management.editRolespacePrivilegeForm.updateGlobalPrivilegeButton": "更新全局权限", "xpack.security.management.editRolespacePrivilegeForm.updatePrivilegeButton": "更新工作区权限", "xpack.security.management.enabledBadge": "已启用", "xpack.security.management.readonlyBadge.text": "只读", @@ -37453,7 +37405,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoAriaLabel": "打开帮助弹出框", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipContent": "请访问我们的{createEsqlRuleTypeLink}以开始使用 ES|QL 规则。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlInfoTooltipLink": "文档", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryFieldRequiredError": "ES|QL 查询必填。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.esqlQueryLabel": "ES|QL 查询", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldAnomalyThresholdLabel": "异常分数阈值", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldMachineLearningJobIdLabel": "Machine Learning 作业", @@ -44144,7 +44095,6 @@ "xpack.spaces.management.spaceDetails.footerActions.updateSpace": "应用更改", "xpack.spaces.management.spaceDetails.keepEditingButton": "保存然后离开", "xpack.spaces.management.spaceDetails.leavePageButton": "离开", - "xpack.spaces.management.spaceDetails.privilegeForm.heading": "定义给定角色在此工作区中应具有的权限。", "xpack.spaces.management.spaceDetails.roles.assign": "分配新角色", "xpack.spaces.management.spaceDetails.roles.assign.privilegeConflictMsg.description": "在此批量更新设置会覆盖当前的单个设置。", "xpack.spaces.management.spaceDetails.roles.assign.privilegeConflictMsg.title": "权限将仅适用于此工作区。", @@ -44191,7 +44141,6 @@ "xpack.spaces.management.spaceIdentifier.kibanaURLForSpaceIdentifierDescription": "创建后,将无法更改 URL 标识符。", "xpack.spaces.management.spaceIdentifier.urlIdentifierTitle": "URL 标识符", "xpack.spaces.management.spacesGridPage.actionsColumnName": "操作", - "xpack.spaces.management.spacesGridPage.allFeaturesEnabled": "所有功能", "xpack.spaces.management.spacesGridPage.createSpaceButtonLabel": "创建工作区", "xpack.spaces.management.spacesGridPage.currentSpaceMarkerText": "当前", "xpack.spaces.management.spacesGridPage.deleteActionDescription": "删除 {spaceName}", @@ -44201,13 +44150,10 @@ "xpack.spaces.management.spacesGridPage.editSpaceActionDescription": "编辑 {spaceName}。", "xpack.spaces.management.spacesGridPage.editSpaceActionName": "编辑", "xpack.spaces.management.spacesGridPage.errorTitle": "加载工作区时出错", - "xpack.spaces.management.spacesGridPage.featuresColumnName": "功能可见", "xpack.spaces.management.spacesGridPage.identifierColumnName": "标识符", "xpack.spaces.management.spacesGridPage.loadingTitle": "正在加载……", - "xpack.spaces.management.spacesGridPage.noFeaturesEnabled": "没有可见功能", "xpack.spaces.management.spacesGridPage.searchPlaceholder": "搜索", "xpack.spaces.management.spacesGridPage.solutionColumnName": "解决方案视图", - "xpack.spaces.management.spacesGridPage.someFeaturesEnabled": "{enabledFeatureCount}/{totalFeatureCount}", "xpack.spaces.management.spacesGridPage.spaceColumnName": "工作区", "xpack.spaces.management.spacesGridPage.spacesTitle": "工作区", "xpack.spaces.management.spacesGridPage.switchSpaceActionDescription": "切换到 {spaceName}", @@ -46739,7 +46685,6 @@ "xpack.transform.groupBy.popoverForm.fieldLabel": "字段", "xpack.transform.groupBy.popoverForm.intervalError": "时间间隔无效。", "xpack.transform.groupBy.popoverForm.intervalLabel": "时间间隔", - "xpack.transform.groupBy.popoverForm.intervalPercents": "输入百分位数的逗号分隔列表", "xpack.transform.groupBy.popoverForm.invalidSizeErrorMessage": "输入有效的正数", "xpack.transform.groupBy.popoverForm.missingBucketCheckboxHelpText": "选择包括没有值的文档。", "xpack.transform.groupby.popoverForm.missingBucketCheckboxLabel": "包括缺失的存储桶", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx index 1a5e3f278eb5..845234554314 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_select.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useBulkEditSelect } from './use_bulk_edit_select'; import { RuleTableItem } from '../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_create_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_create_connector.test.tsx index 5fe09dd8ae90..f33092c3dea9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_create_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_create_connector.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '../../common/lib/kibana'; import { useCreateConnector } from './use_create_connector'; @@ -29,7 +29,7 @@ describe('useCreateConnector', () => { }); it('executes correctly', async () => { - const { result, waitForNextUpdate } = renderHook(() => useCreateConnector()); + const { result } = renderHook(() => useCreateConnector()); act(() => { result.current.createConnector({ @@ -40,10 +40,10 @@ describe('useCreateConnector', () => { }); }); - await waitForNextUpdate(); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith('/api/actions/connector', { - body: '{"name":"test","config":{},"secrets":{},"connector_type_id":".test"}', - }); + await waitFor(() => + expect(useKibanaMock().services.http.post).toHaveBeenCalledWith('/api/actions/connector', { + body: '{"name":"test","config":{},"secrets":{},"connector_type_id":".test"}', + }) + ); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_execute_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_execute_connector.test.tsx index 4b93900ea6b4..a381296e6bc9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_execute_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_execute_connector.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '../../common/lib/kibana'; import { useExecuteConnector } from './use_execute_connector'; @@ -29,17 +29,17 @@ describe('useExecuteConnector', () => { }); it('executes correctly', async () => { - const { result, waitForNextUpdate } = renderHook(() => useExecuteConnector()); + const { result } = renderHook(() => useExecuteConnector()); act(() => { result.current.executeConnector({ connectorId: 'test-id', params: {} }); }); - await waitForNextUpdate(); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - '/api/actions/connector/test-id/_execute', - { body: '{"params":{}}' } + await waitFor(() => + expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( + '/api/actions/connector/test-id/_execute', + { body: '{"params":{}}' } + ) ); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_query_delay_setting.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_query_delay_setting.test.tsx index 4b65e355b9c8..231b9b9be49d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_query_delay_setting.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_get_query_delay_setting.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useGetQueryDelaySettings } from './use_get_query_delay_settings'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_license.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_license.test.ts index 1b2fc7784f4e..ec203e4bdacf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_license.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_license.test.ts @@ -6,7 +6,7 @@ */ import { BehaviorSubject } from 'rxjs'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useLicense } from './use_license'; import { useKibana } from '../../common/lib/kibana'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_alert_summary.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_alert_summary.test.ts index e56c8aa1348b..92c1e3bc5ca6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_alert_summary.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_alert_summary.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { ValidFeatureId } from '@kbn/rule-data-utils'; import { useKibana } from '../../common/lib/kibana'; import { @@ -34,7 +34,7 @@ describe('useLoadAlertSummary', () => { ...mockedAlertSummaryResponse, }); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useLoadAlertSummary({ featureIds, timeRange: mockedAlertSummaryTimeRange, @@ -49,7 +49,7 @@ describe('useLoadAlertSummary', () => { }, }); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); const { alertSummary, error } = result.current; expect(alertSummary).toEqual({ @@ -70,7 +70,7 @@ describe('useLoadAlertSummary', () => { ...mockedAlertSummaryResponse, }); - const { waitForNextUpdate } = renderHook(() => + renderHook(() => useLoadAlertSummary({ featureIds, timeRange: mockedAlertSummaryTimeRange, @@ -78,8 +78,6 @@ describe('useLoadAlertSummary', () => { }) ); - await waitForNextUpdate(); - const body = JSON.stringify({ fixed_interval: fixedInterval, gte: utcFrom, @@ -87,11 +85,14 @@ describe('useLoadAlertSummary', () => { featureIds, filter: [filter], }); - expect(mockedPostAPI).toHaveBeenCalledWith( - '/internal/rac/alerts/_alert_summary', - expect.objectContaining({ - body, - }) + + await waitFor(() => + expect(mockedPostAPI).toHaveBeenCalledWith( + '/internal/rac/alerts/_alert_summary', + expect.objectContaining({ + body, + }) + ) ); }); @@ -99,15 +100,13 @@ describe('useLoadAlertSummary', () => { const error = new Error('Fetch Alert Summary Failed'); mockedPostAPI.mockRejectedValueOnce(error); - const { result, waitForNextUpdate } = renderHook(() => + const { result } = renderHook(() => useLoadAlertSummary({ featureIds, timeRange: mockedAlertSummaryTimeRange, }) ); - await waitForNextUpdate(); - - expect(result.current.error).toMatch(error.message); + await waitFor(() => expect(result.current.error).toMatch(error.message)); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx index c0e143119f6f..e1cb5f6f4f5e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx @@ -5,13 +5,12 @@ * 2.0. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor, renderHook } from '@testing-library/react'; import { useLoadRuleAggregationsQuery as useLoadRuleAggregations } from './use_load_rule_aggregations_query'; import { RuleStatus } from '../../types'; import { useKibana } from '../../common/lib/kibana'; import { IToasts } from '@kbn/core-notifications-browser'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { waitFor } from '@testing-library/react'; jest.mock('../../common/lib/kibana'); jest.mock('../lib/rule_api/aggregate_kuery_filter', () => ({ @@ -75,7 +74,7 @@ describe('useLoadRuleAggregations', () => { refresh: undefined, }; - const { rerender, result, waitForNextUpdate } = renderHook( + const { rerender, result } = renderHook( () => { return useLoadRuleAggregations(params); }, @@ -83,20 +82,21 @@ describe('useLoadRuleAggregations', () => { ); rerender(); - await waitForNextUpdate(); - expect(loadRuleAggregationsWithKueryFilter).toBeCalledWith( - expect.objectContaining({ - searchText: '', - typesFilter: [], - actionTypesFilter: [], - ruleExecutionStatusesFilter: [], - ruleLastRunOutcomesFilter: [], - ruleStatusesFilter: [], - tagsFilter: [], - }) - ); - expect(result.current.rulesStatusesTotal).toEqual(MOCK_AGGS.ruleExecutionStatus); + await waitFor(() => { + expect(loadRuleAggregationsWithKueryFilter).toBeCalledWith( + expect.objectContaining({ + searchText: '', + typesFilter: [], + actionTypesFilter: [], + ruleExecutionStatusesFilter: [], + ruleLastRunOutcomesFilter: [], + ruleStatusesFilter: [], + tagsFilter: [], + }) + ); + expect(result.current.rulesStatusesTotal).toEqual(MOCK_AGGS.ruleExecutionStatus); + }); }); it('should call loadRuleAggregation API with params and handle result', async () => { @@ -115,28 +115,26 @@ describe('useLoadRuleAggregations', () => { refresh: undefined, }; - const { rerender, result, waitForNextUpdate } = renderHook( - () => useLoadRuleAggregations(params), - { - wrapper, - } - ); + const { rerender, result } = renderHook(() => useLoadRuleAggregations(params), { + wrapper, + }); rerender(); - await waitForNextUpdate(); - expect(loadRuleAggregationsWithKueryFilter).toBeCalledWith( - expect.objectContaining({ - searchText: 'test', - typesFilter: ['type1', 'type2'], - actionTypesFilter: ['action1', 'action2'], - ruleExecutionStatusesFilter: ['status1', 'status2'], - ruleStatusesFilter: ['enabled', 'snoozed'] as RuleStatus[], - tagsFilter: ['tag1', 'tag2'], - ruleLastRunOutcomesFilter: ['outcome1', 'outcome2'], - }) - ); - expect(result.current.rulesStatusesTotal).toEqual(MOCK_AGGS.ruleExecutionStatus); + await waitFor(() => { + expect(loadRuleAggregationsWithKueryFilter).toBeCalledWith( + expect.objectContaining({ + searchText: 'test', + typesFilter: ['type1', 'type2'], + actionTypesFilter: ['action1', 'action2'], + ruleExecutionStatusesFilter: ['status1', 'status2'], + ruleStatusesFilter: ['enabled', 'snoozed'] as RuleStatus[], + tagsFilter: ['tag1', 'tag2'], + ruleLastRunOutcomesFilter: ['outcome1', 'outcome2'], + }) + ); + expect(result.current.rulesStatusesTotal).toEqual(MOCK_AGGS.ruleExecutionStatus); + }); }); it('should call onError if API fails', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx index 8cc07c87e241..3c87b04cb62e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor, renderHook } from '@testing-library/react'; import { useLoadRulesQuery as useLoadRules } from './use_load_rules_query'; import { RuleExecutionStatusErrorReasons, @@ -15,7 +15,6 @@ import { RuleStatus } from '../../types'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useKibana } from '../../common/lib/kibana'; import { IToasts } from '@kbn/core-notifications-browser'; -import { waitFor } from '@testing-library/react'; jest.mock('../../common/lib/kibana'); jest.mock('../lib/rule_api/rules_kuery_filter', () => ({ @@ -282,7 +281,7 @@ describe('useLoadRules', () => { sort: { field: 'name', direction: 'asc' }, }; - const { result, waitForNextUpdate, rerender } = renderHook(() => useLoadRules(params), { + const { result, rerender } = renderHook(() => useLoadRules(params), { wrapper, }); @@ -291,11 +290,10 @@ describe('useLoadRules', () => { expect(result.current.rulesState.isLoading).toBeTruthy(); rerender(); - await waitForNextUpdate(); + await waitFor(() => expect(result.current.rulesState.isLoading).toBeFalsy()); expect(result.current.rulesState.initialLoad).toBeFalsy(); expect(result.current.hasData).toBeTruthy(); - expect(result.current.rulesState.isLoading).toBeFalsy(); expect(onPage).toBeCalledTimes(0); expect(loadRulesWithKueryFilter).toBeCalledWith( @@ -339,29 +337,29 @@ describe('useLoadRules', () => { sort: { field: 'name', direction: 'asc' }, }; - const { waitForNextUpdate, rerender } = renderHook(() => useLoadRules(params), { + const { rerender } = renderHook(() => useLoadRules(params), { wrapper, }); rerender(); - await waitForNextUpdate(); - - expect(loadRulesWithKueryFilter).toBeCalledWith( - expect.objectContaining({ - page: { - index: 0, - size: 25, - }, - searchText: 'test', - typesFilter: ['type1', 'type2'], - actionTypesFilter: ['action1', 'action2'], - ruleExecutionStatusesFilter: ['status1', 'status2'], - ruleLastRunOutcomesFilter: ['outcome1', 'outcome2'], - ruleParamsFilter: {}, - ruleStatusesFilter: ['enabled', 'snoozed'], - tagsFilter: ['tag1', 'tag2'], - sort: { field: 'name', direction: 'asc' }, - }) + await waitFor(() => + expect(loadRulesWithKueryFilter).toBeCalledWith( + expect.objectContaining({ + page: { + index: 0, + size: 25, + }, + searchText: 'test', + typesFilter: ['type1', 'type2'], + actionTypesFilter: ['action1', 'action2'], + ruleExecutionStatusesFilter: ['status1', 'status2'], + ruleLastRunOutcomesFilter: ['outcome1', 'outcome2'], + ruleParamsFilter: {}, + ruleStatusesFilter: ['enabled', 'snoozed'], + tagsFilter: ['tag1', 'tag2'], + sort: { field: 'name', direction: 'asc' }, + }) + ) ); }); @@ -391,7 +389,7 @@ describe('useLoadRules', () => { sort: { field: 'name', direction: 'asc' }, }; - const { rerender, waitForNextUpdate } = renderHook( + const { rerender } = renderHook( () => { return useLoadRules(params); }, @@ -399,12 +397,12 @@ describe('useLoadRules', () => { ); rerender(); - await waitForNextUpdate(); - - expect(onPage).toHaveBeenCalledWith({ - index: 0, - size: 25, - }); + await waitFor(() => + expect(onPage).toHaveBeenCalledWith({ + index: 0, + size: 25, + }) + ); }); it('should call onError if API fails', async () => { @@ -459,16 +457,14 @@ describe('useLoadRules', () => { sort: { field: 'name', direction: 'asc' }, }; - const { rerender, result, waitForNextUpdate } = renderHook(() => useLoadRules(params), { + const { rerender, result } = renderHook(() => useLoadRules(params), { wrapper, }); expect(result.current.hasData).toBeFalsy(); rerender(); - await waitForNextUpdate(); - - expect(result.current.hasData).toBeFalsy(); + await waitFor(() => expect(result.current.hasData).toBeFalsy()); }); it('hasData should be false, if there is rule types filter and no rules with hasDefaultRuleTypesFiltersOn = true', async () => { @@ -494,16 +490,14 @@ describe('useLoadRules', () => { hasDefaultRuleTypesFiltersOn: true, }; - const { rerender, result, waitForNextUpdate } = renderHook(() => useLoadRules(params), { + const { rerender, result } = renderHook(() => useLoadRules(params), { wrapper, }); expect(result.current.hasData).toBeFalsy(); rerender(); - await waitForNextUpdate(); - - expect(result.current.hasData).toBeFalsy(); + await waitFor(() => expect(result.current.hasData).toBeFalsy()); }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.test.tsx index 081538d1432e..3aa1bcbf07b2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_tags_query.test.tsx @@ -5,12 +5,11 @@ * 2.0. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor, renderHook } from '@testing-library/react'; import { useLoadTagsQuery } from './use_load_tags_query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useKibana } from '../../common/lib/kibana'; import { IToasts } from '@kbn/core-notifications-browser'; -import { waitFor } from '@testing-library/react'; const MOCK_TAGS = ['a', 'b', 'c']; @@ -53,7 +52,7 @@ describe('useLoadTagsQuery', () => { }); it('should call loadRuleTags API and handle result', async () => { - const { rerender, result, waitForNextUpdate } = renderHook( + const { rerender, result } = renderHook( () => useLoadTagsQuery({ enabled: true, @@ -67,18 +66,18 @@ describe('useLoadTagsQuery', () => { ); rerender(); - await waitForNextUpdate(); - - expect(loadRuleTags).toHaveBeenLastCalledWith( - expect.objectContaining({ - search: 'test', - perPage: 50, - page: 1, - }) - ); + await waitFor(() => { + expect(loadRuleTags).toHaveBeenLastCalledWith( + expect.objectContaining({ + search: 'test', + perPage: 50, + page: 1, + }) + ); - expect(result.current.tags).toEqual(MOCK_TAGS); - expect(result.current.hasNextPage).toEqual(false); + expect(result.current.tags).toEqual(MOCK_TAGS); + expect(result.current.hasNextPage).toEqual(false); + }); }); it('should support pagination', async () => { @@ -88,7 +87,7 @@ describe('useLoadTagsQuery', () => { perPage: 5, total: 10, }); - const { rerender, result, waitForNextUpdate } = renderHook( + const { rerender, result } = renderHook( () => useLoadTagsQuery({ enabled: true, @@ -101,17 +100,17 @@ describe('useLoadTagsQuery', () => { ); rerender(); - await waitForNextUpdate(); - - expect(loadRuleTags).toHaveBeenLastCalledWith( - expect.objectContaining({ - perPage: 5, - page: 1, - }) - ); + await waitFor(() => { + expect(loadRuleTags).toHaveBeenLastCalledWith( + expect.objectContaining({ + perPage: 5, + page: 1, + }) + ); - expect(result.current.tags).toEqual(['a', 'b', 'c', 'd', 'e']); - expect(result.current.hasNextPage).toEqual(true); + expect(result.current.tags).toEqual(['a', 'b', 'c', 'd', 'e']); + expect(result.current.hasNextPage).toEqual(true); + }); loadRuleTags.mockResolvedValue({ data: ['a', 'b', 'c', 'd', 'e'], @@ -119,6 +118,7 @@ describe('useLoadTagsQuery', () => { perPage: 5, total: 10, }); + result.current.fetchNextPage(); expect(loadRuleTags).toHaveBeenLastCalledWith( @@ -129,9 +129,7 @@ describe('useLoadTagsQuery', () => { ); rerender(); - await waitForNextUpdate(); - - expect(result.current.hasNextPage).toEqual(false); + await waitFor(() => expect(result.current.hasNextPage).toEqual(false)); }); it('should support pagination when there are no tags', async () => { @@ -142,7 +140,7 @@ describe('useLoadTagsQuery', () => { total: 0, }); - const { rerender, result, waitForNextUpdate } = renderHook( + const { rerender, result } = renderHook( () => useLoadTagsQuery({ enabled: true, @@ -155,17 +153,17 @@ describe('useLoadTagsQuery', () => { ); rerender(); - await waitForNextUpdate(); - - expect(loadRuleTags).toHaveBeenLastCalledWith( - expect.objectContaining({ - perPage: 5, - page: 1, - }) - ); + await waitFor(() => { + expect(loadRuleTags).toHaveBeenLastCalledWith( + expect.objectContaining({ + perPage: 5, + page: 1, + }) + ); - expect(result.current.tags).toEqual([]); - expect(result.current.hasNextPage).toEqual(false); + expect(result.current.tags).toEqual([]); + expect(result.current.hasNextPage).toEqual(false); + }); }); it('should call onError if API fails', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_sub_action.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_sub_action.test.tsx index 1f2552511de0..161f1564b1dd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_sub_action.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_sub_action.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '../../common/lib/kibana'; import { useSubAction, UseSubActionParams } from './use_sub_action'; @@ -30,102 +30,96 @@ describe('useSubAction', () => { }); it('init', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSubAction(params)); - await waitForNextUpdate(); - - expect(result.current).toEqual({ - isLoading: false, - response: {}, - error: null, - }); + const { result } = renderHook(() => useSubAction(params)); + await waitFor(() => + expect(result.current).toEqual({ + isLoading: false, + response: {}, + error: null, + }) + ); }); it('executes the sub action correctly', async () => { - const { waitForNextUpdate } = renderHook(() => useSubAction(params)); - await waitForNextUpdate(); - - expect(mockHttpPost).toHaveBeenCalledWith('/api/actions/connector/test-id/_execute', { - body: '{"params":{"subAction":"test","subActionParams":{"foo":"bar"}}}', - signal: new AbortController().signal, - }); + renderHook(() => useSubAction(params)); + await waitFor(() => + expect(mockHttpPost).toHaveBeenCalledWith('/api/actions/connector/test-id/_execute', { + body: '{"params":{"subAction":"test","subActionParams":{"foo":"bar"}}}', + signal: new AbortController().signal, + }) + ); }); it('executes sub action if subAction parameter changes', async () => { - const { rerender, waitForNextUpdate } = renderHook(useSubAction, { initialProps: params }); - await waitForNextUpdate(); - - expect(mockHttpPost).toHaveBeenCalledTimes(1); + const { rerender } = renderHook(useSubAction, { initialProps: params }); + await waitFor(() => expect(mockHttpPost).toHaveBeenCalledTimes(1)); await act(async () => { rerender({ ...params, subAction: 'test-2' }); - await waitForNextUpdate(); }); - expect(mockHttpPost).toHaveBeenCalledTimes(2); + await waitFor(() => expect(mockHttpPost).toHaveBeenCalledTimes(2)); }); it('executes sub action if connectorId parameter changes', async () => { - const { rerender, waitForNextUpdate } = renderHook(useSubAction, { initialProps: params }); - await waitForNextUpdate(); - - expect(mockHttpPost).toHaveBeenCalledTimes(1); + const { rerender } = renderHook(useSubAction, { initialProps: params }); + await waitFor(() => expect(mockHttpPost).toHaveBeenCalledTimes(1)); await act(async () => { rerender({ ...params, connectorId: 'test-id-2' }); - await waitForNextUpdate(); }); - expect(mockHttpPost).toHaveBeenCalledTimes(2); + await waitFor(() => expect(mockHttpPost).toHaveBeenCalledTimes(2)); }); it('returns memoized response if subActionParams changes but values are equal', async () => { - const { result, rerender, waitForNextUpdate } = renderHook(useSubAction, { + const { result, rerender } = renderHook(useSubAction, { initialProps: { ...params, subActionParams: { foo: 'bar' } }, }); - await waitForNextUpdate(); + await waitFor(() => expect(mockHttpPost).toHaveBeenCalledTimes(1)); - expect(mockHttpPost).toHaveBeenCalledTimes(1); const previous = result.current; await act(async () => { rerender({ ...params, subActionParams: { foo: 'bar' } }); - await waitForNextUpdate(); }); - expect(result.current.response).toBe(previous.response); - expect(mockHttpPost).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(result.current.response).toBe(previous.response); + expect(mockHttpPost).toHaveBeenCalledTimes(1); + }); }); it('executes sub action if subActionParams changes and values are not equal', async () => { - const { result, rerender, waitForNextUpdate } = renderHook(useSubAction, { + const { result, rerender } = renderHook(useSubAction, { initialProps: { ...params, subActionParams: { foo: 'bar' } }, }); - await waitForNextUpdate(); + await waitFor(() => expect(mockHttpPost).toHaveBeenCalledTimes(1)); - expect(mockHttpPost).toHaveBeenCalledTimes(1); const previous = result.current; await act(async () => { rerender({ ...params, subActionParams: { foo: 'baz' } }); - await waitForNextUpdate(); }); - expect(result.current.response).not.toBe(previous.response); - expect(mockHttpPost).toHaveBeenCalledTimes(2); + await waitFor(() => { + expect(result.current.response).not.toBe(previous.response); + expect(mockHttpPost).toHaveBeenCalledTimes(2); + }); }); it('returns an error correctly', async () => { const error = new Error('error executing'); mockHttpPost.mockRejectedValueOnce(error); - const { result, waitForNextUpdate } = renderHook(() => useSubAction(params)); - await waitForNextUpdate(); - - expect(result.current).toEqual({ - isLoading: false, - response: undefined, - error, - }); + const { result } = renderHook(() => useSubAction(params)); + await waitFor(() => + expect(result.current).toEqual({ + isLoading: false, + response: undefined, + error, + }) + ); }); it('should abort on unmount', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_rules_settings.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_rules_settings.test.tsx index c5f61885addd..e1a04ea929b5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_rules_settings.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_update_rules_settings.test.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { act, renderHook } from '@testing-library/react-hooks/dom'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook, act } from '@testing-library/react'; import { useUpdateRuleSettings } from './use_update_rules_settings'; const mockAddDanger = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/system_action_type_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/system_action_type_form.test.tsx index 3fb8207e45ef..440e24017fab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/system_action_type_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/system_action_type_form.test.tsx @@ -209,4 +209,64 @@ describe('action_type_form', () => { expect(setActionParamsProperty).toHaveBeenCalledWith('my-key', 'my-value', 1); }); + + describe('licensing', () => { + const actionTypeIndexDefaultWithLicensing = { + ...actionTypeIndexDefault, + '.test-system-action': { + ...actionTypeIndexDefault['.test-system-action'], + enabledInLicense: false, + minimumLicenseRequired: 'platinum' as const, + }, + }; + + beforeEach(() => { + const actionType = actionTypeRegistryMock.createMockActionTypeModel({ + id: '.test-system-action-with-license', + iconClass: 'test', + selectMessage: 'test', + validateParams: (): Promise<GenericValidationResult<unknown>> => { + const validationResult = { errors: {} }; + return Promise.resolve(validationResult); + }, + actionConnectorFields: null, + actionParamsFields: mockedActionParamsFields, + defaultActionParams: { + dedupKey: 'test', + eventAction: 'resolve', + }, + isSystemActionType: true, + }); + + actionTypeRegistry.get.mockReturnValue(actionType); + + jest.clearAllMocks(); + }); + + it('should render the licensing message if the user does not have the sufficient license', async () => { + render( + <I18nProvider> + <SystemActionTypeForm + actionConnector={actionConnector} + actionItem={actionItem} + connectors={connectors} + onDeleteAction={jest.fn()} + setActionParamsProperty={jest.fn()} + index={1} + actionTypesIndex={actionTypeIndexDefaultWithLicensing} + actionTypeRegistry={actionTypeRegistry} + messageVariables={{ context: [], state: [], params: [] }} + summaryMessageVariables={{ context: [], state: [], params: [] }} + producerId={AlertConsumers.INFRASTRUCTURE} + featureId={AlertConsumers.INFRASTRUCTURE} + ruleTypeId={'test'} + /> + </I18nProvider> + ); + + expect( + await screen.findByText('This feature requires a Platinum license.') + ).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/system_action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/system_action_type_form.tsx index d869449bcd92..36da1b594433 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/system_action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/system_action_type_form.tsx @@ -28,6 +28,7 @@ import { isEmpty, partition, some } from 'lodash'; import { ActionVariable, RuleActionParam } from '@kbn/alerting-plugin/common'; import { ActionGroupWithMessageVariables } from '@kbn/triggers-actions-ui-types'; import { transformActionVariables } from '@kbn/alerts-ui-shared/src/action_variables/transforms'; +import { checkActionFormActionTypeEnabled } from '@kbn/alerts-ui-shared/src/rule_form/utils/check_action_type_enabled'; import { TECH_PREVIEW_DESCRIPTION, TECH_PREVIEW_LABEL } from '../translations'; import { IErrorObject, @@ -167,8 +168,12 @@ export const SystemActionTypeForm = ({ }; const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields; + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionTypesIndex[actionConnector.actionTypeId], + [] + ); - const accordionContent = ( + const accordionContent = checkEnabledResult.isEnabled ? ( <> <EuiSplitPanel.Inner color="plain"> {ParamsFieldsComponent ? ( @@ -212,6 +217,8 @@ export const SystemActionTypeForm = ({ ) : null} </EuiSplitPanel.Inner> </> + ) : ( + checkEnabledResult.messageCard ); return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_page/hooks/use_rule_type_ids_by_feature_id.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_page/hooks/use_rule_type_ids_by_feature_id.test.ts index 3fd8ec70d9ff..03f1f66ede9f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_page/hooks/use_rule_type_ids_by_feature_id.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_page/hooks/use_rule_type_ids_by_feature_id.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks/dom'; +import { renderHook } from '@testing-library/react'; import { useRuleTypeIdsByFeatureId } from './use_rule_type_ids_by_feature_id'; import { ruleTypesIndex } from '../../../mock/rule_types_index'; import { MULTI_CONSUMER_RULE_TYPE_IDS } from '../../../constants'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cases/use_case_view_navigation.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cases/use_case_view_navigation.test.ts index 904a8cae4eec..6b1a14c0f7d2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cases/use_case_view_navigation.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cases/use_case_view_navigation.test.ts @@ -6,7 +6,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '../../../../common/lib/kibana'; import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; import { useCaseViewNavigation } from './use_case_view_navigation'; @@ -25,8 +25,8 @@ describe('useCaseViewNavigation', () => { useKibanaMock().services.application.navigateToApp = navigateToApp; }); - it('calls navigateToApp with correct arguments', () => { - const { result, waitFor } = renderHook(() => useCaseViewNavigation(), { + it('calls navigateToApp with correct arguments', async () => { + const { result } = renderHook(() => useCaseViewNavigation(), { wrapper: appMockRender.AppWrapper, }); @@ -34,7 +34,7 @@ describe('useCaseViewNavigation', () => { result.current.navigateToCaseView({ caseId: 'test-id' }); }); - waitFor(() => { + await waitFor(() => { expect(navigateToApp).toHaveBeenCalledWith('testAppId', { deepLinkId: 'cases', path: '/test-id', @@ -42,8 +42,8 @@ describe('useCaseViewNavigation', () => { }); }); - it('calls navigateToApp with correct arguments and bypass current app id', () => { - const { result, waitFor } = renderHook(() => useCaseViewNavigation('superAppId'), { + it('calls navigateToApp with correct arguments and bypass current app id', async () => { + const { result } = renderHook(() => useCaseViewNavigation('superAppId'), { wrapper: appMockRender.AppWrapper, }); @@ -51,7 +51,7 @@ describe('useCaseViewNavigation', () => { result.current.navigateToCaseView({ caseId: 'test-id' }); }); - waitFor(() => { + await waitFor(() => { expect(navigateToApp).toHaveBeenCalledWith('superAppId', { deepLinkId: 'cases', path: '/test-id', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_get_muted_alerts.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_get_muted_alerts.test.tsx index 8d65532c5f10..2718ccd8ca88 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_get_muted_alerts.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_get_muted_alerts.test.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; import * as api from '../apis/get_rules_muted_alerts'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '../../../../../common/lib/kibana'; import { AppMockRenderer, createAppMockRenderer } from '../../../test_utils'; import { useGetMutedAlerts } from './use_get_muted_alerts'; @@ -31,12 +30,10 @@ describe('useGetMutedAlerts', () => { it('calls the api when invoked with the correct parameters', async () => { const muteAlertInstanceSpy = jest.spyOn(api, 'getMutedAlerts'); - const { waitForNextUpdate } = renderHook(() => useGetMutedAlerts(ruleIds), { + renderHook(() => useGetMutedAlerts(ruleIds), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - await waitFor(() => { expect(muteAlertInstanceSpy).toHaveBeenCalledWith( expect.anything(), @@ -53,18 +50,16 @@ describe('useGetMutedAlerts', () => { wrapper: appMockRender.AppWrapper, }); - expect(spy).not.toHaveBeenCalled(); + await waitFor(() => expect(spy).not.toHaveBeenCalled()); }); it('shows a toast error when the api returns an error', async () => { const spy = jest.spyOn(api, 'getMutedAlerts').mockRejectedValue(new Error('An error')); - const { waitForNextUpdate } = renderHook(() => useGetMutedAlerts(ruleIds), { + renderHook(() => useGetMutedAlerts(ruleIds), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - await waitFor(() => { expect(spy).toHaveBeenCalled(); expect(addErrorMock).toHaveBeenCalled(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_mute_alert.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_mute_alert.test.tsx index f7e8b94aa2e6..74d93a0504ca 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_mute_alert.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_mute_alert.test.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks/dom'; import * as api from '../../../../lib/rule_api/mute_alert'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '../../../../../common/lib/kibana'; import { AppMockRenderer, createAppMockRenderer } from '../../../test_utils'; import { useMuteAlert } from './use_mute_alert'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_unmute_alert.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_unmute_alert.test.tsx index d24a481ba30b..178dc5bb6ed9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_unmute_alert.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/alert_mute/use_unmute_alert.test.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks/dom'; import * as api from '../../../../lib/rule_api/unmute_alert'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '../../../../../common/lib/kibana'; import { AppMockRenderer, createAppMockRenderer } from '../../../test_utils'; import { useUnmuteAlert } from './use_unmute_alert'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx index d84909e746f2..0f136e15156d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_actions.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useBulkActions, useBulkAddToCaseActions, useBulkUntrackActions } from './use_bulk_actions'; import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; import { createCasesServiceMock } from '../index.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_cases.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_cases.test.tsx index b4598f56c02f..e79955715d4e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_cases.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_cases.test.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; import * as api from './apis/bulk_get_cases'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { useKibana } from '../../../../common/lib/kibana'; import { useBulkGetCases } from './use_bulk_get_cases'; import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; @@ -35,18 +34,18 @@ describe('useBulkGetCases', () => { const spy = jest.spyOn(api, 'bulkGetCases'); spy.mockResolvedValue(response); - const { waitForNextUpdate } = renderHook(() => useBulkGetCases(['case-1'], true), { + renderHook(() => useBulkGetCases(['case-1'], true), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - - expect(spy).toHaveBeenCalledWith( - expect.anything(), - { - ids: ['case-1'], - }, - expect.any(AbortSignal) + await waitFor(() => + expect(spy).toHaveBeenCalledWith( + expect.anything(), + { + ids: ['case-1'], + }, + expect.any(AbortSignal) + ) ); }); @@ -58,18 +57,16 @@ describe('useBulkGetCases', () => { wrapper: appMockRender.AppWrapper, }); - expect(spy).not.toHaveBeenCalled(); + await waitFor(() => expect(spy).not.toHaveBeenCalled()); }); it('shows a toast error when the api return an error', async () => { const spy = jest.spyOn(api, 'bulkGetCases').mockRejectedValue(new Error('An error')); - const { waitForNextUpdate } = renderHook(() => useBulkGetCases(['case-1'], true), { + renderHook(() => useBulkGetCases(['case-1'], true), { wrapper: appMockRender.AppWrapper, }); - await waitForNextUpdate(); - await waitFor(() => { expect(spy).toHaveBeenCalledWith( expect.anything(), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_maintenance_windows.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_maintenance_windows.test.ts index b1f8a65e1603..7a2ffd32ad2c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_maintenance_windows.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_bulk_get_maintenance_windows.test.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { MaintenanceWindowStatus } from '@kbn/alerting-plugin/common'; import * as api from './apis/bulk_get_maintenance_windows'; import { coreMock } from '@kbn/core/public/mocks'; @@ -96,7 +95,7 @@ describe('useBulkGetMaintenanceWindows', () => { const spy = jest.spyOn(api, 'bulkGetMaintenanceWindows'); spy.mockResolvedValue(response); - const { waitForNextUpdate, result } = renderHook( + const { result } = renderHook( () => useBulkGetMaintenanceWindows({ ids: ['test-id'], @@ -107,9 +106,7 @@ describe('useBulkGetMaintenanceWindows', () => { } ); - await waitForNextUpdate(); - - expect(result.current.data?.get('test-id')).toEqual(mockMaintenanceWindow); + await waitFor(() => expect(result.current.data?.get('test-id')).toEqual(mockMaintenanceWindow)); expect(spy).toHaveBeenCalledWith({ http: expect.anything(), @@ -132,7 +129,7 @@ describe('useBulkGetMaintenanceWindows', () => { } ); - expect(spy).not.toHaveBeenCalled(); + await waitFor(() => expect(spy).not.toHaveBeenCalled()); }); it('does not call the api if license is not platinum', async () => { @@ -152,7 +149,7 @@ describe('useBulkGetMaintenanceWindows', () => { } ); - expect(spy).not.toHaveBeenCalled(); + await waitFor(() => expect(spy).not.toHaveBeenCalled()); }); it('does not call the api if capabilities are not adequate', async () => { @@ -177,7 +174,7 @@ describe('useBulkGetMaintenanceWindows', () => { } ); - expect(spy).not.toHaveBeenCalled(); + await waitFor(() => expect(spy).not.toHaveBeenCalled()); }); it('shows a toast error when the api return an error', async () => { @@ -185,7 +182,7 @@ describe('useBulkGetMaintenanceWindows', () => { .spyOn(api, 'bulkGetMaintenanceWindows') .mockRejectedValue(new Error('An error')); - const { waitForNextUpdate } = renderHook( + renderHook( () => useBulkGetMaintenanceWindows({ ids: ['test-id'], @@ -196,8 +193,6 @@ describe('useBulkGetMaintenanceWindows', () => { } ); - await waitForNextUpdate(); - await waitFor(() => { expect(spy).toHaveBeenCalledWith({ http: expect.anything(), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.test.tsx index fb79f87162bc..d6ecf3d9ab20 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.test.tsx @@ -9,12 +9,12 @@ import React, { FunctionComponent } from 'react'; import { EuiDataGridColumn } from '@elastic/eui'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, waitFor, renderHook } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { BrowserFields } from '@kbn/alerting-types'; import { testQueryClientConfig } from '@kbn/alerts-ui-shared/src/common/test_utils/test_query_client_config'; import { fetchAlertsFields } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerts_fields'; -import { useColumns, UseColumnsArgs, UseColumnsResp } from './use_columns'; +import { useColumns } from './use_columns'; import { AlertsTableStorage } from '../../alerts_table_state'; import { createStartServicesMock } from '../../../../../common/lib/kibana/kibana_react.mock'; import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; @@ -151,10 +151,7 @@ describe('useColumns', () => { test('onColumnResize', async () => { const localDefaultColumns = [...defaultColumns]; const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(localDefaultColumns); - const { result, rerender } = renderHook< - React.PropsWithChildren<UseColumnsArgs>, - UseColumnsResp - >( + const { result, rerender } = renderHook( () => useColumns({ defaultColumns, @@ -186,7 +183,7 @@ describe('useColumns', () => { test('check if initial width for the last column does not exist', async () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ defaultColumns, @@ -211,7 +208,7 @@ describe('useColumns', () => { const alertsFields = { testField: { name: 'testField', type: 'string', searchable: true, aggregatable: true }, }; - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ alertsFields, @@ -231,7 +228,7 @@ describe('useColumns', () => { describe('visibleColumns', () => { test('hide all columns with onChangeVisibleColumns', async () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ defaultColumns, @@ -253,7 +250,7 @@ describe('useColumns', () => { test('show all columns with onChangeVisibleColumns', async () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ defaultColumns, @@ -282,7 +279,7 @@ describe('useColumns', () => { test('should populate visibleColumns correctly', async () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ defaultColumns, @@ -300,10 +297,7 @@ describe('useColumns', () => { test('should change visibleColumns if provided defaultColumns change', async () => { let localDefaultColumns = [...defaultColumns]; let localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(localDefaultColumns); - const { result, rerender } = renderHook< - React.PropsWithChildren<UseColumnsArgs>, - UseColumnsResp - >( + const { result, rerender } = renderHook( () => useColumns({ defaultColumns: localDefaultColumns, @@ -340,10 +334,7 @@ describe('useColumns', () => { describe('columns', () => { test('should changes the column list when defaultColumns has been updated', async () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result, waitFor } = renderHook< - React.PropsWithChildren<UseColumnsArgs>, - UseColumnsResp - >( + const { result } = renderHook( () => useColumns({ defaultColumns, @@ -362,7 +353,7 @@ describe('useColumns', () => { describe('onToggleColumns', () => { test('should update the list of columns when on Toggle Columns is called', () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ defaultColumns, @@ -383,7 +374,7 @@ describe('useColumns', () => { test('should update the list of visible columns when onToggleColumn is called', async () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ defaultColumns, @@ -412,7 +403,7 @@ describe('useColumns', () => { test('should update the column details in the storage when onToggleColumn is called', () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ defaultColumns, @@ -445,7 +436,7 @@ describe('useColumns', () => { describe('onResetColumns', () => { test('should restore visible columns defaults', () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); - const { result } = renderHook<React.PropsWithChildren<UseColumnsArgs>, UseColumnsResp>( + const { result } = renderHook( () => useColumns({ defaultColumns, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_pagination.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_pagination.test.ts index 24bddbe90ec5..b53855f991c5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_pagination.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_pagination.test.ts @@ -5,7 +5,8 @@ * 2.0. */ import { usePagination } from './use_pagination'; -import { renderHook, act } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; describe('usePagination', () => { const onPageChange = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_sorting.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_sorting.test.ts index 092ec0ed0eb4..95efe7c9c8c5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_sorting.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_sorting.test.ts @@ -5,7 +5,8 @@ * 2.0. */ import { useSorting } from './use_sorting'; -import { renderHook, act } from '@testing-library/react-hooks'; + +import { renderHook, act } from '@testing-library/react'; describe('useSorting', () => { const onSortChange = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/hooks/use_rules_list_filter_store.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/hooks/use_rules_list_filter_store.test.tsx index 89b5e56aa33d..ee2a8637b991 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/hooks/use_rules_list_filter_store.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/hooks/use_rules_list_filter_store.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import * as useLocalStorage from 'react-use/lib/useLocalStorage'; import { useRulesListFilterStore } from './use_rules_list_filter_store'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.test.tsx index ad623631fe85..c3afafd71605 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.test.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { render, renderHook } from '@testing-library/react'; import React from 'react'; import { RulesListColumns, diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts index be838e00f251..5be26c69922a 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/credential_store.test.ts @@ -5,16 +5,17 @@ * 2.0. */ -import { CoreKibanaRequest } from '@kbn/core/server'; -import { loggingSystemMock, httpServerMock } from '@kbn/core/server/mocks'; +import { kibanaRequestFactory } from '@kbn/core-http-server-utils'; import { securityMock } from '@kbn/security-plugin/server/mocks'; -import { ReindexStep, ReindexStatus, ReindexSavedObject } from '../../../common/types'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; import { credentialStoreFactory } from './credential_store'; +import { ReindexStep, ReindexStatus, type ReindexSavedObject } from '../../../common/types'; const basicAuthHeader = 'Basic abc'; const logMock = loggingSystemMock.create().get(); -const requestMock = CoreKibanaRequest.from( +const requestMock = kibanaRequestFactory( httpServerMock.createRawRequest({ headers: { authorization: basicAuthHeader, diff --git a/x-pack/plugins/upgrade_assistant/tsconfig.json b/x-pack/plugins/upgrade_assistant/tsconfig.json index 34e02a404131..85fb886ca2af 100644 --- a/x-pack/plugins/upgrade_assistant/tsconfig.json +++ b/x-pack/plugins/upgrade_assistant/tsconfig.json @@ -39,7 +39,10 @@ "@kbn/shared-ux-link-redirect-app", "@kbn/react-kibana-context-render", "@kbn/data-views-plugin", - "@kbn/deeplinks-observability" + "@kbn/deeplinks-observability", + "@kbn/core-logging-server-mocks", + "@kbn/core-http-server-mocks", + "@kbn/core-http-server-utils" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/accessibility/apps/group1/roles.ts b/x-pack/test/accessibility/apps/group1/roles.ts index cf798bcb853f..8a9fe187ba35 100644 --- a/x-pack/test/accessibility/apps/group1/roles.ts +++ b/x-pack/test/accessibility/apps/group1/roles.ts @@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const a11y = getService('a11y'); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); + const find = getService('find'); const retry = getService('retry'); const kibanaServer = getService('kibanaServer'); @@ -82,6 +83,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('a11y test for customize feature privilege', async () => { + await testSubjects.click('spaceSelectorComboBox'); + const globalSpaceOption = await find.byCssSelector(`#spaceOption_\\*`); + await globalSpaceOption.click(); await testSubjects.click('featureCategory_kibana'); await a11y.testAppSnapshot(); await testSubjects.click('cancelSpacePrivilegeButton'); diff --git a/x-pack/test/accessibility/apps/group3/enterprise_search.ts b/x-pack/test/accessibility/apps/group3/enterprise_search.ts index 3ef1c02c7b8a..976f16a6c715 100644 --- a/x-pack/test/accessibility/apps/group3/enterprise_search.ts +++ b/x-pack/test/accessibility/apps/group3/enterprise_search.ts @@ -46,15 +46,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await a11y.testAppSnapshot(); }); - - it('loads a setup guide', async function () { - await testSubjects.click('setupGuideLink'); - await retry.waitFor( - 'setup guide visible', - async () => await testSubjects.exists('setupGuide') - ); - await a11y.testAppSnapshot(); - }); }); describe('Content', () => { diff --git a/x-pack/test/alerting_api_integration/packages/helpers/kibana.jsonc b/x-pack/test/alerting_api_integration/packages/helpers/kibana.jsonc index 0995f28c9c5d..2a95a25e41dd 100644 --- a/x-pack/test/alerting_api_integration/packages/helpers/kibana.jsonc +++ b/x-pack/test/alerting_api_integration/packages/helpers/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "test-helper", "id": "@kbn/alerting-api-integration-helpers", - "owner": "@elastic/response-ops", + "owner": [ + "@elastic/response-ops" + ], + "group": "platform", + "visibility": "private", "devOnly": true -} +} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/cases/common/roles.ts b/x-pack/test/api_integration/apis/cases/common/roles.ts index 21ad6943ba0d..c50076b301f7 100644 --- a/x-pack/test/api_integration/apis/cases/common/roles.ts +++ b/x-pack/test/api_integration/apis/cases/common/roles.ts @@ -136,6 +136,56 @@ export const secCasesV2All: Role = { }, }; +export const secCasesV2NoReopenWithCreateComment: Role = { + name: 'sec_cases_v2_no_reopen_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + siem: ['all'], + securitySolutionCasesV2: ['read', 'update', 'create', 'delete', 'create_comment'], + actions: ['all'], + actionsSimulators: ['all'], + }, + spaces: ['*'], + }, + ], + }, +}; + +export const secCasesV2NoCreateCommentWithReopen: Role = { + name: 'sec_cases_v2_create_comment_no_reopen_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + siem: ['all'], + securitySolutionCasesV2: ['read', 'update', 'create', 'delete', 'case_reopen'], + actions: ['all'], + actionsSimulators: ['all'], + }, + spaces: ['*'], + }, + ], + }, +}; + export const secAllSpace1: Role = { name: 'sec_all_role_space1_api_int', privileges: { @@ -434,6 +484,56 @@ export const casesV2All: Role = { }, }; +export const casesV2NoReopenWithCreateComment: Role = { + name: 'cases_v2_no_reopen_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + generalCasesV2: ['read', 'update', 'create', 'delete', 'create_comment'], + actions: ['all'], + actionsSimulators: ['all'], + }, + }, + ], + }, +}; + +export const casesV2NoCreateCommentWithReopen: Role = { + name: 'cases_v2_no_create_comment_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + generalCasesV2: ['read', 'update', 'create', 'delete', 'case_reopen'], + actions: ['all'], + actionsSimulators: ['all'], + }, + }, + ], + }, +}; + export const casesRead: Role = { name: 'cases_read_role_api_int', privileges: { @@ -583,6 +683,56 @@ export const obsCasesV2All: Role = { }, }; +export const obsCasesV2NoReopenWithCreateComment: Role = { + name: 'obs_cases_v2_no_reopen_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + observabilityCasesV2: ['read', 'update', 'create', 'delete', 'create_comment'], + actions: ['all'], + actionsSimulators: ['all'], + }, + }, + ], + }, +}; + +export const obsCasesV2NoCreateCommentWithReopen: Role = { + name: 'obs_cases_v2_no_create_comment_role_api_int', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + observabilityCasesV2: ['read', 'update', 'create', 'delete', 'case_reopen'], + actions: ['all'], + actionsSimulators: ['all'], + }, + }, + ], + }, +}; + export const obsCasesRead: Role = { name: 'obs_cases_read_role_api_int', privileges: { @@ -613,6 +763,8 @@ export const roles = [ secAllCasesNoDelete, secAll, secCasesV2All, + secCasesV2NoReopenWithCreateComment, + secCasesV2NoCreateCommentWithReopen, secAllSpace1, secAllCasesRead, secAllCasesNone, @@ -625,11 +777,15 @@ export const roles = [ casesNoDelete, casesAll, casesV2All, + casesV2NoReopenWithCreateComment, + casesV2NoCreateCommentWithReopen, casesRead, obsCasesOnlyDelete, obsCasesOnlyReadDelete, obsCasesNoDelete, obsCasesAll, obsCasesV2All, + obsCasesV2NoReopenWithCreateComment, + obsCasesV2NoCreateCommentWithReopen, obsCasesRead, ]; diff --git a/x-pack/test/api_integration/apis/cases/common/users.ts b/x-pack/test/api_integration/apis/cases/common/users.ts index a64b9767498f..d3b05c5d3ddf 100644 --- a/x-pack/test/api_integration/apis/cases/common/users.ts +++ b/x-pack/test/api_integration/apis/cases/common/users.ts @@ -31,6 +31,12 @@ import { secReadCasesAll, secReadCasesNone, secReadCasesRead, + casesV2NoReopenWithCreateComment, + obsCasesV2NoReopenWithCreateComment, + secCasesV2NoReopenWithCreateComment, + secCasesV2NoCreateCommentWithReopen, + casesV2NoCreateCommentWithReopen, + obsCasesV2NoCreateCommentWithReopen, } from './roles'; /** @@ -67,6 +73,18 @@ export const secCasesV2AllUser: User = { roles: [secCasesV2All.name], }; +export const secCasesV2NoReopenWithCreateCommentUser: User = { + username: 'sec_cases_v2_no_reopen_with_create_comment_user_api_int', + password: 'password', + roles: [secCasesV2NoReopenWithCreateComment.name], +}; + +export const secCasesV2NoCreateCommentWithReopenUser: User = { + username: 'sec_cases_v2_no_create_comment_with_reopen_user_api_int', + password: 'password', + roles: [secCasesV2NoCreateCommentWithReopen.name], +}; + export const secAllSpace1User: User = { username: 'sec_all_space1_user_api_int', password: 'password', @@ -143,6 +161,18 @@ export const casesV2AllUser: User = { roles: [casesV2All.name], }; +export const casesV2NoReopenWithCreateCommentUser: User = { + username: 'cases_v2_no_reopen_with_create_comment_user_api_int', + password: 'password', + roles: [casesV2NoReopenWithCreateComment.name], +}; + +export const casesV2NoCreateCommentWithReopenUser: User = { + username: 'cases_v2_no_create_comment_with_reopen_user_api_int', + password: 'password', + roles: [casesV2NoCreateCommentWithReopen.name], +}; + export const casesReadUser: User = { username: 'cases_read_user_api_int', password: 'password', @@ -183,6 +213,18 @@ export const obsCasesV2AllUser: User = { roles: [obsCasesV2All.name], }; +export const obsCasesV2NoReopenWithCreateCommentUser: User = { + username: 'obs_cases_v2_no_reopen_with_create_comment_user_api_int', + password: 'password', + roles: [obsCasesV2NoReopenWithCreateComment.name], +}; + +export const obsCasesV2NoCreateCommentWithReopenUser: User = { + username: 'obs_cases_v2_no_create_comment_with_reopen_user_api_int', + password: 'password', + roles: [obsCasesV2NoCreateCommentWithReopen.name], +}; + export const obsCasesReadUser: User = { username: 'obs_cases_read_user_api_int', password: 'password', @@ -211,6 +253,8 @@ export const users = [ secAllCasesNoDeleteUser, secAllUser, secCasesV2AllUser, + secCasesV2NoReopenWithCreateCommentUser, + secCasesV2NoCreateCommentWithReopenUser, secAllSpace1User, secAllCasesReadUser, secAllCasesNoneUser, @@ -223,12 +267,16 @@ export const users = [ casesNoDeleteUser, casesAllUser, casesV2AllUser, + casesV2NoReopenWithCreateCommentUser, + casesV2NoCreateCommentWithReopenUser, casesReadUser, obsCasesOnlyDeleteUser, obsCasesOnlyReadDeleteUser, obsCasesNoDeleteUser, obsCasesAllUser, obsCasesV2AllUser, + obsCasesV2NoReopenWithCreateCommentUser, + obsCasesV2NoCreateCommentWithReopenUser, obsCasesReadUser, obsSecCasesAllUser, obsSecCasesReadUser, diff --git a/x-pack/test/api_integration/apis/cases/privileges.ts b/x-pack/test/api_integration/apis/cases/privileges.ts index 53a1767f5c1a..4e2baeeffa51 100644 --- a/x-pack/test/api_integration/apis/cases/privileges.ts +++ b/x-pack/test/api_integration/apis/cases/privileges.ts @@ -40,6 +40,12 @@ import { secReadCasesNoneUser, secReadCasesReadUser, secReadUser, + casesV2NoReopenWithCreateCommentUser, + casesV2NoCreateCommentWithReopenUser, + obsCasesV2NoReopenWithCreateCommentUser, + obsCasesV2NoCreateCommentWithReopenUser, + secCasesV2NoReopenWithCreateCommentUser, + secCasesV2NoCreateCommentWithReopenUser, } from './common/users'; import { getPostCaseRequest } from '../../../cases_api_integration/common/lib/mock'; @@ -183,6 +189,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, { user: casesAllUser, owner: CASES_APP_ID }, { user: casesV2AllUser, owner: CASES_APP_ID }, + { user: casesV2NoCreateCommentWithReopenUser, owner: CASES_APP_ID }, + { user: obsCasesV2NoCreateCommentWithReopenUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV2NoCreateCommentWithReopenUser, owner: SECURITY_SOLUTION_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can reopen a case`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -206,6 +215,35 @@ export default ({ getService }: FtrProviderContext): void => { }); } + for (const { user, owner } of [ + { user: casesV2NoReopenWithCreateCommentUser, owner: CASES_APP_ID }, + { user: obsCasesV2NoReopenWithCreateCommentUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV2NoReopenWithCreateCommentUser, owner: SECURITY_SOLUTION_APP_ID }, + ]) { + it(`User ${ + user.username + } with role(s) ${user.roles.join()} CANNOT reopen a case`, async () => { + const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); + await updateCaseStatus({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + status: 'closed' as CaseStatuses, + version: '2', + expectedHttpCode: 200, + auth: { user, space: null }, + }); + + await updateCaseStatus({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + status: 'open' as CaseStatuses, + version: '3', + expectedHttpCode: 403, + auth: { user, space: null }, + }); + }); + } + for (const { user, owner } of [ { user: secAllUser, owner: SECURITY_SOLUTION_APP_ID }, { user: secCasesV2AllUser, owner: SECURITY_SOLUTION_APP_ID }, @@ -213,6 +251,9 @@ export default ({ getService }: FtrProviderContext): void => { { user: obsCasesV2AllUser, owner: OBSERVABILITY_APP_ID }, { user: casesAllUser, owner: CASES_APP_ID }, { user: casesV2AllUser, owner: CASES_APP_ID }, + { user: casesV2NoReopenWithCreateCommentUser, owner: CASES_APP_ID }, + { user: obsCasesV2NoReopenWithCreateCommentUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV2NoReopenWithCreateCommentUser, owner: SECURITY_SOLUTION_APP_ID }, ]) { it(`User ${user.username} with role(s) ${user.roles.join()} can add comments`, async () => { const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); @@ -230,5 +271,29 @@ export default ({ getService }: FtrProviderContext): void => { }); }); } + + for (const { user, owner } of [ + { user: casesV2NoCreateCommentWithReopenUser, owner: CASES_APP_ID }, + { user: obsCasesV2NoCreateCommentWithReopenUser, owner: OBSERVABILITY_APP_ID }, + { user: secCasesV2NoCreateCommentWithReopenUser, owner: SECURITY_SOLUTION_APP_ID }, + ]) { + it(`User ${ + user.username + } with role(s) ${user.roles.join()} CANNOT add comments`, async () => { + const caseInfo = await createCase(supertest, getPostCaseRequest({ owner })); + const comment: UserCommentAttachmentPayload = { + comment: 'test', + owner, + type: AttachmentType.user, + }; + await createComment({ + params: comment, + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + expectedHttpCode: 403, + auth: { user, space: null }, + }); + }); + } }); }; diff --git a/x-pack/test/api_integration/apis/content_management/dashboard_search.ts b/x-pack/test/api_integration/apis/content_management/dashboard_search.ts new file mode 100644 index 000000000000..9a1739d508d1 --- /dev/null +++ b/x-pack/test/api_integration/apis/content_management/dashboard_search.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { sampleDashboard } from './helpers'; + +export default function ({ getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const supertest = getService('supertest'); + + describe('search dashboards', function () { + const createPayload = { + ...sampleDashboard, + options: { + ...sampleDashboard.options, + references: [ + { + type: 'tag', + id: 'tag1', + name: 'tag-ref-tag1', + }, + { + type: 'index-pattern', + id: 'index-pattern1', + name: 'index-pattern-ref-index-pattern1', + }, + ], + }, + }; + before(async () => { + await kibanaServer.savedObjects.clean({ + types: ['dashboard'], + }); + + await supertest + .post('/api/content_management/rpc/create') + .set('kbn-xsrf', 'true') + .send(createPayload) + .expect(200); + }); + + it('can specify references to return', async () => { + const searchPayload = { + contentTypeId: 'dashboard', + version: 3, + query: {}, + options: {}, + }; + + { + const { body } = await supertest + .post('/api/content_management/rpc/search') + .set('kbn-xsrf', 'true') + .send(searchPayload) + .expect(200); + + expect(body.result.result.hits[0].references).to.eql(createPayload.options.references); + } + + { + const { body } = await supertest + .post('/api/content_management/rpc/search') + .set('kbn-xsrf', 'true') + .send({ ...searchPayload, options: { includeReferences: ['tag'] } }) + .expect(200); + + expect(body.result.result.hits[0].references).to.eql([createPayload.options.references[0]]); + } + }); + }); +} diff --git a/x-pack/test/api_integration/apis/content_management/index.ts b/x-pack/test/api_integration/apis/content_management/index.ts index 992aafa74d27..26541d6e7e46 100644 --- a/x-pack/test/api_integration/apis/content_management/index.ts +++ b/x-pack/test/api_integration/apis/content_management/index.ts @@ -12,5 +12,6 @@ export default function ({ loadTestFile, getService }: FtrProviderContext) { loadTestFile(require.resolve('./created_by')); loadTestFile(require.resolve('./updated_by')); loadTestFile(require.resolve('./favorites')); + loadTestFile(require.resolve('./dashboard_search')); }); } diff --git a/x-pack/test/api_integration/apis/ml/notifications/count_notifications.ts b/x-pack/test/api_integration/apis/ml/notifications/count_notifications.ts index a066999a08b5..e1ddf399d572 100644 --- a/x-pack/test/api_integration/apis/ml/notifications/count_notifications.ts +++ b/x-pack/test/api_integration/apis/ml/notifications/count_notifications.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import moment from 'moment'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; import { getCommonRequestHeader } from '../../../../functional/services/ml/common_api'; @@ -14,20 +13,25 @@ import { getCommonRequestHeader } from '../../../../functional/services/ml/commo export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); const ml = getService('ml'); + let testStart: number; describe('GET notifications count', () => { + before(async () => { + testStart = Date.now(); + }); + describe('when no ML entities present', () => { it('return a default response', async () => { const { body, status } = await supertest .get(`/internal/ml/notifications/count`) - .query({ lastCheckedAt: moment().subtract(7, 'd').valueOf() }) + .query({ lastCheckedAt: testStart }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(getCommonRequestHeader('1')); ml.api.assertResponseStatusCode(200, status, body); - expect(body.info).to.eql(0); - expect(body.warning).to.eql(0); - expect(body.error).to.eql(0); + expect(body.info).to.eql(0, `Expecting info count to be 0, got ${body.info}`); + expect(body.warning).to.eql(0, `Expecting warning count to be 0, got ${body.warning}`); + expect(body.error).to.eql(0, `Expecting error count to be 0, got ${body.error}`); }); }); @@ -36,10 +40,11 @@ export default ({ getService }: FtrProviderContext) => { await ml.api.initSavedObjects(); await ml.testResources.setKibanaTimeZoneToUTC(); - const adJobConfig = ml.commonConfig.getADFqSingleMetricJobConfig('fq_job'); + const jobId = `fq_job_${Date.now()}`; + const adJobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobId); await ml.api.createAnomalyDetectionJob(adJobConfig); - await ml.api.waitForJobNotificationsToIndex('fq_job'); + await ml.api.waitForJobNotificationsToIndex(jobId); }); after(async () => { @@ -50,14 +55,14 @@ export default ({ getService }: FtrProviderContext) => { it('return notifications count by level', async () => { const { body, status } = await supertest .get(`/internal/ml/notifications/count`) - .query({ lastCheckedAt: moment().subtract(7, 'd').valueOf() }) + .query({ lastCheckedAt: testStart }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(getCommonRequestHeader('1')); ml.api.assertResponseStatusCode(200, status, body); - expect(body.info).to.eql(1); - expect(body.warning).to.eql(0); - expect(body.error).to.eql(0); + expect(body.info).to.eql(1, `Expecting info count to be 1, got ${body.info}`); + expect(body.warning).to.eql(0, `Expecting warning count to be 0, got ${body.warning}`); + expect(body.error).to.eql(0, `Expecting error count to be 0, got ${body.error}`); }); it('returns an error for unauthorized user', async () => { diff --git a/x-pack/test/api_integration/apis/ml/notifications/get_notifications.ts b/x-pack/test/api_integration/apis/ml/notifications/get_notifications.ts index e13f1869e665..5e5f2c584c46 100644 --- a/x-pack/test/api_integration/apis/ml/notifications/get_notifications.ts +++ b/x-pack/test/api_integration/apis/ml/notifications/get_notifications.ts @@ -18,9 +18,11 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); const ml = getService('ml'); + let testStart: number; describe('GET notifications', () => { before(async () => { + testStart = Date.now(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/bm_classification'); await ml.api.initSavedObjects(); await ml.testResources.setKibanaTimeZoneToUTC(); @@ -45,7 +47,7 @@ export default ({ getService }: FtrProviderContext) => { it('return all notifications ', async () => { const { body, status } = await supertest .get(`/internal/ml/notifications`) - .query({ earliest: 'now-1d', latest: 'now' }) + .query({ earliest: testStart, latest: 'now' }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(getCommonRequestHeader('1')); ml.api.assertResponseStatusCode(200, status, body); @@ -56,7 +58,7 @@ export default ({ getService }: FtrProviderContext) => { it('return notifications based on the query string', async () => { const { body, status } = await supertest .get(`/internal/ml/notifications`) - .query({ earliest: 'now-1d', latest: 'now', queryString: 'job_type:anomaly_detector' }) + .query({ earliest: testStart, latest: 'now', queryString: 'job_type:anomaly_detector' }) .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) .set(getCommonRequestHeader('1')); ml.api.assertResponseStatusCode(200, status, body); @@ -72,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => { it('supports sorting asc sorting by field', async () => { const { body, status } = await supertest .get(`/internal/ml/notifications`) - .query({ earliest: 'now-1d', latest: 'now', sortField: 'job_id', sortDirection: 'asc' }) + .query({ earliest: testStart, latest: 'now', sortField: 'job_id', sortDirection: 'asc' }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(getCommonRequestHeader('1')); ml.api.assertResponseStatusCode(200, status, body); @@ -83,7 +85,7 @@ export default ({ getService }: FtrProviderContext) => { it('supports sorting desc sorting by field', async () => { const { body, status } = await supertest .get(`/internal/ml/notifications`) - .query({ earliest: 'now-1h', latest: 'now', sortField: 'job_id', sortDirection: 'desc' }) + .query({ earliest: testStart, latest: 'now', sortField: 'job_id', sortDirection: 'desc' }) .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) .set(getCommonRequestHeader('1')); ml.api.assertResponseStatusCode(200, status, body); diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts index 78f4347cd091..a3953a87b82b 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts @@ -32,7 +32,6 @@ export default ({ getService }: FtrProviderContext) => { }); after(async () => { - await ml.api.cleanMlIndices(); await esDeleteAllIndices('user-index_dfa*'); // delete created ingest pipelines @@ -42,6 +41,7 @@ export default ({ getService }: FtrProviderContext) => { ) ); await ml.testResources.cleanMLSavedObjects(); + await ml.api.cleanMlIndices(); }); it('returns all trained models with associated pipelines including aliases', async () => { diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts index aee2e1e2c721..6be47ba8d961 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor.ts @@ -304,7 +304,9 @@ export default function ({ getService }: FtrProviderContext) { .send(httpMonitorJson); expect(apiResponse.status).eql(403); - expect(apiResponse.body.message).eql('Forbidden'); + expect(apiResponse.body.message).eql( + 'API [POST /api/synthetics/monitors] is unauthorized for user, this action is granted by the Kibana privileges [uptime-write]' + ); }); }); diff --git a/x-pack/test/api_integration/apis/synthetics/index.ts b/x-pack/test/api_integration/apis/synthetics/index.ts index 15e76126e955..27c0febf5939 100644 --- a/x-pack/test/api_integration/apis/synthetics/index.ts +++ b/x-pack/test/api_integration/apis/synthetics/index.ts @@ -16,6 +16,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await esDeleteAllIndices('synthetics*'); }); + loadTestFile(require.resolve('./synthetics_api_security')); loadTestFile(require.resolve('./edit_monitor_public_api')); loadTestFile(require.resolve('./add_monitor_public_api')); loadTestFile(require.resolve('./synthetics_enablement')); diff --git a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts index 498da8c6b180..d1dda60c8d7c 100644 --- a/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts +++ b/x-pack/test/api_integration/apis/synthetics/services/synthetics_monitor_test_service.ts @@ -175,7 +175,7 @@ export class SyntheticsMonitorTestService { } } - async addsNewSpace() { + async addsNewSpace(uptimePermissions: string[] = ['all']) { const username = 'admin'; const password = `${username}-password`; const roleName = 'uptime-role'; @@ -190,7 +190,8 @@ export class SyntheticsMonitorTestService { kibana: [ { feature: { - uptime: ['all'], + uptime: uptimePermissions, + slo: ['all'], }, spaces: ['*'], }, diff --git a/x-pack/test/api_integration/apis/synthetics/synthetics_api_security.ts b/x-pack/test/api_integration/apis/synthetics/synthetics_api_security.ts new file mode 100644 index 000000000000..ddd0d4b20994 --- /dev/null +++ b/x-pack/test/api_integration/apis/synthetics/synthetics_api_security.ts @@ -0,0 +1,161 @@ +/* + * 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 { + syntheticsAppPublicRestApiRoutes, + syntheticsAppRestApiRoutes, +} from '@kbn/synthetics-plugin/server/routes'; +import expect from '@kbn/expect'; +import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { SyntheticsMonitorTestService } from './services/synthetics_monitor_test_service'; + +export default function ({ getService }: FtrProviderContext) { + describe('SyntheticsAPISecurity', function () { + this.tags('skipCloud'); + + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + const monitorTestService = new SyntheticsMonitorTestService(getService); + const kibanaServer = getService('kibanaServer'); + + const assertPermissions = async ( + method: 'GET' | 'POST' | 'PUT' | 'DELETE', + path: string, + options: { + statusCodes: number[]; + SPACE_ID: string; + username: string; + password: string; + writeAccess?: boolean; + tags?: string; + } + ) => { + let resp; + const { statusCodes, SPACE_ID, username, password, writeAccess } = options; + const tags = !writeAccess ? '[uptime-read]' : options.tags ?? '[uptime-read,uptime-write]'; + const getStatusMessage = (respStatus: string) => + `Expected ${statusCodes?.join( + ',' + )}, got ${respStatus} status code doesn't match, for path: ${path} and method ${method}`; + + const getBodyMessage = (tg?: string) => + `API [${method} ${path}] is unauthorized for user, this action is granted by the Kibana privileges ${ + tg ?? tags + }`; + + const verifyPermissionsBody = (res: any, msg: string) => { + if (res.status === 403 && !res.body.message.includes('MissingIndicesPrivileges:')) { + expect(decodeURIComponent(res.body.message)).to.eql(msg); + } + }; + + switch (method) { + case 'GET': + resp = await supertestWithoutAuth + .get(`/s/${SPACE_ID}${path}`) + .auth(username, password) + .set('kbn-xsrf', 'true') + .send({}); + expect(statusCodes.includes(resp.status)).to.eql(true, getStatusMessage(resp.status)); + verifyPermissionsBody(resp, getBodyMessage('[uptime-read]')); + break; + case 'PUT': + resp = await supertestWithoutAuth + .put(`/s/${SPACE_ID}${path}`) + .auth(username, password) + .set('kbn-xsrf', 'true') + .send({}); + expect(statusCodes.includes(resp.status)).to.eql(true, getStatusMessage(resp.status)); + verifyPermissionsBody(resp, getBodyMessage()); + break; + case 'POST': + resp = await supertestWithoutAuth + .post(`/s/${SPACE_ID}${path}`) + .auth(username, password) + .set('kbn-xsrf', 'true') + .send({}); + expect(statusCodes.includes(resp.status)).to.eql(true, getStatusMessage(resp.status)); + verifyPermissionsBody(resp, getBodyMessage()); + break; + case 'DELETE': + resp = await supertestWithoutAuth + .delete(`/s/${SPACE_ID}${path}`) + .auth(username, password) + .set('kbn-xsrf', 'true') + .send({}); + expect(statusCodes.includes(resp.status)).to.eql(true, getStatusMessage(resp.status)); + verifyPermissionsBody(resp, getBodyMessage()); + break; + } + + return resp; + }; + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + const allRoutes = syntheticsAppRestApiRoutes.concat(syntheticsAppPublicRestApiRoutes); + + it('throws permissions errors for un-auth user', async () => { + const { SPACE_ID, username, password } = await monitorTestService.addsNewSpace([]); + + for (const routeFn of allRoutes) { + const route = routeFn(); + await assertPermissions(route.method, route.path, { + statusCodes: [403], + SPACE_ID, + username, + password, + writeAccess: route.writeAccess ?? true, + }); + } + }); + + it('throws permissions errors for read user', async () => { + const { SPACE_ID, username, password } = await monitorTestService.addsNewSpace(['read']); + + for (const routeFn of allRoutes) { + const route = routeFn(); + if (route.writeAccess === false) { + continue; + } + await assertPermissions(route.method, route.path, { + statusCodes: [200, 403, 500, 400, 404], + SPACE_ID, + username, + password, + writeAccess: route.writeAccess ?? true, + tags: '[uptime-write]', + }); + } + }); + + it('no permissions errors for all user', async () => { + const { SPACE_ID, username, password } = await monitorTestService.addsNewSpace(['all']); + + for (const routeFn of allRoutes) { + const route = routeFn(); + if ( + (route.method === 'DELETE' && route.path === SYNTHETICS_API_URLS.SYNTHETICS_ENABLEMENT) || + SYNTHETICS_API_URLS.SYNTHETICS_PROJECT_APIKEY + ) { + continue; + } + await assertPermissions(route.method, route.path, { + statusCodes: [400, 200, 404], + SPACE_ID, + username, + password, + writeAccess: route.writeAccess ?? true, + tags: '[uptime-write]', + }); + } + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/index.ts index 46ad39938055..617f9bcae89b 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/index.ts @@ -11,7 +11,6 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) describe('custom_dashboards', () => { loadTestFile(require.resolve('./dependency_metrics.spec.ts')); loadTestFile(require.resolve('./metadata.spec.ts')); - loadTestFile(require.resolve('./service_dependencies.spec.ts')); loadTestFile(require.resolve('./top_dependencies.spec.ts')); loadTestFile(require.resolve('./top_operations.spec.ts')); loadTestFile(require.resolve('./top_spans.spec.ts')); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/service_dependencies.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/service_dependencies.spec.ts deleted file mode 100644 index 4105989e5509..000000000000 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/service_dependencies.spec.ts +++ /dev/null @@ -1,72 +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 { DependencyNode } from '@kbn/apm-plugin/common/connections'; -import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -import { generateData } from './generate_data'; - -export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { - const apmApiClient = getService('apmApi'); - const synthtrace = getService('synthtrace'); - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - const dependencyName = 'elasticsearch'; - const serviceName = 'synth-go'; - - async function callApi() { - return await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/dependencies', - params: { - path: { serviceName }, - query: { - environment: 'production', - numBuckets: 20, - offset: '1d', - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - }, - }, - }); - } - - describe('Dependency for service', () => { - describe('when data is not loaded', () => { - it('handles empty state #1', async () => { - const { status, body } = await callApi(); - - expect(status).to.be(200); - expect(body.serviceDependencies).to.empty(); - }); - }); - - describe('when data is loaded', () => { - let apmSynthtraceEsClient: ApmSynthtraceEsClient; - - before(async () => { - apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); - await generateData({ apmSynthtraceEsClient, start, end }); - }); - after(() => apmSynthtraceEsClient.clean()); - - it('returns a list of dependencies for a service', async () => { - const { status, body } = await callApi(); - - expect(status).to.be(200); - expect( - body.serviceDependencies.map( - ({ location }) => (location as DependencyNode).dependencyName - ) - ).to.eql([dependencyName]); - - const currentStatsLatencyValues = - body.serviceDependencies[0].currentStats.latency.timeseries; - expect(currentStatsLatencyValues.every(({ y }) => y === 1000000)).to.be(true); - }); - }); - }); -} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/indices.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/indices.spec.ts index 92976e6bce88..af0249d2a335 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/indices.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/indices.spec.ts @@ -21,9 +21,10 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon let apmSynthtraceEsClient: ApmSynthtraceEsClient; - describe('Diagnostics: Indices', () => { - describe.skip('When there is no data', () => { - it('returns empty response`', async () => { + // Failing tests were skipped because the current solution for verifying ingest pipelines needs improvement + describe.skip('Diagnostics: Indices', () => { + describe('When there is no data', () => { + it('returns empty response', async () => { const { status, body } = await apmApiClient.adminUser({ endpoint: 'GET /internal/apm/diagnostics', }); @@ -34,7 +35,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); - describe.skip('When data is ingested', () => { + describe('When data is ingested', () => { before(async () => { const instance = apm .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) @@ -68,7 +69,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); - describe.skip('When data is ingested without the necessary index templates', () => { + describe('When data is ingested without the necessary index templates', () => { before(async () => { await es.indices.deleteDataStream({ name: 'traces-apm-*' }); await es.indices.deleteIndexTemplate({ name: ['traces-apm'] }); @@ -114,7 +115,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); - describe.skip('ingest pipelines', () => { + describe('ingest pipelines', () => { before(async () => { const instance = apm .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) @@ -138,7 +139,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon await apmSynthtraceEsClient.clean(); }); - describe.skip('an ingest pipeline is removed', () => { + describe('an ingest pipeline is removed', () => { before(async () => { const datastreamToUpdate = await es.indices.getDataStream({ name: 'metrics-apm.internal-default', @@ -168,7 +169,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); - describe.skip('an ingest pipeline is changed', () => { + describe('an ingest pipeline is changed', () => { before(async () => { const datastreamToUpdate = await es.indices.getDataStream({ name: 'metrics-apm.internal-default', diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts index 8b80eab51157..74013fcc56e8 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts @@ -12,32 +12,35 @@ export default function apmApiIntegrationTests({ }: DeploymentAgnosticFtrProviderContext) { describe('APM', function () { loadTestFile(require.resolve('./agent_explorer')); - loadTestFile(require.resolve('./errors')); loadTestFile(require.resolve('./alerts')); - loadTestFile(require.resolve('./mobile')); + loadTestFile(require.resolve('./cold_start')); + loadTestFile(require.resolve('./correlations')); loadTestFile(require.resolve('./custom_dashboards')); + loadTestFile(require.resolve('./data_view')); loadTestFile(require.resolve('./dependencies')); + loadTestFile(require.resolve('./diagnostics')); + loadTestFile(require.resolve('./entities')); loadTestFile(require.resolve('./environment')); loadTestFile(require.resolve('./error_rate')); - loadTestFile(require.resolve('./data_view')); - loadTestFile(require.resolve('./correlations')); - loadTestFile(require.resolve('./entities')); - loadTestFile(require.resolve('./cold_start')); - loadTestFile(require.resolve('./metrics')); - loadTestFile(require.resolve('./services')); + loadTestFile(require.resolve('./errors')); loadTestFile(require.resolve('./historical_data')); - loadTestFile(require.resolve('./observability_overview')); - loadTestFile(require.resolve('./latency')); loadTestFile(require.resolve('./infrastructure')); - loadTestFile(require.resolve('./service_maps')); loadTestFile(require.resolve('./inspect')); + loadTestFile(require.resolve('./latency')); + loadTestFile(require.resolve('./metrics')); + loadTestFile(require.resolve('./mobile')); + loadTestFile(require.resolve('./observability_overview')); loadTestFile(require.resolve('./service_groups')); - loadTestFile(require.resolve('./time_range_metadata')); - loadTestFile(require.resolve('./diagnostics')); + loadTestFile(require.resolve('./service_maps')); loadTestFile(require.resolve('./service_nodes')); + loadTestFile(require.resolve('./service_overview')); + loadTestFile(require.resolve('./services')); + loadTestFile(require.resolve('./settings')); loadTestFile(require.resolve('./span_links')); loadTestFile(require.resolve('./suggestions')); loadTestFile(require.resolve('./throughput')); - loadTestFile(require.resolve('./service_overview')); + loadTestFile(require.resolve('./time_range_metadata')); + loadTestFile(require.resolve('./traces')); + loadTestFile(require.resolve('./transactions')); }); } diff --git a/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instances_detailed_statistics.spec.snap b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/__snapshots__/instances_detailed_statistics.spec.snap similarity index 71% rename from x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instances_detailed_statistics.spec.snap rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/__snapshots__/instances_detailed_statistics.spec.snap index a5444cc6a6e3..dfea3c44472a 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/__snapshots__/instances_detailed_statistics.spec.snap +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/__snapshots__/instances_detailed_statistics.spec.snap @@ -1,69 +1,69 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM API tests service_overview/instances_detailed_statistics.spec.ts basic apm_8.0.0 Service overview instances detailed statistics when data is loaded fetching data with comparison returns the right data for current and previous periods 5`] = ` +exports[`Deployment-agnostic APM API integration tests APM service_overview Service Overview Instances detailed statistics when data is loaded fetching data with comparison returns the right data for current and previous periods 5`] = ` Object { "currentPeriod": Object { - "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad": Object { + "instance-a": Object { "cpuUsage": Array [ Object { "x": 1627974300000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974360000, - "y": 0.001, + "y": 0.5, }, Object { "x": 1627974420000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974480000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974540000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974600000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974660000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974720000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974780000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974840000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974900000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974960000, - "y": 0.001, + "y": 0.5, }, Object { "x": 1627975020000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627975080000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627975140000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627975200000, @@ -73,11 +73,11 @@ Object { "errorRate": Array [ Object { "x": 1627974300000, - "y": 0, + "y": null, }, Object { "x": 1627974360000, - "y": null, + "y": 0, }, Object { "x": 1627974420000, @@ -93,15 +93,15 @@ Object { }, Object { "x": 1627974600000, - "y": 0.125, + "y": 0, }, Object { "x": 1627974660000, - "y": 0.6, + "y": 0, }, Object { "x": 1627974720000, - "y": 0.2, + "y": 0, }, Object { "x": 1627974780000, @@ -113,7 +113,7 @@ Object { }, Object { "x": 1627974900000, - "y": 0.0666666666666667, + "y": 0, }, Object { "x": 1627974960000, @@ -129,7 +129,7 @@ Object { }, Object { "x": 1627975140000, - "y": 0.181818181818182, + "y": 0, }, Object { "x": 1627975200000, @@ -139,63 +139,63 @@ Object { "latency": Array [ Object { "x": 1627974300000, - "y": 34887.8888888889, + "y": null, }, Object { "x": 1627974360000, - "y": null, + "y": 1000000, }, Object { "x": 1627974420000, - "y": 4983, + "y": 1000000, }, Object { "x": 1627974480000, - "y": 41285.4, + "y": 1000000, }, Object { "x": 1627974540000, - "y": 13820.3333333333, + "y": 1000000, }, Object { "x": 1627974600000, - "y": 13782, + "y": 1000000, }, Object { "x": 1627974660000, - "y": 13392.6, + "y": 1000000, }, Object { "x": 1627974720000, - "y": 6991, + "y": 1000000, }, Object { "x": 1627974780000, - "y": 6885.85714285714, + "y": 1000000, }, Object { "x": 1627974840000, - "y": 7935, + "y": 1000000, }, Object { "x": 1627974900000, - "y": 10828.3333333333, + "y": 1000000, }, Object { "x": 1627974960000, - "y": 6079, + "y": 1000000, }, Object { "x": 1627975020000, - "y": 5217, + "y": 1000000, }, Object { "x": 1627975080000, - "y": 8477.76923076923, + "y": 1000000, }, Object { "x": 1627975140000, - "y": 5937.18181818182, + "y": 1000000, }, Object { "x": 1627975200000, @@ -205,130 +205,130 @@ Object { "memoryUsage": Array [ Object { "x": 1627974300000, - "y": 0.786640167236328, + "y": 0.416666666666667, }, Object { "x": 1627974360000, - "y": 0.786666870117188, + "y": 0.416666666666667, }, Object { "x": 1627974420000, - "y": 0.786960601806641, + "y": 0.416666666666667, }, Object { "x": 1627974480000, - "y": 0.787132263183594, + "y": 0.416666666666667, }, Object { "x": 1627974540000, - "y": 0.787441253662109, + "y": 0.416666666666667, }, Object { "x": 1627974600000, - "y": 0.787555694580078, + "y": 0.416666666666667, }, Object { "x": 1627974660000, - "y": 0.788524627685547, + "y": 0.416666666666667, }, Object { "x": 1627974720000, - "y": 0.788822174072266, + "y": 0.416666666666667, }, Object { "x": 1627974780000, - "y": 0.789054870605469, + "y": 0.416666666666667, }, Object { "x": 1627974840000, - "y": 0.78936767578125, + "y": 0.416666666666667, }, Object { "x": 1627974900000, - "y": 0.789985656738281, + "y": 0.416666666666667, }, Object { "x": 1627974960000, - "y": 0.790130615234375, + "y": 0.416666666666667, }, Object { "x": 1627975020000, - "y": 0.790508270263672, + "y": 0.416666666666667, }, Object { "x": 1627975080000, - "y": 0.791069030761719, + "y": 0.416666666666667, }, Object { "x": 1627975140000, - "y": 0.791587829589844, + "y": 0.416666666666667, }, Object { "x": 1627975200000, "y": null, }, ], - "serviceNodeName": "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad", + "serviceNodeName": "instance-a", "throughput": Array [ Object { "x": 1627974300000, - "y": 9, + "y": 0, }, Object { "x": 1627974360000, - "y": 0, + "y": 20, }, Object { "x": 1627974420000, - "y": 4, + "y": 20, }, Object { "x": 1627974480000, - "y": 5, + "y": 20, }, Object { "x": 1627974540000, - "y": 6, + "y": 20, }, Object { "x": 1627974600000, - "y": 8, + "y": 20, }, Object { "x": 1627974660000, - "y": 5, + "y": 20, }, Object { "x": 1627974720000, - "y": 5, + "y": 20, }, Object { "x": 1627974780000, - "y": 7, + "y": 20, }, Object { "x": 1627974840000, - "y": 2, + "y": 20, }, Object { "x": 1627974900000, - "y": 15, + "y": 20, }, Object { "x": 1627974960000, - "y": 3, + "y": 20, }, Object { "x": 1627975020000, - "y": 8, + "y": 20, }, Object { "x": 1627975080000, - "y": 13, + "y": 20, }, Object { "x": 1627975140000, - "y": 11, + "y": 20, }, Object { "x": 1627975200000, @@ -338,77 +338,77 @@ Object { }, }, "previousPeriod": Object { - "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad": Object { + "instance-a": Object { "cpuUsage": Array [ Object { "x": 1627974300000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974360000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974420000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974480000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974540000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974600000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974660000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974720000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974780000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974840000, - "y": 0.0045, + "y": 0.5, }, Object { "x": 1627974900000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974960000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627975020000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627975080000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627975140000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627975200000, - "y": null, + "y": 0.5, }, ], "errorRate": Array [ Object { "x": 1627974300000, - "y": 0, + "y": null, }, Object { "x": 1627974360000, @@ -416,11 +416,11 @@ Object { }, Object { "x": 1627974420000, - "y": 0.333333333333333, + "y": 0, }, Object { "x": 1627974480000, - "y": 0.181818181818182, + "y": 0, }, Object { "x": 1627974540000, @@ -432,11 +432,11 @@ Object { }, Object { "x": 1627974660000, - "y": 0.166666666666667, + "y": 0, }, Object { "x": 1627974720000, - "y": 0.181818181818182, + "y": 0, }, Object { "x": 1627974780000, @@ -448,11 +448,11 @@ Object { }, Object { "x": 1627974900000, - "y": 0.0833333333333333, + "y": 0, }, Object { "x": 1627974960000, - "y": 0.0769230769230769, + "y": 0, }, Object { "x": 1627975020000, @@ -460,214 +460,214 @@ Object { }, Object { "x": 1627975080000, - "y": 0.1, + "y": 0, }, Object { "x": 1627975140000, - "y": 0.153846153846154, + "y": 0, }, Object { "x": 1627975200000, - "y": null, + "y": 0, }, ], "latency": Array [ Object { "x": 1627974300000, - "y": 11839, + "y": null, }, Object { "x": 1627974360000, - "y": 7407, + "y": 1000000, }, Object { "x": 1627974420000, - "y": 1925569.66666667, + "y": 1000000, }, Object { "x": 1627974480000, - "y": 9017.18181818182, + "y": 1000000, }, Object { "x": 1627974540000, - "y": 63575, + "y": 1000000, }, Object { "x": 1627974600000, - "y": 7577.66666666667, + "y": 1000000, }, Object { "x": 1627974660000, - "y": 6844.33333333333, + "y": 1000000, }, Object { "x": 1627974720000, - "y": 503471, + "y": 1000000, }, Object { "x": 1627974780000, - "y": 6247.8, + "y": 1000000, }, Object { "x": 1627974840000, - "y": 1137247, + "y": 1000000, }, Object { "x": 1627974900000, - "y": 27951.6666666667, + "y": 1000000, }, Object { "x": 1627974960000, - "y": 10248.8461538462, + "y": 1000000, }, Object { "x": 1627975020000, - "y": 13529, + "y": 1000000, }, Object { "x": 1627975080000, - "y": 6691247.8, + "y": 1000000, }, Object { "x": 1627975140000, - "y": 12098.6923076923, + "y": 1000000, }, Object { "x": 1627975200000, - "y": null, + "y": 1000000, }, ], "memoryUsage": Array [ Object { "x": 1627974300000, - "y": 0.780715942382813, + "y": 0.416666666666667, }, Object { "x": 1627974360000, - "y": 0.780921936035156, + "y": 0.416666666666667, }, Object { "x": 1627974420000, - "y": 0.781166076660156, + "y": 0.416666666666667, }, Object { "x": 1627974480000, - "y": 0.781524658203125, + "y": 0.416666666666667, }, Object { "x": 1627974540000, - "y": 0.781723022460938, + "y": 0.416666666666667, }, Object { "x": 1627974600000, - "y": 0.782463073730469, + "y": 0.416666666666667, }, Object { "x": 1627974660000, - "y": 0.782634735107422, + "y": 0.416666666666667, }, Object { "x": 1627974720000, - "y": 0.782939910888672, + "y": 0.416666666666667, }, Object { "x": 1627974780000, - "y": 0.783458709716797, + "y": 0.416666666666667, }, Object { "x": 1627974840000, - "y": 0.783935546875, + "y": 0.416666666666667, }, Object { "x": 1627974900000, - "y": 0.784690856933594, + "y": 0.416666666666667, }, Object { "x": 1627974960000, - "y": 0.785182952880859, + "y": 0.416666666666667, }, Object { "x": 1627975020000, - "y": 0.785446166992188, + "y": 0.416666666666667, }, Object { "x": 1627975080000, - "y": 0.786224365234375, + "y": 0.416666666666667, }, Object { "x": 1627975140000, - "y": 0.786415100097656, + "y": 0.416666666666667, }, Object { "x": 1627975200000, - "y": null, + "y": 0.416666666666667, }, ], - "serviceNodeName": "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad", + "serviceNodeName": "instance-a", "throughput": Array [ Object { "x": 1627974300000, - "y": 4, + "y": 0, }, Object { "x": 1627974360000, - "y": 2, + "y": 20, }, Object { "x": 1627974420000, - "y": 3, + "y": 20, }, Object { "x": 1627974480000, - "y": 11, + "y": 20, }, Object { "x": 1627974540000, - "y": 4, + "y": 20, }, Object { "x": 1627974600000, - "y": 6, + "y": 20, }, Object { "x": 1627974660000, - "y": 6, + "y": 20, }, Object { "x": 1627974720000, - "y": 11, + "y": 20, }, Object { "x": 1627974780000, - "y": 10, + "y": 20, }, Object { "x": 1627974840000, - "y": 10, + "y": 20, }, Object { "x": 1627974900000, - "y": 12, + "y": 20, }, Object { "x": 1627974960000, - "y": 13, + "y": 20, }, Object { "x": 1627975020000, - "y": 8, + "y": 20, }, Object { "x": 1627975080000, - "y": 10, + "y": 20, }, Object { "x": 1627975140000, - "y": 13, + "y": 20, }, Object { "x": 1627975200000, - "y": 0, + "y": 20, }, ], }, @@ -675,130 +675,130 @@ Object { } `; -exports[`APM API tests service_overview/instances_detailed_statistics.spec.ts basic apm_8.0.0 Service overview instances detailed statistics when data is loaded fetching data without comparison returns the right data 3`] = ` +exports[`Deployment-agnostic APM API integration tests APM service_overview Service Overview Instances detailed statistics when data is loaded fetching data without comparison returns the right data 3`] = ` Object { "currentPeriod": Object { - "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad": Object { + "instance-a": Object { "cpuUsage": Array [ Object { "x": 1627973400000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627973460000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627973520000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627973580000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627973640000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627973700000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627973760000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627973820000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627973880000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627973940000, - "y": 0.0045, + "y": 0.5, }, Object { "x": 1627974000000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974060000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974120000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974180000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974240000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974300000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974360000, - "y": 0.001, + "y": 0.5, }, Object { "x": 1627974420000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974480000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974540000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974600000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974660000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974720000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974780000, - "y": 0.0015, + "y": 0.5, }, Object { "x": 1627974840000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627974900000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627974960000, - "y": 0.001, + "y": 0.5, }, Object { "x": 1627975020000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627975080000, - "y": 0.002, + "y": 0.5, }, Object { "x": 1627975140000, - "y": 0.0025, + "y": 0.5, }, Object { "x": 1627975200000, @@ -808,7 +808,7 @@ Object { "errorRate": Array [ Object { "x": 1627973400000, - "y": 0, + "y": null, }, Object { "x": 1627973460000, @@ -816,11 +816,11 @@ Object { }, Object { "x": 1627973520000, - "y": 0.333333333333333, + "y": 0, }, Object { "x": 1627973580000, - "y": 0.181818181818182, + "y": 0, }, Object { "x": 1627973640000, @@ -832,11 +832,11 @@ Object { }, Object { "x": 1627973760000, - "y": 0.166666666666667, + "y": 0, }, Object { "x": 1627973820000, - "y": 0.181818181818182, + "y": 0, }, Object { "x": 1627973880000, @@ -848,11 +848,11 @@ Object { }, Object { "x": 1627974000000, - "y": 0.0833333333333333, + "y": 0, }, Object { "x": 1627974060000, - "y": 0.0769230769230769, + "y": 0, }, Object { "x": 1627974120000, @@ -860,11 +860,11 @@ Object { }, Object { "x": 1627974180000, - "y": 0.1, + "y": 0, }, Object { "x": 1627974240000, - "y": 0.153846153846154, + "y": 0, }, Object { "x": 1627974300000, @@ -872,7 +872,7 @@ Object { }, Object { "x": 1627974360000, - "y": null, + "y": 0, }, Object { "x": 1627974420000, @@ -888,15 +888,15 @@ Object { }, Object { "x": 1627974600000, - "y": 0.125, + "y": 0, }, Object { "x": 1627974660000, - "y": 0.6, + "y": 0, }, Object { "x": 1627974720000, - "y": 0.2, + "y": 0, }, Object { "x": 1627974780000, @@ -908,7 +908,7 @@ Object { }, Object { "x": 1627974900000, - "y": 0.0666666666666667, + "y": 0, }, Object { "x": 1627974960000, @@ -924,7 +924,7 @@ Object { }, Object { "x": 1627975140000, - "y": 0.181818181818182, + "y": 0, }, Object { "x": 1627975200000, @@ -934,123 +934,123 @@ Object { "latency": Array [ Object { "x": 1627973400000, - "y": 11839, + "y": null, }, Object { "x": 1627973460000, - "y": 7407, + "y": 1000000, }, Object { "x": 1627973520000, - "y": 1925569.66666667, + "y": 1000000, }, Object { "x": 1627973580000, - "y": 9017.18181818182, + "y": 1000000, }, Object { "x": 1627973640000, - "y": 63575, + "y": 1000000, }, Object { "x": 1627973700000, - "y": 7577.66666666667, + "y": 1000000, }, Object { "x": 1627973760000, - "y": 6844.33333333333, + "y": 1000000, }, Object { "x": 1627973820000, - "y": 503471, + "y": 1000000, }, Object { "x": 1627973880000, - "y": 6247.8, + "y": 1000000, }, Object { "x": 1627973940000, - "y": 1137247, + "y": 1000000, }, Object { "x": 1627974000000, - "y": 27951.6666666667, + "y": 1000000, }, Object { "x": 1627974060000, - "y": 10248.8461538462, + "y": 1000000, }, Object { "x": 1627974120000, - "y": 13529, + "y": 1000000, }, Object { "x": 1627974180000, - "y": 6691247.8, + "y": 1000000, }, Object { "x": 1627974240000, - "y": 12098.6923076923, + "y": 1000000, }, Object { "x": 1627974300000, - "y": 34887.8888888889, + "y": 1000000, }, Object { "x": 1627974360000, - "y": null, + "y": 1000000, }, Object { "x": 1627974420000, - "y": 4983, + "y": 1000000, }, Object { "x": 1627974480000, - "y": 41285.4, + "y": 1000000, }, Object { "x": 1627974540000, - "y": 13820.3333333333, + "y": 1000000, }, Object { "x": 1627974600000, - "y": 13782, + "y": 1000000, }, Object { "x": 1627974660000, - "y": 13392.6, + "y": 1000000, }, Object { "x": 1627974720000, - "y": 6991, + "y": 1000000, }, Object { "x": 1627974780000, - "y": 6885.85714285714, + "y": 1000000, }, Object { "x": 1627974840000, - "y": 7935, + "y": 1000000, }, Object { "x": 1627974900000, - "y": 10828.3333333333, + "y": 1000000, }, Object { "x": 1627974960000, - "y": 6079, + "y": 1000000, }, Object { "x": 1627975020000, - "y": 5217, + "y": 1000000, }, Object { "x": 1627975080000, - "y": 8477.76923076923, + "y": 1000000, }, Object { "x": 1627975140000, - "y": 5937.18181818182, + "y": 1000000, }, Object { "x": 1627975200000, @@ -1060,250 +1060,250 @@ Object { "memoryUsage": Array [ Object { "x": 1627973400000, - "y": 0.780715942382813, + "y": 0.416666666666667, }, Object { "x": 1627973460000, - "y": 0.780921936035156, + "y": 0.416666666666667, }, Object { "x": 1627973520000, - "y": 0.781166076660156, + "y": 0.416666666666667, }, Object { "x": 1627973580000, - "y": 0.781524658203125, + "y": 0.416666666666667, }, Object { "x": 1627973640000, - "y": 0.781723022460938, + "y": 0.416666666666667, }, Object { "x": 1627973700000, - "y": 0.782463073730469, + "y": 0.416666666666667, }, Object { "x": 1627973760000, - "y": 0.782634735107422, + "y": 0.416666666666667, }, Object { "x": 1627973820000, - "y": 0.782939910888672, + "y": 0.416666666666667, }, Object { "x": 1627973880000, - "y": 0.783458709716797, + "y": 0.416666666666667, }, Object { "x": 1627973940000, - "y": 0.783935546875, + "y": 0.416666666666667, }, Object { "x": 1627974000000, - "y": 0.784690856933594, + "y": 0.416666666666667, }, Object { "x": 1627974060000, - "y": 0.785182952880859, + "y": 0.416666666666667, }, Object { "x": 1627974120000, - "y": 0.785446166992188, + "y": 0.416666666666667, }, Object { "x": 1627974180000, - "y": 0.786224365234375, + "y": 0.416666666666667, }, Object { "x": 1627974240000, - "y": 0.786415100097656, + "y": 0.416666666666667, }, Object { "x": 1627974300000, - "y": 0.786640167236328, + "y": 0.416666666666667, }, Object { "x": 1627974360000, - "y": 0.786666870117188, + "y": 0.416666666666667, }, Object { "x": 1627974420000, - "y": 0.786960601806641, + "y": 0.416666666666667, }, Object { "x": 1627974480000, - "y": 0.787132263183594, + "y": 0.416666666666667, }, Object { "x": 1627974540000, - "y": 0.787441253662109, + "y": 0.416666666666667, }, Object { "x": 1627974600000, - "y": 0.787555694580078, + "y": 0.416666666666667, }, Object { "x": 1627974660000, - "y": 0.788524627685547, + "y": 0.416666666666667, }, Object { "x": 1627974720000, - "y": 0.788822174072266, + "y": 0.416666666666667, }, Object { "x": 1627974780000, - "y": 0.789054870605469, + "y": 0.416666666666667, }, Object { "x": 1627974840000, - "y": 0.78936767578125, + "y": 0.416666666666667, }, Object { "x": 1627974900000, - "y": 0.789985656738281, + "y": 0.416666666666667, }, Object { "x": 1627974960000, - "y": 0.790130615234375, + "y": 0.416666666666667, }, Object { "x": 1627975020000, - "y": 0.790508270263672, + "y": 0.416666666666667, }, Object { "x": 1627975080000, - "y": 0.791069030761719, + "y": 0.416666666666667, }, Object { "x": 1627975140000, - "y": 0.791587829589844, + "y": 0.416666666666667, }, Object { "x": 1627975200000, "y": null, }, ], - "serviceNodeName": "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad", + "serviceNodeName": "instance-a", "throughput": Array [ Object { "x": 1627973400000, - "y": 4, + "y": 0, }, Object { "x": 1627973460000, - "y": 2, + "y": 20, }, Object { "x": 1627973520000, - "y": 3, + "y": 20, }, Object { "x": 1627973580000, - "y": 11, + "y": 20, }, Object { "x": 1627973640000, - "y": 4, + "y": 20, }, Object { "x": 1627973700000, - "y": 6, + "y": 20, }, Object { "x": 1627973760000, - "y": 6, + "y": 20, }, Object { "x": 1627973820000, - "y": 11, + "y": 20, }, Object { "x": 1627973880000, - "y": 10, + "y": 20, }, Object { "x": 1627973940000, - "y": 10, + "y": 20, }, Object { "x": 1627974000000, - "y": 12, + "y": 20, }, Object { "x": 1627974060000, - "y": 13, + "y": 20, }, Object { "x": 1627974120000, - "y": 8, + "y": 20, }, Object { "x": 1627974180000, - "y": 10, + "y": 20, }, Object { "x": 1627974240000, - "y": 13, + "y": 20, }, Object { "x": 1627974300000, - "y": 9, + "y": 20, }, Object { "x": 1627974360000, - "y": 0, + "y": 20, }, Object { "x": 1627974420000, - "y": 4, + "y": 20, }, Object { "x": 1627974480000, - "y": 5, + "y": 20, }, Object { "x": 1627974540000, - "y": 6, + "y": 20, }, Object { "x": 1627974600000, - "y": 8, + "y": 20, }, Object { "x": 1627974660000, - "y": 5, + "y": 20, }, Object { "x": 1627974720000, - "y": 5, + "y": 20, }, Object { "x": 1627974780000, - "y": 7, + "y": 20, }, Object { "x": 1627974840000, - "y": 2, + "y": 20, }, Object { "x": 1627974900000, - "y": 15, + "y": 20, }, Object { "x": 1627974960000, - "y": 3, + "y": 20, }, Object { "x": 1627975020000, - "y": 8, + "y": 20, }, Object { "x": 1627975080000, - "y": 13, + "y": 20, }, Object { "x": 1627975140000, - "y": 11, + "y": 20, }, Object { "x": 1627975200000, diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/dependencies/index.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/dependencies/index.spec.ts index cb7f81304c3b..a579e196e7aa 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/dependencies/index.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/dependencies/index.spec.ts @@ -4,301 +4,328 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import expect from '@kbn/expect'; import { last, pick } from 'lodash'; +import { DependencyNode } from '@kbn/apm-plugin/common/connections'; import type { ValuesType } from 'utility-types'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { type Node, NodeType } from '@kbn/apm-plugin/common/connections'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '@kbn/apm-plugin/common/environment_filter_values'; -import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { roundNumber } from '../../utils/common'; import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import { roundNumber } from '../../utils/common'; +import { generateDependencyData } from '../generate_data'; import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils'; export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const es = getService('es'); - - const { start, end } = { - start: '2021-08-03T06:50:15.910Z', - end: '2021-08-03T07:20:15.910Z', - }; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const dependencyName = 'elasticsearch'; + const serviceName = 'synth-go'; function getName(node: Node) { return node.type === NodeType.service ? node.serviceName : node.dependencyName; } - describe('Service Overview', () => { - describe('Dependencies', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await apmApiClient.readUser({ - endpoint: `GET /internal/apm/services/{serviceName}/dependencies`, - params: { - path: { serviceName: 'opbeans-java' }, - query: { - start, - end, - numBuckets: 20, - environment: ENVIRONMENT_ALL.value, - }, - }, - }); + async function callApi() { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/dependencies', + params: { + path: { serviceName }, + query: { + environment: 'production', + numBuckets: 20, + offset: '1d', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + }, + }, + }); + } - expect(response.status).to.be(200); - expect(response.body.serviceDependencies).to.eql([]); - }); + describe('Dependency for service', () => { + describe('when data is not loaded', () => { + it('handles empty state #1', async () => { + const { status, body } = await callApi(); + + expect(status).to.be(200); + expect(body.serviceDependencies).to.be.empty(); }); + }); - describe('when specific data is loaded', () => { - let response: { - status: number; - body: APIReturnType<'GET /internal/apm/services/{serviceName}/dependencies'>; - }; + describe('when specific data is loaded', () => { + let response: { + status: number; + body: APIReturnType<'GET /internal/apm/services/{serviceName}/dependencies'>; + }; + + const indices = { + metric: 'apm-dependencies-metric', + transaction: 'apm-dependencies-transaction', + span: 'apm-dependencies-span', + }; + + const startTime = new Date(start).getTime(); + const endTime = new Date(end).getTime(); + + after(async () => { + const allIndices = Object.values(indices).join(','); + const indexExists = await es.indices.exists({ index: allIndices }); + if (indexExists) { + await es.indices.delete({ + index: allIndices, + }); + } + }); - const indices = { - metric: 'apm-dependencies-metric', - transaction: 'apm-dependencies-transaction', - span: 'apm-dependencies-span', - }; + before(async () => { + await es.indices.create({ + index: indices.metric, + body: { + mappings: apmDependenciesMapping, + }, + }); - const startTime = new Date(start).getTime(); - const endTime = new Date(end).getTime(); - - after(async () => { - const allIndices = Object.values(indices).join(','); - const indexExists = await es.indices.exists({ index: allIndices }); - if (indexExists) { - await es.indices.delete({ - index: allIndices, - }); - } + await es.indices.create({ + index: indices.transaction, + body: { + mappings: apmDependenciesMapping, + }, }); - before(async () => { - await es.indices.create({ - index: indices.metric, - body: { - mappings: apmDependenciesMapping, - }, - }); + await es.indices.create({ + index: indices.span, + body: { + mappings: apmDependenciesMapping, + }, + }); - await es.indices.create({ - index: indices.transaction, - body: { - mappings: apmDependenciesMapping, + const docs = [ + ...createServiceDependencyDocs({ + service: { + name: 'opbeans-java', + environment: 'production', }, - }); - - await es.indices.create({ - index: indices.span, - body: { - mappings: apmDependenciesMapping, + agentName: 'java', + span: { + type: 'external', + subtype: 'http', }, - }); - - const docs = [ - ...createServiceDependencyDocs({ - service: { - name: 'opbeans-java', - environment: 'production', - }, - agentName: 'java', - span: { - type: 'external', - subtype: 'http', - }, - resource: 'opbeans-node:3000', - outcome: 'success', - responseTime: { - count: 2, - sum: 10, - }, - time: startTime, - to: { - service: { - name: 'opbeans-node', - }, - agentName: 'nodejs', - }, - }), - ...createServiceDependencyDocs({ - service: { - name: 'opbeans-java', - environment: 'production', - }, - agentName: 'java', - span: { - type: 'external', - subtype: 'http', - }, - resource: 'opbeans-node:3000', - outcome: 'failure', - responseTime: { - count: 1, - sum: 10, - }, - time: startTime, - }), - ...createServiceDependencyDocs({ + resource: 'opbeans-node:3000', + outcome: 'success', + responseTime: { + count: 2, + sum: 10, + }, + time: startTime, + to: { service: { - name: 'opbeans-java', - environment: 'production', - }, - agentName: 'java', - span: { - type: 'external', - subtype: 'http', - }, - resource: 'postgres', - outcome: 'success', - responseTime: { - count: 1, - sum: 3, + name: 'opbeans-node', }, - time: startTime, - }), - ...createServiceDependencyDocs({ + agentName: 'nodejs', + }, + }), + ...createServiceDependencyDocs({ + service: { + name: 'opbeans-java', + environment: 'production', + }, + agentName: 'java', + span: { + type: 'external', + subtype: 'http', + }, + resource: 'opbeans-node:3000', + outcome: 'failure', + responseTime: { + count: 1, + sum: 10, + }, + time: startTime, + }), + ...createServiceDependencyDocs({ + service: { + name: 'opbeans-java', + environment: 'production', + }, + agentName: 'java', + span: { + type: 'external', + subtype: 'http', + }, + resource: 'postgres', + outcome: 'success', + responseTime: { + count: 1, + sum: 3, + }, + time: startTime, + }), + ...createServiceDependencyDocs({ + service: { + name: 'opbeans-java', + environment: 'production', + }, + agentName: 'java', + span: { + type: 'external', + subtype: 'http', + }, + resource: 'opbeans-node-via-proxy', + outcome: 'success', + responseTime: { + count: 1, + sum: 1, + }, + time: endTime - 1, + to: { service: { - name: 'opbeans-java', - environment: 'production', - }, - agentName: 'java', - span: { - type: 'external', - subtype: 'http', - }, - resource: 'opbeans-node-via-proxy', - outcome: 'success', - responseTime: { - count: 1, - sum: 1, + name: 'opbeans-node', }, - time: endTime - 1, - to: { - service: { - name: 'opbeans-node', - }, - agentName: 'nodejs', - }, - }), - ]; - - const bulkActions = docs.reduce( - (prev, doc) => { - return [...prev, { index: { _index: indices[doc.processor.event] } }, doc]; + agentName: 'nodejs', }, - [] as Array< - | { - index: { - _index: string; - }; - } - | ValuesType<typeof docs> - > - ); - - await es.bulk({ - body: bulkActions, - refresh: 'wait_for', - }); + }), + ]; + + const bulkActions = docs.reduce( + (prev, doc) => { + return [...prev, { index: { _index: indices[doc.processor.event] } }, doc]; + }, + [] as Array< + | { + index: { + _index: string; + }; + } + | ValuesType<typeof docs> + > + ); + + await es.bulk({ + body: bulkActions, + refresh: 'wait_for', + }); - response = await apmApiClient.readUser({ - endpoint: `GET /internal/apm/services/{serviceName}/dependencies`, - params: { - path: { serviceName: 'opbeans-java' }, - query: { - start, - end, - numBuckets: 20, - environment: ENVIRONMENT_ALL.value, - }, + response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/dependencies`, + params: { + path: { serviceName: 'opbeans-java' }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + numBuckets: 20, + environment: ENVIRONMENT_ALL.value, }, - }); + }, }); + }); - it('returns a 200', () => { - expect(response.status).to.be(200); - }); + it('returns a 200', () => { + expect(response.status).to.be(200); + }); - it('returns two dependencies', () => { - expect(response.body.serviceDependencies.length).to.be(2); - }); + it('returns two dependencies', () => { + expect(response.body.serviceDependencies.length).to.be(2); + }); - it('returns opbeans-node as a dependency', () => { - const opbeansNode = response.body.serviceDependencies.find( - (item) => getName(item.location) === 'opbeans-node' - ); - - expect(opbeansNode !== undefined).to.be(true); - - const values = { - latency: roundNumber(opbeansNode?.currentStats.latency.value), - throughput: roundNumber(opbeansNode?.currentStats.throughput.value), - errorRate: roundNumber(opbeansNode?.currentStats.errorRate.value), - impact: opbeansNode?.currentStats.impact, - ...pick(opbeansNode?.location, 'serviceName', 'type', 'agentName', 'environment'), - }; - - const count = 4; - const sum = 21; - const errors = 1; - - expect(values).to.eql({ - agentName: 'nodejs', - environment: ENVIRONMENT_NOT_DEFINED.value, - serviceName: 'opbeans-node', - type: 'service', - errorRate: roundNumber(errors / count), - latency: roundNumber(sum / count), - throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)), - impact: 100, - }); + it('returns opbeans-node as a dependency', () => { + const opbeansNode = response.body.serviceDependencies.find( + (item) => getName(item.location) === 'opbeans-node' + ); - const firstValue = roundNumber(opbeansNode?.currentStats.latency.timeseries[0].y); - const lastValue = roundNumber(last(opbeansNode?.currentStats.latency.timeseries)?.y); + expect(opbeansNode !== undefined).to.be(true); - expect(firstValue).to.be(roundNumber(20 / 3)); - expect(lastValue).to.be(1); + const values = { + latency: roundNumber(opbeansNode?.currentStats.latency.value), + throughput: roundNumber(opbeansNode?.currentStats.throughput.value), + errorRate: roundNumber(opbeansNode?.currentStats.errorRate.value), + impact: opbeansNode?.currentStats.impact, + ...pick(opbeansNode?.location, 'serviceName', 'type', 'agentName', 'environment'), + }; + + const count = 4; + const sum = 21; + const errors = 1; + + expect(values).to.eql({ + agentName: 'nodejs', + environment: ENVIRONMENT_NOT_DEFINED.value, + serviceName: 'opbeans-node', + type: 'service', + errorRate: roundNumber(errors / count), + latency: roundNumber(sum / count), + throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)), + impact: 100, }); - it('returns postgres as an external dependency', () => { - const postgres = response.body.serviceDependencies.find( - (item) => getName(item.location) === 'postgres' - ); - - expect(postgres !== undefined).to.be(true); - - const values = { - latency: roundNumber(postgres?.currentStats.latency.value), - throughput: roundNumber(postgres?.currentStats.throughput.value), - errorRate: roundNumber(postgres?.currentStats.errorRate.value), - impact: postgres?.currentStats.impact, - ...pick(postgres?.location, 'spanType', 'spanSubtype', 'dependencyName', 'type'), - }; - - const count = 1; - const sum = 3; - const errors = 0; - - expect(values).to.eql({ - spanType: 'external', - spanSubtype: 'http', - dependencyName: 'postgres', - type: 'dependency', - errorRate: roundNumber(errors / count), - latency: roundNumber(sum / count), - throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)), - impact: 0, - }); + const firstValue = roundNumber(opbeansNode?.currentStats.latency.timeseries[0].y); + const lastValue = roundNumber(last(opbeansNode?.currentStats.latency.timeseries)?.y); + + expect(firstValue).to.be(roundNumber(20 / 3)); + expect(lastValue).to.be(1); + }); + + it('returns postgres as an external dependency', () => { + const postgres = response.body.serviceDependencies.find( + (item) => getName(item.location) === 'postgres' + ); + + expect(postgres !== undefined).to.be(true); + + const values = { + latency: roundNumber(postgres?.currentStats.latency.value), + throughput: roundNumber(postgres?.currentStats.throughput.value), + errorRate: roundNumber(postgres?.currentStats.errorRate.value), + impact: postgres?.currentStats.impact, + ...pick(postgres?.location, 'spanType', 'spanSubtype', 'dependencyName', 'type'), + }; + + const count = 1; + const sum = 3; + const errors = 0; + + expect(values).to.eql({ + spanType: 'external', + spanSubtype: 'http', + dependencyName: 'postgres', + type: 'dependency', + errorRate: roundNumber(errors / count), + latency: roundNumber(sum / count), + throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)), + impact: 0, }); }); + }); - // UNSUPPORTED TEST CASES - when data is loaded - // TODO: These tests should be migrated to use synthtrace: https://github.com/elastic/kibana/issues/200743 + describe('when data is loaded', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + await generateDependencyData({ apmSynthtraceEsClient, start, end }); + }); + after(() => apmSynthtraceEsClient.clean()); + + it('returns a list of dependencies for a service', async () => { + const { status, body } = await callApi(); + + expect(status).to.be(200); + expect( + body.serviceDependencies.map( + ({ location }) => (location as DependencyNode).dependencyName + ) + ).to.eql([dependencyName]); + + const currentStatsLatencyValues = + body.serviceDependencies[0].currentStats.latency.timeseries; + expect(currentStatsLatencyValues.every(({ y }) => y === 1000000)).to.be(true); + }); }); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/generate_data.ts new file mode 100644 index 000000000000..150a3ff00fde --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/generate_data.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; + +export const dataConfig = { + rate: 20, + transaction: { + name: 'GET /api/product/list', + duration: 1000, + }, + span: { + name: 'GET apm-*/_search', + type: 'db', + subType: 'elasticsearch', + destination: 'elasticsearch', + }, +} as const; + +export async function generateServiceData({ + apmSynthtraceEsClient, + start, + end, + name, + environment, + agentName, +}: { + apmSynthtraceEsClient: ApmSynthtraceEsClient; + start: number; + end: number; + name: string; + environment: string; + agentName: string; +}) { + const instance = apm.service({ name, environment, agentName }).instance('instance-a'); + const { rate, transaction, span } = dataConfig; + + await apmSynthtraceEsClient.index([ + timerange(start, end) + .interval('1m') + .rate(rate) + .generator((timestamp) => + instance + .transaction({ transactionName: transaction.name }) + .timestamp(timestamp) + .duration(transaction.duration) + .success() + .children( + instance + .span({ spanName: span.name, spanType: span.type, spanSubtype: span.subType }) + .duration(transaction.duration) + .success() + .destination(span.destination) + .timestamp(timestamp) + ) + ), + timerange(start, end) + .interval('1m') + .rate(rate) + .generator((timestamp) => + instance + .appMetrics({ + 'system.process.cpu.total.norm.pct': 0.5, + 'system.memory.total': 120000, + 'system.process.cgroup.memory.mem.usage.bytes': 50000, + }) + .timestamp(timestamp) + ), + ]); +} + +export async function generateDependencyData({ + apmSynthtraceEsClient, + start, + end, +}: { + apmSynthtraceEsClient: ApmSynthtraceEsClient; + start: number; + end: number; +}) { + const instance = apm + .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) + .instance('instance-a'); + const { rate, transaction, span } = dataConfig; + + await apmSynthtraceEsClient.index( + timerange(start, end) + .interval('1m') + .rate(rate) + .generator((timestamp) => + instance + .transaction({ transactionName: transaction.name }) + .timestamp(timestamp) + .duration(transaction.duration) + .success() + .children( + instance + .span({ spanName: span.name, spanType: span.type, spanSubtype: span.subType }) + .duration(transaction.duration) + .success() + .destination(span.destination) + .timestamp(timestamp) + ) + ) + ); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/instances_detailed_statistics.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/instances_detailed_statistics.spec.ts index b2596ae43c95..7fd39b650b71 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/instances_detailed_statistics.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_overview/instances_detailed_statistics.spec.ts @@ -6,12 +6,19 @@ */ import expect from '@kbn/expect'; +import moment from 'moment'; +import type { Coordinate } from '@kbn/apm-plugin/typings/timeseries'; import { LatencyAggregationType } from '@kbn/apm-plugin/common/latency_aggregation_types'; +import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number'; import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { getServiceNodeIds } from './get_service_node_ids'; +import { generateServiceData } from './generate_data'; export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const serviceName = 'opbeans-java'; @@ -20,6 +27,11 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon end: '2021-08-03T07:20:15.910Z', }; + interface Response { + status: number; + body: APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; + } + describe('Service Overview', () => { describe('Instances detailed statistics', () => { describe('when data is not loaded', () => { @@ -49,8 +61,166 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon }); }); - // UNSUPPORTED TEST CASES - when data is loaded - // TODO: These tests should be migrated to use synthtrace: https://github.com/elastic/kibana/issues/200743 + describe('when data is loaded', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + await generateServiceData({ + apmSynthtraceEsClient, + start: new Date(start).getTime(), + end: new Date(end).getTime(), + name: serviceName, + environment: 'ENVIRONMENT_ALL', + agentName: 'java', + }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('fetching data without comparison', () => { + let response: Response; + let serviceNodeIds: string[]; + + beforeEach(async () => { + serviceNodeIds = await getServiceNodeIds({ + apmApiClient, + start, + end, + }); + + response = await apmApiClient.readUser({ + endpoint: + 'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics', + params: { + path: { serviceName }, + query: { + latencyAggregationType: LatencyAggregationType.avg, + start, + end, + numBuckets: 20, + transactionType: 'request', + serviceNodeIds: JSON.stringify(serviceNodeIds), + environment: 'ENVIRONMENT_ALL', + kuery: '', + }, + }, + }); + }); + + it('returns a service node item', () => { + expect(Object.values(response.body.currentPeriod).length).to.be.greaterThan(0); + expect(Object.values(response.body.previousPeriod)).to.eql(0); + }); + + it('returns statistics for each service node', () => { + const item = response.body.currentPeriod[serviceNodeIds[0]]; + + expect(item?.cpuUsage?.some((point) => isFiniteNumber(point.y))).to.be(true); + expect(item?.memoryUsage?.some((point) => isFiniteNumber(point.y))).to.be(true); + expect(item?.errorRate?.some((point) => isFiniteNumber(point.y))).to.be(true); + expect(item?.throughput?.some((point) => isFiniteNumber(point.y))).to.be(true); + expect(item?.latency?.some((point) => isFiniteNumber(point.y))).to.be(true); + }); + + it('returns the right data', () => { + expectSnapshot(Object.values(response.body.currentPeriod).length).toMatchInline(`1`); + + expectSnapshot(Object.keys(response.body.currentPeriod)).toMatchInline(` + Array [ + "instance-a", + ] + `); + + expectSnapshot(response.body).toMatch(); + }); + }); + + describe('fetching data with comparison', () => { + let response: Response; + let serviceNodeIds: string[]; + + beforeEach(async () => { + serviceNodeIds = await getServiceNodeIds({ + apmApiClient, + start, + end, + }); + response = await apmApiClient.readUser({ + endpoint: + 'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics', + params: { + path: { serviceName }, + query: { + latencyAggregationType: LatencyAggregationType.avg, + numBuckets: 20, + transactionType: 'request', + serviceNodeIds: JSON.stringify(serviceNodeIds), + start: moment(end).subtract(15, 'minutes').toISOString(), + end, + offset: '15m', + environment: 'ENVIRONMENT_ALL', + kuery: '', + }, + }, + }); + }); + + it('returns a service node item for current and previous periods', () => { + expect(Object.values(response.body.currentPeriod).length).to.be.greaterThan(0); + expect(Object.values(response.body.previousPeriod).length).to.be.greaterThan(0); + }); + + it('returns statistics for current and previous periods', () => { + const currentPeriodItem = response.body.currentPeriod[serviceNodeIds[0]]; + + function hasValidYCoordinate(point: Coordinate) { + return isFiniteNumber(point.y); + } + + expect(currentPeriodItem?.cpuUsage?.some(hasValidYCoordinate)).to.be(true); + expect(currentPeriodItem?.memoryUsage?.some(hasValidYCoordinate)).to.be(true); + expect(currentPeriodItem?.errorRate?.some(hasValidYCoordinate)).to.be(true); + expect(currentPeriodItem?.throughput?.some(hasValidYCoordinate)).to.be(true); + expect(currentPeriodItem?.latency?.some(hasValidYCoordinate)).to.be(true); + + const previousPeriodItem = response.body.previousPeriod[serviceNodeIds[0]]; + + expect(previousPeriodItem?.cpuUsage?.some(hasValidYCoordinate)).to.be(true); + expect(previousPeriodItem?.memoryUsage?.some(hasValidYCoordinate)).to.be(true); + expect(previousPeriodItem?.errorRate?.some(hasValidYCoordinate)).to.be(true); + expect(previousPeriodItem?.throughput?.some(hasValidYCoordinate)).to.be(true); + expect(previousPeriodItem?.latency?.some(hasValidYCoordinate)).to.be(true); + }); + + it('returns the right data for current and previous periods', () => { + expectSnapshot(Object.values(response.body.currentPeriod).length).toMatchInline(`1`); + expectSnapshot(Object.values(response.body.previousPeriod).length).toMatchInline(`1`); + + expectSnapshot(Object.keys(response.body.currentPeriod)).toMatchInline(` + Array [ + "instance-a", + ] + `); + expectSnapshot(Object.keys(response.body.previousPeriod)).toMatchInline(` + Array [ + "instance-a", + ] + `); + + expectSnapshot(response.body).toMatch(); + }); + + it('matches x-axis on current period and previous period', () => { + const currentLatencyItems = response.body.currentPeriod[serviceNodeIds[0]]?.latency; + const previousLatencyItems = response.body.previousPeriod[serviceNodeIds[0]]?.latency; + + expect(currentLatencyItems?.map(({ x }) => x)).to.be.eql( + previousLatencyItems?.map(({ x }) => x) + ); + }); + }); + }); }); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/derived_annotations.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/derived_annotations.spec.ts index 3af97dea84c7..272ddb876573 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/derived_annotations.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/services/derived_annotations.spec.ts @@ -118,7 +118,7 @@ export default function annotationApiTests({ getService }: DeploymentAgnosticFtr }); response = ( - await apmApiClient.readUser({ + await apmApiClient.publicApi({ endpoint: 'GET /api/apm/services/{serviceName}/annotation/search 2023-10-31', params: { path: { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/agent_keys/agent_keys.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/agent_keys/agent_keys.spec.ts new file mode 100644 index 000000000000..925820c0e7c1 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/agent_keys/agent_keys.spec.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { PrivilegeType, ClusterPrivilegeType } from '@kbn/apm-plugin/common/privilege_type'; +import type { RoleCredentials } from '../../../../../services'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import { expectToReject } from '../../../../../../../apm_api_integration/common/utils/expect_to_reject'; +import type { ApmApiError } from '../../../../../services/apm_api'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const samlAuth = getService('samlAuth'); + + const agentKeyName = 'test'; + const allApplicationPrivileges = [PrivilegeType.AGENT_CONFIG, PrivilegeType.EVENT]; + const clusterPrivileges = [ClusterPrivilegeType.MANAGE_OWN_API_KEY]; + + async function createAgentKey(roleAuthc: RoleCredentials) { + return await apmApiClient.publicApi({ + endpoint: 'POST /api/apm/agent_keys 2023-10-31', + params: { + body: { + name: agentKeyName, + privileges: allApplicationPrivileges, + }, + }, + roleAuthc, + }); + } + + async function invalidateAgentKey(id: string) { + return await apmApiClient.writeUser({ + endpoint: 'POST /internal/apm/api_key/invalidate', + params: { + body: { id }, + }, + }); + } + + async function getAgentKeys() { + return await apmApiClient.writeUser({ endpoint: 'GET /internal/apm/agent_keys' }); + } + + describe('When the user does not have the required privileges', () => { + let roleAuthc: RoleCredentials; + + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('editor'); + }); + + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + + describe('When the user does not have the required cluster privileges', () => { + it('should return an error when creating an agent key', async () => { + const error = await expectToReject<ApmApiError>(() => createAgentKey(roleAuthc)); + expect(error.res.status).to.be(403); + expect(error.res.body.message).contain('is missing the following requested privilege'); + expect(error.res.body.attributes).to.eql({ + _inspect: [], + data: { + missingPrivileges: allApplicationPrivileges, + missingClusterPrivileges: clusterPrivileges, + }, + }); + }); + + it('should return an error when invalidating an agent key', async () => { + const error = await expectToReject<ApmApiError>(() => invalidateAgentKey(agentKeyName)); + expect(error.res.status).to.be(500); + }); + + it('should return an error when getting a list of agent keys', async () => { + const error = await expectToReject<ApmApiError>(() => getAgentKeys()); + expect(error.res.status).to.be(500); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/anomaly_detection/basic.spec.ts similarity index 63% rename from x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/anomaly_detection/basic.spec.ts index 86290fc35f12..195e4ff5e7bd 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/anomaly_detection/basic.spec.ts @@ -6,17 +6,13 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { ApmApiError } from '../../../common/apm_api_supertest'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import type { ApmApiError } from '../../../../../services/apm_api'; -export default function apiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); +export default function apiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); - type SupertestAsUser = - | typeof apmApiClient.readUser - | typeof apmApiClient.writeUser - | typeof apmApiClient.noAccessUser; + type SupertestAsUser = typeof apmApiClient.readUser | typeof apmApiClient.writeUser; function getJobs(user: SupertestAsUser) { return user({ endpoint: `GET /internal/apm/settings/anomaly-detection/jobs` }); @@ -34,7 +30,6 @@ export default function apiTest({ getService }: FtrProviderContext) { async function expectForbidden(user: SupertestAsUser) { try { await getJobs(user); - expect(true).to.be(false); } catch (e) { const err = e as ApmApiError; expect(err.res.status).to.be(403); @@ -42,20 +37,14 @@ export default function apiTest({ getService }: FtrProviderContext) { try { await createJobs(user, ['production', 'staging']); - expect(true).to.be(false); } catch (e) { const err = e as ApmApiError; expect(err.res.status).to.be(403); } } - registry.when('ML jobs return a 403 for', { config: 'basic', archives: [] }, () => { + describe('ML jobs return a 403 for', () => { describe('basic', function () { - this.tags('skipFIPS'); - it('user without access', async () => { - await expectForbidden(apmApiClient.noAccessUser); - }); - it('read user', async () => { await expectForbidden(apmApiClient.readUser); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/anomaly_detection/read_user.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/anomaly_detection/read_user.spec.ts new file mode 100644 index 000000000000..0de23a4c27f2 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/anomaly_detection/read_user.spec.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import type { ApmApiError } from '../../../../../services/apm_api'; + +export default function apiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + function getJobs() { + return apmApiClient.readUser({ endpoint: `GET /internal/apm/settings/anomaly-detection/jobs` }); + } + + function createJobs(environments: string[]) { + return apmApiClient.readUser({ + endpoint: `POST /internal/apm/settings/anomaly-detection/jobs`, + params: { + body: { environments }, + }, + }); + } + + describe('ML jobs', () => { + describe(`when readUser has read access to ML`, () => { + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs(); + + expect(body.jobs).not.to.be(undefined); + expect(body.hasLegacyJobs).to.be(false); + }); + }); + + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + try { + await createJobs(['production', 'staging']); + expect(true).to.be(false); + } catch (e) { + const err = e as ApmApiError; + expect(err.res.status).to.be(403); + } + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/apm_indices/apm_indices.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/apm_indices/apm_indices.spec.ts new file mode 100644 index 000000000000..1fe51b69fccb --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/apm_indices/apm_indices.spec.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + APM_INDEX_SETTINGS_SAVED_OBJECT_ID, + APM_INDEX_SETTINGS_SAVED_OBJECT_TYPE, +} from '@kbn/apm-data-access-plugin/server/saved_objects/apm_indices'; +import expect from '@kbn/expect'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function apmIndicesTests({ getService }: DeploymentAgnosticFtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const apmApiClient = getService('apmApi'); + + async function deleteSavedObject() { + try { + return await kibanaServer.savedObjects.delete({ + type: APM_INDEX_SETTINGS_SAVED_OBJECT_TYPE, + id: APM_INDEX_SETTINGS_SAVED_OBJECT_ID, + }); + } catch (e) { + if (e.response.status !== 404) { + throw e; + } + } + } + + describe('APM Indices', () => { + beforeEach(async () => { + await deleteSavedObject(); + }); + + afterEach(async () => { + await deleteSavedObject(); + }); + + it('returns APM Indices', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/settings/apm-indices', + }); + expect(response.status).to.be(200); + expect(response.body).to.eql({ + transaction: 'traces-apm*,apm-*,traces-*.otel-*', + span: 'traces-apm*,apm-*,traces-*.otel-*', + error: 'logs-apm*,apm-*,logs-*.otel-*', + metric: 'metrics-apm*,apm-*,metrics-*.otel-*', + onboarding: 'apm-*', + sourcemap: 'apm-*', + }); + }); + + it('updates apm indices', async () => { + const INDEX_VALUE = 'foo-*'; + + const writeResponse = await apmApiClient.writeUser({ + endpoint: 'POST /internal/apm/settings/apm-indices/save', + params: { + body: { transaction: INDEX_VALUE }, + }, + }); + expect(writeResponse.status).to.be(200); + + const readResponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/settings/apm-indices', + }); + + expect(readResponse.status).to.be(200); + expect(readResponse.body.transaction).to.eql(INDEX_VALUE); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/custom_link/custom_link.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/custom_link/custom_link.spec.ts new file mode 100644 index 000000000000..992481459111 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/custom_link/custom_link.spec.ts @@ -0,0 +1,200 @@ +/* + * 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 type { CustomLink } from '@kbn/apm-plugin/common/custom_link/custom_link_types'; +import type { ApmApiError } from '../../../../../services/apm_api'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import { ARCHIVER_ROUTES } from '../../constants/archiver'; + +export default function customLinksTests({ getService }: DeploymentAgnosticFtrProviderContext) { + const esArchiver = getService('esArchiver'); + const apmApiClient = getService('apmApi'); + const log = getService('log'); + + const archiveName = '8.0.0'; + + describe('Custom links with data', () => { + before(async () => { + await esArchiver.load(ARCHIVER_ROUTES[archiveName]); + + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + + await createCustomLink(customLink); + }); + + after(async () => { + await esArchiver.unload(ARCHIVER_ROUTES[archiveName]); + }); + + it('should fail if the user does not have write access', async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + + const err = await expectToReject<ApmApiError>(() => createCustomLinkAsReadUser(customLink)); + expect(err.res.status).to.be(403); + }); + + it('fetches a custom link', async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + const { label, url, filters } = body.customLinks[0]; + + expect(status).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'with filters', + url: 'https://elastic.co', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + }); + }); + + it(`creates a custom link as write user`, async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + + await createCustomLink(customLink); + }); + + it(`updates a custom link as write user`, async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + expect(status).to.equal(200); + + const id = body.customLinks[0].id!; + await updateCustomLink(id, { + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + + const { status: newStatus, body: newBody } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + + const { label, url, filters } = newBody.customLinks[0]; + expect(newStatus).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + }); + + it(`deletes a custom link as write user`, async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(status).to.equal(200); + expect(body.customLinks.length).to.be(1); + + const id = body.customLinks[0].id!; + await deleteCustomLink(id); + + const { status: newStatus, body: newBody } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(newStatus).to.equal(200); + expect(newBody.customLinks.length).to.be(0); + }); + }); + + function searchCustomLinks(filters?: any) { + return apmApiClient.readUser({ + endpoint: 'GET /internal/apm/settings/custom_links', + params: { + query: filters, + }, + }); + } + + async function createCustomLink(customLink: CustomLink) { + log.debug('creating configuration', customLink); + + return apmApiClient.writeUser({ + endpoint: 'POST /internal/apm/settings/custom_links', + params: { + body: customLink, + }, + }); + } + + async function createCustomLinkAsReadUser(customLink: CustomLink) { + log.debug('creating configuration', customLink); + + return apmApiClient.readUser({ + endpoint: 'POST /internal/apm/settings/custom_links', + params: { + body: customLink, + }, + }); + } + + async function updateCustomLink(id: string, customLink: CustomLink) { + log.debug('updating configuration', id, customLink); + + return apmApiClient.writeUser({ + endpoint: 'PUT /internal/apm/settings/custom_links/{id}', + params: { + path: { id }, + body: customLink, + }, + }); + } + + async function deleteCustomLink(id: string) { + log.debug('deleting configuration', id); + + return apmApiClient.writeUser({ + endpoint: 'DELETE /internal/apm/settings/custom_links/{id}', + params: { path: { id } }, + }); + } +} + +async function expectToReject<T extends Error>(fn: () => Promise<any>): Promise<T> { + try { + await fn(); + } catch (e) { + return e; + } + throw new Error(`Expected fn to throw`); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/index.ts new file mode 100644 index 000000000000..5690ce79690c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/settings/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('settings', () => { + loadTestFile(require.resolve('./agent_keys/agent_keys.spec.ts')); + loadTestFile(require.resolve('./anomaly_detection/basic.spec.ts')); + loadTestFile(require.resolve('./anomaly_detection/read_user.spec.ts')); + loadTestFile(require.resolve('./apm_indices/apm_indices.spec.ts')); + loadTestFile(require.resolve('./custom_link/custom_link.spec.ts')); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.spec.snap b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/__snapshots__/top_traces.spec.snap similarity index 53% rename from x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.spec.snap rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/__snapshots__/top_traces.spec.snap index 77aba0cdd3bf..b2a69a7a098d 100644 --- a/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.spec.snap +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/__snapshots__/top_traces.spec.snap @@ -1,76 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM API tests traces/top_traces.spec.ts basic apm_8.0.0 Top traces when data is loaded returns the correct buckets 1`] = ` +exports[`Deployment-agnostic APM API integration tests APM Traces Top traces when data is loaded returns the correct buckets 1`] = ` Array [ - Object { - "agentName": "java", - "averageResponseTime": 1639, - "impact": 0, - "key": Object { - "service.name": "opbeans-java", - "transaction.name": "DispatcherServlet#doPost", - }, - "serviceName": "opbeans-java", - "transactionName": "DispatcherServlet#doPost", - "transactionType": "request", - "transactionsPerMinute": 0.0333333333333333, - }, - Object { - "agentName": "nodejs", - "averageResponseTime": 3279, - "impact": 0.00144735571024101, - "key": Object { - "service.name": "opbeans-node", - "transaction.name": "POST /api/orders", - }, - "serviceName": "opbeans-node", - "transactionName": "POST /api/orders", - "transactionType": "request", - "transactionsPerMinute": 0.0333333333333333, - }, - Object { - "agentName": "nodejs", - "averageResponseTime": 6175, - "impact": 0.00400317408637392, - "key": Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/products/:id", - }, - "serviceName": "opbeans-node", - "transactionName": "GET /api/products/:id", - "transactionType": "request", - "transactionsPerMinute": 0.0333333333333333, - }, - Object { - "agentName": "dotnet", - "averageResponseTime": 3495, - "impact": 0.00472243927164613, - "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "POST Orders/Post", - }, - "serviceName": "opbeans-dotnet", - "transactionName": "POST Orders/Post", - "transactionType": "request", - "transactionsPerMinute": 0.0666666666666667, - }, - Object { - "agentName": "python", - "averageResponseTime": 7039, - "impact": 0.00476568343615943, - "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.product", - }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.product", - "transactionType": "request", - "transactionsPerMinute": 0.0333333333333333, - }, Object { "agentName": "ruby", - "averageResponseTime": 6303, - "impact": 0.00967875004525193, + "averageResponseTime": 5664, + "impact": 0, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::OrdersController#create", @@ -78,12 +13,12 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::OrdersController#create", "transactionType": "request", - "transactionsPerMinute": 0.0666666666666667, + "transactionsPerMinute": 0.0166666666666667, }, Object { "agentName": "java", - "averageResponseTime": 7209.66666666667, - "impact": 0.0176418540534865, + "averageResponseTime": 6031, + "impact": 0.000810693162092553, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#products", @@ -91,12 +26,12 @@ Array [ "serviceName": "opbeans-java", "transactionName": "APIRestController#products", "transactionType": "request", - "transactionsPerMinute": 0.1, + "transactionsPerMinute": 0.0166666666666667, }, Object { "agentName": "java", - "averageResponseTime": 4511, - "impact": 0.0224401912465233, + "averageResponseTime": 4506.5, + "impact": 0.00739785122574376, "key": Object { "service.name": "opbeans-java", "transaction.name": "APIRestController#orders", @@ -104,103 +39,51 @@ Array [ "serviceName": "opbeans-java", "transactionName": "APIRestController#orders", "transactionType": "request", - "transactionsPerMinute": 0.2, - }, - Object { - "agentName": "python", - "averageResponseTime": 7607, - "impact": 0.0254072704525173, - "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.order", - }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.order", - "transactionType": "request", - "transactionsPerMinute": 0.133333333333333, - }, - Object { - "agentName": "nodejs", - "averageResponseTime": 10143, - "impact": 0.025408152986487, - "key": Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/types", - }, - "serviceName": "opbeans-node", - "transactionName": "GET /api/types", - "transactionType": "request", - "transactionsPerMinute": 0.1, - }, - Object { - "agentName": "ruby", - "averageResponseTime": 6105.66666666667, - "impact": 0.0308842762682221, - "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Api::TypesController#index", - }, - "serviceName": "opbeans-ruby", - "transactionName": "Api::TypesController#index", - "transactionType": "request", - "transactionsPerMinute": 0.2, - }, - Object { - "agentName": "java", - "averageResponseTime": 6116.33333333333, - "impact": 0.0309407584422802, - "key": Object { - "service.name": "opbeans-java", - "transaction.name": "APIRestController#customerWhoBought", - }, - "serviceName": "opbeans-java", - "transactionName": "APIRestController#customerWhoBought", - "transactionType": "request", - "transactionsPerMinute": 0.2, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "java", - "averageResponseTime": 12543, - "impact": 0.0317623975680329, + "averageResponseTime": 9534, + "impact": 0.00854872625966807, "key": Object { "service.name": "opbeans-java", - "transaction.name": "APIRestController#customers", + "transaction.name": "APIRestController#product", }, "serviceName": "opbeans-java", - "transactionName": "APIRestController#customers", + "transactionName": "APIRestController#product", "transactionType": "request", - "transactionsPerMinute": 0.1, + "transactionsPerMinute": 0.0166666666666667, }, Object { "agentName": "nodejs", - "averageResponseTime": 5551, - "impact": 0.0328461492827744, + "averageResponseTime": 10075, + "impact": 0.00974378075746663, "key": Object { "service.name": "opbeans-node", - "transaction.name": "GET /api/orders/:id", + "transaction.name": "POST /api", }, "serviceName": "opbeans-node", - "transactionName": "GET /api/orders/:id", + "transactionName": "POST /api", "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, + "transactionsPerMinute": 0.0166666666666667, }, Object { "agentName": "java", - "averageResponseTime": 13183, - "impact": 0.0334568627897785, + "averageResponseTime": 4839.33333333333, + "impact": 0.0195582486571321, "key": Object { "service.name": "opbeans-java", - "transaction.name": "APIRestController#stats", + "transaction.name": "APIRestController#topProducts", }, "serviceName": "opbeans-java", - "transactionName": "APIRestController#stats", + "transactionName": "APIRestController#topProducts", "transactionType": "request", - "transactionsPerMinute": 0.1, + "transactionsPerMinute": 0.05, }, Object { "agentName": "go", - "averageResponseTime": 8050.2, - "impact": 0.0340764016364792, + "averageResponseTime": 7530.5, + "impact": 0.020757721101318, "key": Object { "service.name": "opbeans-go", "transaction.name": "POST /api/orders", @@ -208,51 +91,25 @@ Array [ "serviceName": "opbeans-go", "transactionName": "POST /api/orders", "transactionType": "request", - "transactionsPerMinute": 0.166666666666667, - }, - Object { - "agentName": "ruby", - "averageResponseTime": 10079, - "impact": 0.0341337663445071, - "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Api::OrdersController#show", - }, - "serviceName": "opbeans-ruby", - "transactionName": "Api::OrdersController#show", - "transactionType": "request", - "transactionsPerMinute": 0.133333333333333, - }, - Object { - "agentName": "nodejs", - "averageResponseTime": 8463, - "impact": 0.0358979517498557, - "key": Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/products/:id/customers", - }, - "serviceName": "opbeans-node", - "transactionName": "GET /api/products/:id/customers", - "transactionType": "request", - "transactionsPerMinute": 0.166666666666667, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "ruby", - "averageResponseTime": 10799, - "impact": 0.0366754641771254, + "averageResponseTime": 7582.5, + "impact": 0.0209874543134642, "key": Object { "service.name": "opbeans-ruby", - "transaction.name": "Api::ProductsController#show", + "transaction.name": "Api::CustomersController#show", }, "serviceName": "opbeans-ruby", - "transactionName": "Api::ProductsController#show", + "transactionName": "Api::CustomersController#show", "transactionType": "request", - "transactionsPerMinute": 0.133333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "ruby", - "averageResponseTime": 7428.33333333333, - "impact": 0.0378880658514371, + "averageResponseTime": 8001, + "impact": 0.0228363648766017, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::TypesController#show", @@ -260,12 +117,12 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::TypesController#show", "transactionType": "request", - "transactionsPerMinute": 0.2, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "java", - "averageResponseTime": 3105.13333333333, - "impact": 0.039659311528543, + "averageResponseTime": 3259.4, + "impact": 0.0234880119687469, "key": Object { "service.name": "opbeans-java", "transaction.name": "ResourceHttpRequestHandler", @@ -273,454 +130,402 @@ Array [ "serviceName": "opbeans-java", "transactionName": "ResourceHttpRequestHandler", "transactionType": "request", - "transactionsPerMinute": 0.5, + "transactionsPerMinute": 0.0833333333333333, }, Object { - "agentName": "java", - "averageResponseTime": 6883.57142857143, - "impact": 0.0410784261517549, + "agentName": "nodejs", + "averageResponseTime": 5675, + "impact": 0.0250961444537697, "key": Object { - "service.name": "opbeans-java", - "transaction.name": "APIRestController#order", + "service.name": "opbeans-node", + "transaction.name": "GET /api/products/top", }, - "serviceName": "opbeans-java", - "transactionName": "APIRestController#order", + "serviceName": "opbeans-node", + "transactionName": "GET /api/products/top", "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, + "transactionsPerMinute": 0.05, }, Object { - "agentName": "dotnet", - "averageResponseTime": 3505, - "impact": 0.0480460318422139, + "agentName": "nodejs", + "averageResponseTime": 8537, + "impact": 0.0252043841402617, "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Products/Get", + "service.name": "opbeans-node", + "transaction.name": "GET /api/products/:id", }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Products/Get", + "serviceName": "opbeans-node", + "transactionName": "GET /api/products/:id", "transactionType": "request", - "transactionsPerMinute": 0.533333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "java", - "averageResponseTime": 5621.4, - "impact": 0.0481642913941483, + "averageResponseTime": 6006.33333333333, + "impact": 0.0272918638083201, "key": Object { "service.name": "opbeans-java", - "transaction.name": "APIRestController#topProducts", + "transaction.name": "APIRestController#customerWhoBought", }, "serviceName": "opbeans-java", - "transactionName": "APIRestController#topProducts", - "transactionType": "request", - "transactionsPerMinute": 0.333333333333333, - }, - Object { - "agentName": "nodejs", - "averageResponseTime": 8428.71428571429, - "impact": 0.0506239135675883, - "key": Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/orders", - }, - "serviceName": "opbeans-node", - "transactionName": "GET /api/orders", + "transactionName": "APIRestController#customerWhoBought", "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, + "transactionsPerMinute": 0.05, }, Object { "agentName": "nodejs", - "averageResponseTime": 8520.14285714286, - "impact": 0.0511887353081702, + "averageResponseTime": 18064, + "impact": 0.0273912676020372, "key": Object { "service.name": "opbeans-node", - "transaction.name": "GET /api/customers/:id", + "transaction.name": "GET /api/products", }, "serviceName": "opbeans-node", - "transactionName": "GET /api/customers/:id", + "transactionName": "GET /api/products", "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, + "transactionsPerMinute": 0.0166666666666667, }, Object { "agentName": "nodejs", - "averageResponseTime": 6683.44444444444, - "impact": 0.0516388276326964, + "averageResponseTime": 19217, + "impact": 0.0299382136943879, "key": Object { "service.name": "opbeans-node", - "transaction.name": "GET /api/products/top", + "transaction.name": "GET /api/customers", }, "serviceName": "opbeans-node", - "transactionName": "GET /api/products/top", - "transactionType": "request", - "transactionsPerMinute": 0.3, - }, - Object { - "agentName": "dotnet", - "averageResponseTime": 3482.78947368421, - "impact": 0.0569534471979838, - "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Types/Get", - }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Types/Get", + "transactionName": "GET /api/customers", "transactionType": "request", - "transactionsPerMinute": 0.633333333333333, + "transactionsPerMinute": 0.0166666666666667, }, Object { "agentName": "python", - "averageResponseTime": 16703, - "impact": 0.057517386404596, + "averageResponseTime": 22023, + "impact": 0.0361365924759457, "key": Object { "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.product_type", + "transaction.name": "GET opbeans.views.customer", }, "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.product_type", - "transactionType": "request", - "transactionsPerMinute": 0.133333333333333, - }, - Object { - "agentName": "dotnet", - "averageResponseTime": 4943, - "impact": 0.0596266425920813, - "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Products/Get {id}", - }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Products/Get {id}", - "transactionType": "request", - "transactionsPerMinute": 0.466666666666667, - }, - Object { - "agentName": "nodejs", - "averageResponseTime": 7892.33333333333, - "impact": 0.0612407972225879, - "key": Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/types/:id", - }, - "serviceName": "opbeans-node", - "transactionName": "GET /api/types/:id", + "transactionName": "GET opbeans.views.customer", "transactionType": "request", - "transactionsPerMinute": 0.3, + "transactionsPerMinute": 0.0166666666666667, }, Object { - "agentName": "dotnet", - "averageResponseTime": 6346.42857142857, - "impact": 0.0769666700279444, + "agentName": "ruby", + "averageResponseTime": 12106, + "impact": 0.0409720347969828, "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Orders/Get {id}", + "service.name": "opbeans-ruby", + "transaction.name": "Api::ProductsController#index", }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Orders/Get {id}", + "serviceName": "opbeans-ruby", + "transactionName": "Api::ProductsController#index", "transactionType": "request", - "transactionsPerMinute": 0.466666666666667, + "transactionsPerMinute": 0.0333333333333333, }, Object { - "agentName": "go", - "averageResponseTime": 7052.84615384615, - "impact": 0.0794704188998674, + "agentName": "ruby", + "averageResponseTime": 12331.5, + "impact": 0.0419682817073472, "key": Object { - "service.name": "opbeans-go", - "transaction.name": "GET /api/products", + "service.name": "opbeans-ruby", + "transaction.name": "Api::TypesController#index", }, - "serviceName": "opbeans-go", - "transactionName": "GET /api/products", + "serviceName": "opbeans-ruby", + "transactionName": "Api::TypesController#index", "transactionType": "request", - "transactionsPerMinute": 0.433333333333333, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "java", - "averageResponseTime": 10484.3333333333, - "impact": 0.0818285496667966, + "averageResponseTime": 12897.5, + "impact": 0.0444688393626299, "key": Object { "service.name": "opbeans-java", - "transaction.name": "APIRestController#product", + "transaction.name": "APIRestController#customers", }, "serviceName": "opbeans-java", - "transactionName": "APIRestController#product", + "transactionName": "APIRestController#customers", "transactionType": "request", - "transactionsPerMinute": 0.3, + "transactionsPerMinute": 0.0333333333333333, }, Object { - "agentName": "nodejs", - "averageResponseTime": 23711, - "impact": 0.0822565786420813, + "agentName": "java", + "averageResponseTime": 6831.75, + "impact": 0.0478529862953978, "key": Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/stats", + "service.name": "opbeans-java", + "transaction.name": "APIRestController#customer", }, - "serviceName": "opbeans-node", - "transactionName": "GET /api/stats", + "serviceName": "opbeans-java", + "transactionName": "APIRestController#customer", "transactionType": "request", - "transactionsPerMinute": 0.133333333333333, + "transactionsPerMinute": 0.0666666666666667, }, Object { - "agentName": "dotnet", - "averageResponseTime": 4491.36363636364, - "impact": 0.0857567083657495, + "agentName": "ruby", + "averageResponseTime": 29231, + "impact": 0.0520588712562267, "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Types/Get {id}", + "service.name": "opbeans-ruby", + "transaction.name": "Api::ProductsController#show", }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Types/Get {id}", + "serviceName": "opbeans-ruby", + "transactionName": "Api::ProductsController#show", "transactionType": "request", - "transactionsPerMinute": 0.733333333333333, + "transactionsPerMinute": 0.0166666666666667, }, Object { "agentName": "python", - "averageResponseTime": 20715.8, - "impact": 0.089965512867054, + "averageResponseTime": 16222.5, + "impact": 0.0591585111008193, "key": Object { "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.stats", + "transaction.name": "GET opbeans.views.orders", }, "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.stats", + "transactionName": "GET opbeans.views.orders", "transactionType": "request", - "transactionsPerMinute": 0.166666666666667, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "nodejs", - "averageResponseTime": 9036.33333333333, - "impact": 0.0942519803576885, + "averageResponseTime": 17629, + "impact": 0.065372352694733, "key": Object { "service.name": "opbeans-node", - "transaction.name": "GET /api/products", + "transaction.name": "GET /api/stats", }, "serviceName": "opbeans-node", - "transactionName": "GET /api/products", + "transactionName": "GET /api/stats", "transactionType": "request", - "transactionsPerMinute": 0.4, + "transactionsPerMinute": 0.0333333333333333, }, Object { - "agentName": "java", - "averageResponseTime": 7504.06666666667, - "impact": 0.0978924329825326, + "agentName": "ruby", + "averageResponseTime": 9031.5, + "impact": 0.0672897414268756, "key": Object { - "service.name": "opbeans-java", - "transaction.name": "APIRestController#customer", + "service.name": "opbeans-ruby", + "transaction.name": "Api::OrdersController#show", }, - "serviceName": "opbeans-java", - "transactionName": "APIRestController#customer", + "serviceName": "opbeans-ruby", + "transactionName": "Api::OrdersController#show", "transactionType": "request", - "transactionsPerMinute": 0.5, + "transactionsPerMinute": 0.0666666666666667, }, Object { - "agentName": "go", - "averageResponseTime": 4250.55555555556, - "impact": 0.0998375378516613, + "agentName": "python", + "averageResponseTime": 38278, + "impact": 0.0720434517397453, "key": Object { - "service.name": "opbeans-go", - "transaction.name": "GET /api/types/:id", + "service.name": "opbeans-python", + "transaction.name": "GET opbeans.views.products", }, - "serviceName": "opbeans-go", - "transactionName": "GET /api/types/:id", + "serviceName": "opbeans-python", + "transactionName": "GET opbeans.views.products", "transactionType": "request", - "transactionsPerMinute": 0.9, + "transactionsPerMinute": 0.0166666666666667, }, Object { - "agentName": "nodejs", - "averageResponseTime": 21343, - "impact": 0.11156906191034, + "agentName": "python", + "averageResponseTime": 19176.5, + "impact": 0.0722091247292738, "key": Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/customers", + "service.name": "opbeans-python", + "transaction.name": "GET opbeans.views.product", }, - "serviceName": "opbeans-node", - "transactionName": "GET /api/customers", + "serviceName": "opbeans-python", + "transactionName": "GET opbeans.views.product", "transactionType": "request", - "transactionsPerMinute": 0.2, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "ruby", - "averageResponseTime": 16655, - "impact": 0.116142352941114, + "averageResponseTime": 21443, + "impact": 0.0822224002163734, "key": Object { "service.name": "opbeans-ruby", - "transaction.name": "Api::ProductsController#top", + "transaction.name": "Api::OrdersController#index", }, "serviceName": "opbeans-ruby", - "transactionName": "Api::ProductsController#top", + "transactionName": "Api::OrdersController#index", "transactionType": "request", - "transactionsPerMinute": 0.266666666666667, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "go", - "averageResponseTime": 5749, - "impact": 0.12032203382142, + "averageResponseTime": 7299.66666666667, + "impact": 0.0842369837690393, "key": Object { "service.name": "opbeans-go", - "transaction.name": "GET /api/types", + "transaction.name": "GET /api/orders", }, "serviceName": "opbeans-go", - "transactionName": "GET /api/types", + "transactionName": "GET /api/orders", "transactionType": "request", - "transactionsPerMinute": 0.8, + "transactionsPerMinute": 0.1, }, Object { "agentName": "ruby", - "averageResponseTime": 9951, - "impact": 0.121502864272824, + "averageResponseTime": 11738, + "impact": 0.0912040852220091, "key": Object { "service.name": "opbeans-ruby", - "transaction.name": "Api::StatsController#index", + "transaction.name": "Api::ProductsController#top", }, "serviceName": "opbeans-ruby", - "transactionName": "Api::StatsController#index", + "transactionName": "Api::ProductsController#top", "transactionType": "request", - "transactionsPerMinute": 0.466666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { - "agentName": "go", - "averageResponseTime": 14040.6, - "impact": 0.122466591367692, + "agentName": "nodejs", + "averageResponseTime": 9640.8, + "impact": 0.0939697196605374, "key": Object { - "service.name": "opbeans-go", - "transaction.name": "GET /api/customers", + "service.name": "opbeans-node", + "transaction.name": "GET /api/orders/:id", }, - "serviceName": "opbeans-go", - "transactionName": "GET /api/customers", + "serviceName": "opbeans-node", + "transactionName": "GET /api/orders/:id", "transactionType": "request", - "transactionsPerMinute": 0.333333333333333, + "transactionsPerMinute": 0.0833333333333333, }, Object { "agentName": "ruby", - "averageResponseTime": 20963.5714285714, - "impact": 0.128060974201361, + "averageResponseTime": 8349.33333333333, + "impact": 0.0981490969430418, "key": Object { "service.name": "opbeans-ruby", - "transaction.name": "Api::OrdersController#index", + "transaction.name": "Api::StatsController#index", }, "serviceName": "opbeans-ruby", - "transactionName": "Api::OrdersController#index", + "transactionName": "Api::StatsController#index", "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, + "transactionsPerMinute": 0.1, }, Object { - "agentName": "python", - "averageResponseTime": 22874.4285714286, - "impact": 0.139865748579522, + "agentName": "nodejs", + "averageResponseTime": 9213.16666666667, + "impact": 0.109598205006055, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.customer", + "service.name": "opbeans-node", + "transaction.name": "GET /api/orders", }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.customer", + "serviceName": "opbeans-node", + "transactionName": "GET /api/orders", "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, + "transactionsPerMinute": 0.1, }, Object { - "agentName": "python", - "averageResponseTime": 32203.8, - "impact": 0.140658264084276, + "agentName": "nodejs", + "averageResponseTime": 14031.25, + "impact": 0.111466996327935, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.customers", + "service.name": "opbeans-node", + "transaction.name": "GET /api/types/:id", }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.customers", + "serviceName": "opbeans-node", + "transactionName": "GET /api/types/:id", "transactionType": "request", - "transactionsPerMinute": 0.166666666666667, + "transactionsPerMinute": 0.0666666666666667, }, Object { - "agentName": "go", - "averageResponseTime": 4482.11111111111, - "impact": 0.140955678032051, + "agentName": "nodejs", + "averageResponseTime": 8779.85714285714, + "impact": 0.123249659343199, "key": Object { - "service.name": "opbeans-go", + "service.name": "opbeans-node", "transaction.name": "GET /api/customers/:id", }, - "serviceName": "opbeans-go", + "serviceName": "opbeans-node", "transactionName": "GET /api/customers/:id", "transactionType": "request", - "transactionsPerMinute": 1.2, + "transactionsPerMinute": 0.116666666666667, }, Object { - "agentName": "ruby", - "averageResponseTime": 12582.3846153846, - "impact": 0.142910490774846, + "agentName": "go", + "averageResponseTime": 6746.3, + "impact": 0.13651233439825, "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Api::ProductsController#index", + "service.name": "opbeans-go", + "transaction.name": "GET /api/orders/:id", }, - "serviceName": "opbeans-ruby", - "transactionName": "Api::ProductsController#index", + "serviceName": "opbeans-go", + "transactionName": "GET /api/orders/:id", "transactionType": "request", - "transactionsPerMinute": 0.433333333333333, + "transactionsPerMinute": 0.166666666666667, }, Object { - "agentName": "ruby", - "averageResponseTime": 10009.9473684211, - "impact": 0.166401779979233, + "agentName": "go", + "averageResponseTime": 7179.9, + "impact": 0.146090442166188, "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Api::CustomersController#show", + "service.name": "opbeans-go", + "transaction.name": "GET /api/types/:id", }, - "serviceName": "opbeans-ruby", - "transactionName": "Api::CustomersController#show", + "serviceName": "opbeans-go", + "transactionName": "GET /api/types/:id", "transactionType": "request", - "transactionsPerMinute": 0.633333333333333, + "transactionsPerMinute": 0.166666666666667, }, Object { - "agentName": "python", - "averageResponseTime": 27825.2857142857, - "impact": 0.170450845832029, + "agentName": "nodejs", + "averageResponseTime": 8171.11111111111, + "impact": 0.149936264496442, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.product_types", + "service.name": "opbeans-node", + "transaction.name": "GET /api/products/:id/customers", }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.product_types", + "serviceName": "opbeans-node", + "transactionName": "GET /api/products/:id/customers", "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, + "transactionsPerMinute": 0.15, }, Object { - "agentName": "python", - "averageResponseTime": 20562.2, - "impact": 0.180021926732983, + "agentName": "go", + "averageResponseTime": 9247.625, + "impact": 0.150910421674869, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.orders", + "service.name": "opbeans-go", + "transaction.name": "GET /api/customers", }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.orders", + "serviceName": "opbeans-go", + "transactionName": "GET /api/customers", "transactionType": "request", - "transactionsPerMinute": 0.333333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { - "agentName": "dotnet", - "averageResponseTime": 7106.76470588235, - "impact": 0.21180020991247, + "agentName": "python", + "averageResponseTime": 19205, + "impact": 0.157181696571819, "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Customers/Get {id}", + "service.name": "opbeans-python", + "transaction.name": "GET opbeans.views.product_type", }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Customers/Get {id}", + "serviceName": "opbeans-python", + "transactionName": "GET opbeans.views.product_type", "transactionType": "request", - "transactionsPerMinute": 1.13333333333333, + "transactionsPerMinute": 0.0666666666666667, }, Object { "agentName": "go", - "averageResponseTime": 8612.51724137931, - "impact": 0.218977858687708, + "averageResponseTime": 10272.25, + "impact": 0.169017374943732, "key": Object { "service.name": "opbeans-go", - "transaction.name": "GET /api/orders/:id", + "transaction.name": "GET /api/types", }, "serviceName": "opbeans-go", - "transactionName": "GET /api/orders/:id", + "transactionName": "GET /api/types", "transactionType": "request", - "transactionsPerMinute": 0.966666666666667, + "transactionsPerMinute": 0.133333333333333, }, Object { "agentName": "ruby", - "averageResponseTime": 11295, - "impact": 0.277663720068132, + "averageResponseTime": 10371, + "impact": 0.170762463766765, "key": Object { "service.name": "opbeans-ruby", "transaction.name": "Api::CustomersController#index", @@ -728,77 +533,64 @@ Array [ "serviceName": "opbeans-ruby", "transactionName": "Api::CustomersController#index", "transactionType": "request", - "transactionsPerMinute": 0.933333333333333, + "transactionsPerMinute": 0.133333333333333, }, Object { - "agentName": "python", - "averageResponseTime": 65035.8, - "impact": 0.285535040543522, + "agentName": "java", + "averageResponseTime": 28415.3333333333, + "impact": 0.175794504702042, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.product_customers", + "service.name": "opbeans-java", + "transaction.name": "DispatcherServlet#doGet", }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.product_customers", + "serviceName": "opbeans-java", + "transactionName": "DispatcherServlet#doGet", "transactionType": "request", - "transactionsPerMinute": 0.166666666666667, + "transactionsPerMinute": 0.05, }, Object { "agentName": "go", - "averageResponseTime": 30999.4705882353, - "impact": 0.463640986028375, + "averageResponseTime": 6374, + "impact": 0.184608307744956, "key": Object { "service.name": "opbeans-go", - "transaction.name": "GET /api/stats", + "transaction.name": "GET /api/products", }, "serviceName": "opbeans-go", - "transactionName": "GET /api/stats", + "transactionName": "GET /api/products", "transactionType": "request", - "transactionsPerMinute": 0.566666666666667, + "transactionsPerMinute": 0.233333333333333, }, Object { "agentName": "go", - "averageResponseTime": 20197.4, - "impact": 0.622424732781511, + "averageResponseTime": 6257.46666666667, + "impact": 0.194827017739071, "key": Object { "service.name": "opbeans-go", - "transaction.name": "GET /api/products/:id/customers", + "transaction.name": "GET /api/customers/:id", }, "serviceName": "opbeans-go", - "transactionName": "GET /api/products/:id/customers", + "transactionName": "GET /api/customers/:id", "transactionType": "request", - "transactionsPerMinute": 1.16666666666667, + "transactionsPerMinute": 0.25, }, Object { "agentName": "python", - "averageResponseTime": 64681.6666666667, - "impact": 0.68355874339377, + "averageResponseTime": 111027.5, + "impact": 0.47800191836068, "key": Object { "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.top_products", + "transaction.name": "GET opbeans.views.stats", }, "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.top_products", - "transactionType": "request", - "transactionsPerMinute": 0.4, - }, - Object { - "agentName": "dotnet", - "averageResponseTime": 41416.1428571429, - "impact": 0.766127739061111, - "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Customers/Get", - }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Customers/Get", + "transactionName": "GET opbeans.views.stats", "transactionType": "request", - "transactionsPerMinute": 0.7, + "transactionsPerMinute": 0.0333333333333333, }, Object { "agentName": "go", - "averageResponseTime": 19429, - "impact": 0.821597646656097, + "averageResponseTime": 19844.9333333333, + "impact": 0.645042262296039, "key": Object { "service.name": "opbeans-go", "transaction.name": "GET /api/products/:id", @@ -806,38 +598,38 @@ Array [ "serviceName": "opbeans-go", "transactionName": "GET /api/products/:id", "transactionType": "request", - "transactionsPerMinute": 1.6, + "transactionsPerMinute": 0.25, }, Object { - "agentName": "dotnet", - "averageResponseTime": 62390.652173913, - "impact": 1.26497653527507, + "agentName": "python", + "averageResponseTime": 153199, + "impact": 0.664313344437989, "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Products/Customerwhobought {id}", + "service.name": "opbeans-python", + "transaction.name": "GET opbeans.views.top_products", }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Products/Customerwhobought {id}", + "serviceName": "opbeans-python", + "transactionName": "GET opbeans.views.top_products", "transactionType": "request", - "transactionsPerMinute": 0.766666666666667, + "transactionsPerMinute": 0.0333333333333333, }, Object { - "agentName": "python", - "averageResponseTime": 33266.2, - "impact": 1.76006661931225, + "agentName": "go", + "averageResponseTime": 22825, + "impact": 0.895045012467666, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "opbeans.tasks.sync_orders", + "service.name": "opbeans-go", + "transaction.name": "GET /api/stats", }, - "serviceName": "opbeans-python", - "transactionName": "opbeans.tasks.sync_orders", - "transactionType": "celery", - "transactionsPerMinute": 2, + "serviceName": "opbeans-go", + "transactionName": "GET /api/stats", + "transactionType": "request", + "transactionsPerMinute": 0.3, }, Object { "agentName": "nodejs", - "averageResponseTime": 38491.4444444444, - "impact": 1.83293391905112, + "averageResponseTime": 28576.8666666667, + "impact": 0.934371362235332, "key": Object { "service.name": "opbeans-node", "transaction.name": "GET /api", @@ -845,90 +637,77 @@ Array [ "serviceName": "opbeans-node", "transactionName": "GET /api", "transactionType": "request", - "transactionsPerMinute": 1.8, - }, - Object { - "agentName": "dotnet", - "averageResponseTime": 118488.6, - "impact": 2.08995781717084, - "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Stats/Get", - }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Stats/Get", - "transactionType": "request", - "transactionsPerMinute": 0.666666666666667, + "transactionsPerMinute": 0.25, }, Object { - "agentName": "dotnet", - "averageResponseTime": 250440.142857143, - "impact": 4.64001412901584, + "agentName": "go", + "averageResponseTime": 75613.8461538462, + "impact": 2.1588648457865, "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Products/Top", + "service.name": "opbeans-go", + "transaction.name": "GET /api/products/:id/customers", }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Products/Top", + "serviceName": "opbeans-go", + "transactionName": "GET /api/products/:id/customers", "transactionType": "request", - "transactionsPerMinute": 0.7, + "transactionsPerMinute": 0.216666666666667, }, Object { - "agentName": "java", - "averageResponseTime": 312096.523809524, - "impact": 5.782704992387, + "agentName": "python", + "averageResponseTime": 500211, + "impact": 2.19739375623124, "key": Object { - "service.name": "opbeans-java", - "transaction.name": "DispatcherServlet#doGet", + "service.name": "opbeans-python", + "transaction.name": "GET opbeans.views.customers", }, - "serviceName": "opbeans-java", - "transactionName": "DispatcherServlet#doGet", + "serviceName": "opbeans-python", + "transactionName": "GET opbeans.views.customers", "transactionType": "request", - "transactionsPerMinute": 0.7, + "transactionsPerMinute": 0.0333333333333333, }, Object { - "agentName": "ruby", - "averageResponseTime": 91519.7032967033, - "impact": 7.34855500859826, + "agentName": "rum-js", + "averageResponseTime": 631900, + "impact": 13.9459899869012, "key": Object { - "service.name": "opbeans-ruby", - "transaction.name": "Rack", + "service.name": "opbeans-rum", + "transaction.name": "/customers", }, - "serviceName": "opbeans-ruby", - "transactionName": "Rack", - "transactionType": "request", - "transactionsPerMinute": 3.03333333333333, + "serviceName": "opbeans-rum", + "transactionName": "/customers", + "transactionType": "page-load", + "transactionsPerMinute": 0.166666666666667, }, Object { "agentName": "rum-js", - "averageResponseTime": 648269.769230769, - "impact": 7.43611473386403, + "averageResponseTime": 1163466.66666667, + "impact": 38.5384885525045, "key": Object { "service.name": "opbeans-rum", - "transaction.name": "/customers", + "transaction.name": "/products", }, "serviceName": "opbeans-rum", - "transactionName": "/customers", + "transactionName": "/products", "transactionType": "page-load", - "transactionsPerMinute": 0.433333333333333, + "transactionsPerMinute": 0.25, }, Object { - "agentName": "python", - "averageResponseTime": 1398919.72727273, - "impact": 13.5790895084132, + "agentName": "ruby", + "averageResponseTime": 404792.666666667, + "impact": 40.2254151113471, "key": Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.products", + "service.name": "opbeans-ruby", + "transaction.name": "Rack", }, - "serviceName": "opbeans-python", - "transactionName": "GET opbeans.views.products", + "serviceName": "opbeans-ruby", + "transactionName": "Rack", "transactionType": "request", - "transactionsPerMinute": 0.366666666666667, + "transactionsPerMinute": 0.75, }, Object { "agentName": "rum-js", - "averageResponseTime": 1199907.57142857, - "impact": 14.8239822181408, + "averageResponseTime": 1756666.66666667, + "impact": 46.5526432992941, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/orders", @@ -936,38 +715,12 @@ Array [ "serviceName": "opbeans-rum", "transactionName": "/orders", "transactionType": "page-load", - "transactionsPerMinute": 0.466666666666667, - }, - Object { - "agentName": "rum-js", - "averageResponseTime": 955876.052631579, - "impact": 16.026822184214, - "key": Object { - "service.name": "opbeans-rum", - "transaction.name": "/products", - }, - "serviceName": "opbeans-rum", - "transactionName": "/products", - "transactionType": "page-load", - "transactionsPerMinute": 0.633333333333333, - }, - Object { - "agentName": "go", - "averageResponseTime": 965009.526315789, - "impact": 16.1799735991728, - "key": Object { - "service.name": "opbeans-go", - "transaction.name": "GET /api/orders", - }, - "serviceName": "opbeans-go", - "transactionName": "GET /api/orders", - "transactionType": "request", - "transactionsPerMinute": 0.633333333333333, + "transactionsPerMinute": 0.2, }, Object { "agentName": "rum-js", - "averageResponseTime": 1213675.30769231, - "impact": 27.8474053933734, + "averageResponseTime": 1008291.66666667, + "impact": 53.4424306904839, "key": Object { "service.name": "opbeans-rum", "transaction.name": "/dashboard", @@ -975,12 +728,12 @@ Array [ "serviceName": "opbeans-rum", "transactionName": "/dashboard", "transactionType": "page-load", - "transactionsPerMinute": 0.866666666666667, + "transactionsPerMinute": 0.4, }, Object { "agentName": "nodejs", - "averageResponseTime": 924019.363636364, - "impact": 35.8796065162284, + "averageResponseTime": 987612.65, + "impact": 87.2518831606925, "key": Object { "service.name": "opbeans-node", "transaction.name": "Update shipping status", @@ -988,72 +741,33 @@ Array [ "serviceName": "opbeans-node", "transactionName": "Update shipping status", "transactionType": "Worker", - "transactionsPerMinute": 1.46666666666667, + "transactionsPerMinute": 0.666666666666667, }, Object { "agentName": "nodejs", - "averageResponseTime": 1060469.15384615, - "impact": 36.498655556576, + "averageResponseTime": 947544.214285714, + "impact": 87.8976786828476, "key": Object { "service.name": "opbeans-node", - "transaction.name": "Process payment", + "transaction.name": "Process completed order", }, "serviceName": "opbeans-node", - "transactionName": "Process payment", + "transactionName": "Process completed order", "transactionType": "Worker", - "transactionsPerMinute": 1.3, - }, - Object { - "agentName": "python", - "averageResponseTime": 118686.822222222, - "impact": 37.7068083771466, - "key": Object { - "service.name": "opbeans-python", - "transaction.name": "opbeans.tasks.update_stats", - }, - "serviceName": "opbeans-python", - "transactionName": "opbeans.tasks.update_stats", - "transactionType": "celery", - "transactionsPerMinute": 12, + "transactionsPerMinute": 0.7, }, Object { "agentName": "nodejs", - "averageResponseTime": 1039228.27659574, - "impact": 43.1048035741496, + "averageResponseTime": 1077989.66666667, + "impact": 100, "key": Object { "service.name": "opbeans-node", - "transaction.name": "Process completed order", + "transaction.name": "Process payment", }, "serviceName": "opbeans-node", - "transactionName": "Process completed order", + "transactionName": "Process payment", "transactionType": "Worker", - "transactionsPerMinute": 1.56666666666667, - }, - Object { - "agentName": "python", - "averageResponseTime": 1949922.55555556, - "impact": 61.9499776921889, - "key": Object { - "service.name": "opbeans-python", - "transaction.name": "opbeans.tasks.sync_customers", - }, - "serviceName": "opbeans-python", - "transactionName": "opbeans.tasks.sync_customers", - "transactionType": "celery", - "transactionsPerMinute": 1.2, - }, - Object { - "agentName": "dotnet", - "averageResponseTime": 5963775, - "impact": 100, - "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Orders/Get", - }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Orders/Get", - "transactionType": "request", - "transactionsPerMinute": 0.633333333333333, + "transactionsPerMinute": 0.7, }, ] `; diff --git a/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/critical_path.spec.ts similarity index 79% rename from x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/critical_path.spec.ts index 6d55c55ba6db..1f1d28215307 100644 --- a/x-pack/test/apm_api_integration/tests/traces/critical_path.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/critical_path.spec.ts @@ -10,13 +10,13 @@ import expect from '@kbn/expect'; import { Assign } from '@kbn/utility-types'; import { compact, invert, sortBy, uniq } from 'lodash'; import { Readable } from 'stream'; -import { SupertestReturnType } from '../../common/apm_api_supertest'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { SupertestReturnType } from '../../../../services/apm_api'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const start = new Date('2022-01-01T00:00:00.000Z').getTime(); const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; @@ -33,79 +33,86 @@ export default function ApiTest({ getService }: FtrProviderContext) { children: FormattedNode[]; } - // format tree in somewhat concise format for easier testing - function formatTree(nodes: HydratedNode[]): FormattedNode[] { - return sortBy( - nodes.map((node) => { - const name = - node.metadata?.['processor.event'] === 'transaction' - ? node.metadata['transaction.name'] - : node.metadata?.['span.name'] || 'root'; - return { name, value: node.countExclusive, children: formatTree(node.children) }; - }), - (node) => node.name - ); - } - - async function fetchAndBuildCriticalPathTree( - options: { fn: () => SynthtraceGenerator<ApmFields> } & ( - | { serviceName: string; transactionName: string } - | {} - ) - ) { - const { fn } = options; - - const generator = fn(); - - const unserialized = Array.from(generator); - - const serialized = unserialized.flatMap((event) => event.serialize()); - - const traceIds = compact(uniq(serialized.map((event) => event['trace.id']))); - - await apmSynthtraceEsClient.index(Readable.from(unserialized)); - - return apmApiClient - .readUser({ - endpoint: 'POST /internal/apm/traces/aggregated_critical_path', - params: { - body: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - traceIds, - serviceName: 'serviceName' in options ? options.serviceName : null, - transactionName: 'transactionName' in options ? options.transactionName : null, + describe('Aggregated critical path', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + // format tree in somewhat concise format for easier testing + function formatTree(nodes: HydratedNode[]): FormattedNode[] { + return sortBy( + nodes.map((node) => { + const name = + node.metadata?.['processor.event'] === 'transaction' + ? node.metadata['transaction.name'] + : node.metadata?.['span.name'] || 'root'; + return { name, value: node.countExclusive, children: formatTree(node.children) }; + }), + (node) => node.name + ); + } + + async function fetchAndBuildCriticalPathTree( + options: { fn: () => SynthtraceGenerator<ApmFields> } & ( + | { serviceName: string; transactionName: string } + | {} + ) + ) { + const { fn } = options; + + const generator = fn(); + + const unserialized = Array.from(generator); + + const serialized = unserialized.flatMap((event) => event.serialize()); + + const traceIds = compact(uniq(serialized.map((event) => event['trace.id']))); + + await apmSynthtraceEsClient.index(Readable.from(unserialized)); + + return apmApiClient + .readUser({ + endpoint: 'POST /internal/apm/traces/aggregated_critical_path', + params: { + body: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + traceIds, + serviceName: 'serviceName' in options ? options.serviceName : null, + transactionName: 'transactionName' in options ? options.transactionName : null, + }, }, - }, - }) - .then((response) => { - const criticalPath = response.body.criticalPath!; + }) + .then((response) => { + const criticalPath = response.body.criticalPath!; - const nodeIdByOperationId = invert(criticalPath.operationIdByNodeId); + const nodeIdByOperationId = invert(criticalPath.operationIdByNodeId); - const { rootNodes, maxDepth } = getAggregatedCriticalPathRootNodes({ - criticalPath, - }); + const { rootNodes, maxDepth } = getAggregatedCriticalPathRootNodes({ + criticalPath, + }); + + function hydrateNode(node: Node): HydratedNode { + return { + ...node, + metadata: criticalPath.metadata[criticalPath.operationIdByNodeId[node.nodeId]], + children: node.children.map(hydrateNode), + }; + } - function hydrateNode(node: Node): HydratedNode { return { - ...node, - metadata: criticalPath.metadata[criticalPath.operationIdByNodeId[node.nodeId]], - children: node.children.map(hydrateNode), + rootNodes: rootNodes.map(hydrateNode), + maxDepth, + criticalPath, + nodeIdByOperationId, }; - } - - return { - rootNodes: rootNodes.map(hydrateNode), - maxDepth, - criticalPath, - nodeIdByOperationId, - }; - }); - } + }); + } + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + }); + + after(() => apmSynthtraceEsClient.clean()); - // FLAKY: https://github.com/elastic/kibana/issues/177542 - registry.when('Aggregated critical path', { config: 'basic', archives: [] }, () => { it('builds up the correct tree for a single transaction', async () => { const java = apm .service({ name: 'java', environment: 'production', agentName: 'java' }) @@ -427,7 +434,5 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, ]); }); - - after(() => apmSynthtraceEsClient.clean()); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/find_traces.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/find_traces.spec.ts new file mode 100644 index 000000000000..b1bd8467456d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/find_traces.spec.ts @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { TraceSearchType } from '@kbn/apm-plugin/common/trace_explorer'; +import { Environment } from '@kbn/apm-plugin/common/environment_rt'; +import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; +import { sortBy } from 'lodash'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmApiError } from '../../../../services/apm_api'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { generateTrace } from './generate_trace'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const start = new Date('2022-01-01T00:00:00.000Z').getTime(); + const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; + + // for EQL sequences to work, events need a slight time offset, + // as ES will sort based on @timestamp. to acommodate this offset + // we also add a little bit of a buffer to the requested time range + const endWithOffset = end + 100000; + + describe('Find traces', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + async function fetchTraceSamples({ + query, + type, + environment, + }: { + query: string; + type: TraceSearchType; + environment: Environment; + }) { + return apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/find`, + params: { + query: { + query, + type, + start: new Date(start).toISOString(), + end: new Date(endWithOffset).toISOString(), + environment, + }, + }, + }); + } + + function fetchTraces(traceSamples: Array<{ traceId: string; transactionId: string }>) { + if (!traceSamples.length) { + return []; + } + + return Promise.all( + traceSamples.map(async ({ traceId, transactionId }) => { + const response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}`, + params: { + path: { traceId }, + query: { + start: new Date(start).toISOString(), + end: new Date(endWithOffset).toISOString(), + entryTransactionId: transactionId, + }, + }, + }); + return response.body.traceItems.traceDocs; + }) + ); + } + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('when traces do not exist', () => { + it('handles empty state', async () => { + const response = await fetchTraceSamples({ + query: '', + type: TraceSearchType.kql, + environment: ENVIRONMENT_ALL.value, + }); + + expect(response.status).to.be(200); + expect(response.body).to.eql({ + traceSamples: [], + }); + }); + }); + + describe('when traces exist', () => { + before(() => { + const java = apm + .service({ name: 'java', environment: 'production', agentName: 'java' }) + .instance('java'); + + const node = apm + .service({ name: 'node', environment: 'development', agentName: 'nodejs' }) + .instance('node'); + + const python = apm + .service({ name: 'python', environment: 'production', agentName: 'python' }) + .instance('python'); + + return apmSynthtraceEsClient.index( + timerange(start, end) + .interval('15m') + .rate(1) + .generator((timestamp) => { + return [ + generateTrace(timestamp, [java, node]), + generateTrace(timestamp, [node, java], 'redis'), + generateTrace(timestamp, [python], 'redis'), + generateTrace(timestamp, [python, node, java], 'elasticsearch'), + generateTrace(timestamp, [java, python, node]), + ]; + }) + ); + }); + + describe('when using KQL', () => { + describe('and the query is empty', () => { + it('returns all trace samples', async () => { + const { + body: { traceSamples }, + } = await fetchTraceSamples({ + query: '', + type: TraceSearchType.kql, + environment: 'ENVIRONMENT_ALL', + }); + + expect(traceSamples.length).to.eql(5); + }); + }); + + describe('and query is set', () => { + it('returns the relevant traces', async () => { + const { + body: { traceSamples }, + } = await fetchTraceSamples({ + query: 'span.destination.service.resource:elasticsearch', + type: TraceSearchType.kql, + environment: 'ENVIRONMENT_ALL', + }); + + expect(traceSamples.length).to.eql(1); + }); + }); + }); + + describe('when using EQL', () => { + describe('and the query is invalid', () => { + it.skip('returns a 400', async function () { + try { + await fetchTraceSamples({ + query: '', + type: TraceSearchType.eql, + environment: 'ENVIRONMENT_ALL', + }); + this.fail(); + } catch (error: unknown) { + const apiError = error as ApmApiError; + expect(apiError.res.status).to.eql(400); + } + }); + }); + + describe('and the query is set', () => { + it('returns the correct trace samples for transaction sequences', async () => { + const { + body: { traceSamples }, + } = await fetchTraceSamples({ + query: `sequence by trace.id + [ transaction where service.name == "java" ] + [ transaction where service.name == "node" ]`, + type: TraceSearchType.eql, + environment: 'ENVIRONMENT_ALL', + }); + + const traces = await fetchTraces(traceSamples); + + expect(traces.length).to.eql(2); + + const mapped = traces.map((traceDocs) => { + return sortBy(traceDocs, '@timestamp') + .filter((doc) => doc.processor.event === 'transaction') + .map((doc) => doc.service.name); + }); + + expect(mapped).to.eql([ + ['java', 'node'], + ['java', 'python', 'node'], + ]); + }); + }); + + it('returns the correct trace samples for join sequences', async () => { + const { + body: { traceSamples }, + } = await fetchTraceSamples({ + query: `sequence by trace.id + [ span where service.name == "java" ] by span.id + [ transaction where service.name == "python" ] by parent.id`, + type: TraceSearchType.eql, + environment: 'ENVIRONMENT_ALL', + }); + + const traces = await fetchTraces(traceSamples); + + expect(traces.length).to.eql(1); + + const mapped = traces.map((traceDocs) => { + return sortBy(traceDocs, '@timestamp') + .filter((doc) => doc.processor.event === 'transaction') + .map((doc) => doc.service.name); + }); + + expect(mapped).to.eql([['java', 'python', 'node']]); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/traces/generate_trace.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/generate_trace.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/traces/generate_trace.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/generate_trace.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/index.ts new file mode 100644 index 000000000000..d54216b3f5d8 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Traces', () => { + loadTestFile(require.resolve('./large_trace/large_trace.spec.ts')); + loadTestFile(require.resolve('./critical_path.spec.ts')); + loadTestFile(require.resolve('./find_traces.spec.ts')); + loadTestFile(require.resolve('./span_details.spec.ts')); + loadTestFile(require.resolve('./top_traces.spec.ts')); + loadTestFile(require.resolve('./trace_by_id.spec.ts')); + loadTestFile(require.resolve('./transaction_details.spec.ts')); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/large_trace/generate_large_trace.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/traces/large_trace/generate_large_trace.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/large_trace/generate_large_trace.ts diff --git a/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/large_trace/large_trace.spec.ts similarity index 86% rename from x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/large_trace/large_trace.spec.ts index 023db5c0d2ba..7a0952b856c6 100644 --- a/x-pack/test/apm_api_integration/tests/traces/large_trace/large_trace.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/large_trace/large_trace.spec.ts @@ -14,8 +14,9 @@ import { import type { Client } from '@elastic/elasticsearch'; import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import expect from '@kbn/expect'; -import { ApmApiClient } from '../../../common/config'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmApiClient } from '../../../../../services/apm_api'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; import { generateLargeTrace } from './generate_large_trace'; const start = new Date('2023-01-01T00:00:00.000Z').getTime(); @@ -23,16 +24,17 @@ const end = new Date('2023-01-01T00:01:00.000Z').getTime() - 1; const rootTransactionName = 'Long trace'; const environment = 'long_trace_scenario'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/177660 - registry.when('Large trace', { config: 'basic', archives: [] }, () => { + describe('Large trace', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + describe('when the trace is large (>15.000 items)', () => { - before(() => { + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); return generateLargeTrace({ start, end, diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/span_details.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/span_details.spec.ts new file mode 100644 index 000000000000..d5b2efc3479a --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/span_details.spec.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { Readable } from 'stream'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const start = new Date('2022-01-01T00:00:00.000Z').getTime(); + const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; + + describe('Span details', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + async function fetchSpanDetails({ + traceId, + spanId, + parentTransactionId, + }: { + traceId: string; + spanId: string; + parentTransactionId?: string; + }) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}/spans/{spanId}`, + params: { + path: { traceId, spanId }, + query: { + parentTransactionId, + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + }, + }, + }); + } + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('when data is not loaded', () => { + it('handles empty state', async () => { + const response = await fetchSpanDetails({ + traceId: 'foo', + spanId: 'bar', + }); + + expect(response.status).to.be(200); + expect(response.body).to.eql({}); + }); + }); + + describe('when data is loaded', () => { + let traceId: string; + let spanId: string; + let parentTransactionId: string; + before(async () => { + const instanceJava = apm + .service({ name: 'synth-apple', environment: 'production', agentName: 'java' }) + .instance('instance-b'); + const events = timerange(start, end) + .interval('1m') + .rate(1) + .generator((timestamp) => { + return [ + instanceJava + .transaction({ transactionName: 'GET /apple 🍏' }) + .timestamp(timestamp) + .duration(1000) + .failure() + .errors( + instanceJava + .error({ message: '[ResponseError] index_not_found_exception' }) + .timestamp(timestamp + 50) + ) + .children( + instanceJava + .span({ + spanName: 'get_green_apple_🍏', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) + .timestamp(timestamp + 50) + .duration(900) + .success() + ), + ]; + }); + + const unserialized = Array.from(events); + + const entities = unserialized.flatMap((event) => event.serialize()); + + const span = entities.find((entity) => { + return entity['processor.event'] === 'span'; + }); + spanId = span?.['span.id']!; + parentTransactionId = span?.['parent.id']!; + traceId = span?.['trace.id']!; + + await apmSynthtraceEsClient.index(Readable.from(unserialized)); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('span details', () => { + let spanDetails: Awaited<ReturnType<typeof fetchSpanDetails>>['body']; + before(async () => { + const response = await fetchSpanDetails({ + traceId, + spanId, + parentTransactionId, + }); + expect(response.status).to.eql(200); + spanDetails = response.body; + }); + it('returns span details', () => { + expect(spanDetails.span?.span.name).to.eql('get_green_apple_🍏'); + expect(spanDetails.parentTransaction?.transaction.name).to.eql('GET /apple 🍏'); + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/top_traces.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/top_traces.spec.ts new file mode 100644 index 000000000000..1f2abda3d42f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/top_traces.spec.ts @@ -0,0 +1,144 @@ +/* + * 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 { sortBy } from 'lodash'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import archives_metadata from '../constants/archives_metadata'; +import { ARCHIVER_ROUTES } from '../constants/archiver'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const esArchiver = getService('esArchiver'); + + const archiveName = '8.0.0'; + const metadata = archives_metadata[archiveName]; + + // url parameters + const { start, end } = metadata; + + describe('Top traces', () => { + describe('when data is not loaded', () => { + it('handles empty state', async () => { + const response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces`, + params: { + query: { + start, + end, + kuery: '', + environment: 'ENVIRONMENT_ALL', + probability: 1, + }, + }, + }); + + expect(response.status).to.be(200); + expect(response.body.items.length).to.be(0); + }); + }); + + describe('when data is loaded', () => { + let response: any; + before(async () => { + await esArchiver.load(ARCHIVER_ROUTES[archiveName]); + response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/traces', + params: { + query: { + start, + end, + kuery: '', + environment: 'ENVIRONMENT_ALL', + probability: 1, + }, + }, + }); + }); + + after(async () => { + await esArchiver.unload(ARCHIVER_ROUTES[archiveName]); + }); + + it('returns the correct status code', () => { + expect(response.status).to.be(200); + }); + + it('returns the correct number of buckets', () => { + expectSnapshot(response.body.items.length).toMatchInline(`59`); + }); + + it('returns the correct buckets', () => { + const sortedItems = sortBy(response.body.items, 'impact'); + + const firstItem = sortedItems[0]; + const lastItem = sortedItems[sortedItems.length - 1]; + + const groups = sortedItems.map((item) => item.key).slice(0, 5); + + expectSnapshot(sortedItems).toMatch(); + + expectSnapshot(firstItem).toMatchInline(` + Object { + "agentName": "ruby", + "averageResponseTime": 5664, + "impact": 0, + "key": Object { + "service.name": "opbeans-ruby", + "transaction.name": "Api::OrdersController#create", + }, + "serviceName": "opbeans-ruby", + "transactionName": "Api::OrdersController#create", + "transactionType": "request", + "transactionsPerMinute": 0.0166666666666667, + } + `); + + expectSnapshot(lastItem).toMatchInline(` + Object { + "agentName": "nodejs", + "averageResponseTime": 1077989.66666667, + "impact": 100, + "key": Object { + "service.name": "opbeans-node", + "transaction.name": "Process payment", + }, + "serviceName": "opbeans-node", + "transactionName": "Process payment", + "transactionType": "Worker", + "transactionsPerMinute": 0.7, + } + `); + + expectSnapshot(groups).toMatchInline(` + Array [ + Object { + "service.name": "opbeans-ruby", + "transaction.name": "Api::OrdersController#create", + }, + Object { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#products", + }, + Object { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#orders", + }, + Object { + "service.name": "opbeans-java", + "transaction.name": "APIRestController#product", + }, + Object { + "service.name": "opbeans-node", + "transaction.name": "POST /api", + }, + ] + `); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/trace_by_id.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/trace_by_id.spec.ts new file mode 100644 index 000000000000..5599844e744b --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/trace_by_id.spec.ts @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { Readable } from 'stream'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const start = new Date('2022-01-01T00:00:00.000Z').getTime(); + const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; + + describe('Trace by ID', () => { + describe('Trace does not exist', () => { + it('handles empty state', async () => { + const response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}`, + params: { + path: { traceId: 'foo' }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + entryTransactionId: 'foo', + }, + }, + }); + + expect(response.status).to.be(200); + expect(response.body).to.eql({ + traceItems: { + exceedsMax: false, + traceDocs: [], + errorDocs: [], + spanLinksCountById: {}, + traceDocsTotal: 0, + maxTraceItems: 5000, + }, + }); + }); + }); + + describe('Trace exists', () => { + let entryTransactionId: string; + let serviceATraceId: string; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + const instanceJava = apm + .service({ name: 'synth-apple', environment: 'production', agentName: 'java' }) + .instance('instance-b'); + const events = timerange(start, end) + .interval('1m') + .rate(1) + .generator((timestamp) => { + return [ + instanceJava + .transaction({ transactionName: 'GET /apple 🍏' }) + .timestamp(timestamp) + .duration(1000) + .failure() + .errors( + instanceJava + .error({ message: '[ResponseError] index_not_found_exception' }) + .timestamp(timestamp + 50) + ) + .children( + instanceJava + .span({ + spanName: 'get_green_apple_🍏', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) + .timestamp(timestamp + 50) + .duration(900) + .success() + ), + ]; + }); + const unserialized = Array.from(events); + + const serialized = unserialized.flatMap((event) => event.serialize()); + + entryTransactionId = serialized[0]['transaction.id']!; + serviceATraceId = serialized[0]['trace.id']!; + + await apmSynthtraceEsClient.index(Readable.from(unserialized)); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('return trace', () => { + let traces: APIReturnType<'GET /internal/apm/traces/{traceId}'>; + before(async () => { + const response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}`, + params: { + path: { traceId: serviceATraceId }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + entryTransactionId, + }, + }, + }); + + expect(response.status).to.eql(200); + traces = response.body; + }); + + it('returns some errors', () => { + expect(traces.traceItems.errorDocs.length).to.be.greaterThan(0); + expect(traces.traceItems.errorDocs[0].error.exception?.[0].message).to.eql( + '[ResponseError] index_not_found_exception' + ); + }); + + it('returns some trace docs', () => { + expect(traces.traceItems.traceDocs.length).to.be.greaterThan(0); + expect( + traces.traceItems.traceDocs.map((item) => { + if (item.span && 'name' in item.span) { + return item.span.name; + } + if (item.transaction && 'name' in item.transaction) { + return item.transaction.name; + } + }) + ).to.eql(['GET /apple 🍏', 'get_green_apple_🍏']); + }); + + it('returns entry transaction details', () => { + expect(traces.entryTransaction).to.not.be(undefined); + expect(traces.entryTransaction?.transaction.id).to.equal(entryTransactionId); + expect(traces.entryTransaction?.transaction.name).to.equal('GET /apple 🍏'); + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/transaction_details.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/transaction_details.spec.ts new file mode 100644 index 000000000000..e29006e29989 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/traces/transaction_details.spec.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { Readable } from 'stream'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const start = new Date('2022-01-01T00:00:00.000Z').getTime(); + const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; + + async function fetchTransactionDetails({ + traceId, + transactionId, + }: { + traceId: string; + transactionId: string; + }) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/traces/{traceId}/transactions/{transactionId}`, + params: { + path: { + traceId, + transactionId, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + }, + }, + }); + } + + describe('Transaction details', () => { + describe('when data is not loaded', () => { + it('handles empty state', async () => { + const response = await fetchTransactionDetails({ + traceId: 'foo', + transactionId: 'bar', + }); + + expect(response.status).to.be(200); + expect(response.body).to.eql({}); + }); + }); + + describe('when data is loaded', () => { + let traceId: string; + let transactionId: string; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + const instanceJava = apm + .service({ name: 'synth-apple', environment: 'production', agentName: 'java' }) + .instance('instance-b'); + const events = timerange(start, end) + .interval('1m') + .rate(1) + .generator((timestamp) => { + return [ + instanceJava + .transaction({ transactionName: 'GET /apple 🍏' }) + .timestamp(timestamp) + .duration(1000) + .failure() + .errors( + instanceJava + .error({ message: '[ResponseError] index_not_found_exception' }) + .timestamp(timestamp + 50) + ) + .children( + instanceJava + .span({ + spanName: 'get_green_apple_🍏', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) + .timestamp(timestamp + 50) + .duration(900) + .success() + ), + ]; + }); + + const unserialized = Array.from(events); + + const entities = unserialized.flatMap((event) => event.serialize()); + + const transaction = entities[0]; + transactionId = transaction?.['transaction.id']!; + traceId = transaction?.['trace.id']!; + + await apmSynthtraceEsClient.index(Readable.from(unserialized)); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('transaction details', () => { + let transactionDetails: Awaited<ReturnType<typeof fetchTransactionDetails>>['body']; + before(async () => { + const response = await fetchTransactionDetails({ + traceId, + transactionId, + }); + expect(response.status).to.eql(200); + transactionDetails = response.body; + }); + it('returns transaction details', () => { + expect(transactionDetails.transaction.name).to.eql('GET /apple 🍏'); + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/breakdown.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/breakdown.spec.ts new file mode 100644 index 000000000000..73437daa0865 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/breakdown.spec.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import archives from '../constants/archives_metadata'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const archiveName = 'apm_8.0.0'; + const { start, end } = archives[archiveName]; + const transactionType = 'request'; + + describe('Breakdown', () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/transaction/charts/breakdown', + params: { + path: { serviceName: 'opbeans-node' }, + query: { + start, + end, + transactionType, + environment: 'ENVIRONMENT_ALL', + kuery: '', + }, + }, + }); + + expect(response.status).to.be(200); + expect(response.body).to.eql({ timeseries: [] }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/error_rate.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/error_rate.spec.ts new file mode 100644 index 000000000000..976f33a59488 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/error_rate.spec.ts @@ -0,0 +1,436 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { buildQueryFromFilters } from '@kbn/es-query'; +import { first, last } from 'lodash'; +import moment from 'moment'; +import { + APIClientRequestParamsOf, + APIReturnType, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; +import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +type ErrorRate = + APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate'>; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + // url parameters + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function fetchErrorCharts( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate'>['params'] + > + ) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/transactions/charts/error_rate`, + params: { + path: { serviceName: overrides?.path?.serviceName || 'opbeans-go' }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + transactionType: 'request', + environment: 'ENVIRONMENT_ALL', + kuery: '', + documentType: ApmDocumentType.TransactionMetric, + rollupInterval: RollupInterval.OneMinute, + bucketSizeInSeconds: 60, + ...overrides?.query, + }, + }, + }); + } + + describe('Error rate', () => { + describe('Error rate when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await fetchErrorCharts(); + expect(response.status).to.be(200); + + const body = response.body as ErrorRate; + expect(body).to.be.eql({ + currentPeriod: { timeseries: [], average: null }, + previousPeriod: { timeseries: [], average: null }, + }); + }); + + it('handles the empty state with comparison data', async () => { + const response = await fetchErrorCharts({ + query: { + start: moment(end).subtract(7, 'minutes').toISOString(), + offset: '7m', + }, + }); + expect(response.status).to.be(200); + + const body = response.body as ErrorRate; + expect(body).to.be.eql({ + currentPeriod: { timeseries: [], average: null }, + previousPeriod: { timeseries: [], average: null }, + }); + }); + }); + + describe('Error rate when data is loaded', () => { + const config = { + firstTransaction: { + name: 'GET /apple 🍎 ', + successRate: 50, + failureRate: 50, + }, + secondTransaction: { + name: 'GET /pear 🍎 ', + successRate: 25, + failureRate: 75, + }, + }; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + const serviceGoProdInstance = apm + .service({ name: 'opbeans-go', environment: 'production', agentName: 'go' }) + .instance('instance-a'); + + const { firstTransaction, secondTransaction } = config; + + const documents = [ + timerange(start, end) + .ratePerMinute(firstTransaction.successRate) + .generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: firstTransaction.name }) + .timestamp(timestamp) + .duration(1000) + .success() + ), + timerange(start, end) + .ratePerMinute(firstTransaction.failureRate) + .generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: firstTransaction.name }) + .duration(1000) + .timestamp(timestamp) + .failure() + ), + timerange(start, end) + .ratePerMinute(secondTransaction.successRate) + .generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: secondTransaction.name }) + .timestamp(timestamp) + .duration(1000) + .success() + ), + timerange(start, end) + .ratePerMinute(secondTransaction.failureRate) + .generator((timestamp) => + serviceGoProdInstance + .transaction({ transactionName: secondTransaction.name }) + .duration(1000) + .timestamp(timestamp) + .failure() + ), + ]; + await apmSynthtraceEsClient.index(documents); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('returns the transaction error rate', () => { + let errorRateResponse: ErrorRate; + + before(async () => { + const response = await fetchErrorCharts({ + query: { transactionName: config.firstTransaction.name }, + }); + errorRateResponse = response.body; + }); + + it('returns some data', () => { + expect(errorRateResponse.currentPeriod.average).to.be.greaterThan(0); + expect(errorRateResponse.previousPeriod.average).to.be(null); + + expect(errorRateResponse.currentPeriod.timeseries).not.to.be.empty(); + expect(errorRateResponse.previousPeriod.timeseries).to.empty(); + + const nonNullDataPoints = errorRateResponse.currentPeriod.timeseries.filter( + ({ y }) => y !== null + ); + + expect(nonNullDataPoints).not.to.be.empty(); + }); + + it('has the correct start date', () => { + expect( + new Date(first(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() + ).to.eql('2021-01-01T00:00:00.000Z'); + }); + + it('has the correct end date', () => { + expect( + new Date(last(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() + ).to.eql('2021-01-01T00:14:00.000Z'); + }); + + it('has the correct number of buckets', () => { + expect(errorRateResponse.currentPeriod.timeseries.length).to.be.eql(15); + }); + + it('has the correct calculation for average', () => { + expect(errorRateResponse.currentPeriod.average).to.eql( + config.firstTransaction.failureRate / 100 + ); + }); + }); + + describe('returns the transaction error rate with comparison data per transaction name', () => { + let errorRateResponse: ErrorRate; + + before(async () => { + const query = { + transactionName: config.firstTransaction.name, + start: moment(end).subtract(7, 'minutes').toISOString(), + offset: '7m', + }; + + const response = await fetchErrorCharts({ query }); + + errorRateResponse = response.body; + }); + + it('returns some data', () => { + expect(errorRateResponse.currentPeriod.average).to.be.greaterThan(0); + expect(errorRateResponse.previousPeriod.average).to.be.greaterThan(0); + + expect(errorRateResponse.currentPeriod.timeseries).not.to.be.empty(); + expect(errorRateResponse.previousPeriod.timeseries).not.to.be.empty(); + + const currentPeriodNonNullDataPoints = errorRateResponse.currentPeriod.timeseries.filter( + ({ y }) => y !== null + ); + + const previousPeriodNonNullDataPoints = + errorRateResponse.previousPeriod.timeseries.filter(({ y }) => y !== null); + + expect(currentPeriodNonNullDataPoints).not.to.be.empty(); + expect(previousPeriodNonNullDataPoints).not.to.be.empty(); + }); + + it('has the correct start date', () => { + expect( + new Date(first(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() + ).to.eql('2021-01-01T00:07:00.000Z'); + expect( + new Date(first(errorRateResponse.previousPeriod.timeseries)?.x ?? NaN).toISOString() + ).to.eql('2021-01-01T00:07:00.000Z'); + }); + + it('has the correct end date', () => { + expect( + new Date(last(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() + ).to.eql('2021-01-01T00:14:00.000Z'); + expect( + new Date(last(errorRateResponse.previousPeriod.timeseries)?.x ?? NaN).toISOString() + ).to.eql('2021-01-01T00:14:00.000Z'); + }); + + it('has the correct number of buckets', () => { + expect(errorRateResponse.currentPeriod.timeseries.length).to.eql(8); + expect(errorRateResponse.previousPeriod.timeseries.length).to.eql(8); + }); + + it('has the correct calculation for average', () => { + expect(errorRateResponse.currentPeriod.average).to.eql( + config.firstTransaction.failureRate / 100 + ); + expect(errorRateResponse.previousPeriod.average).to.eql( + config.firstTransaction.failureRate / 100 + ); + }); + + it('matches x-axis on current period and previous period', () => { + expect(errorRateResponse.currentPeriod.timeseries.map(({ x }) => x)).to.be.eql( + errorRateResponse.previousPeriod.timeseries.map(({ x }) => x) + ); + }); + }); + + describe('returns the same error rate for tx metrics and service tx metrics ', () => { + let txMetricsErrorRateResponse: ErrorRate; + let serviceTxMetricsErrorRateResponse: ErrorRate; + + before(async () => { + const [txMetricsResponse, serviceTxMetricsResponse] = await Promise.all([ + fetchErrorCharts(), + fetchErrorCharts({ + query: { documentType: ApmDocumentType.ServiceTransactionMetric }, + }), + ]); + + txMetricsErrorRateResponse = txMetricsResponse.body; + serviceTxMetricsErrorRateResponse = serviceTxMetricsResponse.body; + }); + + describe('has the correct calculation for average', () => { + const expectedFailureRate = + (config.firstTransaction.failureRate + config.secondTransaction.failureRate) / 2 / 100; + + it('for tx metrics', () => { + expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); + }); + + it('for service tx metrics', () => { + expect(serviceTxMetricsErrorRateResponse.currentPeriod.average).to.eql( + expectedFailureRate + ); + }); + }); + }); + + describe('handles kuery', () => { + let txMetricsErrorRateResponse: ErrorRate; + + before(async () => { + const txMetricsResponse = await fetchErrorCharts({ + query: { + kuery: 'transaction.name : "GET /pear 🍎 "', + }, + }); + txMetricsErrorRateResponse = txMetricsResponse.body; + }); + + describe('has the correct calculation for average with kuery', () => { + const expectedFailureRate = config.secondTransaction.failureRate / 100; + + it('for tx metrics', () => { + expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); + }); + }); + }); + + describe('handles filters', () => { + const filters = [ + { + meta: { + disabled: false, + negate: false, + alias: null, + key: 'transaction.name', + params: ['GET /api/product/list'], + type: 'phrases', + }, + query: { + bool: { + minimum_should_match: 1, + should: { + match_phrase: { + 'transaction.name': 'GET /pear 🍎 ', + }, + }, + }, + }, + }, + ]; + const serializedFilters = JSON.stringify(buildQueryFromFilters(filters, undefined)); + let txMetricsErrorRateResponse: ErrorRate; + + before(async () => { + const txMetricsResponse = await fetchErrorCharts({ + query: { + filters: serializedFilters, + }, + }); + txMetricsErrorRateResponse = txMetricsResponse.body; + }); + + describe('has the correct calculation for average with filter', () => { + const expectedFailureRate = config.secondTransaction.failureRate / 100; + + it('for tx metrics', () => { + expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); + }); + }); + + describe('has the correct calculation for average with negate filter', () => { + const expectedFailureRate = config.secondTransaction.failureRate / 100; + + it('for tx metrics', () => { + expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); + }); + }); + }); + + describe('handles negate filters', () => { + const filters = [ + { + meta: { + disabled: false, + negate: true, + alias: null, + key: 'transaction.name', + params: ['GET /api/product/list'], + type: 'phrases', + }, + query: { + bool: { + minimum_should_match: 1, + should: { + match_phrase: { + 'transaction.name': 'GET /pear 🍎 ', + }, + }, + }, + }, + }, + ]; + const serializedFilters = JSON.stringify(buildQueryFromFilters(filters, undefined)); + let txMetricsErrorRateResponse: ErrorRate; + + before(async () => { + const txMetricsResponse = await fetchErrorCharts({ + query: { + filters: serializedFilters, + }, + }); + txMetricsErrorRateResponse = txMetricsResponse.body; + }); + + describe('has the correct calculation for average with filter', () => { + const expectedFailureRate = config.firstTransaction.failureRate / 100; + + it('for tx metrics', () => { + expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); + }); + }); + }); + + describe('handles bad filters request', () => { + it('for tx metrics', async () => { + try { + await fetchErrorCharts({ + query: { + filters: '{}}}', + }, + }); + } catch (e) { + expect(e.res.status).to.eql(400); + } + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/index.ts new file mode 100644 index 000000000000..30715c862c7f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('transactions', () => { + loadTestFile(require.resolve('./breakdown.spec.ts')); + loadTestFile(require.resolve('./error_rate.spec.ts')); + loadTestFile(require.resolve('./latency_overall_distribution.spec.ts')); + loadTestFile(require.resolve('./latency.spec.ts')); + loadTestFile(require.resolve('./transactions_groups_alerts.spec.ts')); + loadTestFile(require.resolve('./transactions_groups_detailed_statistics.spec.ts')); + loadTestFile(require.resolve('./transactions_groups_main_statistics.spec.ts')); + loadTestFile(require.resolve('./trace_samples.spec.ts')); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/latency.spec.ts similarity index 96% rename from x-pack/test/apm_api_integration/tests/transactions/latency.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/latency.spec.ts index eefe5cfb0d0f..f369bc63ca4e 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/latency.spec.ts @@ -17,15 +17,15 @@ import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; import { meanBy } from 'lodash'; import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; type LatencyChartReturnType = APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/latency'>; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const serviceName = 'synth-go'; const start = new Date('2021-01-01T00:00:00.000Z').getTime(); @@ -57,10 +57,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - registry.when( - 'Latency with a basic license when data is not loaded ', - { config: 'basic', archives: [] }, - () => { + describe('Latency', () => { + describe('when data is not loaded ', () => { it('handles the empty state', async () => { const response = await fetchLatencyCharts(); expect(response.status).to.be(200); @@ -70,19 +68,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(latencyChartReturn.currentPeriod.latencyTimeseries.length).to.be(0); expect(latencyChartReturn.previousPeriod.latencyTimeseries.length).to.be(0); }); - } - ); - - // FLAKY: https://github.com/elastic/kibana/issues/177596 - registry.when( - 'Latency with a basic license when data is loaded', - { config: 'basic', archives: [] }, - () => { + }); + + describe('when data is loaded', () => { const GO_PROD_RATE = 80; const GO_DEV_RATE = 20; const GO_PROD_DURATION = 1000; const GO_DEV_DURATION = 500; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); const serviceGoProdInstance = apm .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); @@ -439,6 +435,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } }); }); - } - ); + }); + }); } diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/latency_overall_distribution.spec.ts similarity index 68% rename from x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/latency_overall_distribution.spec.ts index 0f6060517db3..2b78a7b10088 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/latency_overall_distribution.spec.ts @@ -7,12 +7,12 @@ import expect from '@kbn/expect'; import { LatencyDistributionChartType } from '@kbn/apm-plugin/common/latency_distribution_chart_types'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { ARCHIVER_ROUTES } from '../constants/archiver'; +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const esArchiver = getService('esArchiver'); const endpoint = 'POST /internal/apm/latency/overall_distribution/transactions'; // This matches the parameters used for the other tab's search strategy approach in `../correlations/*`. @@ -29,10 +29,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }); - registry.when.skip( - 'latency overall distribution without data', - { config: 'trial', archives: [] }, - () => { + describe('Latency overall distribution', () => { + describe('without data', () => { it('handles the empty state', async () => { const response = await apmApiClient.readUser({ endpoint, @@ -43,14 +41,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body?.percentileThresholdValue).to.be(undefined); expect(response.body?.overallHistogram?.length).to.be(undefined); }); - } - ); + }); - registry.when.skip( - 'latency overall distribution with data and default args', - // This uses the same archive used for the other tab's search strategy approach in `../correlations/*`. - { config: 'trial', archives: ['8.0.0'] }, - () => { + describe('with data and default args', () => { + before(async () => { + await esArchiver.load(ARCHIVER_ROUTES['8.0.0']); + }); + after(async () => { + await esArchiver.unload(ARCHIVER_ROUTES['8.0.0']); + }); + + // This uses the same archive used for the other tab's search strategy approach in `../correlations/*`. it('returns percentileThresholdValue and overall histogram', async () => { const response = await apmApiClient.readUser({ endpoint, @@ -62,6 +63,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body?.percentileThresholdValue).to.be(1309695.875); expect(response.body?.overallHistogram?.length).to.be(101); }); - } - ); + }); + }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/trace_samples.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/trace_samples.spec.ts new file mode 100644 index 000000000000..004165905916 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/trace_samples.spec.ts @@ -0,0 +1,42 @@ +/* + * 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 archives from '../constants/archives_metadata'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + + const archiveName = 'apm_8.0.0'; + const { start, end } = archives[archiveName]; + + describe('Transaction trace samples', () => { + describe('when data is not loaded', () => { + it('handles empty state', async () => { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/transactions/traces/samples', + params: { + path: { serviceName: 'opbeans-java' }, + query: { + start, + end, + transactionType: 'request', + environment: 'ENVIRONMENT_ALL', + transactionName: 'APIRestController#stats', + kuery: '', + }, + }, + }); + + expect(response.status).to.be(200); + + expect(response.body.traceSamples.length).to.be(0); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_alerts.spec.ts similarity index 73% rename from x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_alerts.spec.ts index f6b2c7c3a74a..07b19ecc0b2a 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_alerts.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_alerts.spec.ts @@ -13,26 +13,26 @@ import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { AggregationType } from '@kbn/apm-plugin/common/rules/apm_rule_types'; import { ApmRuleType } from '@kbn/rule-data-utils'; -import { waitForAlertsForRule } from '../alerts/helpers/wait_for_alerts_for_rule'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { createApmRule, runRuleSoon, ApmAlertFields } from '../alerts/helpers/alerting_api_helper'; -import { waitForActiveRule } from '../alerts/helpers/wait_for_active_rule'; -import { cleanupRuleAndAlertState } from '../alerts/helpers/cleanup_rule_and_alert_state'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { APM_ACTION_VARIABLE_INDEX, APM_ALERTS_INDEX } from '../alerts/helpers/alerting_helper'; type TransactionsGroupsMainStatistics = APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics'>; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - const supertest = getService('supertest'); - const es = getService('es'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + const alertingApi = getService('alertingApi'); + const samlAuth = getService('samlAuth'); + const serviceName = 'synth-go'; const dayInMs = 24 * 60 * 60 * 1000; const start = Date.now() - dayInMs; const end = Date.now() + dayInMs; - const logger = getService('log'); + + type Alerts = Awaited<ReturnType<typeof alertingApi.waitForAlertInIndex>>; async function getTransactionGroups(overrides?: { path?: { @@ -69,12 +69,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }); expect(response.status).to.be(200); + return response.body as TransactionsGroupsMainStatistics; } - // FLAKY: https://github.com/elastic/kibana/issues/177617 - registry.when('when data is loaded', { config: 'basic', archives: [] }, () => { - describe('Alerts', () => { + describe('Transaction groups alerts', function () { + // fails on MKI, see https://github.com/elastic/kibana/issues/201531 + this.tags(['failsOnMKI']); + + describe('when data is loaded', () => { const transactions = [ { name: 'GET /api/task/avg', @@ -102,7 +105,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { type: 'request', }, ]; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + let roleAuthc: RoleCredentials; + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); const serviceGoProdInstance = apm .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); @@ -135,16 +143,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { ]); }); - after(() => apmSynthtraceEsClient.clean()); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + await apmSynthtraceEsClient.clean(); + }); - // FLAKY: https://github.com/elastic/kibana/issues/198866 - describe.skip('Transaction groups with avg transaction duration alerts', () => { + describe('with avg transaction duration alerts', () => { let ruleId: string; - let alerts: ApmAlertFields[]; + let alerts: Alerts; before(async () => { - const createdRule = await createApmRule({ - supertest, + const createdRule = await alertingApi.createRule({ name: `Latency threshold | ${serviceName}`, params: { serviceName, @@ -163,30 +172,42 @@ export default function ApiTest({ getService }: FtrProviderContext) { ], }, ruleTypeId: ApmRuleType.TransactionDuration, + consumer: 'apm', + roleAuthc, }); ruleId = createdRule.id; - alerts = await waitForAlertsForRule({ es, ruleId }); + alerts = await alertingApi.waitForAlertInIndex({ + ruleId, + indexName: APM_ALERTS_INDEX, + }); }); after(async () => { - await cleanupRuleAndAlertState({ es, supertest, logger }); + await alertingApi.cleanUpAlerts({ + ruleId, + alertIndexName: APM_ALERTS_INDEX, + connectorIndexName: APM_ACTION_VARIABLE_INDEX, + consumer: 'apm', + roleAuthc, + }); }); it('checks if rule is active', async () => { - const ruleStatus = await waitForActiveRule({ ruleId, supertest }); + const ruleStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + roleAuthc, + }); expect(ruleStatus).to.be('active'); }); it('should successfully run the rule', async () => { - const response = await runRuleSoon({ - ruleId, - supertest, - }); + const response = await alertingApi.runRule(roleAuthc, ruleId); expect(response.status).to.be(204); }); it('indexes alert document', async () => { - expect(alerts.length).to.be(1); + expect(alerts.hits.hits.length).to.be(1); }); it('returns the correct number of alert counts', async () => { @@ -210,13 +231,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - describe('Transaction groups with p99 transaction duration alerts', () => { + describe('with p99 transaction duration alerts', () => { let ruleId: string; - let alerts: ApmAlertFields[]; + let alerts: Alerts; before(async () => { - const createdRule = await createApmRule({ - supertest, + const createdRule = await alertingApi.createRule({ name: `Latency threshold | ${serviceName}`, params: { serviceName, @@ -235,31 +255,43 @@ export default function ApiTest({ getService }: FtrProviderContext) { ], }, ruleTypeId: ApmRuleType.TransactionDuration, + consumer: 'apm', + roleAuthc, }); ruleId = createdRule.id; - alerts = await waitForAlertsForRule({ es, ruleId }); + alerts = await alertingApi.waitForAlertInIndex({ + ruleId, + indexName: APM_ALERTS_INDEX, + }); }); after(async () => { - await cleanupRuleAndAlertState({ es, supertest, logger }); + await alertingApi.cleanUpAlerts({ + ruleId, + alertIndexName: APM_ALERTS_INDEX, + connectorIndexName: APM_ACTION_VARIABLE_INDEX, + consumer: 'apm', + roleAuthc, + }); }); it('checks if rule is active', async () => { - const ruleStatus = await waitForActiveRule({ ruleId, supertest }); + const ruleStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + roleAuthc, + }); expect(ruleStatus).to.be('active'); }); it('should successfully run the rule', async () => { - const response = await runRuleSoon({ - ruleId, - supertest, - }); + const response = await alertingApi.runRule(roleAuthc, ruleId); expect(response.status).to.be(204); }); it('indexes alert document', async () => { - expect(alerts.length).to.be(1); + expect(alerts.hits.hits.length).to.be(1); }); it('returns the correct number of alert counts', async () => { @@ -286,13 +318,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - describe('Transaction groups with error rate alerts', () => { + describe('with error rate alerts', () => { let ruleId: string; - let alerts: ApmAlertFields[]; + let alerts: Alerts; before(async () => { - const createdRule = await createApmRule({ - supertest, + const createdRule = await alertingApi.createRule({ name: `Error rate | ${serviceName}`, params: { serviceName, @@ -310,30 +341,43 @@ export default function ApiTest({ getService }: FtrProviderContext) { ], }, ruleTypeId: ApmRuleType.TransactionErrorRate, + consumer: 'apm', + roleAuthc, }); + ruleId = createdRule.id; - alerts = await waitForAlertsForRule({ es, ruleId }); + alerts = await alertingApi.waitForAlertInIndex({ + ruleId, + indexName: APM_ALERTS_INDEX, + }); }); after(async () => { - await cleanupRuleAndAlertState({ es, supertest, logger }); + await alertingApi.cleanUpAlerts({ + ruleId, + alertIndexName: APM_ALERTS_INDEX, + connectorIndexName: APM_ACTION_VARIABLE_INDEX, + consumer: 'apm', + roleAuthc, + }); }); it('checks if rule is active', async () => { - const ruleStatus = await waitForActiveRule({ ruleId, supertest }); + const ruleStatus = await alertingApi.waitForRuleStatus({ + ruleId, + expectedStatus: 'active', + roleAuthc, + }); expect(ruleStatus).to.be('active'); }); it('should successfully run the rule', async () => { - const response = await runRuleSoon({ - ruleId, - supertest, - }); + const response = await alertingApi.runRule(roleAuthc, ruleId); expect(response.status).to.be(204); }); it('indexes alert document', async () => { - expect(alerts.length).to.be(1); + expect(alerts.hits.hits.length).to.be(1); }); it('returns the correct number of alert counts', async () => { @@ -360,7 +404,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - describe('Transaction groups without alerts', () => { + describe('without alerts', () => { it('returns the correct number of alert counts', async () => { const txGroupsTypeTask = await getTransactionGroups({ query: { transactionType: 'task' }, diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_detailed_statistics.spec.ts similarity index 94% rename from x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_detailed_statistics.spec.ts index 77a4b67b4bc4..bc0ea9e1f501 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_detailed_statistics.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_detailed_statistics.spec.ts @@ -12,16 +12,16 @@ import { LatencyAggregationType } from '@kbn/apm-plugin/common/latency_aggregati import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; import { ApmDocumentType, ApmTransactionDocumentType } from '@kbn/apm-plugin/common/document_type'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { roundNumber } from '../../utils'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import { roundNumber } from '../../../../../../apm_api_integration/utils'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; type TransactionsGroupsDetailedStatistics = APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics'>; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const serviceName = 'synth-go'; const start = new Date('2021-01-01T00:00:00.000Z').getTime(); @@ -71,23 +71,21 @@ export default function ApiTest({ getService }: FtrProviderContext) { return response.body; } - registry.when( - 'Transaction groups detailed statistics when data is not loaded', - { config: 'basic', archives: [] }, - () => { + describe('Transactions groups detailed statistics', () => { + describe('when data is not loaded', () => { it('handles the empty state', async () => { const response = await callApi(); expect(response).to.be.eql({ currentPeriod: {}, previousPeriod: {} }); }); - } - ); + }); - // FLAKY: https://github.com/elastic/kibana/issues/177619 - registry.when('data is loaded', { config: 'basic', archives: [] }, () => { - describe('transactions groups detailed stats', () => { + describe('when data is loaded', () => { const GO_PROD_RATE = 75; const GO_PROD_ERROR_RATE = 25; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); const serviceGoProdInstance = apm .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); diff --git a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_main_statistics.spec.ts similarity index 88% rename from x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_main_statistics.spec.ts index d7c5e78fdcd1..df86780629f4 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_main_statistics.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/transactions/transactions_groups_main_statistics.spec.ts @@ -11,12 +11,12 @@ import { LatencyAggregationType } from '@kbn/apm-plugin/common/latency_aggregati import { ApmDocumentType, ApmTransactionDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const serviceName = 'synth-go'; const start = new Date('2021-01-01T00:00:00.000Z').getTime(); @@ -45,7 +45,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { query: { start: new Date(start).toISOString(), end: new Date(end).toISOString(), - latencyAggregationType: 'avg' as LatencyAggregationType, + latencyAggregationType: LatencyAggregationType.avg, transactionType: 'request', environment: 'ENVIRONMENT_ALL', useDurationSummary: false, @@ -60,22 +60,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { return response.body; } - registry.when( - 'Transaction groups main statistics when data is not loaded', - { config: 'basic', archives: [] }, - () => { + describe('Transaction groups main statistics', () => { + describe('when data is not loaded', () => { it('handles the empty state', async () => { const transactionsGroupsPrimaryStatistics = await callApi(); expect(transactionsGroupsPrimaryStatistics.transactionGroups).to.empty(); expect(transactionsGroupsPrimaryStatistics.maxCountExceeded).to.be(false); }); - } - ); + }); - // FLAKY: https://github.com/elastic/kibana/issues/177620 - registry.when('when data is loaded', { config: 'basic', archives: [] }, () => { - describe('Transaction groups main statistics', () => { + describe('when data is loaded', () => { const GO_PROD_RATE = 75; const GO_PROD_ERROR_RATE = 25; const transactions = [ @@ -92,7 +87,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { duration: 1000, }, ]; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); const serviceGoProdInstance = apm .service({ name: serviceName, environment: 'production', agentName: 'go' }) .instance('instance-a'); diff --git a/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts b/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts index bd423762255a..1cf8493347a6 100644 --- a/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts +++ b/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts @@ -124,7 +124,6 @@ export function createStatefulTestConfig<T extends DeploymentAgnosticCommonServi path.resolve(REPO_ROOT, STATEFUL_ROLES_ROOT_PATH, 'roles.yml'), ], }, - kbnTestServer: { ...xPackAPITestsConfig.get('kbnTestServer'), serverArgs: [ diff --git a/x-pack/test/api_integration/deployment_agnostic/services/apm_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/apm_api.ts index c3f43b57902e..ed2c5ba7ccf1 100644 --- a/x-pack/test/api_integration/deployment_agnostic/services/apm_api.ts +++ b/x-pack/test/api_integration/deployment_agnostic/services/apm_api.ts @@ -16,16 +16,7 @@ import { formatRequest } from '@kbn/server-route-repository'; import { RoleCredentials } from '@kbn/ftr-common-functional-services'; import type { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; -const INTERNAL_API_REGEX = /^\S+\s(\/)?internal\/[^\s]*$/; - -type InternalApi = `${string} /internal/${string}`; -interface ExternalEndpointParams { - roleAuthc: RoleCredentials; -} - -type Options<TEndpoint extends APIEndpoint> = (TEndpoint extends InternalApi - ? {} - : ExternalEndpointParams) & { +type Options<TEndpoint extends APIEndpoint> = { type?: 'form-data'; endpoint: TEndpoint; spaceId?: string; @@ -33,33 +24,28 @@ type Options<TEndpoint extends APIEndpoint> = (TEndpoint extends InternalApi params?: { query?: { _inspect?: boolean } }; }; -function isPublicApi<TEndpoint extends APIEndpoint>( - options: Options<TEndpoint> -): options is Options<TEndpoint> & ExternalEndpointParams { - return !INTERNAL_API_REGEX.test(options.endpoint); -} +type InternalEndpoint<T extends APIEndpoint> = T extends `${string} /internal/${string}` + ? T + : never; + +type PublicEndpoint<T extends APIEndpoint> = T extends `${string} /api/${string}` ? T : never; -function createApmApiClient({ getService }: DeploymentAgnosticFtrProviderContext, role: string) { +function createApmApiClient({ getService }: DeploymentAgnosticFtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const samlAuth = getService('samlAuth'); const logger = getService('log'); - return async <TEndpoint extends APIEndpoint>( - options: Options<TEndpoint> - ): Promise<SupertestReturnType<TEndpoint>> => { + async function makeApiRequest<TEndpoint extends APIEndpoint>({ + options, + headers, + }: { + options: Options<TEndpoint>; + headers: Record<string, string>; + }): Promise<SupertestReturnType<TEndpoint>> { const { endpoint, type } = options; const params = 'params' in options ? (options.params as Record<string, any>) : {}; - const credentials = isPublicApi(options) - ? options.roleAuthc.apiKeyHeader - : await samlAuth.getM2MApiCookieCredentialsWithRoleScope(role); - - const headers: Record<string, string> = { - ...samlAuth.getInternalRequestHeader(), - ...credentials, - }; - const { method, pathname, version } = formatRequest(endpoint, params.path); const pathnameWithSpaceId = options.spaceId ? `/s/${options.spaceId}${pathname}` : pathname; const url = format({ pathname: pathnameWithSpaceId, query: params?.query }); @@ -71,6 +57,7 @@ function createApmApiClient({ getService }: DeploymentAgnosticFtrProviderContext } let res: request.Response; + if (type === 'form-data') { const fields: Array<[string, any]> = Object.entries(params.body); const formDataRequest = supertestWithoutAuth[method](url) @@ -94,6 +81,45 @@ function createApmApiClient({ getService }: DeploymentAgnosticFtrProviderContext } return res; + } + + function makeInternalApiRequest(role: string) { + return async <TEndpoint extends InternalEndpoint<APIEndpoint>>( + options: Options<TEndpoint> + ): Promise<SupertestReturnType<TEndpoint>> => { + const headers: Record<string, string> = { + ...samlAuth.getInternalRequestHeader(), + ...(await samlAuth.getM2MApiCookieCredentialsWithRoleScope(role)), + }; + + return makeApiRequest({ + options, + headers, + }); + }; + } + + function makePublicApiRequest() { + return async <TEndpoint extends PublicEndpoint<APIEndpoint>>( + options: Options<TEndpoint> & { + roleAuthc: RoleCredentials; + } + ): Promise<SupertestReturnType<TEndpoint>> => { + const headers: Record<string, string> = { + ...samlAuth.getInternalRequestHeader(), + ...options.roleAuthc.apiKeyHeader, + }; + + return makeApiRequest({ + options, + headers, + }); + }; + } + + return { + makeInternalApiRequest, + makePublicApiRequest, }; } @@ -129,10 +155,12 @@ export interface SupertestReturnType<TEndpoint extends APIEndpoint> { } export function ApmApiProvider(context: DeploymentAgnosticFtrProviderContext) { + const apmClient = createApmApiClient(context); return { - readUser: createApmApiClient(context, 'viewer'), - adminUser: createApmApiClient(context, 'admin'), - writeUser: createApmApiClient(context, 'editor'), + readUser: apmClient.makeInternalApiRequest('viewer'), + adminUser: apmClient.makeInternalApiRequest('admin'), + writeUser: apmClient.makeInternalApiRequest('editor'), + publicApi: apmClient.makePublicApiRequest(), }; } diff --git a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.spec.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.spec.ts deleted file mode 100644 index fd06ee9f9526..000000000000 --- a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.spec.ts +++ /dev/null @@ -1,253 +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 { omit, sortBy } from 'lodash'; -import { type Node, NodeType } from '@kbn/apm-plugin/common/connections'; -import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; -import type { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - - const archiveName = 'apm_8.0.0'; - const { start, end } = archives[archiveName]; - - function getName(node: Node) { - return node.type === NodeType.service ? node.serviceName : node.dependencyName; - } - - registry.when( - 'Service overview dependencies when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - let response: { - status: number; - body: APIReturnType<'GET /internal/apm/services/{serviceName}/dependencies'>; - }; - - before(async () => { - response = await apmApiClient.readUser({ - endpoint: `GET /internal/apm/services/{serviceName}/dependencies`, - params: { - path: { serviceName: 'opbeans-python' }, - query: { - start, - end, - numBuckets: 20, - environment: ENVIRONMENT_ALL.value, - }, - }, - }); - }); - - it('returns a successful response', () => { - expect(response.status).to.be(200); - }); - - it('returns at least one item', () => { - expect(response.body.serviceDependencies.length).to.be.greaterThan(0); - - expectSnapshot(response.body.serviceDependencies.length).toMatchInline(`4`); - - const { currentStats, ...firstItem } = sortBy( - response.body.serviceDependencies, - 'currentStats.impact' - ).reverse()[0]; - - expectSnapshot(firstItem.location).toMatchInline(` - Object { - "agentName": "dotnet", - "dependencyName": "opbeans:3000", - "environment": "production", - "id": "5948c153c2d8989f92a9c75ef45bb845f53e200d", - "serviceName": "opbeans-dotnet", - "type": "service", - } - `); - - expectSnapshot( - omit(currentStats, [ - 'errorRate.timeseries', - 'throughput.timeseries', - 'latency.timeseries', - 'totalTime.timeseries', - ]) - ).toMatchInline(` - Object { - "errorRate": Object { - "value": 0.163636363636364, - }, - "impact": 100, - "latency": Object { - "value": 1117085.74545455, - }, - "throughput": Object { - "value": 1.83333333333333, - }, - "totalTime": Object { - "value": 61439716, - }, - } - `); - }); - - it('returns the right names', () => { - const names = response.body.serviceDependencies.map((item) => getName(item.location)); - expectSnapshot(names.sort()).toMatchInline(` - Array [ - "elasticsearch", - "opbeans-dotnet", - "postgresql", - "redis", - ] - `); - }); - - it('returns the right service names', () => { - const serviceNames = response.body.serviceDependencies - .map((item) => - item.location.type === NodeType.service ? getName(item.location) : undefined - ) - .filter(Boolean); - - expectSnapshot(serviceNames.sort()).toMatchInline(` - Array [ - "opbeans-dotnet", - ] - `); - }); - - it('returns the right latency values', () => { - const latencyValues = sortBy( - response.body.serviceDependencies.map((item) => ({ - name: getName(item.location), - latency: item.currentStats.latency.value, - })), - 'name' - ); - - expectSnapshot(latencyValues).toMatchInline(` - Array [ - Object { - "latency": 9496.32291666667, - "name": "elasticsearch", - }, - Object { - "latency": 1117085.74545455, - "name": "opbeans-dotnet", - }, - Object { - "latency": 27826.9968314322, - "name": "postgresql", - }, - Object { - "latency": 1468.27242524917, - "name": "redis", - }, - ] - `); - }); - - it('returns the right throughput values', () => { - const throughputValues = sortBy( - response.body.serviceDependencies.map((item) => ({ - name: getName(item.location), - throughput: item.currentStats.throughput.value, - })), - 'name' - ); - - expectSnapshot(throughputValues).toMatchInline(` - Array [ - Object { - "name": "elasticsearch", - "throughput": 3.2, - }, - Object { - "name": "opbeans-dotnet", - "throughput": 1.83333333333333, - }, - Object { - "name": "postgresql", - "throughput": 52.6, - }, - Object { - "name": "redis", - "throughput": 40.1333333333333, - }, - ] - `); - }); - - it('returns the right impact values', () => { - const impactValues = sortBy( - response.body.serviceDependencies.map((item) => ({ - name: getName(item.location), - impact: item.currentStats.impact, - })), - 'name' - ); - - expectSnapshot(impactValues).toMatchInline(` - Array [ - Object { - "impact": 0, - "name": "elasticsearch", - }, - Object { - "impact": 100, - "name": "opbeans-dotnet", - }, - Object { - "impact": 71.0403531954737, - "name": "postgresql", - }, - Object { - "impact": 1.41447268043525, - "name": "redis", - }, - ] - `); - }); - - it('returns the right totalTime values', () => { - const totalTimeValues = sortBy( - response.body.serviceDependencies.map((item) => ({ - name: getName(item.location), - totalTime: item.currentStats.totalTime.value, - })), - 'name' - ); - - expectSnapshot(totalTimeValues).toMatchInline(` - Array [ - Object { - "name": "elasticsearch", - "totalTime": 911647, - }, - Object { - "name": "opbeans-dotnet", - "totalTime": 61439716, - }, - Object { - "name": "postgresql", - "totalTime": 43911001, - }, - Object { - "name": "redis", - "totalTime": 1767800, - }, - ] - `); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/service_overview/get_service_node_ids.ts b/x-pack/test/apm_api_integration/tests/service_overview/get_service_node_ids.ts deleted file mode 100644 index 751f772fb750..000000000000 --- a/x-pack/test/apm_api_integration/tests/service_overview/get_service_node_ids.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { take } from 'lodash'; -import { LatencyAggregationType } from '@kbn/apm-plugin/common/latency_aggregation_types'; -import type { ApmServices } from '../../common/config'; - -export async function getServiceNodeIds({ - apmApiClient, - start, - end, - serviceName = 'opbeans-java', - count = 1, -}: { - apmApiClient: Awaited<ReturnType<ApmServices['apmApiClient']>>; - start: string; - end: string; - serviceName?: string; - count?: number; -}) { - const { body } = await apmApiClient.readUser({ - endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`, - params: { - path: { serviceName }, - query: { - latencyAggregationType: LatencyAggregationType.avg, - start, - end, - transactionType: 'request', - environment: 'ENVIRONMENT_ALL', - kuery: '', - sortField: 'throughput', - sortDirection: 'desc', - }, - }, - }); - - return take(body.currentPeriod.map((item) => item.serviceNodeName).sort(), count); -} diff --git a/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.spec.ts deleted file mode 100644 index af28697a254c..000000000000 --- a/x-pack/test/apm_api_integration/tests/service_overview/instances_detailed_statistics.spec.ts +++ /dev/null @@ -1,179 +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 moment from 'moment'; -import type { Coordinate } from '@kbn/apm-plugin/typings/timeseries'; -import { LatencyAggregationType } from '@kbn/apm-plugin/common/latency_aggregation_types'; -import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number'; -import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import type { FtrProviderContext } from '../../common/ftr_provider_context'; -import archives from '../../common/fixtures/es_archiver/archives_metadata'; -import { getServiceNodeIds } from './get_service_node_ids'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - - const serviceName = 'opbeans-java'; - const archiveName = 'apm_8.0.0'; - const { start, end } = archives[archiveName]; - - interface Response { - status: number; - body: APIReturnType<'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; - } - - registry.when( - 'Service overview instances detailed statistics when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - describe('fetching data without comparison', () => { - let response: Response; - let serviceNodeIds: string[]; - - beforeEach(async () => { - serviceNodeIds = await getServiceNodeIds({ - apmApiClient, - start, - end, - }); - - response = await apmApiClient.readUser({ - endpoint: - 'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics', - params: { - path: { serviceName }, - query: { - latencyAggregationType: LatencyAggregationType.avg, - start, - end, - numBuckets: 20, - transactionType: 'request', - serviceNodeIds: JSON.stringify(serviceNodeIds), - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }, - }); - }); - - it('returns a service node item', () => { - expect(Object.values(response.body.currentPeriod).length).to.be.greaterThan(0); - expect(Object.values(response.body.previousPeriod)).to.eql(0); - }); - - it('returns statistics for each service node', async () => { - const item = response.body.currentPeriod[serviceNodeIds[0]]; - - expect(item?.cpuUsage?.some((point) => isFiniteNumber(point.y))).to.be(true); - expect(item?.memoryUsage?.some((point) => isFiniteNumber(point.y))).to.be(true); - expect(item?.errorRate?.some((point) => isFiniteNumber(point.y))).to.be(true); - expect(item?.throughput?.some((point) => isFiniteNumber(point.y))).to.be(true); - expect(item?.latency?.some((point) => isFiniteNumber(point.y))).to.be(true); - }); - - it('returns the right data', () => { - expectSnapshot(Object.values(response.body.currentPeriod).length).toMatchInline(`1`); - - expectSnapshot(Object.keys(response.body.currentPeriod)).toMatchInline(` - Array [ - "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad", - ] - `); - - expectSnapshot(response.body).toMatch(); - }); - }); - - describe('fetching data with comparison', () => { - let response: Response; - let serviceNodeIds: string[]; - - beforeEach(async () => { - serviceNodeIds = await getServiceNodeIds({ - apmApiClient, - start, - end, - }); - response = await apmApiClient.readUser({ - endpoint: - 'GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics', - params: { - path: { serviceName }, - query: { - latencyAggregationType: LatencyAggregationType.avg, - numBuckets: 20, - transactionType: 'request', - serviceNodeIds: JSON.stringify(serviceNodeIds), - start: moment(end).subtract(15, 'minutes').toISOString(), - end, - offset: '15m', - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }, - }); - }); - - it('returns a service node item for current and previous periods', () => { - expect(Object.values(response.body.currentPeriod).length).to.be.greaterThan(0); - expect(Object.values(response.body.previousPeriod).length).to.be.greaterThan(0); - }); - - it('returns statistics for current and previous periods', () => { - const currentPeriodItem = response.body.currentPeriod[serviceNodeIds[0]]; - - function hasValidYCoordinate(point: Coordinate) { - return isFiniteNumber(point.y); - } - - expect(currentPeriodItem?.cpuUsage?.some(hasValidYCoordinate)).to.be(true); - expect(currentPeriodItem?.memoryUsage?.some(hasValidYCoordinate)).to.be(true); - expect(currentPeriodItem?.errorRate?.some(hasValidYCoordinate)).to.be(true); - expect(currentPeriodItem?.throughput?.some(hasValidYCoordinate)).to.be(true); - expect(currentPeriodItem?.latency?.some(hasValidYCoordinate)).to.be(true); - - const previousPeriodItem = response.body.previousPeriod[serviceNodeIds[0]]; - - expect(previousPeriodItem?.cpuUsage?.some(hasValidYCoordinate)).to.be(true); - expect(previousPeriodItem?.memoryUsage?.some(hasValidYCoordinate)).to.be(true); - expect(previousPeriodItem?.errorRate?.some(hasValidYCoordinate)).to.be(true); - expect(previousPeriodItem?.throughput?.some(hasValidYCoordinate)).to.be(true); - expect(previousPeriodItem?.latency?.some(hasValidYCoordinate)).to.be(true); - }); - - it('returns the right data for current and previous periods', () => { - expectSnapshot(Object.values(response.body.currentPeriod).length).toMatchInline(`1`); - expectSnapshot(Object.values(response.body.previousPeriod).length).toMatchInline(`1`); - - expectSnapshot(Object.keys(response.body.currentPeriod)).toMatchInline(` - Array [ - "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad", - ] - `); - expectSnapshot(Object.keys(response.body.previousPeriod)).toMatchInline(` - Array [ - "31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad", - ] - `); - - expectSnapshot(response.body).toMatch(); - }); - - it('matches x-axis on current period and previous period', () => { - const currentLatencyItems = response.body.currentPeriod[serviceNodeIds[0]]?.latency; - const previousLatencyItems = response.body.previousPeriod[serviceNodeIds[0]]?.latency; - - expect(currentLatencyItems?.map(({ x }) => x)).to.be.eql( - previousLatencyItems?.map(({ x }) => x) - ); - }); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_keys/agent_keys.spec.ts b/x-pack/test/apm_api_integration/tests/settings/agent_keys/agent_keys.spec.ts index 82c869dd09fe..8d8808c282a8 100644 --- a/x-pack/test/apm_api_integration/tests/settings/agent_keys/agent_keys.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/agent_keys/agent_keys.spec.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; import { first } from 'lodash'; -import { PrivilegeType, ClusterPrivilegeType } from '@kbn/apm-plugin/common/privilege_type'; +import { PrivilegeType } from '@kbn/apm-plugin/common/privilege_type'; import { ApmUsername } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { ApmApiError, ApmApiSupertest } from '../../../common/apm_api_supertest'; @@ -19,7 +19,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { const agentKeyName = 'test'; const allApplicationPrivileges = [PrivilegeType.AGENT_CONFIG, PrivilegeType.EVENT]; - const clusterPrivileges = [ClusterPrivilegeType.MANAGE_OWN_API_KEY]; async function createAgentKey(apiClient: ApmApiSupertest, privileges = allApplicationPrivileges) { return await apiClient({ @@ -50,37 +49,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'When the user does not have the required privileges', { config: 'basic', archives: [] }, () => { - describe('When the user does not have the required cluster privileges', () => { - it('should return an error when creating an agent key', async () => { - const error = await expectToReject<ApmApiError>(() => - createAgentKey(apmApiClient.writeUser) - ); - expect(error.res.status).to.be(403); - expect(error.res.body.message).contain('is missing the following requested privilege'); - expect(error.res.body.attributes).to.eql({ - _inspect: [], - data: { - missingPrivileges: allApplicationPrivileges, - missingClusterPrivileges: clusterPrivileges, - }, - }); - }); - - it('should return an error when invalidating an agent key', async () => { - const error = await expectToReject<ApmApiError>(() => - invalidateAgentKey(apmApiClient.writeUser, agentKeyName) - ); - expect(error.res.status).to.be(500); - }); - - it('should return an error when getting a list of agent keys', async () => { - const error = await expectToReject<ApmApiError>(() => - getAgentKeys(apmApiClient.writeUser) - ); - expect(error.res.status).to.be(500); - }); - }); - describe('When the user does not have the required application privileges', () => { allApplicationPrivileges.map((privilege) => { it(`should return an error when creating an agent key with ${privilege} privilege`, async () => { diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.spec.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.spec.ts index cc56731fec07..fa9bcb1d0700 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.spec.ts @@ -35,37 +35,35 @@ export default function apiTest({ getService }: FtrProviderContext) { } registry.when('ML jobs', { config: 'trial', archives: [] }, () => { - (['readUser', 'apmAllPrivilegesWithoutWriteSettingsUser'] as ApmApiClientKey[]).forEach( - (user) => { - describe(`when ${user} has read access to ML`, () => { - before(async () => { - const res = await getJobs({ user }); - const jobIds = res.body.jobs.map((job: any) => job.jobId); - await deleteJobs(jobIds); - }); + (['apmAllPrivilegesWithoutWriteSettingsUser'] as ApmApiClientKey[]).forEach((user) => { + describe(`when ${user} has read access to ML`, () => { + before(async () => { + const res = await getJobs({ user }); + const jobIds = res.body.jobs.map((job: any) => job.jobId); + await deleteJobs(jobIds); + }); - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs({ user }); + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs({ user }); - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); }); + }); - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - try { - await createJobs(['production', 'staging'], { user }); - expect(true).to.be(false); - } catch (e) { - const err = e as ApmApiError; - expect(err.res.status).to.be(403); - } - }); + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + try { + await createJobs(['production', 'staging'], { user }); + expect(true).to.be(false); + } catch (e) { + const err = e as ApmApiError; + expect(err.res.status).to.be(403); + } }); }); - } - ); + }); + }); }); } diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.spec.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.spec.ts index 652da64384dd..40e62b1ddc96 100644 --- a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.spec.ts @@ -35,59 +35,57 @@ export default function apiTest({ getService }: FtrProviderContext) { } registry.when('ML jobs', { config: 'trial', archives: [] }, () => { - (['writeUser', 'apmReadPrivilegesWithWriteSettingsUser'] as ApmApiClientKey[]).forEach( - (user) => { - describe(`when ${user} has write access to ML`, () => { - before(async () => { - const res = await getJobs({ user }); - const jobIds = res.body.jobs.map((job: any) => job.jobId); - await deleteJobs(jobIds); - }); + (['apmReadPrivilegesWithWriteSettingsUser'] as ApmApiClientKey[]).forEach((user) => { + describe(`when ${user} has write access to ML`, () => { + before(async () => { + const res = await getJobs({ user }); + const jobIds = res.body.jobs.map((job: any) => job.jobId); + await deleteJobs(jobIds); + }); + + after(async () => { + const res = await getJobs({ user }); + const jobIds = res.body.jobs.map((job: any) => job.jobId); + await deleteJobs(jobIds); + }); - after(async () => { - const res = await getJobs({ user }); - const jobIds = res.body.jobs.map((job: any) => job.jobId); - await deleteJobs(jobIds); + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs({ user }); + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); }); + }); - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs({ user }); - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); + describe('when calling create endpoint', () => { + it('creates two jobs', async () => { + await createJobs(['production', 'staging'], { user }); + + const { body } = await getJobs({ user }); + expect(body.hasLegacyJobs).to.be(false); + expect(countBy(body.jobs, 'environment')).to.eql({ + production: 1, + staging: 1, }); }); - describe('when calling create endpoint', () => { - it('creates two jobs', async () => { + describe('with existing ML jobs', () => { + before(async () => { await createJobs(['production', 'staging'], { user }); + }); + it('skips duplicate job creation', async () => { + await createJobs(['production', 'test'], { user }); const { body } = await getJobs({ user }); - expect(body.hasLegacyJobs).to.be(false); expect(countBy(body.jobs, 'environment')).to.eql({ production: 1, staging: 1, - }); - }); - - describe('with existing ML jobs', () => { - before(async () => { - await createJobs(['production', 'staging'], { user }); - }); - it('skips duplicate job creation', async () => { - await createJobs(['production', 'test'], { user }); - - const { body } = await getJobs({ user }); - expect(countBy(body.jobs, 'environment')).to.eql({ - production: 1, - staging: 1, - test: 1, - }); + test: 1, }); }); }); }); - } - ); + }); + }); }); } diff --git a/x-pack/test/apm_api_integration/tests/settings/apm_indices/apm_indices.spec.ts b/x-pack/test/apm_api_integration/tests/settings/apm_indices/apm_indices.spec.ts index 41bc0448e063..6fb597762625 100644 --- a/x-pack/test/apm_api_integration/tests/settings/apm_indices/apm_indices.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/apm_indices/apm_indices.spec.ts @@ -107,40 +107,6 @@ export default function apmIndicesTests({ getService }: FtrProviderContext) { await deleteSavedObject(); }); - it('[trial] returns APM Indices', async () => { - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/settings/apm-indices', - }); - expect(response.status).to.be(200); - expect(response.body).to.eql({ - transaction: 'traces-apm*,apm-*,traces-*.otel-*', - span: 'traces-apm*,apm-*,traces-*.otel-*', - error: 'logs-apm*,apm-*,logs-*.otel-*', - metric: 'metrics-apm*,apm-*,metrics-*.otel-*', - onboarding: 'apm-*', - sourcemap: 'apm-*', - }); - }); - - it('[trial] updates apm indices', async () => { - const INDEX_VALUE = 'foo-*'; - - const writeResponse = await apmApiClient.writeUser({ - endpoint: 'POST /internal/apm/settings/apm-indices/save', - params: { - body: { transaction: INDEX_VALUE }, - }, - }); - expect(writeResponse.status).to.be(200); - - const readResponse = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/settings/apm-indices', - }); - - expect(readResponse.status).to.be(200); - expect(readResponse.body.transaction).to.eql(INDEX_VALUE); - }); - it('[trial] updates apm indices as read privileges with modify settings user', async () => { const INDEX_VALUE = 'foo-*'; diff --git a/x-pack/test/apm_api_integration/tests/settings/custom_link/custom_link.spec.ts b/x-pack/test/apm_api_integration/tests/settings/custom_link/custom_link.spec.ts index 490a2fc76868..e6b565cde2b3 100644 --- a/x-pack/test/apm_api_integration/tests/settings/custom_link/custom_link.spec.ts +++ b/x-pack/test/apm_api_integration/tests/settings/custom_link/custom_link.spec.ts @@ -91,79 +91,77 @@ export default function customLinksTests({ getService }: FtrProviderContext) { }); }); - (['writeUser', 'apmReadPrivilegesWithWriteSettingsUser'] as ApmApiClientKey[]).forEach( - (user) => { - it(`creates a custom link as ${user}`, async () => { - const customLink = { - url: 'https://elastic.co', - label: 'with filters', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - } as CustomLink; + (['apmReadPrivilegesWithWriteSettingsUser'] as ApmApiClientKey[]).forEach((user) => { + it(`creates a custom link as ${user}`, async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + + await createCustomLink(customLink, { user }); + }); - await createCustomLink(customLink, { user }); + it(`updates a custom link as ${user}`, async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', }); + expect(status).to.equal(200); - it(`updates a custom link as ${user}`, async () => { - const { status, body } = await searchCustomLinks({ - 'service.name': 'baz', - 'transaction.type': 'qux', - }); - expect(status).to.equal(200); - - const id = body.customLinks[0].id!; - await updateCustomLink( - id, - { - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', - filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, - ], - }, - { user } - ); - - const { status: newStatus, body: newBody } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); - - const { label, url, filters } = newBody.customLinks[0]; - expect(newStatus).to.equal(200); - expect({ label, url, filters }).to.eql({ + const id = body.customLinks[0].id!; + await updateCustomLink( + id, + { label: 'foo', url: 'https://elastic.co?service.name={{service.name}}', filters: [ { key: 'service.name', value: 'quz' }, { key: 'transaction.name', value: 'bar' }, ], - }); + }, + { user } + ); + + const { status: newStatus, body: newBody } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', }); - it(`deletes a custom link as ${user}`, async () => { - const { status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); - expect(status).to.equal(200); - expect(body.customLinks.length).to.be(1); - - const id = body.customLinks[0].id!; - await deleteCustomLink(id, { user }); - - const { status: newStatus, body: newBody } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); - expect(newStatus).to.equal(200); - expect(newBody.customLinks.length).to.be(0); + const { label, url, filters } = newBody.customLinks[0]; + expect(newStatus).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], }); - } - ); + }); + + it(`deletes a custom link as ${user}`, async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(status).to.equal(200); + expect(body.customLinks.length).to.be(1); + + const id = body.customLinks[0].id!; + await deleteCustomLink(id, { user }); + + const { status: newStatus, body: newBody } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(newStatus).to.equal(200); + expect(newBody.customLinks.length).to.be(0); + }); + }); it('fetches a transaction sample', async () => { const response = await apmApiClient.readUser({ diff --git a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts b/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts deleted file mode 100644 index 369490ae06d4..000000000000 --- a/x-pack/test/apm_api_integration/tests/traces/find_traces.spec.ts +++ /dev/null @@ -1,225 +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 { apm, timerange } from '@kbn/apm-synthtrace-client'; -import expect from '@kbn/expect'; -import { TraceSearchType } from '@kbn/apm-plugin/common/trace_explorer'; -import { Environment } from '@kbn/apm-plugin/common/environment_rt'; -import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; -import { sortBy } from 'lodash'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { ApmApiError } from '../../common/apm_api_supertest'; -import { generateTrace } from './generate_trace'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2022-01-01T00:00:00.000Z').getTime(); - const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; - - // for EQL sequences to work, events need a slight time offset, - // as ES will sort based on @timestamp. to acommodate this offset - // we also add a little bit of a buffer to the requested time range - const endWithOffset = end + 100000; - - async function fetchTraceSamples({ - query, - type, - environment, - }: { - query: string; - type: TraceSearchType; - environment: Environment; - }) { - return apmApiClient.readUser({ - endpoint: `GET /internal/apm/traces/find`, - params: { - query: { - query, - type, - start: new Date(start).toISOString(), - end: new Date(endWithOffset).toISOString(), - environment, - }, - }, - }); - } - - function fetchTraces(traceSamples: Array<{ traceId: string; transactionId: string }>) { - if (!traceSamples.length) { - return []; - } - - return Promise.all( - traceSamples.map(async ({ traceId, transactionId }) => { - const response = await apmApiClient.readUser({ - endpoint: `GET /internal/apm/traces/{traceId}`, - params: { - path: { traceId }, - query: { - start: new Date(start).toISOString(), - end: new Date(endWithOffset).toISOString(), - entryTransactionId: transactionId, - }, - }, - }); - return response.body.traceItems.traceDocs; - }) - ); - } - - registry.when('Find traces when traces do not exist', { config: 'basic', archives: [] }, () => { - it('handles empty state', async () => { - const response = await fetchTraceSamples({ - query: '', - type: TraceSearchType.kql, - environment: ENVIRONMENT_ALL.value, - }); - - expect(response.status).to.be(200); - expect(response.body).to.eql({ - traceSamples: [], - }); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177543 - registry.when('Find traces when traces exist', { config: 'basic', archives: [] }, () => { - before(() => { - const java = apm - .service({ name: 'java', environment: 'production', agentName: 'java' }) - .instance('java'); - - const node = apm - .service({ name: 'node', environment: 'development', agentName: 'nodejs' }) - .instance('node'); - - const python = apm - .service({ name: 'python', environment: 'production', agentName: 'python' }) - .instance('python'); - - return apmSynthtraceEsClient.index( - timerange(start, end) - .interval('15m') - .rate(1) - .generator((timestamp) => { - return [ - generateTrace(timestamp, [java, node]), - generateTrace(timestamp, [node, java], 'redis'), - generateTrace(timestamp, [python], 'redis'), - generateTrace(timestamp, [python, node, java], 'elasticsearch'), - generateTrace(timestamp, [java, python, node]), - ]; - }) - ); - }); - - describe('when using KQL', () => { - describe('and the query is empty', () => { - it('returns all trace samples', async () => { - const { - body: { traceSamples }, - } = await fetchTraceSamples({ - query: '', - type: TraceSearchType.kql, - environment: 'ENVIRONMENT_ALL', - }); - - expect(traceSamples.length).to.eql(5); - }); - }); - - describe('and query is set', () => { - it('returns the relevant traces', async () => { - const { - body: { traceSamples }, - } = await fetchTraceSamples({ - query: 'span.destination.service.resource:elasticsearch', - type: TraceSearchType.kql, - environment: 'ENVIRONMENT_ALL', - }); - - expect(traceSamples.length).to.eql(1); - }); - }); - }); - - describe('when using EQL', () => { - describe('and the query is invalid', () => { - it.skip('returns a 400', async function () { - try { - await fetchTraceSamples({ - query: '', - type: TraceSearchType.eql, - environment: 'ENVIRONMENT_ALL', - }); - this.fail(); - } catch (error: unknown) { - const apiError = error as ApmApiError; - expect(apiError.res.status).to.eql(400); - } - }); - }); - - describe('and the query is set', () => { - it('returns the correct trace samples for transaction sequences', async () => { - const { - body: { traceSamples }, - } = await fetchTraceSamples({ - query: `sequence by trace.id - [ transaction where service.name == "java" ] - [ transaction where service.name == "node" ]`, - type: TraceSearchType.eql, - environment: 'ENVIRONMENT_ALL', - }); - - const traces = await fetchTraces(traceSamples); - - expect(traces.length).to.eql(2); - - const mapped = traces.map((traceDocs) => { - return sortBy(traceDocs, '@timestamp') - .filter((doc) => doc.processor.event === 'transaction') - .map((doc) => doc.service.name); - }); - - expect(mapped).to.eql([ - ['java', 'node'], - ['java', 'python', 'node'], - ]); - }); - }); - - it('returns the correct trace samples for join sequences', async () => { - const { - body: { traceSamples }, - } = await fetchTraceSamples({ - query: `sequence by trace.id - [ span where service.name == "java" ] by span.id - [ transaction where service.name == "python" ] by parent.id`, - type: TraceSearchType.eql, - environment: 'ENVIRONMENT_ALL', - }); - - const traces = await fetchTraces(traceSamples); - - expect(traces.length).to.eql(1); - - const mapped = traces.map((traceDocs) => { - return sortBy(traceDocs, '@timestamp') - .filter((doc) => doc.processor.event === 'transaction') - .map((doc) => doc.service.name); - }); - - expect(mapped).to.eql([['java', 'python', 'node']]); - }); - }); - - after(() => apmSynthtraceEsClient.clean()); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/traces/span_details.spec.ts b/x-pack/test/apm_api_integration/tests/traces/span_details.spec.ts deleted file mode 100644 index a428ea9cb2e5..000000000000 --- a/x-pack/test/apm_api_integration/tests/traces/span_details.spec.ts +++ /dev/null @@ -1,125 +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 { apm, timerange } from '@kbn/apm-synthtrace-client'; -import expect from '@kbn/expect'; -import { Readable } from 'stream'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2022-01-01T00:00:00.000Z').getTime(); - const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; - - async function fetchSpanDetails({ - traceId, - spanId, - parentTransactionId, - }: { - traceId: string; - spanId: string; - parentTransactionId?: string; - }) { - return await apmApiClient.readUser({ - endpoint: `GET /internal/apm/traces/{traceId}/spans/{spanId}`, - params: { - path: { traceId, spanId }, - query: { - parentTransactionId, - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - }, - }, - }); - } - - registry.when('Span details dont exist', { config: 'basic', archives: [] }, () => { - it('handles empty state', async () => { - const response = await fetchSpanDetails({ - traceId: 'foo', - spanId: 'bar', - }); - - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177544 - registry.when('Span details', { config: 'basic', archives: [] }, () => { - let traceId: string; - let spanId: string; - let parentTransactionId: string; - before(async () => { - const instanceJava = apm - .service({ name: 'synth-apple', environment: 'production', agentName: 'java' }) - .instance('instance-b'); - const events = timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => { - return [ - instanceJava - .transaction({ transactionName: 'GET /apple 🍏' }) - .timestamp(timestamp) - .duration(1000) - .failure() - .errors( - instanceJava - .error({ message: '[ResponseError] index_not_found_exception' }) - .timestamp(timestamp + 50) - ) - .children( - instanceJava - .span({ - spanName: 'get_green_apple_🍏', - spanType: 'db', - spanSubtype: 'elasticsearch', - }) - .timestamp(timestamp + 50) - .duration(900) - .success() - ), - ]; - }); - - const unserialized = Array.from(events); - - const entities = unserialized.flatMap((event) => event.serialize()); - - const span = entities.find((entity) => { - return entity['processor.event'] === 'span'; - }); - spanId = span?.['span.id']!; - parentTransactionId = span?.['parent.id']!; - traceId = span?.['trace.id']!; - - await apmSynthtraceEsClient.index(Readable.from(unserialized)); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('span details', () => { - let spanDetails: Awaited<ReturnType<typeof fetchSpanDetails>>['body']; - before(async () => { - const response = await fetchSpanDetails({ - traceId, - spanId, - parentTransactionId, - }); - expect(response.status).to.eql(200); - spanDetails = response.body; - }); - it('returns span details', () => { - expect(spanDetails.span?.span.name).to.eql('get_green_apple_🍏'); - expect(spanDetails.parentTransaction?.transaction.name).to.eql('GET /apple 🍏'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/traces/top_traces.spec.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.spec.ts deleted file mode 100644 index b49133240c86..000000000000 --- a/x-pack/test/apm_api_integration/tests/traces/top_traces.spec.ts +++ /dev/null @@ -1,140 +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 { sortBy } from 'lodash'; -import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - // url parameters - const { start, end } = metadata; - - registry.when('Top traces when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles empty state', async () => { - const response = await apmApiClient.readUser({ - endpoint: `GET /internal/apm/traces`, - params: { - query: { - start, - end, - kuery: '', - environment: 'ENVIRONMENT_ALL', - probability: 1, - }, - }, - }); - - expect(response.status).to.be(200); - expect(response.body.items.length).to.be(0); - }); - }); - - registry.when( - 'Top traces when data is loaded', - { config: 'basic', archives: [archiveName] }, - () => { - let response: any; - before(async () => { - response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/traces', - params: { - query: { - start, - end, - kuery: '', - environment: 'ENVIRONMENT_ALL', - probability: 1, - }, - }, - }); - }); - - it('returns the correct status code', async () => { - expect(response.status).to.be(200); - }); - - it('returns the correct number of buckets', async () => { - expectSnapshot(response.body.items.length).toMatchInline(`81`); - }); - - it('returns the correct buckets', async () => { - const sortedItems = sortBy(response.body.items, 'impact'); - - const firstItem = sortedItems[0]; - const lastItem = sortedItems[sortedItems.length - 1]; - - const groups = sortedItems.map((item) => item.key).slice(0, 5); - - expectSnapshot(sortedItems).toMatch(); - - expectSnapshot(firstItem).toMatchInline(` - Object { - "agentName": "java", - "averageResponseTime": 1639, - "impact": 0, - "key": Object { - "service.name": "opbeans-java", - "transaction.name": "DispatcherServlet#doPost", - }, - "serviceName": "opbeans-java", - "transactionName": "DispatcherServlet#doPost", - "transactionType": "request", - "transactionsPerMinute": 0.0333333333333333, - } - `); - - expectSnapshot(lastItem).toMatchInline(` - Object { - "agentName": "dotnet", - "averageResponseTime": 5963775, - "impact": 100, - "key": Object { - "service.name": "opbeans-dotnet", - "transaction.name": "GET Orders/Get", - }, - "serviceName": "opbeans-dotnet", - "transactionName": "GET Orders/Get", - "transactionType": "request", - "transactionsPerMinute": 0.633333333333333, - } - `); - - expectSnapshot(groups).toMatchInline(` - Array [ - Object { - "service.name": "opbeans-java", - "transaction.name": "DispatcherServlet#doPost", - }, - Object { - "service.name": "opbeans-node", - "transaction.name": "POST /api/orders", - }, - Object { - "service.name": "opbeans-node", - "transaction.name": "GET /api/products/:id", - }, - Object { - "service.name": "opbeans-dotnet", - "transaction.name": "POST Orders/Post", - }, - Object { - "service.name": "opbeans-python", - "transaction.name": "GET opbeans.views.product", - }, - ] - `); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts b/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts deleted file mode 100644 index de07f3664104..000000000000 --- a/x-pack/test/apm_api_integration/tests/traces/trace_by_id.spec.ts +++ /dev/null @@ -1,145 +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 type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import expect from '@kbn/expect'; -import { Readable } from 'stream'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2022-01-01T00:00:00.000Z').getTime(); - const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; - - registry.when('Trace does not exist', { config: 'basic', archives: [] }, () => { - it('handles empty state', async () => { - const response = await apmApiClient.readUser({ - endpoint: `GET /internal/apm/traces/{traceId}`, - params: { - path: { traceId: 'foo' }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - entryTransactionId: 'foo', - }, - }, - }); - - expect(response.status).to.be(200); - expect(response.body).to.eql({ - traceItems: { - exceedsMax: false, - traceDocs: [], - errorDocs: [], - spanLinksCountById: {}, - traceDocsTotal: 0, - maxTraceItems: 5000, - }, - }); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177545 - registry.when('Trace exists', { config: 'basic', archives: [] }, () => { - let entryTransactionId: string; - let serviceATraceId: string; - - before(async () => { - const instanceJava = apm - .service({ name: 'synth-apple', environment: 'production', agentName: 'java' }) - .instance('instance-b'); - const events = timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => { - return [ - instanceJava - .transaction({ transactionName: 'GET /apple 🍏' }) - .timestamp(timestamp) - .duration(1000) - .failure() - .errors( - instanceJava - .error({ message: '[ResponseError] index_not_found_exception' }) - .timestamp(timestamp + 50) - ) - .children( - instanceJava - .span({ - spanName: 'get_green_apple_🍏', - spanType: 'db', - spanSubtype: 'elasticsearch', - }) - .timestamp(timestamp + 50) - .duration(900) - .success() - ), - ]; - }); - const unserialized = Array.from(events); - - const serialized = unserialized.flatMap((event) => event.serialize()); - - entryTransactionId = serialized[0]['transaction.id']!; - serviceATraceId = serialized[0]['trace.id']!; - - await apmSynthtraceEsClient.index(Readable.from(unserialized)); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('return trace', () => { - let traces: APIReturnType<'GET /internal/apm/traces/{traceId}'>; - before(async () => { - const response = await apmApiClient.readUser({ - endpoint: `GET /internal/apm/traces/{traceId}`, - params: { - path: { traceId: serviceATraceId }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - entryTransactionId, - }, - }, - }); - - expect(response.status).to.eql(200); - traces = response.body; - }); - - it('returns some errors', () => { - expect(traces.traceItems.errorDocs.length).to.be.greaterThan(0); - expect(traces.traceItems.errorDocs[0].error.exception?.[0].message).to.eql( - '[ResponseError] index_not_found_exception' - ); - }); - - it('returns some trace docs', () => { - expect(traces.traceItems.traceDocs.length).to.be.greaterThan(0); - expect( - traces.traceItems.traceDocs.map((item) => { - if (item.span && 'name' in item.span) { - return item.span.name; - } - if (item.transaction && 'name' in item.transaction) { - return item.transaction.name; - } - }) - ).to.eql(['GET /apple 🍏', 'get_green_apple_🍏']); - }); - - it('returns entry transaction details', () => { - expect(traces.entryTransaction).to.not.be(undefined); - expect(traces.entryTransaction?.transaction.id).to.equal(entryTransactionId); - expect(traces.entryTransaction?.transaction.name).to.equal('GET /apple 🍏'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/traces/transaction_details.spec.ts b/x-pack/test/apm_api_integration/tests/traces/transaction_details.spec.ts deleted file mode 100644 index 3665bfd8e8ea..000000000000 --- a/x-pack/test/apm_api_integration/tests/traces/transaction_details.spec.ts +++ /dev/null @@ -1,119 +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 { apm, timerange } from '@kbn/apm-synthtrace-client'; -import expect from '@kbn/expect'; -import { Readable } from 'stream'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2022-01-01T00:00:00.000Z').getTime(); - const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; - - async function fetchTransactionDetails({ - traceId, - transactionId, - }: { - traceId: string; - transactionId: string; - }) { - return await apmApiClient.readUser({ - endpoint: `GET /internal/apm/traces/{traceId}/transactions/{transactionId}`, - params: { - path: { - traceId, - transactionId, - }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - }, - }, - }); - } - - registry.when('Transaction details dont exist', { config: 'basic', archives: [] }, () => { - it('handles empty state', async () => { - const response = await fetchTransactionDetails({ - traceId: 'foo', - transactionId: 'bar', - }); - - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177546 - registry.when('Transaction details', { config: 'basic', archives: [] }, () => { - let traceId: string; - let transactionId: string; - before(async () => { - const instanceJava = apm - .service({ name: 'synth-apple', environment: 'production', agentName: 'java' }) - .instance('instance-b'); - const events = timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => { - return [ - instanceJava - .transaction({ transactionName: 'GET /apple 🍏' }) - .timestamp(timestamp) - .duration(1000) - .failure() - .errors( - instanceJava - .error({ message: '[ResponseError] index_not_found_exception' }) - .timestamp(timestamp + 50) - ) - .children( - instanceJava - .span({ - spanName: 'get_green_apple_🍏', - spanType: 'db', - spanSubtype: 'elasticsearch', - }) - .timestamp(timestamp + 50) - .duration(900) - .success() - ), - ]; - }); - - const unserialized = Array.from(events); - - const entities = unserialized.flatMap((event) => event.serialize()); - - const transaction = entities[0]; - transactionId = transaction?.['transaction.id']!; - traceId = transaction?.['trace.id']!; - - await apmSynthtraceEsClient.index(Readable.from(unserialized)); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('transaction details', () => { - let transactionDetails: Awaited<ReturnType<typeof fetchTransactionDetails>>['body']; - before(async () => { - const response = await fetchTransactionDetails({ - traceId, - transactionId, - }); - expect(response.status).to.eql(200); - transactionDetails = response.body; - }); - it('returns transaction details', () => { - expect(transactionDetails.transaction.name).to.eql('GET /apple 🍏'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.spec.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.spec.snap deleted file mode 100644 index e21c870c7eb5..000000000000 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.spec.snap +++ /dev/null @@ -1,146 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`APM API tests basic apm_8.0.0 Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` -Array [ - Object { - "averageResponseTime": 3279, - "impact": 0, - "key": "POST /api/orders", - "p95": 3264, - "serviceName": "opbeans-node", - "transactionName": "POST /api/orders", - "transactionType": "request", - "transactionsPerMinute": 0.0333333333333333, - }, - Object { - "averageResponseTime": 2119, - "impact": 0.030253201010799, - "key": "GET /*", - "p95": 2296, - "serviceName": "opbeans-node", - "transactionName": "GET /*", - "transactionType": "request", - "transactionsPerMinute": 0.1, - }, - Object { - "averageResponseTime": 5167, - "impact": 0.0693425383792029, - "key": "GET /api/products/:id", - "p95": 6144, - "serviceName": "opbeans-node", - "transactionName": "GET /api/products/:id", - "transactionType": "request", - "transactionsPerMinute": 0.0666666666666667, - }, - Object { - "averageResponseTime": 5551, - "impact": 0.349690833515986, - "key": "GET /api/orders/:id", - "p95": 11696, - "serviceName": "opbeans-node", - "transactionName": "GET /api/orders/:id", - "transactionType": "request", - "transactionsPerMinute": 0.233333333333333, - }, - Object { - "averageResponseTime": 9607, - "impact": 0.723177313441051, - "key": "GET /api/types", - "p95": 18672, - "serviceName": "opbeans-node", - "transactionName": "GET /api/types", - "transactionType": "request", - "transactionsPerMinute": 0.266666666666667, - }, - Object { - "averageResponseTime": 8669.22222222222, - "impact": 0.734647581660545, - "key": "GET /api/products/:id/customers", - "p95": 15920, - "serviceName": "opbeans-node", - "transactionName": "GET /api/products/:id/customers", - "transactionType": "request", - "transactionsPerMinute": 0.3, - }, - Object { - "averageResponseTime": 7571, - "impact": 0.860741901273131, - "key": "GET /api/types/:id", - "p95": 13552, - "serviceName": "opbeans-node", - "transactionName": "GET /api/types/:id", - "transactionType": "request", - "transactionsPerMinute": 0.4, - }, - Object { - "averageResponseTime": 8753.90909090909, - "impact": 0.914220675379615, - "key": "GET /api/customers/:id", - "p95": 11248, - "serviceName": "opbeans-node", - "transactionName": "GET /api/customers/:id", - "transactionType": "request", - "transactionsPerMinute": 0.366666666666667, - }, - Object { - "averageResponseTime": 7807, - "impact": 1.04204487263284, - "key": "GET /api/products/top", - "p95": 14000, - "serviceName": "opbeans-node", - "transactionName": "GET /api/products/top", - "transactionType": "request", - "transactionsPerMinute": 0.466666666666667, - }, - Object { - "averageResponseTime": 11913.6666666667, - "impact": 1.37294294450729, - "key": "GET /api/orders", - "p95": 15008, - "serviceName": "opbeans-node", - "transactionName": "GET /api/orders", - "transactionType": "request", - "transactionsPerMinute": 0.4, - }, - Object { - "averageResponseTime": 9062.52941176471, - "impact": 1.48203335322037, - "key": "GET /api/products", - "p95": 15728, - "serviceName": "opbeans-node", - "transactionName": "GET /api/products", - "transactionType": "request", - "transactionsPerMinute": 0.566666666666667, - }, - Object { - "averageResponseTime": 19858.2, - "impact": 1.91960393665109, - "key": "GET /api/stats", - "p95": 33984, - "serviceName": "opbeans-node", - "transactionName": "GET /api/stats", - "transactionType": "request", - "transactionsPerMinute": 0.333333333333333, - }, - Object { - "averageResponseTime": 22276.8181818182, - "impact": 2.37628180493074, - "key": "GET /api/customers", - "p95": 26304, - "serviceName": "opbeans-node", - "transactionName": "GET /api/customers", - "transactionType": "request", - "transactionsPerMinute": 0.366666666666667, - }, - Object { - "averageResponseTime": 107130.621052632, - "impact": 100, - "key": "GET /api", - "p95": 151520, - "serviceName": "opbeans-node", - "transactionName": "GET /api", - "transactionType": "request", - "transactionsPerMinute": 3.16666666666667, - }, -] -`; diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.spec.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.spec.snap deleted file mode 100644 index 4b3f2293498c..000000000000 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.spec.snap +++ /dev/null @@ -1,1751 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transaction charts when data is loaded returns the correct data 4`] = ` -Object { - "apmTimeseries": Object { - "overallAvgDuration": 563605.417040359, - "responseTimes": Object { - "avg": Array [ - Object { - "x": 1607435850000, - "y": null, - }, - Object { - "x": 1607435880000, - "y": 233725.666666667, - }, - Object { - "x": 1607435910000, - "y": 761099.333333333, - }, - Object { - "x": 1607435940000, - "y": 444231.666666667, - }, - Object { - "x": 1607435970000, - "y": 999194.666666667, - }, - Object { - "x": 1607436000000, - "y": 558128.666666667, - }, - Object { - "x": 1607436030000, - "y": 842340, - }, - Object { - "x": 1607436060000, - "y": 1070088, - }, - Object { - "x": 1607436090000, - "y": 1289537.66666667, - }, - Object { - "x": 1607436120000, - "y": 320373, - }, - Object { - "x": 1607436150000, - "y": 412243.857142857, - }, - Object { - "x": 1607436180000, - "y": 604852, - }, - Object { - "x": 1607436210000, - "y": 1293499, - }, - Object { - "x": 1607436240000, - "y": 272394.571428571, - }, - Object { - "x": 1607436270000, - "y": 930978.4, - }, - Object { - "x": 1607436300000, - "y": 906360, - }, - Object { - "x": 1607436330000, - "y": 232498.25, - }, - Object { - "x": 1607436360000, - "y": 201226.333333333, - }, - Object { - "x": 1607436390000, - "y": 621694.833333333, - }, - Object { - "x": 1607436420000, - "y": 1935481, - }, - Object { - "x": 1607436450000, - "y": 1157048, - }, - Object { - "x": 1607436480000, - "y": 717248.333333333, - }, - Object { - "x": 1607436510000, - "y": 660264.833333333, - }, - Object { - "x": 1607436540000, - "y": 1305048, - }, - Object { - "x": 1607436570000, - "y": 715224, - }, - Object { - "x": 1607436600000, - "y": 144978.5, - }, - Object { - "x": 1607436630000, - "y": 102661, - }, - Object { - "x": 1607436660000, - "y": 810296.5, - }, - Object { - "x": 1607436690000, - "y": 938002.25, - }, - Object { - "x": 1607436720000, - "y": 63220, - }, - Object { - "x": 1607436750000, - "y": 737306.2, - }, - Object { - "x": 1607436780000, - "y": 963865.75, - }, - Object { - "x": 1607436810000, - "y": 38124, - }, - Object { - "x": 1607436840000, - "y": 860345.6, - }, - Object { - "x": 1607436870000, - "y": null, - }, - Object { - "x": 1607436900000, - "y": 742157, - }, - Object { - "x": 1607436930000, - "y": 584849, - }, - Object { - "x": 1607436960000, - "y": 165453.2, - }, - Object { - "x": 1607436990000, - "y": 334794, - }, - Object { - "x": 1607437020000, - "y": 1397727.5, - }, - Object { - "x": 1607437050000, - "y": 1104933, - }, - Object { - "x": 1607437080000, - "y": 755694.571428571, - }, - Object { - "x": 1607437110000, - "y": 252777.25, - }, - Object { - "x": 1607437140000, - "y": 708401.333333333, - }, - Object { - "x": 1607437170000, - "y": 1153244, - }, - Object { - "x": 1607437200000, - "y": 730186.25, - }, - Object { - "x": 1607437230000, - "y": 270504.222222222, - }, - Object { - "x": 1607437260000, - "y": 938813.333333333, - }, - Object { - "x": 1607437290000, - "y": 171339, - }, - Object { - "x": 1607437320000, - "y": 345618.2, - }, - Object { - "x": 1607437350000, - "y": 1100982.25, - }, - Object { - "x": 1607437380000, - "y": 724415, - }, - Object { - "x": 1607437410000, - "y": 1273571.5, - }, - Object { - "x": 1607437440000, - "y": 329748, - }, - Object { - "x": 1607437470000, - "y": 231693.538461538, - }, - Object { - "x": 1607437500000, - "y": 620042, - }, - Object { - "x": 1607437530000, - "y": null, - }, - Object { - "x": 1607437560000, - "y": 640575.666666667, - }, - Object { - "x": 1607437590000, - "y": 177960.714285714, - }, - Object { - "x": 1607437620000, - "y": 1142976, - }, - Object { - "x": 1607437650000, - "y": 530845.5, - }, - ], - "p95": Array [ - Object { - "x": 1607435850000, - "y": null, - }, - Object { - "x": 1607435880000, - "y": 1032160, - }, - Object { - "x": 1607435910000, - "y": 1400704, - }, - Object { - "x": 1607435940000, - "y": 831360, - }, - Object { - "x": 1607435970000, - "y": 1118208, - }, - Object { - "x": 1607436000000, - "y": 921472, - }, - Object { - "x": 1607436030000, - "y": 995328, - }, - Object { - "x": 1607436060000, - "y": 1064960, - }, - Object { - "x": 1607436090000, - "y": 1560576, - }, - Object { - "x": 1607436120000, - "y": 610176, - }, - Object { - "x": 1607436150000, - "y": 1490912, - }, - Object { - "x": 1607436180000, - "y": 614400, - }, - Object { - "x": 1607436210000, - "y": 1286144, - }, - Object { - "x": 1607436240000, - "y": 1114096, - }, - Object { - "x": 1607436270000, - "y": 1843072, - }, - Object { - "x": 1607436300000, - "y": 1118208, - }, - Object { - "x": 1607436330000, - "y": 481152, - }, - Object { - "x": 1607436360000, - "y": 548848, - }, - Object { - "x": 1607436390000, - "y": 1695680, - }, - Object { - "x": 1607436420000, - "y": 1933312, - }, - Object { - "x": 1607436450000, - "y": 1429504, - }, - Object { - "x": 1607436480000, - "y": 1081216, - }, - Object { - "x": 1607436510000, - "y": 1572832, - }, - Object { - "x": 1607436540000, - "y": 1751040, - }, - Object { - "x": 1607436570000, - "y": 1087488, - }, - Object { - "x": 1607436600000, - "y": 1294320, - }, - Object { - "x": 1607436630000, - "y": 198528, - }, - Object { - "x": 1607436660000, - "y": 880640, - }, - Object { - "x": 1607436690000, - "y": 1257472, - }, - Object { - "x": 1607436720000, - "y": 180192, - }, - Object { - "x": 1607436750000, - "y": 1179520, - }, - Object { - "x": 1607436780000, - "y": 1556224, - }, - Object { - "x": 1607436810000, - "y": 37888, - }, - Object { - "x": 1607436840000, - "y": 1261504, - }, - Object { - "x": 1607436870000, - "y": null, - }, - Object { - "x": 1607436900000, - "y": 1087488, - }, - Object { - "x": 1607436930000, - "y": 581632, - }, - Object { - "x": 1607436960000, - "y": 294784, - }, - Object { - "x": 1607436990000, - "y": 1245152, - }, - Object { - "x": 1607437020000, - "y": 1654784, - }, - Object { - "x": 1607437050000, - "y": 1097728, - }, - Object { - "x": 1607437080000, - "y": 1433584, - }, - Object { - "x": 1607437110000, - "y": 925568, - }, - Object { - "x": 1607437140000, - "y": 919552, - }, - Object { - "x": 1607437170000, - "y": 1146880, - }, - Object { - "x": 1607437200000, - "y": 1507072, - }, - Object { - "x": 1607437230000, - "y": 1318880, - }, - Object { - "x": 1607437260000, - "y": 1867712, - }, - Object { - "x": 1607437290000, - "y": 210944, - }, - Object { - "x": 1607437320000, - "y": 1449952, - }, - Object { - "x": 1607437350000, - "y": 1462272, - }, - Object { - "x": 1607437380000, - "y": 724992, - }, - Object { - "x": 1607437410000, - "y": 1335296, - }, - Object { - "x": 1607437440000, - "y": 329728, - }, - Object { - "x": 1607437470000, - "y": 1409008, - }, - Object { - "x": 1607437500000, - "y": 763904, - }, - Object { - "x": 1607437530000, - "y": null, - }, - Object { - "x": 1607437560000, - "y": 1474528, - }, - Object { - "x": 1607437590000, - "y": 598000, - }, - Object { - "x": 1607437620000, - "y": 1138688, - }, - Object { - "x": 1607437650000, - "y": 822272, - }, - ], - "p99": Array [ - Object { - "x": 1607435850000, - "y": null, - }, - Object { - "x": 1607435880000, - "y": 1032160, - }, - Object { - "x": 1607435910000, - "y": 1400704, - }, - Object { - "x": 1607435940000, - "y": 831360, - }, - Object { - "x": 1607435970000, - "y": 1118208, - }, - Object { - "x": 1607436000000, - "y": 921472, - }, - Object { - "x": 1607436030000, - "y": 995328, - }, - Object { - "x": 1607436060000, - "y": 1064960, - }, - Object { - "x": 1607436090000, - "y": 1560576, - }, - Object { - "x": 1607436120000, - "y": 610176, - }, - Object { - "x": 1607436150000, - "y": 1490912, - }, - Object { - "x": 1607436180000, - "y": 614400, - }, - Object { - "x": 1607436210000, - "y": 1286144, - }, - Object { - "x": 1607436240000, - "y": 1114096, - }, - Object { - "x": 1607436270000, - "y": 1843072, - }, - Object { - "x": 1607436300000, - "y": 1118208, - }, - Object { - "x": 1607436330000, - "y": 481152, - }, - Object { - "x": 1607436360000, - "y": 548848, - }, - Object { - "x": 1607436390000, - "y": 1695680, - }, - Object { - "x": 1607436420000, - "y": 1933312, - }, - Object { - "x": 1607436450000, - "y": 1429504, - }, - Object { - "x": 1607436480000, - "y": 1081216, - }, - Object { - "x": 1607436510000, - "y": 1572832, - }, - Object { - "x": 1607436540000, - "y": 1751040, - }, - Object { - "x": 1607436570000, - "y": 1087488, - }, - Object { - "x": 1607436600000, - "y": 1294320, - }, - Object { - "x": 1607436630000, - "y": 198528, - }, - Object { - "x": 1607436660000, - "y": 880640, - }, - Object { - "x": 1607436690000, - "y": 1257472, - }, - Object { - "x": 1607436720000, - "y": 180192, - }, - Object { - "x": 1607436750000, - "y": 1179520, - }, - Object { - "x": 1607436780000, - "y": 1556224, - }, - Object { - "x": 1607436810000, - "y": 37888, - }, - Object { - "x": 1607436840000, - "y": 1261504, - }, - Object { - "x": 1607436870000, - "y": null, - }, - Object { - "x": 1607436900000, - "y": 1087488, - }, - Object { - "x": 1607436930000, - "y": 581632, - }, - Object { - "x": 1607436960000, - "y": 294784, - }, - Object { - "x": 1607436990000, - "y": 1245152, - }, - Object { - "x": 1607437020000, - "y": 1654784, - }, - Object { - "x": 1607437050000, - "y": 1097728, - }, - Object { - "x": 1607437080000, - "y": 1433584, - }, - Object { - "x": 1607437110000, - "y": 925568, - }, - Object { - "x": 1607437140000, - "y": 919552, - }, - Object { - "x": 1607437170000, - "y": 1146880, - }, - Object { - "x": 1607437200000, - "y": 1507072, - }, - Object { - "x": 1607437230000, - "y": 1318880, - }, - Object { - "x": 1607437260000, - "y": 1867712, - }, - Object { - "x": 1607437290000, - "y": 210944, - }, - Object { - "x": 1607437320000, - "y": 1449952, - }, - Object { - "x": 1607437350000, - "y": 1462272, - }, - Object { - "x": 1607437380000, - "y": 724992, - }, - Object { - "x": 1607437410000, - "y": 1335296, - }, - Object { - "x": 1607437440000, - "y": 329728, - }, - Object { - "x": 1607437470000, - "y": 1466352, - }, - Object { - "x": 1607437500000, - "y": 763904, - }, - Object { - "x": 1607437530000, - "y": null, - }, - Object { - "x": 1607437560000, - "y": 1474528, - }, - Object { - "x": 1607437590000, - "y": 598000, - }, - Object { - "x": 1607437620000, - "y": 1138688, - }, - Object { - "x": 1607437650000, - "y": 822272, - }, - ], - }, - "tpmBuckets": Array [ - Object { - "avg": 3, - "dataPoints": Array [ - Object { - "x": 1607435850000, - "y": 0, - }, - Object { - "x": 1607435880000, - "y": 8, - }, - Object { - "x": 1607435910000, - "y": 4, - }, - Object { - "x": 1607435940000, - "y": 2, - }, - Object { - "x": 1607435970000, - "y": 0, - }, - Object { - "x": 1607436000000, - "y": 2, - }, - Object { - "x": 1607436030000, - "y": 0, - }, - Object { - "x": 1607436060000, - "y": 0, - }, - Object { - "x": 1607436090000, - "y": 0, - }, - Object { - "x": 1607436120000, - "y": 2, - }, - Object { - "x": 1607436150000, - "y": 10, - }, - Object { - "x": 1607436180000, - "y": 0, - }, - Object { - "x": 1607436210000, - "y": 0, - }, - Object { - "x": 1607436240000, - "y": 10, - }, - Object { - "x": 1607436270000, - "y": 2, - }, - Object { - "x": 1607436300000, - "y": 0, - }, - Object { - "x": 1607436330000, - "y": 4, - }, - Object { - "x": 1607436360000, - "y": 4, - }, - Object { - "x": 1607436390000, - "y": 6, - }, - Object { - "x": 1607436420000, - "y": 0, - }, - Object { - "x": 1607436450000, - "y": 0, - }, - Object { - "x": 1607436480000, - "y": 2, - }, - Object { - "x": 1607436510000, - "y": 6, - }, - Object { - "x": 1607436540000, - "y": 0, - }, - Object { - "x": 1607436570000, - "y": 0, - }, - Object { - "x": 1607436600000, - "y": 14, - }, - Object { - "x": 1607436630000, - "y": 8, - }, - Object { - "x": 1607436660000, - "y": 0, - }, - Object { - "x": 1607436690000, - "y": 0, - }, - Object { - "x": 1607436720000, - "y": 8, - }, - Object { - "x": 1607436750000, - "y": 2, - }, - Object { - "x": 1607436780000, - "y": 2, - }, - Object { - "x": 1607436810000, - "y": 2, - }, - Object { - "x": 1607436840000, - "y": 2, - }, - Object { - "x": 1607436870000, - "y": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - }, - Object { - "x": 1607436930000, - "y": 0, - }, - Object { - "x": 1607436960000, - "y": 4, - }, - Object { - "x": 1607436990000, - "y": 8, - }, - Object { - "x": 1607437020000, - "y": 0, - }, - Object { - "x": 1607437050000, - "y": 0, - }, - Object { - "x": 1607437080000, - "y": 6, - }, - Object { - "x": 1607437110000, - "y": 6, - }, - Object { - "x": 1607437140000, - "y": 0, - }, - Object { - "x": 1607437170000, - "y": 0, - }, - Object { - "x": 1607437200000, - "y": 2, - }, - Object { - "x": 1607437230000, - "y": 14, - }, - Object { - "x": 1607437260000, - "y": 2, - }, - Object { - "x": 1607437290000, - "y": 0, - }, - Object { - "x": 1607437320000, - "y": 4, - }, - Object { - "x": 1607437350000, - "y": 0, - }, - Object { - "x": 1607437380000, - "y": 0, - }, - Object { - "x": 1607437410000, - "y": 0, - }, - Object { - "x": 1607437440000, - "y": 0, - }, - Object { - "x": 1607437470000, - "y": 22, - }, - Object { - "x": 1607437500000, - "y": 0, - }, - Object { - "x": 1607437530000, - "y": 0, - }, - Object { - "x": 1607437560000, - "y": 6, - }, - Object { - "x": 1607437590000, - "y": 6, - }, - Object { - "x": 1607437620000, - "y": 0, - }, - Object { - "x": 1607437650000, - "y": 0, - }, - ], - "key": "HTTP 2xx", - }, - Object { - "avg": 0.1, - "dataPoints": Array [ - Object { - "x": 1607435850000, - "y": 0, - }, - Object { - "x": 1607435880000, - "y": 0, - }, - Object { - "x": 1607435910000, - "y": 0, - }, - Object { - "x": 1607435940000, - "y": 0, - }, - Object { - "x": 1607435970000, - "y": 0, - }, - Object { - "x": 1607436000000, - "y": 0, - }, - Object { - "x": 1607436030000, - "y": 0, - }, - Object { - "x": 1607436060000, - "y": 0, - }, - Object { - "x": 1607436090000, - "y": 0, - }, - Object { - "x": 1607436120000, - "y": 0, - }, - Object { - "x": 1607436150000, - "y": 0, - }, - Object { - "x": 1607436180000, - "y": 0, - }, - Object { - "x": 1607436210000, - "y": 0, - }, - Object { - "x": 1607436240000, - "y": 0, - }, - Object { - "x": 1607436270000, - "y": 0, - }, - Object { - "x": 1607436300000, - "y": 0, - }, - Object { - "x": 1607436330000, - "y": 0, - }, - Object { - "x": 1607436360000, - "y": 0, - }, - Object { - "x": 1607436390000, - "y": 0, - }, - Object { - "x": 1607436420000, - "y": 0, - }, - Object { - "x": 1607436450000, - "y": 0, - }, - Object { - "x": 1607436480000, - "y": 0, - }, - Object { - "x": 1607436510000, - "y": 0, - }, - Object { - "x": 1607436540000, - "y": 0, - }, - Object { - "x": 1607436570000, - "y": 0, - }, - Object { - "x": 1607436600000, - "y": 0, - }, - Object { - "x": 1607436630000, - "y": 0, - }, - Object { - "x": 1607436660000, - "y": 0, - }, - Object { - "x": 1607436690000, - "y": 0, - }, - Object { - "x": 1607436720000, - "y": 0, - }, - Object { - "x": 1607436750000, - "y": 0, - }, - Object { - "x": 1607436780000, - "y": 0, - }, - Object { - "x": 1607436810000, - "y": 0, - }, - Object { - "x": 1607436840000, - "y": 0, - }, - Object { - "x": 1607436870000, - "y": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - }, - Object { - "x": 1607436930000, - "y": 0, - }, - Object { - "x": 1607436960000, - "y": 0, - }, - Object { - "x": 1607436990000, - "y": 0, - }, - Object { - "x": 1607437020000, - "y": 0, - }, - Object { - "x": 1607437050000, - "y": 0, - }, - Object { - "x": 1607437080000, - "y": 0, - }, - Object { - "x": 1607437110000, - "y": 0, - }, - Object { - "x": 1607437140000, - "y": 0, - }, - Object { - "x": 1607437170000, - "y": 0, - }, - Object { - "x": 1607437200000, - "y": 0, - }, - Object { - "x": 1607437230000, - "y": 0, - }, - Object { - "x": 1607437260000, - "y": 0, - }, - Object { - "x": 1607437290000, - "y": 0, - }, - Object { - "x": 1607437320000, - "y": 2, - }, - Object { - "x": 1607437350000, - "y": 0, - }, - Object { - "x": 1607437380000, - "y": 0, - }, - Object { - "x": 1607437410000, - "y": 0, - }, - Object { - "x": 1607437440000, - "y": 0, - }, - Object { - "x": 1607437470000, - "y": 0, - }, - Object { - "x": 1607437500000, - "y": 0, - }, - Object { - "x": 1607437530000, - "y": 0, - }, - Object { - "x": 1607437560000, - "y": 0, - }, - Object { - "x": 1607437590000, - "y": 4, - }, - Object { - "x": 1607437620000, - "y": 0, - }, - Object { - "x": 1607437650000, - "y": 0, - }, - ], - "key": "HTTP 4xx", - }, - Object { - "avg": 0.0666666666666667, - "dataPoints": Array [ - Object { - "x": 1607435850000, - "y": 0, - }, - Object { - "x": 1607435880000, - "y": 0, - }, - Object { - "x": 1607435910000, - "y": 0, - }, - Object { - "x": 1607435940000, - "y": 0, - }, - Object { - "x": 1607435970000, - "y": 0, - }, - Object { - "x": 1607436000000, - "y": 0, - }, - Object { - "x": 1607436030000, - "y": 0, - }, - Object { - "x": 1607436060000, - "y": 0, - }, - Object { - "x": 1607436090000, - "y": 0, - }, - Object { - "x": 1607436120000, - "y": 0, - }, - Object { - "x": 1607436150000, - "y": 0, - }, - Object { - "x": 1607436180000, - "y": 0, - }, - Object { - "x": 1607436210000, - "y": 0, - }, - Object { - "x": 1607436240000, - "y": 0, - }, - Object { - "x": 1607436270000, - "y": 0, - }, - Object { - "x": 1607436300000, - "y": 0, - }, - Object { - "x": 1607436330000, - "y": 0, - }, - Object { - "x": 1607436360000, - "y": 0, - }, - Object { - "x": 1607436390000, - "y": 0, - }, - Object { - "x": 1607436420000, - "y": 0, - }, - Object { - "x": 1607436450000, - "y": 0, - }, - Object { - "x": 1607436480000, - "y": 0, - }, - Object { - "x": 1607436510000, - "y": 0, - }, - Object { - "x": 1607436540000, - "y": 0, - }, - Object { - "x": 1607436570000, - "y": 0, - }, - Object { - "x": 1607436600000, - "y": 4, - }, - Object { - "x": 1607436630000, - "y": 0, - }, - Object { - "x": 1607436660000, - "y": 0, - }, - Object { - "x": 1607436690000, - "y": 0, - }, - Object { - "x": 1607436720000, - "y": 0, - }, - Object { - "x": 1607436750000, - "y": 0, - }, - Object { - "x": 1607436780000, - "y": 0, - }, - Object { - "x": 1607436810000, - "y": 0, - }, - Object { - "x": 1607436840000, - "y": 0, - }, - Object { - "x": 1607436870000, - "y": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - }, - Object { - "x": 1607436930000, - "y": 0, - }, - Object { - "x": 1607436960000, - "y": 0, - }, - Object { - "x": 1607436990000, - "y": 0, - }, - Object { - "x": 1607437020000, - "y": 0, - }, - Object { - "x": 1607437050000, - "y": 0, - }, - Object { - "x": 1607437080000, - "y": 0, - }, - Object { - "x": 1607437110000, - "y": 0, - }, - Object { - "x": 1607437140000, - "y": 0, - }, - Object { - "x": 1607437170000, - "y": 0, - }, - Object { - "x": 1607437200000, - "y": 0, - }, - Object { - "x": 1607437230000, - "y": 0, - }, - Object { - "x": 1607437260000, - "y": 0, - }, - Object { - "x": 1607437290000, - "y": 0, - }, - Object { - "x": 1607437320000, - "y": 0, - }, - Object { - "x": 1607437350000, - "y": 0, - }, - Object { - "x": 1607437380000, - "y": 0, - }, - Object { - "x": 1607437410000, - "y": 0, - }, - Object { - "x": 1607437440000, - "y": 0, - }, - Object { - "x": 1607437470000, - "y": 0, - }, - Object { - "x": 1607437500000, - "y": 0, - }, - Object { - "x": 1607437530000, - "y": 0, - }, - Object { - "x": 1607437560000, - "y": 0, - }, - Object { - "x": 1607437590000, - "y": 0, - }, - Object { - "x": 1607437620000, - "y": 0, - }, - Object { - "x": 1607437650000, - "y": 0, - }, - ], - "key": "HTTP 5xx", - }, - Object { - "avg": 4.26666666666667, - "dataPoints": Array [ - Object { - "x": 1607435850000, - "y": 0, - }, - Object { - "x": 1607435880000, - "y": 4, - }, - Object { - "x": 1607435910000, - "y": 8, - }, - Object { - "x": 1607435940000, - "y": 4, - }, - Object { - "x": 1607435970000, - "y": 6, - }, - Object { - "x": 1607436000000, - "y": 4, - }, - Object { - "x": 1607436030000, - "y": 4, - }, - Object { - "x": 1607436060000, - "y": 2, - }, - Object { - "x": 1607436090000, - "y": 6, - }, - Object { - "x": 1607436120000, - "y": 2, - }, - Object { - "x": 1607436150000, - "y": 4, - }, - Object { - "x": 1607436180000, - "y": 4, - }, - Object { - "x": 1607436210000, - "y": 2, - }, - Object { - "x": 1607436240000, - "y": 4, - }, - Object { - "x": 1607436270000, - "y": 8, - }, - Object { - "x": 1607436300000, - "y": 4, - }, - Object { - "x": 1607436330000, - "y": 4, - }, - Object { - "x": 1607436360000, - "y": 2, - }, - Object { - "x": 1607436390000, - "y": 6, - }, - Object { - "x": 1607436420000, - "y": 2, - }, - Object { - "x": 1607436450000, - "y": 6, - }, - Object { - "x": 1607436480000, - "y": 4, - }, - Object { - "x": 1607436510000, - "y": 6, - }, - Object { - "x": 1607436540000, - "y": 6, - }, - Object { - "x": 1607436570000, - "y": 6, - }, - Object { - "x": 1607436600000, - "y": 2, - }, - Object { - "x": 1607436630000, - "y": 4, - }, - Object { - "x": 1607436660000, - "y": 4, - }, - Object { - "x": 1607436690000, - "y": 8, - }, - Object { - "x": 1607436720000, - "y": 0, - }, - Object { - "x": 1607436750000, - "y": 8, - }, - Object { - "x": 1607436780000, - "y": 6, - }, - Object { - "x": 1607436810000, - "y": 0, - }, - Object { - "x": 1607436840000, - "y": 8, - }, - Object { - "x": 1607436870000, - "y": 0, - }, - Object { - "x": 1607436900000, - "y": 6, - }, - Object { - "x": 1607436930000, - "y": 2, - }, - Object { - "x": 1607436960000, - "y": 6, - }, - Object { - "x": 1607436990000, - "y": 4, - }, - Object { - "x": 1607437020000, - "y": 4, - }, - Object { - "x": 1607437050000, - "y": 2, - }, - Object { - "x": 1607437080000, - "y": 8, - }, - Object { - "x": 1607437110000, - "y": 2, - }, - Object { - "x": 1607437140000, - "y": 6, - }, - Object { - "x": 1607437170000, - "y": 2, - }, - Object { - "x": 1607437200000, - "y": 6, - }, - Object { - "x": 1607437230000, - "y": 4, - }, - Object { - "x": 1607437260000, - "y": 4, - }, - Object { - "x": 1607437290000, - "y": 4, - }, - Object { - "x": 1607437320000, - "y": 4, - }, - Object { - "x": 1607437350000, - "y": 8, - }, - Object { - "x": 1607437380000, - "y": 4, - }, - Object { - "x": 1607437410000, - "y": 4, - }, - Object { - "x": 1607437440000, - "y": 2, - }, - Object { - "x": 1607437470000, - "y": 4, - }, - Object { - "x": 1607437500000, - "y": 6, - }, - Object { - "x": 1607437530000, - "y": 0, - }, - Object { - "x": 1607437560000, - "y": 6, - }, - Object { - "x": 1607437590000, - "y": 4, - }, - Object { - "x": 1607437620000, - "y": 2, - }, - Object { - "x": 1607437650000, - "y": 4, - }, - ], - "key": "success", - }, - ], - }, -} -`; diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.spec.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.spec.snap deleted file mode 100644 index 2f78b03adbf4..000000000000 --- a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.spec.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`APM Transaction Overview when data is loaded and fetching transaction charts with uiFilters when not defined environments selected should return the correct anomaly boundaries 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 0, - "y0": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - "y0": 0, - }, - Object { - "x": 1607437650000, - "y": 0, - "y0": 0, - }, -] -`; - -exports[`APM Transaction Overview when data is loaded and fetching transaction charts with uiFilters with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, - }, - Object { - "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, - Object { - "x": 1607437650000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, -] -`; - -exports[`APM Transaction Overview when data is loaded and fetching transaction charts with uiFilters with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, - }, - Object { - "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, - Object { - "x": 1607437650000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, -] -`; diff --git a/x-pack/test/apm_api_integration/tests/transactions/breakdown.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/breakdown.spec.ts index 6b7848262c69..d7cd6d5b8777 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/breakdown.spec.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/breakdown.spec.ts @@ -18,27 +18,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { const transactionType = 'request'; const transactionName = 'GET /api'; - registry.when('Breakdown when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles the empty state', async () => { - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/transaction/charts/breakdown', - params: { - path: { serviceName: 'opbeans-node' }, - query: { - start, - end, - transactionType, - environment: 'ENVIRONMENT_ALL', - kuery: '', - }, - }, - }); - - expect(response.status).to.be(200); - expect(response.body).to.eql({ timeseries: [] }); - }); - }); - registry.when( 'Breakdown when data is loaded', { config: 'basic', archives: [archiveName] }, diff --git a/x-pack/test/apm_api_integration/tests/transactions/error_rate.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/error_rate.spec.ts deleted file mode 100644 index 724390fdfa61..000000000000 --- a/x-pack/test/apm_api_integration/tests/transactions/error_rate.spec.ts +++ /dev/null @@ -1,433 +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 { apm, timerange } from '@kbn/apm-synthtrace-client'; -import expect from '@kbn/expect'; -import { buildQueryFromFilters } from '@kbn/es-query'; -import { first, last } from 'lodash'; -import moment from 'moment'; -import { - APIClientRequestParamsOf, - APIReturnType, -} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; -import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; -import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -type ErrorRate = - APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - // url parameters - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - - async function fetchErrorCharts( - overrides?: RecursivePartial< - APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate'>['params'] - > - ) { - return await apmApiClient.readUser({ - endpoint: `GET /internal/apm/services/{serviceName}/transactions/charts/error_rate`, - params: { - path: { serviceName: overrides?.path?.serviceName || 'opbeans-go' }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - transactionType: 'request', - environment: 'ENVIRONMENT_ALL', - kuery: '', - documentType: ApmDocumentType.TransactionMetric, - rollupInterval: RollupInterval.OneMinute, - bucketSizeInSeconds: 60, - ...overrides?.query, - }, - }, - }); - } - - registry.when('Error rate when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles the empty state', async () => { - const response = await fetchErrorCharts(); - expect(response.status).to.be(200); - - const body = response.body as ErrorRate; - expect(body).to.be.eql({ - currentPeriod: { timeseries: [], average: null }, - previousPeriod: { timeseries: [], average: null }, - }); - }); - - it('handles the empty state with comparison data', async () => { - const response = await fetchErrorCharts({ - query: { - start: moment(end).subtract(7, 'minutes').toISOString(), - offset: '7m', - }, - }); - expect(response.status).to.be(200); - - const body = response.body as ErrorRate; - expect(body).to.be.eql({ - currentPeriod: { timeseries: [], average: null }, - previousPeriod: { timeseries: [], average: null }, - }); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177598 - registry.when('Error rate when data is loaded', { config: 'basic', archives: [] }, () => { - const config = { - firstTransaction: { - name: 'GET /apple 🍎 ', - successRate: 50, - failureRate: 50, - }, - secondTransaction: { - name: 'GET /pear 🍎 ', - successRate: 25, - failureRate: 75, - }, - }; - before(async () => { - const serviceGoProdInstance = apm - .service({ name: 'opbeans-go', environment: 'production', agentName: 'go' }) - .instance('instance-a'); - - const { firstTransaction, secondTransaction } = config; - - const documents = [ - timerange(start, end) - .ratePerMinute(firstTransaction.successRate) - .generator((timestamp) => - serviceGoProdInstance - .transaction({ transactionName: firstTransaction.name }) - .timestamp(timestamp) - .duration(1000) - .success() - ), - timerange(start, end) - .ratePerMinute(firstTransaction.failureRate) - .generator((timestamp) => - serviceGoProdInstance - .transaction({ transactionName: firstTransaction.name }) - .duration(1000) - .timestamp(timestamp) - .failure() - ), - timerange(start, end) - .ratePerMinute(secondTransaction.successRate) - .generator((timestamp) => - serviceGoProdInstance - .transaction({ transactionName: secondTransaction.name }) - .timestamp(timestamp) - .duration(1000) - .success() - ), - timerange(start, end) - .ratePerMinute(secondTransaction.failureRate) - .generator((timestamp) => - serviceGoProdInstance - .transaction({ transactionName: secondTransaction.name }) - .duration(1000) - .timestamp(timestamp) - .failure() - ), - ]; - await apmSynthtraceEsClient.index(documents); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('returns the transaction error rate', () => { - let errorRateResponse: ErrorRate; - - before(async () => { - const response = await fetchErrorCharts({ - query: { transactionName: config.firstTransaction.name }, - }); - errorRateResponse = response.body; - }); - - it('returns some data', () => { - expect(errorRateResponse.currentPeriod.average).to.be.greaterThan(0); - expect(errorRateResponse.previousPeriod.average).to.be(null); - - expect(errorRateResponse.currentPeriod.timeseries).not.to.be.empty(); - expect(errorRateResponse.previousPeriod.timeseries).to.empty(); - - const nonNullDataPoints = errorRateResponse.currentPeriod.timeseries.filter( - ({ y }) => y !== null - ); - - expect(nonNullDataPoints).not.to.be.empty(); - }); - - it('has the correct start date', () => { - expect( - new Date(first(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() - ).to.eql('2021-01-01T00:00:00.000Z'); - }); - - it('has the correct end date', () => { - expect( - new Date(last(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() - ).to.eql('2021-01-01T00:14:00.000Z'); - }); - - it('has the correct number of buckets', () => { - expect(errorRateResponse.currentPeriod.timeseries.length).to.be.eql(15); - }); - - it('has the correct calculation for average', () => { - expect(errorRateResponse.currentPeriod.average).to.eql( - config.firstTransaction.failureRate / 100 - ); - }); - }); - - describe('returns the transaction error rate with comparison data per transaction name', () => { - let errorRateResponse: ErrorRate; - - before(async () => { - const query = { - transactionName: config.firstTransaction.name, - start: moment(end).subtract(7, 'minutes').toISOString(), - offset: '7m', - }; - - const response = await fetchErrorCharts({ query }); - - errorRateResponse = response.body; - }); - - it('returns some data', () => { - expect(errorRateResponse.currentPeriod.average).to.be.greaterThan(0); - expect(errorRateResponse.previousPeriod.average).to.be.greaterThan(0); - - expect(errorRateResponse.currentPeriod.timeseries).not.to.be.empty(); - expect(errorRateResponse.previousPeriod.timeseries).not.to.be.empty(); - - const currentPeriodNonNullDataPoints = errorRateResponse.currentPeriod.timeseries.filter( - ({ y }) => y !== null - ); - - const previousPeriodNonNullDataPoints = errorRateResponse.previousPeriod.timeseries.filter( - ({ y }) => y !== null - ); - - expect(currentPeriodNonNullDataPoints).not.to.be.empty(); - expect(previousPeriodNonNullDataPoints).not.to.be.empty(); - }); - - it('has the correct start date', () => { - expect( - new Date(first(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() - ).to.eql('2021-01-01T00:07:00.000Z'); - expect( - new Date(first(errorRateResponse.previousPeriod.timeseries)?.x ?? NaN).toISOString() - ).to.eql('2021-01-01T00:07:00.000Z'); - }); - - it('has the correct end date', () => { - expect( - new Date(last(errorRateResponse.currentPeriod.timeseries)?.x ?? NaN).toISOString() - ).to.eql('2021-01-01T00:14:00.000Z'); - expect( - new Date(last(errorRateResponse.previousPeriod.timeseries)?.x ?? NaN).toISOString() - ).to.eql('2021-01-01T00:14:00.000Z'); - }); - - it('has the correct number of buckets', () => { - expect(errorRateResponse.currentPeriod.timeseries.length).to.eql(8); - expect(errorRateResponse.previousPeriod.timeseries.length).to.eql(8); - }); - - it('has the correct calculation for average', () => { - expect(errorRateResponse.currentPeriod.average).to.eql( - config.firstTransaction.failureRate / 100 - ); - expect(errorRateResponse.previousPeriod.average).to.eql( - config.firstTransaction.failureRate / 100 - ); - }); - - it('matches x-axis on current period and previous period', () => { - expect(errorRateResponse.currentPeriod.timeseries.map(({ x }) => x)).to.be.eql( - errorRateResponse.previousPeriod.timeseries.map(({ x }) => x) - ); - }); - }); - - describe('returns the same error rate for tx metrics and service tx metrics ', () => { - let txMetricsErrorRateResponse: ErrorRate; - let serviceTxMetricsErrorRateResponse: ErrorRate; - - before(async () => { - const [txMetricsResponse, serviceTxMetricsResponse] = await Promise.all([ - fetchErrorCharts(), - fetchErrorCharts({ - query: { documentType: ApmDocumentType.ServiceTransactionMetric }, - }), - ]); - - txMetricsErrorRateResponse = txMetricsResponse.body; - serviceTxMetricsErrorRateResponse = serviceTxMetricsResponse.body; - }); - - describe('has the correct calculation for average', () => { - const expectedFailureRate = - (config.firstTransaction.failureRate + config.secondTransaction.failureRate) / 2 / 100; - - it('for tx metrics', () => { - expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); - }); - - it('for service tx metrics', () => { - expect(serviceTxMetricsErrorRateResponse.currentPeriod.average).to.eql( - expectedFailureRate - ); - }); - }); - }); - - describe('handles kuery', () => { - let txMetricsErrorRateResponse: ErrorRate; - - before(async () => { - const txMetricsResponse = await fetchErrorCharts({ - query: { - kuery: 'transaction.name : "GET /pear 🍎 "', - }, - }); - txMetricsErrorRateResponse = txMetricsResponse.body; - }); - - describe('has the correct calculation for average with kuery', () => { - const expectedFailureRate = config.secondTransaction.failureRate / 100; - - it('for tx metrics', () => { - expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); - }); - }); - }); - - describe('handles filters', () => { - const filters = [ - { - meta: { - disabled: false, - negate: false, - alias: null, - key: 'transaction.name', - params: ['GET /api/product/list'], - type: 'phrases', - }, - query: { - bool: { - minimum_should_match: 1, - should: { - match_phrase: { - 'transaction.name': 'GET /pear 🍎 ', - }, - }, - }, - }, - }, - ]; - const serializedFilters = JSON.stringify(buildQueryFromFilters(filters, undefined)); - let txMetricsErrorRateResponse: ErrorRate; - - before(async () => { - const txMetricsResponse = await fetchErrorCharts({ - query: { - filters: serializedFilters, - }, - }); - txMetricsErrorRateResponse = txMetricsResponse.body; - }); - - describe('has the correct calculation for average with filter', () => { - const expectedFailureRate = config.secondTransaction.failureRate / 100; - - it('for tx metrics', () => { - expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); - }); - }); - - describe('has the correct calculation for average with negate filter', () => { - const expectedFailureRate = config.secondTransaction.failureRate / 100; - - it('for tx metrics', () => { - expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); - }); - }); - }); - - describe('handles negate filters', () => { - const filters = [ - { - meta: { - disabled: false, - negate: true, - alias: null, - key: 'transaction.name', - params: ['GET /api/product/list'], - type: 'phrases', - }, - query: { - bool: { - minimum_should_match: 1, - should: { - match_phrase: { - 'transaction.name': 'GET /pear 🍎 ', - }, - }, - }, - }, - }, - ]; - const serializedFilters = JSON.stringify(buildQueryFromFilters(filters, undefined)); - let txMetricsErrorRateResponse: ErrorRate; - - before(async () => { - const txMetricsResponse = await fetchErrorCharts({ - query: { - filters: serializedFilters, - }, - }); - txMetricsErrorRateResponse = txMetricsResponse.body; - }); - - describe('has the correct calculation for average with filter', () => { - const expectedFailureRate = config.firstTransaction.failureRate / 100; - - it('for tx metrics', () => { - expect(txMetricsErrorRateResponse.currentPeriod.average).to.eql(expectedFailureRate); - }); - }); - }); - - describe('handles bad filters request', () => { - it('for tx metrics', async () => { - try { - await fetchErrorCharts({ - query: { - filters: '{}}}', - }, - }); - } catch (e) { - expect(e.res.status).to.eql(400); - } - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts index 5edc1f5a1abc..1aad31ecc4e5 100644 --- a/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/trace_samples.spec.ts @@ -16,33 +16,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - registry.when( - 'Transaction trace samples response structure when data is not loaded', - { config: 'basic', archives: [] }, - () => { - it('handles empty state', async () => { - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/transactions/traces/samples', - params: { - path: { serviceName: 'opbeans-java' }, - query: { - start, - end, - transactionType: 'request', - environment: 'ENVIRONMENT_ALL', - transactionName: 'APIRestController#stats', - kuery: '', - }, - }, - }); - - expect(response.status).to.be(200); - - expect(response.body.traceSamples.length).to.be(0); - }); - } - ); - registry.when( 'Transaction trace samples response structure when data is loaded', { config: 'basic', archives: [archiveName] }, diff --git a/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts b/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts index 2065d1307fbd..8f48bbb7d1bf 100644 --- a/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts +++ b/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts @@ -65,12 +65,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.navigateToIntegrationCspList(); await pageObjects.header.waitUntilLoadingHasFinished(); - expect(await cisIntegration.getFirstCspmIntegrationPageIntegration()).to.be( + expect(await cisIntegration.getFirstCspmIntegrationPageAgentlessIntegration()).to.be( integrationPolicyName ); - expect(await cisIntegration.getFirstCspmIntegrationPageAgent()).to.be( - `Agentless policy for ${integrationPolicyName}` - ); + expect(await cisIntegration.getFirstCspmIntegrationPageAgentlessStatus()).to.be('Pending'); }); it(`should show setup technology selector in edit mode`, async () => { @@ -97,7 +95,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.navigateToIntegrationCspList(); await pageObjects.header.waitUntilLoadingHasFinished(); - await cisIntegration.navigateToEditIntegrationPage(); + await cisIntegration.navigateToEditAgentlessIntegrationPage(); await pageObjects.header.waitUntilLoadingHasFinished(); expect(await cisIntegration.showSetupTechnologyComponent()).to.be(true); diff --git a/x-pack/test/cloud_security_posture_functional/cloud_tests/agentless_api_sanity.ts b/x-pack/test/cloud_security_posture_functional/cloud_tests/agentless_api_sanity.ts new file mode 100644 index 000000000000..da4eb02a813c --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/cloud_tests/agentless_api_sanity.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../ftr_provider_context'; +// eslint-disable-next-line import/no-default-export +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const queryBar = getService('queryBar'); + const pageObjects = getPageObjects(['common', 'header', 'cisAddIntegration', 'findings']); + + describe('Agentless Cloud - Sanity Tests', function () { + this.tags(['cloud_security_posture_ui_sanity']); + + it(`should have agentless agent findings for AWS provider`, async () => { + const findings = pageObjects.findings; + + await findings.navigateToLatestFindingsPage(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await queryBar.setQuery('agent.name: *agentless* and cloud.provider : "aws"'); + await queryBar.submitQuery(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + const agentlessFindingsRowsCount = await findings + .createDataTableObject('latest_findings_table') + .getRowsCount(); + + expect(agentlessFindingsRowsCount).to.be.greaterThan(0); + }); + + it(`should have agentless agent findings for Azure provider`, async () => { + const findings = pageObjects.findings; + + await findings.navigateToLatestFindingsPage(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await queryBar.setQuery('agent.name: *agentless* and cloud.provider : "gcp"'); + await queryBar.submitQuery(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + const agentlessFindingsRowsCount = await findings + .createDataTableObject('latest_findings_table') + .getRowsCount(); + + expect(agentlessFindingsRowsCount).to.be.greaterThan(0); + }); + }); +} diff --git a/x-pack/test/cloud_security_posture_functional/cloud_tests/agentless_sanity.ts b/x-pack/test/cloud_security_posture_functional/cloud_tests/agentless_sanity.ts deleted file mode 100644 index d1a27bf5d8c1..000000000000 --- a/x-pack/test/cloud_security_posture_functional/cloud_tests/agentless_sanity.ts +++ /dev/null @@ -1,98 +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 { CLOUD_CREDENTIALS_PACKAGE_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; -import expect from '@kbn/expect'; -import type { FtrProviderContext } from '../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects([ - 'common', - 'cspSecurity', - 'security', - 'header', - 'cisAddIntegration', - ]); - - const CIS_AWS_OPTION_TEST_ID = 'cisAwsTestId'; - - const AWS_SINGLE_ACCOUNT_TEST_ID = 'awsSingleTestId'; - - describe('Agentless cloud', function () { - let cisIntegration: typeof pageObjects.cisAddIntegration; - let cisIntegrationAws: typeof pageObjects.cisAddIntegration.cisAws; - - before(async () => { - cisIntegration = pageObjects.cisAddIntegration; - cisIntegrationAws = pageObjects.cisAddIntegration.cisAws; // Start the usage api mock server on port 8081 - }); - - after(async () => { - await pageObjects.cspSecurity.logout(); - }); - - it(`should create agentless-agent`, async () => { - const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; - await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION - ); - - await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); - await cisIntegration.clickOptionButton(AWS_SINGLE_ACCOUNT_TEST_ID); - - await cisIntegration.inputIntegrationName(integrationPolicyName); - - await cisIntegration.selectSetupTechnology('agentless'); - await cisIntegration.selectAwsCredentials('direct'); - - await pageObjects.header.waitUntilLoadingHasFinished(); - - await cisIntegration.clickSaveButton(); - await pageObjects.header.waitUntilLoadingHasFinished(); - - expect(await cisIntegrationAws.showPostInstallCloudFormationModal()).to.be(false); - - await cisIntegration.navigateToIntegrationCspList(); - await pageObjects.header.waitUntilLoadingHasFinished(); - - expect(await cisIntegration.getFirstCspmIntegrationPageIntegration()).to.be( - integrationPolicyName - ); - expect(await cisIntegration.getFirstCspmIntegrationPageAgent()).to.be( - `Agentless policy for ${integrationPolicyName}` - ); - }); - - it(`should create default agent-based agent`, async () => { - const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; - - await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( - CLOUD_CREDENTIALS_PACKAGE_VERSION - ); - - await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); - await cisIntegration.clickOptionButton(AWS_SINGLE_ACCOUNT_TEST_ID); - - await cisIntegration.inputIntegrationName(integrationPolicyName); - - await cisIntegration.clickSaveButton(); - await pageObjects.header.waitUntilLoadingHasFinished(); - - expect(await cisIntegrationAws.showPostInstallCloudFormationModal()).to.be(true); - - const agentPolicyName = await cisIntegration.getAgentBasedPolicyValue(); - - await cisIntegration.navigateToIntegrationCspList(); - await pageObjects.header.waitUntilLoadingHasFinished(); - - expect(await cisIntegration.getFirstCspmIntegrationPageIntegration()).to.be( - integrationPolicyName - ); - expect(await cisIntegration.getFirstCspmIntegrationPageAgent()).to.be(agentPolicyName); - }); - }); -} diff --git a/x-pack/test/cloud_security_posture_functional/cloud_tests/index.ts b/x-pack/test/cloud_security_posture_functional/cloud_tests/index.ts index 80afb0456332..37e74d1d6ede 100644 --- a/x-pack/test/cloud_security_posture_functional/cloud_tests/index.ts +++ b/x-pack/test/cloud_security_posture_functional/cloud_tests/index.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile }: FtrProviderContext) { describe('Cloud Security Posture', function () { + loadTestFile(require.resolve('./agentless_api_sanity')); loadTestFile(require.resolve('./dashboard_sanity')); loadTestFile(require.resolve('./benchmark_sanity')); loadTestFile(require.resolve('./findings_sanity')); diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts index 563507d70558..fe95c0887711 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts @@ -218,6 +218,10 @@ export function AddCisIntegrationFormPageProvider({ await testSubjects.click('integrationNameLink'); }; + const navigateToEditAgentlessIntegrationPage = async () => { + await testSubjects.click('agentlessIntegrationNameLink'); + }; + const navigateToAddIntegrationKspmPage = async (space?: string) => { const options = space ? { @@ -485,7 +489,7 @@ export function AddCisIntegrationFormPageProvider({ await navigateToIntegrationCspList(); await PageObjects.header.waitUntilLoadingHasFinished(); - await navigateToEditIntegrationPage(); + await navigateToEditAgentlessIntegrationPage(); await PageObjects.header.waitUntilLoadingHasFinished(); // Fill out form to edit an agentless integration @@ -498,7 +502,7 @@ export function AddCisIntegrationFormPageProvider({ // Check if the Direct Access Key is updated package policy api with successful toast expect(await testSubjects.exists('policyUpdateSuccessToast')).to.be(true); - await navigateToEditIntegrationPage(); + await navigateToEditAgentlessIntegrationPage(); await PageObjects.header.waitUntilLoadingHasFinished(); }; @@ -511,12 +515,23 @@ export function AddCisIntegrationFormPageProvider({ return await integration.getVisibleText(); }; + const getFirstCspmIntegrationPageAgentlessIntegration = async () => { + const integration = await testSubjects.find('agentlessIntegrationNameLink'); + return await integration.getVisibleText(); + }; + const getFirstCspmIntegrationPageAgent = async () => { const agent = await testSubjects.find('agentPolicyNameLink'); // this is assuming that the agent was just created therefor should be the first element return await agent.getVisibleText(); }; + const getFirstCspmIntegrationPageAgentlessStatus = async () => { + const agent = await testSubjects.find('agentlessStatusBadge'); + // this is assuming that the agent was just created therefor should be the first element + return await agent.getVisibleText(); + }; + const getAgentBasedPolicyValue = async () => { const agentName = await testSubjects.find('createAgentPolicyNameField'); return await agentName.getAttribute('value'); @@ -568,10 +583,13 @@ export function AddCisIntegrationFormPageProvider({ testSubjectIds, inputIntegrationName, getFirstCspmIntegrationPageIntegration, + getFirstCspmIntegrationPageAgentlessIntegration, getFirstCspmIntegrationPageAgent, + getFirstCspmIntegrationPageAgentlessStatus, getAgentBasedPolicyValue, showSuccessfulToast, showSetupTechnologyComponent, navigateToEditIntegrationPage, + navigateToEditAgentlessIntegrationPage, }; } diff --git a/x-pack/test/fleet_api_integration/apis/agents/status.ts b/x-pack/test/fleet_api_integration/apis/agents/status.ts index 42b60a0624a9..4b56601078da 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/status.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/status.ts @@ -334,5 +334,31 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .expect(400); }); + + it('should return incoming data status for specified agents', async () => { + // force install the system package to override package verification + await supertest + .post(`/api/fleet/epm/packages/system/1.50.0`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + + const { body: apiResponse1 } = await supertest + .get(`/api/fleet/agent_status/data?agentsIds=agent1&agentsIds=agent2`) + .expect(200); + const { body: apiResponse2 } = await supertest + .get( + `/api/fleet/agent_status/data?agentsIds=agent1&agentsIds=agent2&pkgName=system&pkgVersion=1.50.0` + ) + .expect(200); + expect(apiResponse1).to.eql({ + items: [{ agent1: { data: false } }, { agent2: { data: false } }], + dataPreview: [], + }); + expect(apiResponse2).to.eql({ + items: [{ agent1: { data: false } }, { agent2: { data: false } }], + dataPreview: [], + }); + }); }); } diff --git a/x-pack/test/functional/apps/api_keys/home_page.ts b/x-pack/test/functional/apps/api_keys/home_page.ts index 2c212c11166f..a12973bac24b 100644 --- a/x-pack/test/functional/apps/api_keys/home_page.ts +++ b/x-pack/test/functional/apps/api_keys/home_page.ts @@ -419,5 +419,134 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); }); }); + + describe('querying API keys', function () { + before(async () => { + await clearAllApiKeys(es, log); + await security.testUser.setRoles(['kibana_admin', 'test_api_keys']); + + await es.transport.request({ + method: 'POST', + path: '/_security/cross_cluster/api_key', + body: { + name: 'test_cross_cluster', + expiration: '1d', + access: { + search: [ + { + names: ['*'], + }, + ], + replication: [ + { + names: ['*'], + }, + ], + }, + }, + }); + + await es.security.createApiKey({ + name: 'my api key', + expiration: '1d', + role_descriptors: { + role_1: {}, + }, + metadata: { + managed: true, + }, + }); + + await es.security.createApiKey({ + name: 'Alerting: Managed', + expiration: '1d', + role_descriptors: { + role_1: {}, + }, + }); + + await es.security.createApiKey({ + name: 'test_api_key', + expiration: '1s', + role_descriptors: { + role_1: {}, + }, + }); + + await es.security.grantApiKey({ + api_key: { + name: 'test_user_api_key', + expiration: '1d', + }, + grant_type: 'password', + run_as: 'test_user', + username: 'elastic', + password: 'changeme', + }); + + await pageObjects.common.navigateToApp('apiKeys'); + }); + + after(async () => { + await security.testUser.restoreDefaults(); + await clearAllApiKeys(es, log); + }); + + it('active/expired filter buttons work as expected', async () => { + await pageObjects.apiKeys.clickExpiryFilters('active'); + await ensureApiKeysExist(['my api key', 'Alerting: Managed', 'test_cross_cluster']); + expect(await pageObjects.apiKeys.doesApiKeyExist('test_api_key')).to.be(false); + + await pageObjects.apiKeys.clickExpiryFilters('expired'); + await ensureApiKeysExist(['test_api_key']); + expect(await pageObjects.apiKeys.doesApiKeyExist('my api key')).to.be(false); + + // reset filter buttons + await pageObjects.apiKeys.clickExpiryFilters('expired'); + }); + + it('api key type filter buttons work as expected', async () => { + await pageObjects.apiKeys.clickTypeFilters('personal'); + + await ensureApiKeysExist(['test_api_key']); + + await pageObjects.apiKeys.clickTypeFilters('cross_cluster'); + + await ensureApiKeysExist(['test_cross_cluster']); + + await pageObjects.apiKeys.clickTypeFilters('managed'); + + await ensureApiKeysExist(['my api key', 'Alerting: Managed']); + + // reset filters by simulate clicking the managed filter button again + await pageObjects.apiKeys.clickTypeFilters('managed'); + }); + + it('username filter buttons work as expected', async () => { + await pageObjects.apiKeys.clickUserNameDropdown(); + expect( + await testSubjects.exists('userProfileSelectableOption-system_indices_superuser') + ).to.be(true); + expect(await testSubjects.exists('userProfileSelectableOption-test_user')).to.be(true); + + await testSubjects.click('userProfileSelectableOption-test_user'); + + await ensureApiKeysExist(['test_user_api_key']); + await testSubjects.click('userProfileSelectableOption-test_user'); + + await testSubjects.click('userProfileSelectableOption-system_indices_superuser'); + + await ensureApiKeysExist(['my api key', 'Alerting: Managed', 'test_cross_cluster']); + }); + + it.skip('search bar works as expected', async () => { + await pageObjects.apiKeys.setSearchBarValue('test_user_api_key'); + + await ensureApiKeysExist(['test_user_api_key']); + + await pageObjects.apiKeys.setSearchBarValue('"my api key"'); + await ensureApiKeysExist(['my api key']); + }); + }); }); }; diff --git a/x-pack/test/functional/apps/canvas/datasource.ts b/x-pack/test/functional/apps/canvas/datasource.ts index 480510ede4e7..78010fd66f4a 100644 --- a/x-pack/test/functional/apps/canvas/datasource.ts +++ b/x-pack/test/functional/apps/canvas/datasource.ts @@ -29,6 +29,10 @@ export default function canvasExpressionTest({ getService, getPageObjects }: Ftr await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern.json' ); + // canvas application is only available when installation contains canvas workpads + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default' + ); await kibanaServer.uiSettings.update({ defaultIndex: 'kibana_sample_data_flights', @@ -46,6 +50,9 @@ export default function canvasExpressionTest({ getService, getPageObjects }: Ftr await kibanaServer.importExport.unload( 'test/functional/fixtures/kbn_archiver/kibana_sample_data_flights_index_pattern.json' ); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default' + ); }); describe('esdocs', function () { diff --git a/x-pack/test/functional/apps/canvas/embeddables/maps.ts b/x-pack/test/functional/apps/canvas/embeddables/maps.ts index ac6a861e9796..bd3b984e91a6 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/maps.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/maps.ts @@ -18,6 +18,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('maps in canvas', function () { before(async () => { await kibanaServer.savedObjects.cleanStandardList(); + // canvas application is only available when installation contains canvas workpads + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default' + ); // open canvas home await canvas.goToListingPage(); // create new workpad @@ -25,6 +29,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await canvas.setWorkpadName('maps tests'); }); + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + describe('by-value', () => { it('creates new map embeddable', async () => { const originalEmbeddableCount = await canvas.getEmbeddableCount(); diff --git a/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts b/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts index f89af8b6a15c..d6fd2fefbaf2 100644 --- a/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts +++ b/x-pack/test/functional/apps/canvas/embeddables/saved_search.ts @@ -20,6 +20,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/dashboard/current/kibana' ); + // canvas application is only available when installation contains canvas workpads + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default' + ); // open canvas home await canvas.goToListingPage(); // create new workpad diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts index 8922bca6d7fd..995d26d5efc9 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/time_to_visualize_security.ts @@ -101,7 +101,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('edits to a by value lens panel are properly applied', async () => { await dashboard.waitForRenderComplete(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await lens.switchToVisualization('pie'); await lens.saveAndReturn(); await dashboard.waitForRenderComplete(); @@ -112,7 +112,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('disables save to library button without visualize save permissions', async () => { await dashboard.waitForRenderComplete(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); const saveButton = await testSubjects.find('lnsApp_saveButton'); expect(await saveButton.getAttribute('disabled')).to.equal('true'); await lens.saveAndReturn(); diff --git a/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts b/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts index a974eb8c1284..804790e7ee06 100644 --- a/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts +++ b/x-pack/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts @@ -47,7 +47,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('edits to a by value lens panel are properly applied', async () => { await dashboard.waitForRenderComplete(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await lens.switchToVisualization('pie'); await lens.saveAndReturn(); await dashboard.waitForRenderComplete(); @@ -59,7 +59,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('editing and saving a lens by value panel retains number of panels', async () => { const originalPanelCount = await dashboard.getPanelCount(); await dashboard.waitForRenderComplete(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await lens.switchToVisualization('treemap'); await lens.saveAndReturn(); await dashboard.waitForRenderComplete(); @@ -71,7 +71,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const newTitle = 'look out library, here I come!'; const originalPanelCount = await dashboard.getPanelCount(); await dashboard.waitForRenderComplete(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await lens.save(newTitle, false, true); await dashboard.waitForRenderComplete(); const newPanelCount = await dashboard.getPanelCount(); diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts index 81fade0255cf..df5860fd20a8 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts @@ -69,7 +69,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // All panels should be editable. This will catch cases where an error does not create an error embeddable. const panelTitles = await dashboard.getPanelTitles(); for (const title of panelTitles) { - await dashboardPanelActions.expectExistsEditPanelAction(title, true); + await dashboardPanelActions.expectExistsEditPanelAction(title); } }); diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts index d5070b931b18..78b34f1d5593 100644 --- a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts +++ b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts @@ -58,7 +58,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('by reference', () => { it('can add a custom time range to panel', async () => { - await dashboardPanelActions.legacySaveToLibrary('My by reference visualization'); + await dashboardPanelActions.saveToLibrary('My by reference visualization'); await dashboardPanelActions.customizePanel(); await dashboardCustomizePanel.enableCustomTimeRange(); await dashboardCustomizePanel.openDatePickerQuickMenu(); diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts b/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts index 7d8456a9e81a..19109ef3b76e 100644 --- a/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts +++ b/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts @@ -92,7 +92,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardPanelActions.customizePanel(); await dashboardCustomizePanel.setCustomPanelTitle('Custom title'); await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.legacySaveToLibrary(getVisTitle(true)); + await dashboardPanelActions.saveToLibrary(getVisTitle(true)); await retry.tryForTime(500, async () => { // need to surround in 'retry' due to delays in HTML updates causing the title read to be behind const [newPanelTitle] = await dashboard.getPanelTitles(); @@ -113,7 +113,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('resetting description on a by reference panel sets it to the library title', async () => { await dashboardPanelActions.navigateToEditorFromFlyout(); - // legacySaveToLibrary UI cannot set description await lens.save( getVisTitle(true), false, @@ -142,7 +141,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardPanelActions.customizePanel(); await dashboardCustomizePanel.setCustomPanelTitle('Custom title'); await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.legacyUnlinkFromLibrary('Custom title'); + await dashboardPanelActions.unlinkFromLibrary('Custom title'); const [newPanelTitle] = await dashboard.getPanelTitles(); expect(newPanelTitle).to.equal('Custom title'); }); @@ -151,7 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardPanelActions.customizePanel(); await dashboardCustomizePanel.setCustomPanelTitle(''); await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.legacySaveToLibrary(getVisTitle(true)); + await dashboardPanelActions.saveToLibrary(getVisTitle(true)); await retry.tryForTime(500, async () => { // need to surround in 'retry' due to delays in HTML updates causing the title read to be behind const [newPanelTitle] = await dashboard.getPanelTitles(); @@ -160,7 +159,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('unlinking a by reference panel without a custom title will keep the library title', async () => { - await dashboardPanelActions.legacyUnlinkFromLibrary(getVisTitle()); + await dashboardPanelActions.unlinkFromLibrary(getVisTitle()); const [newPanelTitle] = await dashboard.getPanelTitles(); expect(newPanelTitle).to.equal(getVisTitle()); }); diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index 0ee6a996652b..7a9a5e3b1a8c 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -51,17 +51,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('discover field visualize button', () => { before(async () => { await kibanaServer.uiSettings.replace(defaultSettings); - }); - beforeEach(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' ); + }); + + beforeEach(async () => { await common.navigateToApp('discover'); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); await setDiscoverTimeRange(); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); }); after(async () => { + await timePicker.resetDefaultAbsoluteRangeViaUiSettings(); await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.unload( 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' @@ -73,7 +79,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await unifiedFieldList.expectFieldListItemVisualize('bytes'); }); - it('visualizes field to Lens and loads fields to the dimesion editor', async () => { + it('visualizes field to Lens and loads fields to the dimension editor', async () => { await unifiedFieldList.findFieldByName('bytes'); await unifiedFieldList.clickFieldListItemVisualize('bytes'); await header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/index_management/index_template_wizard.ts b/x-pack/test/functional/apps/index_management/index_template_wizard.ts index d932b96d4f6a..8103dfc0776d 100644 --- a/x-pack/test/functional/apps/index_management/index_template_wizard.ts +++ b/x-pack/test/functional/apps/index_management/index_template_wizard.ts @@ -70,7 +70,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // Verify that index mode callout is displayed const indexModeCalloutText = await testSubjects.getVisibleText('indexModeCallout'); expect(indexModeCalloutText).to.be( - 'The index.mode setting has been set to Standard within template Logistics. Any changes to index.mode set on this page will be overwritten by the Logistics selection.' + 'The index.mode setting has been set to Standard within the Logistics step. Any changes to index.mode set on this page will be overwritten by the Logistics selection.' ); // Click Next button diff --git a/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts b/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts index 433fc2dbc943..acf383fb946f 100644 --- a/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts +++ b/x-pack/test/functional/apps/lens/group3/add_to_dashboard.ts @@ -67,7 +67,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await lens.assertLegacyMetric('Average of bytes', '5,727.322'); - await dashboardPanelActions.expectNotLinkedToLibrary('New Lens from Modal', true); + await dashboardPanelActions.expectNotLinkedToLibrary('New Lens from Modal'); const panelCount = await dashboard.getPanelCount(); expect(panelCount).to.eql(1); @@ -82,10 +82,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await lens.assertLegacyMetric('Maximum of bytes', '19,986'); - await dashboardPanelActions.expectNotLinkedToLibrary( - 'Artistpreviouslyknownaslens Copy', - true - ); + await dashboardPanelActions.expectNotLinkedToLibrary('Artistpreviouslyknownaslens Copy'); const panelCount = await dashboard.getPanelCount(); expect(panelCount).to.eql(1); @@ -109,7 +106,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await lens.assertLegacyMetric('Average of bytes', '5,727.322'); - await dashboardPanelActions.expectNotLinkedToLibrary('New Lens from Modal', true); + await dashboardPanelActions.expectNotLinkedToLibrary('New Lens from Modal'); const panelCount = await dashboard.getPanelCount(); expect(panelCount).to.eql(2); @@ -131,10 +128,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await lens.assertLegacyMetric('Maximum of bytes', '19,986'); - await dashboardPanelActions.expectNotLinkedToLibrary( - 'Artistpreviouslyknownaslens Copy', - true - ); + await dashboardPanelActions.expectNotLinkedToLibrary('Artistpreviouslyknownaslens Copy'); const panelCount = await dashboard.getPanelCount(); expect(panelCount).to.eql(2); @@ -147,7 +141,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await lens.assertLegacyMetric('Average of bytes', '5,727.322'); - await dashboardPanelActions.expectLinkedToLibrary('New by ref Lens from Modal', true); + await dashboardPanelActions.expectLinkedToLibrary('New by ref Lens from Modal'); const panelCount = await dashboard.getPanelCount(); expect(panelCount).to.eql(1); @@ -162,7 +156,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await lens.assertLegacyMetric('Maximum of bytes', '19,986'); - await dashboardPanelActions.expectLinkedToLibrary('Artistpreviouslyknownaslens by ref', true); + await dashboardPanelActions.expectLinkedToLibrary('Artistpreviouslyknownaslens by ref'); const panelCount = await dashboard.getPanelCount(); expect(panelCount).to.eql(1); @@ -186,7 +180,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await lens.assertLegacyMetric('Average of bytes', '5,727.322'); - await dashboardPanelActions.expectLinkedToLibrary('New Lens by ref from Modal', true); + await dashboardPanelActions.expectLinkedToLibrary('New Lens by ref from Modal'); const panelCount = await dashboard.getPanelCount(); expect(panelCount).to.eql(2); @@ -208,10 +202,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await lens.assertLegacyMetric('Maximum of bytes', '19,986'); - await dashboardPanelActions.expectLinkedToLibrary( - 'Artistpreviouslyknownaslens by ref 2', - true - ); + await dashboardPanelActions.expectLinkedToLibrary('Artistpreviouslyknownaslens by ref 2'); const panelCount = await dashboard.getPanelCount(); expect(panelCount).to.eql(2); diff --git a/x-pack/test/functional/apps/lens/group3/dashboard_inline_editing.ts b/x-pack/test/functional/apps/lens/group3/dashboard_inline_editing.ts index 3790c22c377b..4ff6da617bbd 100644 --- a/x-pack/test/functional/apps/lens/group3/dashboard_inline_editing.ts +++ b/x-pack/test/functional/apps/lens/group3/dashboard_inline_editing.ts @@ -85,7 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.waitForRenderComplete(); await elasticChart.setNewChartUiDebugFlag(true); - await dashboardPanelActions.legacySaveToLibrary('My by reference visualization'); + await dashboardPanelActions.saveToLibrary('My by reference visualization'); await dashboardPanelActions.clickInlineEdit(); @@ -138,6 +138,71 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await timeToVisualize.resetNewDashboard(); }); + it('should reset changes made to the previous chart with adHoc dataView created from dashboard', async () => { + await dashboard.navigateToApp(); + await dashboard.clickNewDashboard(); + + // it creates a XY histogram with a breakdown by ip + await lens.createAndAddLensFromDashboard({ useAdHocDataView: true }); + await elasticChart.setNewChartUiDebugFlag(true); + // now edit inline and remove the breakdown dimension + await dashboardPanelActions.clickInlineEdit(); + await lens.removeDimension('lnsXY_splitDimensionPanel'); + + log.debug('Cancels the changes'); + await testSubjects.click('cancelFlyoutButton'); + await dashboard.waitForRenderComplete(); + + const data = await lens.getCurrentChartDebugStateForVizType('xyVisChart'); + expect(data?.bars?.length).to.be.above(1); + // open the inline editor again and check that the breakdown is still there + await dashboardPanelActions.clickInlineEdit(); + expect(await testSubjects.exists('lnsXY_splitDimensionPanel')).to.be(true); + // exit via cancel again + await testSubjects.click('cancelFlyoutButton'); + }); + + it('should reset changes made to the previous chart created from dashboard', async () => { + await dashboardPanelActions.removePanel(); + + // it creates a XY histogram with a breakdown by ip + await lens.createAndAddLensFromDashboard({}); + + await dashboard.waitForRenderComplete(); + await elasticChart.setNewChartUiDebugFlag(true); + // now edit inline and remove the breakdown dimension + await dashboardPanelActions.clickInlineEdit(); + await lens.removeDimension('lnsXY_splitDimensionPanel'); + + log.debug('Cancels the changes'); + await testSubjects.click('cancelFlyoutButton'); + await dashboard.waitForRenderComplete(); + + const data = await lens.getCurrentChartDebugStateForVizType('xyVisChart'); + expect(data?.bars?.length).to.be.above(1); + // open the inline editor again and check that the breakdown is still there + await dashboardPanelActions.clickInlineEdit(); + expect(await testSubjects.exists('lnsXY_splitDimensionPanel')).to.be(true); + // exit via cancel again + await testSubjects.click('cancelFlyoutButton'); + }); + + it('should apply changes made in the inline editing panel', async () => { + // now delete the breakdown dimension and check that has been saved + await dashboardPanelActions.clickInlineEdit(); + await lens.removeDimension('lnsXY_splitDimensionPanel'); + + log.debug('Applies the changes'); + await testSubjects.click('applyFlyoutButton'); + await dashboard.waitForRenderComplete(); + + const data = await lens.getCurrentChartDebugStateForVizType('xyVisChart'); + expect(data?.bars?.length).to.eql(1); + // reset all things + await elasticChart.setNewChartUiDebugFlag(false); + await timeToVisualize.resetNewDashboard(); + }); + it('should allow adding an annotation', async () => { await loadExistingLens(); await lens.save('xyVisChart Copy', true, false, false, 'new'); diff --git a/x-pack/test/functional/apps/lens/group4/dashboard.ts b/x-pack/test/functional/apps/lens/group4/dashboard.ts index 670ee2cb22da..0efdd026e05f 100644 --- a/x-pack/test/functional/apps/lens/group4/dashboard.ts +++ b/x-pack/test/functional/apps/lens/group4/dashboard.ts @@ -27,6 +27,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const panelActions = getService('dashboardPanelActions'); const inspector = getService('inspector'); const queryBar = getService('queryBar'); + const dashboardDrilldownsManage = getService('dashboardDrilldownsManage'); + const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions'); async function clickInChart(x: number, y: number) { const el = await elasticChart.getCanvas(); @@ -228,11 +230,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await find.clickByButtonText('lnsPieVis'); await dashboardAddPanel.closeAddPanel(); - await panelActions.legacyUnlinkFromLibrary('lnsPieVis'); + await panelActions.unlinkFromLibrary('lnsPieVis'); }); it('save lens panel to embeddable library', async () => { - await panelActions.legacySaveToLibrary('lnsPieVis - copy', 'lnsPieVis'); + await panelActions.saveToLibrary('lnsPieVis - copy', 'lnsPieVis'); await dashboardAddPanel.clickOpenAddPanel(); await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); @@ -320,5 +322,40 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.switchToWindow(windowHandlers[0]); } }); + + it('should add a drilldown to a Lens by-value chart', async () => { + await dashboard.navigateToApp(); + await dashboard.clickNewDashboard(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); + await find.clickByButtonText('lnsPieVis'); + await dashboardAddPanel.closeAddPanel(); + + // add a drilldown to the pie chart + await dashboardDrilldownPanelActions.clickCreateDrilldown(); + await testSubjects.click('actionFactoryItem-OPEN_IN_DISCOVER_DRILLDOWN'); + await dashboardDrilldownsManage.saveChanges(); + await dashboardDrilldownsManage.closeFlyout(); + await header.waitUntilLoadingHasFinished(); + + // check that the drilldown is working now + await clickInChart(5, 5); // hardcoded position of the slice, depends heavy on data and charts implementation + expect( + await find.existsByCssSelector('[data-test-subj^="embeddablePanelAction-D_ACTION"]') + ).to.be(true); + + // save the dashboard + await dashboard.saveDashboard('dashboardWithDrilldown'); + + // re-open the dashboard and check the drilldown is still there + await dashboard.navigateToApp(); + await dashboard.loadSavedDashboard('dashboardWithDrilldown'); + await header.waitUntilLoadingHasFinished(); + + await clickInChart(5, 5); // hardcoded position of the slice, depends heavy on data and charts implementation + expect( + await find.existsByCssSelector('[data-test-subj^="embeddablePanelAction-D_ACTION"]') + ).to.be(true); + }); }); } diff --git a/x-pack/test/functional/apps/lens/group4/show_underlying_data_dashboard.ts b/x-pack/test/functional/apps/lens/group4/show_underlying_data_dashboard.ts index 40169ef15ccf..4227835b2227 100644 --- a/x-pack/test/functional/apps/lens/group4/show_underlying_data_dashboard.ts +++ b/x-pack/test/functional/apps/lens/group4/show_underlying_data_dashboard.ts @@ -23,6 +23,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const listingTable = getService('listingTable'); const testSubjects = getService('testSubjects'); const dashboardPanelActions = getService('dashboardPanelActions'); + const monacoEditor = getService('monacoEditor'); + const dashboardAddPanel = getService('dashboardAddPanel'); const filterBarService = getService('filterBar'); const queryBar = getService('queryBar'); const savedQueryManagementComponent = getService('savedQueryManagementComponent'); @@ -58,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show the open button for a compatible saved visualization with annotations and reference line', async () => { await dashboard.switchToEditMode(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await header.waitUntilLoadingHasFinished(); await lens.createLayer('annotations'); await lens.waitForVisualization('xyVisChart'); @@ -88,7 +90,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should bring both dashboard context and visualization context to discover', async () => { await dashboard.switchToEditMode(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await savedQueryManagementComponent.openSavedQueryManagementComponent(); await queryBar.switchQueryLanguage('lucene'); await savedQueryManagementComponent.closeSavedQueryManagementComponent(); @@ -139,5 +141,53 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.closeCurrentWindow(); await browser.switchToWindow(dashboardWindowHandle); }); + + it.skip('should bring visualization context to discover for Lens ES|QL panels', async () => { + // clear out the dashboard + await dashboard.switchToEditMode(); + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.removePanel(); + await queryBar.setQuery(''); + await queryBar.submitQuery(); + await filterBarService.removeAllFilters(); + + // Create a new panel + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAddNewPanelFromUIActionLink('ES|QL'); + await dashboardAddPanel.expectEditorMenuClosed(); + + const ESQL_QUERY = 'from logs* | stats maxB = max(bytes)'; + // Configure the ES|QL chart + await monacoEditor.setCodeEditorValue(ESQL_QUERY); + await testSubjects.click('ESQLEditor-run-query-button'); + await header.waitUntilLoadingHasFinished(); + + const lensQuery = await monacoEditor.getCodeEditorValue(); + expect(lensQuery).to.equal(ESQL_QUERY); + await testSubjects.click('applyFlyoutButton'); + + // Save the dashboard + await dashboard.clickQuickSave(); + await dashboard.clickCancelOutOfEditMode(); + + // check if it works correctly + await dashboardPanelActions.clickPanelAction(OPEN_IN_DISCOVER_DATA_TEST_SUBJ); + + const [dashboardWindowHandle, discoverWindowHandle] = await browser.getAllWindowHandles(); + await browser.switchToWindow(discoverWindowHandle); + + // wait to discover to load + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + + // now check that all queries and filters are correctly transferred + const discoverQuery = await monacoEditor.getCodeEditorValue(); + expect(discoverQuery).to.equal(ESQL_QUERY); + // Filters and queries should not be carried over. + // There's currently a bug but in this test will check only the right thing + + await browser.closeCurrentWindow(); + await browser.switchToWindow(dashboardWindowHandle); + }); }); } diff --git a/x-pack/test/functional/apps/lens/group6/error_handling.ts b/x-pack/test/functional/apps/lens/group6/error_handling.ts index 1b035fab6397..9ac57287feb0 100644 --- a/x-pack/test/functional/apps/lens/group6/error_handling.ts +++ b/x-pack/test/functional/apps/lens/group6/error_handling.ts @@ -108,7 +108,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.find('emptyPlaceholder'); await dashboard.switchToEditMode(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await timePicker.waitForNoDataPopover(); await timePicker.ensureHiddenNoDataPopover(); diff --git a/x-pack/test/functional/apps/lens/group6/lens_tagging.ts b/x-pack/test/functional/apps/lens/group6/lens_tagging.ts index b6b441249f21..bb39217bd886 100644 --- a/x-pack/test/functional/apps/lens/group6/lens_tagging.ts +++ b/x-pack/test/functional/apps/lens/group6/lens_tagging.ts @@ -90,7 +90,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('retains its saved object tags after save and return', async () => { - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await lens.saveAndReturn(); await header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts index bf799673c249..966102852f63 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/dashboard.ts @@ -117,7 +117,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const titles = await dashboard.getPanelTitles(); expect(titles[0]).to.be(`${visTitle} (converted)`); - await panelActions.expectNotLinkedToLibrary(titles[0], true); + await panelActions.expectNotLinkedToLibrary(titles[0]); await dashboardBadgeActions.expectExistsTimeRangeBadgeAction(); await panelActions.removePanel(); }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/aggregated_scripted_job.ts b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/aggregated_scripted_job.ts index d2171544aa99..0d27f9afe153 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/aggregated_scripted_job.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/aggregated_scripted_job.ts @@ -59,7 +59,6 @@ export default function ({ getService }: FtrProviderContext) { expand_wildcards: ['open'], ignore_unavailable: false, allow_no_indices: true, - ignore_throttled: true, }, query: { match_all: {}, @@ -140,7 +139,6 @@ export default function ({ getService }: FtrProviderContext) { expand_wildcards: ['open'], ignore_unavailable: false, allow_no_indices: true, - ignore_throttled: true, }, query: { bool: { @@ -217,7 +215,6 @@ export default function ({ getService }: FtrProviderContext) { expand_wildcards: ['open'], ignore_unavailable: false, allow_no_indices: true, - ignore_throttled: true, }, query: { match_all: {}, @@ -317,7 +314,6 @@ export default function ({ getService }: FtrProviderContext) { expand_wildcards: ['open'], ignore_unavailable: false, allow_no_indices: true, - ignore_throttled: true, }, query: { bool: { diff --git a/x-pack/test/functional/apps/spaces/enter_space.ts b/x-pack/test/functional/apps/spaces/enter_space.ts index 4cff39e05b41..498b636cdce5 100644 --- a/x-pack/test/functional/apps/spaces/enter_space.ts +++ b/x-pack/test/functional/apps/spaces/enter_space.ts @@ -20,6 +20,10 @@ export default function enterSpaceFunctionalTests({ describe('Enter Space', function () { this.tags('includeFirefox'); before(async () => { + // canvas application is only available when installation contains canvas workpads + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default' + ); await spacesService.create({ id: 'another-space', name: 'Another Space', @@ -45,6 +49,9 @@ export default function enterSpaceFunctionalTests({ await PageObjects.security.forceLogout(); }); after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default' + ); await spacesService.delete('another-space'); await kibanaServer.savedObjects.cleanStandardList(); }); diff --git a/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts index dbc62293f035..408d4686252c 100644 --- a/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts @@ -302,6 +302,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { identifier: 'percentiles(products.base_price)', label: 'products.base_price.percentiles', + form: { + transformPercentilesAggPercentsSelector: [1, 25, 50, 75, 100], + }, }, { identifier: 'filter(customer_phone)', diff --git a/x-pack/test/functional/page_objects/api_keys_page.ts b/x-pack/test/functional/page_objects/api_keys_page.ts index 9b196f70eeef..efff9930f10a 100644 --- a/x-pack/test/functional/page_objects/api_keys_page.ts +++ b/x-pack/test/functional/page_objects/api_keys_page.ts @@ -157,5 +157,34 @@ export function ApiKeysPageProvider({ getService }: FtrProviderContext) { const toast = await testSubjects.find('updateApiKeySuccessToast'); return toast.getVisibleText(); }, + + async clickExpiryFilters(type: 'active' | 'expired') { + const button = await testSubjects.find( + type === 'active' ? 'activeFilterButton' : 'expiredFilterButton' + ); + return button.click(); + }, + + async clickTypeFilters(type: 'personal' | 'managed' | 'cross_cluster') { + const buttonMap = { + personal: 'personalFilterButton', + managed: 'managedFilterButton', + cross_cluster: 'crossClusterFilterButton', + }; + + const button = await testSubjects.find(buttonMap[type]); + return button.click(); + }, + + async clickUserNameDropdown() { + const button = await testSubjects.find('ownerFilterButton'); + return button.click(); + }, + + async setSearchBarValue(query: string) { + const searchBar = await testSubjects.find('apiKeysSearchBar'); + await searchBar.clearValue(); + return searchBar.type(query); + }, }; } diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts index e5a260429467..e0e2a555540b 100644 --- a/x-pack/test/functional/page_objects/index_management_page.ts +++ b/x-pack/test/functional/page_objects/index_management_page.ts @@ -221,14 +221,33 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) }, async expectIndexIsDeleted(indexName: string) { - const table = await find.byCssSelector('table'); - const rows = await table.findAllByTestSubject('indexTableRow'); - const indexNames: string[] = await Promise.all( - rows.map(async (row) => { - return await (await row.findByTestSubject('indexTableIndexNameLink')).getVisibleText(); - }) - ); - expect(indexNames.includes(indexName)).to.be(false); + try { + const table = await find.byCssSelector('table'); + const rows = await table.findAllByTestSubject('indexTableRow'); + + const indexNames = await Promise.all( + rows.map(async (row) => { + try { + return await ( + await row.findByTestSubject('indexTableIndexNameLink') + ).getVisibleText(); + } catch (error) { + // If the current row is stale, it has already been removed + if (error.name === 'StaleElementReferenceError') return undefined; + throw error; // Rethrow unexpected errors + } + }) + ).then((names) => names.filter((name) => name !== undefined)); + + expect(indexNames.includes(indexName)).to.be(false); + } catch (error) { + if (error.name === 'StaleElementReferenceError') { + // If the table itself is stale, it means all rows have been removed + return; // Pass the test since the table is gone + } else { + throw error; // Rethrow unexpected errors + } + } }, async manageIndex(indexName: string) { const id = `checkboxSelectIndex-${indexName}`; diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index f4db890b2695..47eacb260498 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -34,6 +34,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont const browser = getService('browser'); const dashboardAddPanel = getService('dashboardAddPanel'); const queryBar = getService('queryBar'); + const dataViews = getService('dataViews'); const { common, header, timePicker, dashboard, timeToVisualize, unifiedSearch, share } = getPageObjects([ @@ -1486,10 +1487,12 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont title, redirectToOrigin, ignoreTimeFilter, + useAdHocDataView, }: { title?: string; redirectToOrigin?: boolean; ignoreTimeFilter?: boolean; + useAdHocDataView?: boolean; }) { log.debug(`createAndAddLens${title}`); const inViewMode = await dashboard.getIsInViewMode(); @@ -1502,6 +1505,10 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await this.goToTimeRange(); } + if (useAdHocDataView) { + await dataViews.createFromSearchBar({ name: '*stash*', adHoc: true }); + } + await this.configureDimension({ dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', operation: 'average', @@ -2044,5 +2051,9 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont return { maxWidth, maxHeight, minWidth, minHeight, aspectRatio }; }, + + async toggleDebug(enable: boolean = true) { + await browser.execute(`window.ELASTIC_LENS_LOGGER = arguments[0];`, enable); + }, }); } diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index 0a3d988fd750..3d2d1004528d 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -85,7 +85,6 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { const retry = getService('retry'); const esSupertest = getService('esSupertest'); const kbnSupertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); return { assertResponseStatusCode(expectedStatus: number, actualStatus: number, responseBody: object) { @@ -310,8 +309,37 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> Indices deleted.'); }, + async deleteExpiredAnomalyDetectionData() { + log.debug('Deleting expired data ...'); + const { body, status } = await esSupertest.delete('/_ml/_delete_expired_data'); + this.assertResponseStatusCode(200, status, body); + log.debug('> Expired data deleted.'); + }, + + async cleanAnomalyDetection() { + await this.deleteAllAnomalyDetectionJobs(); + await this.deleteAllCalendars(); + await this.deleteAllFilters(); + await this.deleteAllAnnotations(); + await this.deleteExpiredAnomalyDetectionData(); + await this.syncSavedObjects(); + }, + + async cleanDataFrameAnalytics() { + await this.deleteAllDataFrameAnalyticsJobs(); + await this.syncSavedObjects(); + }, + + async cleanTrainedModels() { + await this.deleteAllTrainedModelsIngestPipelines(); + await this.deleteAllTrainedModelsES(); + await this.syncSavedObjects(); + }, + async cleanMlIndices() { - await esDeleteAllIndices('.ml-*'); + await this.cleanAnomalyDetection(); + await this.cleanDataFrameAnalytics(); + await this.cleanTrainedModels(); }, async getJobState(jobId: string): Promise<JOB_STATE> { @@ -537,6 +565,12 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return response; }, + async getAllCalendars(expectedCode = 200) { + const response = await esSupertest.get('/_ml/calendars/_all'); + this.assertResponseStatusCode(expectedCode, response.status, response.body); + return response; + }, + async createCalendar( calendarId: string, requestBody: Partial<Calendar> = { description: '', job_ids: [] } @@ -559,6 +593,14 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> Calendar deleted.'); }, + async deleteAllCalendars() { + log.debug('Deleting all calendars'); + const getAllCalendarsRsp = await this.getAllCalendars(); + for (const calendar of getAllCalendarsRsp.body.calendars) { + await this.deleteCalendar(calendar.calendar_id); + } + }, + async waitForCalendarToExist(calendarId: string, errorMsg?: string) { await retry.waitForWithTimeout(`'${calendarId}' to exist`, 5 * 1000, async () => { if (await this.getCalendar(calendarId, 200)) { @@ -660,6 +702,12 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return response; }, + async getAllAnomalyDetectionJobs() { + const response = await esSupertest.get('/_ml/anomaly_detectors/_all'); + this.assertResponseStatusCode(200, response.status, response.body); + return response; + }, + async getAnomalyDetectionJobsKibana(jobId?: string, space?: string) { const { body, status } = await kbnSupertest .get( @@ -831,6 +879,14 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> AD job deleted.'); }, + async deleteAllAnomalyDetectionJobs() { + log.debug('Deleting all anomaly detection jobs'); + const getAllAdJobsResp = await this.getAllAnomalyDetectionJobs(); + for (const job of getAllAdJobsResp.body.jobs) { + await this.deleteAnomalyDetectionJobES(job.job_id); + } + }, + async getDatafeed(datafeedId: string) { const response = await esSupertest.get(`/_ml/datafeeds/${datafeedId}`); this.assertResponseStatusCode(200, response.status, response.body); @@ -1034,6 +1090,12 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> DFA job created.'); }, + async getAllDataFrameAnalyticsJobs(expectedCode = 200) { + const response = await esSupertest.get('/_ml/data_frame/analytics/_all'); + this.assertResponseStatusCode(expectedCode, response.status, response.body); + return response; + }, + async createDataFrameAnalyticsJobES(jobConfig: DataFrameAnalyticsConfig) { const { id: analyticsId, ...analyticsConfig } = jobConfig; log.debug(`Creating data frame analytic job with id '${analyticsId}' via ES API...`); @@ -1064,6 +1126,14 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> DFA job deleted.'); }, + async deleteAllDataFrameAnalyticsJobs() { + log.debug('Deleting all data frame analytics jobs'); + const getAllDfaJobsResp = await this.getAllDataFrameAnalyticsJobs(); + for (const job of getAllDfaJobsResp.body.data_frame_analytics) { + await this.deleteDataFrameAnalyticsJobES(job.id); + } + }, + async getADJobRecordCount(jobId: string): Promise<number> { const jobStats = await this.getADJobStats(jobId); @@ -1114,12 +1184,24 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { ); }, + async filterExists(filterId: string): Promise<boolean> { + const { status } = await esSupertest.get(`/_ml/filters/${filterId}`); + if (status !== 200) return false; + return true; + }, + async getFilter(filterId: string, expectedCode = 200) { const response = await esSupertest.get(`/_ml/filters/${filterId}`); this.assertResponseStatusCode(expectedCode, response.status, response.body); return response; }, + async getAllFilters(expectedCode = 200) { + const response = await esSupertest.get(`/_ml/filters`); + this.assertResponseStatusCode(expectedCode, response.status, response.body); + return response; + }, + async createFilter(filterId: string, requestBody: object) { log.debug(`Creating filter with id '${filterId}'...`); const { body, status } = await esSupertest.put(`/_ml/filters/${filterId}`).send(requestBody); @@ -1131,12 +1213,27 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async deleteFilter(filterId: string) { log.debug(`Deleting filter with id '${filterId}'...`); - await esSupertest.delete(`/_ml/filters/${filterId}`); + + if ((await this.filterExists(filterId)) === false) { + log.debug('> Filter does not exist, nothing to delete'); + return; + } + + const { body, status } = await esSupertest.delete(`/_ml/filters/${filterId}`); + this.assertResponseStatusCode(200, status, body); await this.waitForFilterToNotExist(filterId, `expected filter '${filterId}' to be deleted`); log.debug('> Filter deleted.'); }, + async deleteAllFilters() { + log.debug('Deleting all filters'); + const getAllFiltersRsp = await this.getAllFilters(); + for (const filter of getAllFiltersRsp.body.filters) { + await this.deleteFilter(filter.filter_id); + } + }, + async assertModelMemoryLimitForJob(jobId: string, expectedMml: string) { const { body: { @@ -1198,6 +1295,25 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return body.hits.hits; }, + async getAllAnnotations() { + log.debug('Fetching all annotations ...'); + + if ( + (await es.indices.exists({ + index: ML_ANNOTATIONS_INDEX_ALIAS_READ, + allow_no_indices: false, + })) === false + ) { + return []; + } + + const body = await es.search<Annotation>({ index: ML_ANNOTATIONS_INDEX_ALIAS_READ }); + expect(body).to.not.be(undefined); + expect(body).to.have.property('hits'); + log.debug('> All annotations fetched.'); + return body.hits.hits; + }, + async getAnnotationById(annotationId: string): Promise<Annotation | undefined> { log.debug(`Fetching annotation '${annotationId}'...`); @@ -1264,6 +1380,24 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { ); }, + async deleteAnnotation(annotationId: string) { + log.debug(`Deleting annotation with id "${annotationId}"`); + const { body, status } = await kbnSupertest + .delete(`/internal/ml/annotations/delete/${annotationId}`) + .set(getCommonRequestHeader('1')); + this.assertResponseStatusCode(200, status, body); + + log.debug('> Annotation deleted'); + }, + + async deleteAllAnnotations() { + log.debug('Deleting all annotations.'); + const allAnnotations = await this.getAllAnnotations(); + for (const annotation of allAnnotations) { + await this.deleteAnnotation(annotation._id!); + } + }, + async runDFAJob(dfaId: string) { log.debug(`Starting data frame analytics job '${dfaId}'...`); const { body: startResponse, status } = await esSupertest @@ -1647,6 +1781,18 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> Ingest pipeline deleted'); }, + async deleteAllTrainedModelsIngestPipelines() { + log.debug(`Deleting all trained models ingest pipelines`); + const getModelsRsp = await this.getTrainedModelsES(); + for (const model of getModelsRsp.trained_model_configs) { + if (this.isInternalModelId(model.model_id)) { + log.debug(`> Skipping internal ${model.model_id}`); + continue; + } + await this.deleteIngestPipeline(model.model_id); + } + }, + async assureMlStatsIndexExists(timeout: number = 60 * 1000) { const params = { index: '.ml-stats-000001', diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index 7d113c30ffeb..dd5546e65a36 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -605,6 +605,12 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi expectedLabel: string, formData: Record<string, any> ) { + const isPopoverFormVisible = await testSubjects.exists( + `transformAggPopoverForm_${expectedLabel}` + ); + if (!isPopoverFormVisible) { + await this.openPopoverForm(expectedLabel); + } await testSubjects.existOrFail(`transformAggPopoverForm_${expectedLabel}`); for (const [testObj, value] of Object.entries(formData)) { @@ -615,12 +621,19 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi case 'transformFilterTermValueSelector': await this.fillFilterTermValue(value); break; + case 'transformPercentilesAggPercentsSelector': + await this.fillPercentilesAggPercents(value); + break; } } await testSubjects.clickWhenNotDisabled('transformApplyAggChanges'); await testSubjects.missingOrFail(`transformAggPopoverForm_${expectedLabel}`); }, + async openPopoverForm(expectedLabel: string) { + await testSubjects.click(`transformAggregationEntryEditButton_${expectedLabel}`); + }, + async selectFilerAggType(value: string) { await testSubjects.selectValue('transformFilterAggTypeSelector', value); }, @@ -629,6 +642,14 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi await comboBox.set('transformFilterTermValueSelector', value); }, + async fillPercentilesAggPercents(value: number[]) { + await comboBox.clear('transformPercentilesAggPercentsSelector'); + for (const val of value) { + // Cast to string since Percentiles are usually passed as numbers + await comboBox.setCustom('transformPercentilesAggPercentsSelector', val.toString()); + } + }, + async assertAdvancedPivotEditorContent(expectedValue: string[]) { const wrapper = await testSubjects.find('transformAdvancedPivotEditor'); const editor = await wrapper.findByCssSelector('.monaco-editor .view-lines'); diff --git a/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts index 010bae3ba4bb..ab4dc572517c 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts @@ -32,8 +32,6 @@ export default function ({ getService }: FtrProviderContext) { const expectedUploadFileTitle = 'artificial_server_log'; before(async () => { - await ml.api.cleanMlIndices(); - await esArchiver.loadIfNeeded( 'x-pack/test/functional/es_archives/ml/module_sample_ecommerce' ); diff --git a/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts index 96aec074ec55..14a4be1ac8fd 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts @@ -32,8 +32,6 @@ export default function ({ getService }: FtrProviderContext) { const expectedUploadFileTitle = 'artificial_server_log'; before(async () => { - await ml.api.cleanMlIndices(); - await esArchiver.loadIfNeeded( 'x-pack/test/functional/es_archives/ml/module_sample_ecommerce' ); diff --git a/x-pack/test/functional_search/tests/solution_navigation.ts b/x-pack/test/functional_search/tests/solution_navigation.ts index 66bf8369b668..b64367b11675 100644 --- a/x-pack/test/functional_search/tests/solution_navigation.ts +++ b/x-pack/test/functional_search/tests/solution_navigation.ts @@ -14,8 +14,10 @@ export default function searchSolutionNavigation({ const { common, solutionNavigation } = getPageObjects(['common', 'solutionNavigation']); const spaces = getService('spaces'); const browser = getService('browser'); + const kibanaServer = getService('kibanaServer'); - describe('Search Solution Navigation', () => { + // FLAKY: https://github.com/elastic/kibana/issues/201037 + describe.skip('Search Solution Navigation', () => { let cleanUp: () => Promise<unknown>; let spaceCreated: { id: string } = { id: '' }; @@ -28,9 +30,18 @@ export default function searchSolutionNavigation({ // Create a space with the search solution and navigate to its home page ({ cleanUp, space: spaceCreated } = await spaces.create({ solution: 'es' })); await browser.navigateTo(spaces.getRootUrl(spaceCreated.id)); + + // canvas application is only available when installation contains canvas workpads + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default' + ); }); after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/canvas/default' + ); + // Clean up space created await cleanUp(); }); diff --git a/x-pack/test/kibana.jsonc b/x-pack/test/kibana.jsonc index 609e59127085..89a09b07fb7d 100644 --- a/x-pack/test/kibana.jsonc +++ b/x-pack/test/kibana.jsonc @@ -2,5 +2,7 @@ "type": "test-helper", "id": "@kbn/test-suites-xpack", "owner": [], + "group": "platform", + "visibility": "shared", "devOnly": true } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/helpers.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/helpers.ts index 25bbeb183a3b..8965504aafc3 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/helpers.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/helpers.ts @@ -30,7 +30,6 @@ export async function createKnowledgeBaseModel(ml: ReturnType<typeof MachineLear export async function deleteKnowledgeBaseModel(ml: ReturnType<typeof MachineLearningProvider>) { await ml.api.stopTrainedModelDeploymentES(TINY_ELSER.id, true); await ml.api.deleteTrainedModelES(TINY_ELSER.id); - await ml.api.cleanMlIndices(); await ml.testResources.cleanMLSavedObjects(); } diff --git a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts index 96f2ff4b00f7..d466abfd552e 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts @@ -15,8 +15,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const ui = getService('observabilityAIAssistantUI'); const testSubjects = getService('testSubjects'); - describe('ai assistant management privileges', () => { - describe('all privileges', () => { + // Failing: See https://github.com/elastic/kibana/issues/191707 + describe.skip('ai assistant management privileges', () => { + // FLAKY: https://github.com/elastic/kibana/issues/191707 + describe.skip('all privileges', () => { before(async () => { await createAndLoginUserWithCustomRole(getPageObjects, getService, { // we need all these privileges to view and modify Obs AI Assistant settings view diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index a6bf7e7e9d5f..e8e24f53551e 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -143,6 +143,7 @@ export default function ({ getService }: FtrProviderContext) { 'endpoint:metadata-check-transforms-task', 'endpoint:user-artifact-packager', 'entity_store:field_retention:enrichment', + 'fleet:bump_agent_policies', 'fleet:check-deleted-files-task', 'fleet:delete-unenrolled-agents-task', 'fleet:deploy_agent_policies', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts index a7447353e805..60d858206d68 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management_removed_types.ts @@ -47,7 +47,8 @@ export default function ({ getService }: FtrProviderContext) { const UNREGISTERED_TASK_TYPE_ID = 'ce7e1250-3322-11eb-94c1-db6995e83f6b'; const REMOVED_TASK_TYPE_ID = 'be7e1250-3322-11eb-94c1-db6995e83f6a'; - describe('not registered task types', () => { + // FLAKY: https://github.com/elastic/kibana/issues/200154 + describe.skip('not registered task types', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/task_manager_removed_types'); }); diff --git a/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts b/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts index b32eafc8c689..0ce8303a291b 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/dashboard/session_sharing/lens.ts @@ -48,7 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Navigating to lens and back should create a new session const byRefSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await lens.saveAndReturn(); await dashboard.waitForRenderComplete(); const newByRefSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle); @@ -56,12 +56,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(byRefSessionId).not.to.eql(newByRefSessionId); // Convert to by-value - await dashboardPanelActions.legacyUnlinkFromLibrary(lensTitle); + await dashboardPanelActions.unlinkFromLibrary(lensTitle); await dashboard.waitForRenderComplete(); const byValueSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle); // Navigating to lens and back should keep the session - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await lens.saveAndReturn(); await dashboard.waitForRenderComplete(); const newByValueSessionId = await dashboardPanelActions.getSearchSessionIdByTitle(lensTitle); diff --git a/x-pack/test/security_api_integration/packages/helpers/kibana.jsonc b/x-pack/test/security_api_integration/packages/helpers/kibana.jsonc index accbad462016..f85c93d50ca5 100644 --- a/x-pack/test/security_api_integration/packages/helpers/kibana.jsonc +++ b/x-pack/test/security_api_integration/packages/helpers/kibana.jsonc @@ -1,6 +1,10 @@ { "type": "shared-common", "id": "@kbn/security-api-integration-helpers", - "owner": "@elastic/kibana-security", + "owner": [ + "@elastic/kibana-security" + ], + "group": "platform", + "visibility": "private", "devOnly": true -} +} \ No newline at end of file diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.data_source_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.data_source_fields.ts index f3b009a8ade4..b95208856a27 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.data_source_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.data_source_fields.ts @@ -897,11 +897,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(fieldDiffObject.data_source).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered conflict + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); @@ -958,7 +958,7 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // version + tags + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // tags expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.eql_query_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.eql_query_fields.ts index f500f8691485..eef3e4b6b7ce 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.eql_query_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.eql_query_fields.ts @@ -382,11 +382,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(fieldDiffObject.eql_query).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // `version` is considered conflict + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); @@ -451,7 +451,7 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); // version + query - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // version + query + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // query expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.esql_query_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.esql_query_fields.ts index f77f59b16a3e..9561393e8454 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.esql_query_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.esql_query_fields.ts @@ -356,11 +356,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(fieldDiffObject.esql_query).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered conflict + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); @@ -420,7 +420,7 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // version + query + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // query expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.kql_query_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.kql_query_fields.ts index 3afb63a4c797..e8f9d2f48b9e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.kql_query_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.kql_query_fields.ts @@ -1042,11 +1042,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(fieldDiffObject.kql_query).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); // `version` is considered an updated field - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // `version` is considered conflict + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); @@ -1114,7 +1114,7 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // version + query + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // query expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.multi_line_string_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.multi_line_string_fields.ts index d9c20fc28b43..23bfd08f5b52 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.multi_line_string_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.multi_line_string_fields.ts @@ -386,7 +386,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(reviewResponse.rules[0].diff.fields.description).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); }); }); @@ -430,7 +430,7 @@ export default ({ getService }: FtrProviderContext): void => { has_base_version: false, }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.number_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.number_fields.ts index 51ab509d1690..bd059ec137a9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.number_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.number_fields.ts @@ -274,11 +274,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(reviewResponse.rules[0].diff.fields.risk_score).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); @@ -322,7 +322,7 @@ export default ({ getService }: FtrProviderContext): void => { has_base_version: false, }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.rule_type_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.rule_type_fields.ts index 0764bac554bf..3f6784108487 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.rule_type_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.rule_type_fields.ts @@ -298,11 +298,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(reviewResponse.rules[0].diff.fields.type).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered a conflict + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); @@ -348,7 +348,7 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(3); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(3); // type + version + query are all considered conflicts + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // type + query are all considered conflicts expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.scalar_array_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.scalar_array_fields.ts index 503a51f84f81..881e8e612217 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.scalar_array_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.scalar_array_fields.ts @@ -423,11 +423,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(reviewResponse.rules[0].diff.fields.tags).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered conflict + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); @@ -472,7 +472,7 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // version + tags + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // tags expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.single_line_string_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.single_line_string_fields.ts index b887e18813ec..6fe10b9fa601 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.single_line_string_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.single_line_string_fields.ts @@ -277,11 +277,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(reviewResponse.rules[0].diff.fields.name).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered a conflict + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); - expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); }); }); @@ -326,7 +326,7 @@ export default ({ getService }: FtrProviderContext): void => { }); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // name + version are both considered conflicts + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // name is considered as a conflict expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts index 8bad52ae41bd..5d0dcec11d9b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts @@ -14,7 +14,8 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const utils = EntityStoreUtils(getService); - describe('@ess @skipInServerlessMKI Entity Store APIs', () => { + // Failing: See https://github.com/elastic/kibana/issues/200758 + describe.skip('@ess @skipInServerlessMKI Entity Store APIs', () => { const dataView = dataViewRouteHelpersFactory(supertest); before(async () => { @@ -42,6 +43,19 @@ export default ({ getService }: FtrProviderContext) => { }); }); + describe('init error handling', () => { + afterEach(async () => { + await dataView.create('security-solution'); + await utils.cleanEngines(); + }); + + it('should return "error" when the security data view does not exist', async () => { + await dataView.delete('security-solution'); + await utils.initEntityEngineForEntityType('host'); + await utils.waitForEngineStatus('host', 'error'); + }); + }); + describe('enablement', () => { afterEach(async () => { await utils.cleanEngines(); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts index bc5eccd16841..9f5b0a3b79e3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts @@ -224,7 +224,7 @@ export default ({ getService }: FtrProviderContext) => { const createRecords = () => createAssetCriticalityRecords(records, es); - it('@skipInServerless should return the first 10 asset criticality records if no args provided', async () => { + it(' should return the first 10 asset criticality records if no args provided', async () => { await createRecords(); const { body } = await assetCriticalityRoutes.list(); @@ -259,7 +259,7 @@ export default ({ getService }: FtrProviderContext) => { ); }); - it('@skipInServerless should only return 1 asset criticality record if per_page=1', async () => { + it('should only return 1 asset criticality record if per_page=1', async () => { await createRecords(); const { body } = await assetCriticalityRoutes.list({ per_page: 1 }); @@ -273,7 +273,7 @@ export default ({ getService }: FtrProviderContext) => { expect(body.records[0].id_value).to.eql(records[0].id_value); }); - it('@skipInServerless should return the next 10 asset criticality records if page=2', async () => { + it('should return the next 10 asset criticality records if page=2', async () => { await createRecords(); const { body } = await assetCriticalityRoutes.list({ page: 2 }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution.ts index e5e721194d01..f13ce4e4a681 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_scoring_task/task_execution.ts @@ -103,7 +103,7 @@ export default ({ getService }: FtrProviderContext): void => { ); }); - it('@skipInServerlessMKI @skipInServerless starts the latest transform', async () => { + it('@skipInServerlessMKI starts the latest transform', async () => { // Transform states that indicate the transform is running happily const TRANSFORM_STARTED_STATES = ['started', 'indexing']; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/data_view.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/data_view.ts index e94f7b7119dd..716454bfbe16 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/data_view.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/data_view.ts @@ -10,7 +10,16 @@ export const dataViewRouteHelpersFactory = ( supertest: SuperTest.Agent, namespace: string = 'default' ) => ({ - create: (name: string) => { + create: async (name: string) => { + const { body: existingDataView, statusCode } = await supertest.get( + `/s/${namespace}/api/data_views/data_view/${name}-${namespace}` + ); + + if (statusCode === 200) { + // data view exists + return existingDataView; + } + return supertest .post(`/s/${namespace}/api/data_views/data_view`) .set('kbn-xsrf', 'foo') diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts index fff1040b81f2..0e7c94613010 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts @@ -51,7 +51,7 @@ export const EntityStoreUtils = ( } }; - const _initEntityEngineForEntityType = async (entityType: EntityType) => { + const initEntityEngineForEntityType = async (entityType: EntityType) => { log.info( `Initializing engine for entity type ${entityType} in namespace ${namespace || 'default'}` ); @@ -72,7 +72,7 @@ export const EntityStoreUtils = ( }; const initEntityEngineForEntityTypesAndWait = async (entityTypes: EntityType[]) => { - await Promise.all(entityTypes.map((entityType) => _initEntityEngineForEntityType(entityType))); + await Promise.all(entityTypes.map((entityType) => initEntityEngineForEntityType(entityType))); await retry.waitForWithTimeout( `Engines to start for entity types: ${entityTypes.join(', ')}`, @@ -90,6 +90,20 @@ export const EntityStoreUtils = ( ); }; + const waitForEngineStatus = async (entityType: EntityType, status: string) => { + await retry.waitForWithTimeout( + `Engine for entity type ${entityType} to be in status ${status}`, + 60_000, + async () => { + const { body } = await api + .getEntityEngine({ params: { entityType } }, namespace) + .expect(200); + log.debug(`Engine status for ${entityType}: ${body.status}`); + return body.status === status; + } + ); + }; + const enableEntityStore = async () => { const res = await api.initEntityStore({ body: {} }, namespace); if (res.status !== 200) { @@ -155,5 +169,7 @@ export const EntityStoreUtils = ( expectEngineAssetsExist, expectEngineAssetsDoNotExist, enableEntityStore, + waitForEngineStatus, + initEntityEngineForEntityType, }; }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/draft_timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/draft_timeline.ts index dafb08124b0e..210cf28163f5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/draft_timeline.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/draft_timeline.ts @@ -27,8 +27,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { timelineType: 'default', }); - const { savedObjectId, version } = - response.body.data && response.body.data.persistTimeline.timeline; + const { savedObjectId, version } = response.body.data && response.body; expect(savedObjectId).to.not.be.empty(); expect(version).to.not.be.empty(); @@ -49,7 +48,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { timelineType, templateTimelineId, templateTimelineVersion, - } = response.body.data && response.body.data.persistTimeline.timeline; + } = response.body.data && response.body; expect(savedObjectId).to.not.be.empty(); expect(version).to.not.be.empty(); @@ -72,7 +71,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { pinnedEventIds: initialPinnedEventIds, noteIds: initialNoteIds, version: initialVersion, - } = response.body.data && response.body.data.persistTimeline.timeline; + } = response.body.data && response.body; expect(initialPinnedEventIds).to.have.length(0, 'should not have any pinned events'); expect(initialNoteIds).to.have.length(0, 'should not have any notes'); @@ -107,7 +106,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { pinnedEventIds, noteIds, status: newStatus, - } = getTimelineRequest.body.data && getTimelineRequest.body.data.getOneTimeline; + } = getTimelineRequest.body.data && getTimelineRequest.body; expect(newStatus).to.be.equal('draft', 'status should still be draft'); expect(pinnedEventIds).to.have.length(1, 'should have one pinned event'); @@ -126,8 +125,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { pinnedEventIds: cleanedPinnedEventIds, noteIds: cleanedNoteIds, version: cleanedVersion, - } = cleanDraftTimelineRequest.body.data && - cleanDraftTimelineRequest.body.data.persistTimeline.timeline; + } = cleanDraftTimelineRequest.body.data && cleanDraftTimelineRequest.body; expect(cleanedPinnedEventIds).to.have.length(0, 'should not have pinned events anymore'); expect(cleanedNoteIds).to.have.length(0, 'should not have notes anymore'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts index e3402e0c6b80..22e9a6f04b6e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('@ess @serverless SecuritySolution Saved Objects', () => { + describe('@ess @serverless @serverlessQA SecuritySolution Saved Objects', () => { loadTestFile(require.resolve('./notes')); loadTestFile(require.resolve('./pinned_events')); loadTestFile(require.resolve('./timeline')); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/notes.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/notes.ts index 8e897509aaf9..f210eb88f9c4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/notes.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/notes.ts @@ -35,8 +35,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { note: { note: myNote, timelineId: 'testTimelineId' }, }); - const { note, noteId, timelineId, version } = - response.body.data && response.body.data.persistNote.note; + const { note, noteId, timelineId, version } = response.body && response.body.note; expect(note).to.be(myNote); expect(noteId).to.not.be.empty(); @@ -56,8 +55,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { note: { note: myNote, timelineId: 'testTimelineId' }, }); - const { noteId, timelineId, version } = - response.body.data && response.body.data.persistNote.note; + const { noteId, timelineId, version } = response.body && response.body.note; const myNewNote = 'new world test'; const responseToTest = await supertest @@ -70,9 +68,9 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { note: { note: myNewNote, timelineId }, }); - expect(responseToTest.body.data!.persistNote.note.note).to.be(myNewNote); - expect(responseToTest.body.data!.persistNote.note.noteId).to.be(noteId); - expect(responseToTest.body.data!.persistNote.note.version).to.not.be.eql(version); + expect(responseToTest.body.note.note).to.be(myNewNote); + expect(responseToTest.body.note.noteId).to.be(noteId); + expect(responseToTest.body.note.version).to.not.be.eql(version); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/pinned_events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/pinned_events.ts index 183c56fc2d8f..3ef92b6a2f21 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/pinned_events.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/pinned_events.ts @@ -28,8 +28,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { timelineId: 'testId', eventId: 'bv4QSGsB9v5HJNSH-7fi', }); - const { eventId, pinnedEventId, timelineId, version } = - response.body.data && response.body.data.persistPinnedEventOnTimeline; + const { eventId, pinnedEventId, timelineId, version } = response.body; expect(eventId).to.be('bv4QSGsB9v5HJNSH-7fi'); expect(pinnedEventId).to.not.be.empty(); @@ -39,7 +38,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { }); describe('unpin an event', () => { - it('returns null', async () => { + it('returns { unpinned: true }', async () => { const response = await supertest .patch(PINNED_EVENT_URL) .set('elastic-api-version', '2023-10-31') @@ -49,8 +48,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { eventId: 'bv4QSGsB9v5HJNSH-7fi', timelineId: 'testId', }); - const { eventId, pinnedEventId, timelineId } = - response.body.data && response.body.data.persistPinnedEventOnTimeline; + const { eventId, pinnedEventId, timelineId } = response.body; const responseToTest = await supertest .patch(PINNED_EVENT_URL) @@ -61,7 +59,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { eventId, timelineId, }); - expect(responseToTest.body.data!.persistPinnedEventOnTimeline).to.be(null); + expect(responseToTest.body).to.eql({ unpinned: true }); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/timeline.ts index 8029fce1c7b9..bd818ddc893a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/timeline.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/tests/timeline.ts @@ -6,10 +6,7 @@ */ import expect from '@kbn/expect'; -import { - TimelineResponse, - TimelineTypeEnum, -} from '@kbn/security-solution-plugin/common/api/timeline'; +import { TimelineTypeEnum } from '@kbn/security-solution-plugin/common/api/timeline'; import { TIMELINE_URL } from '@kbn/security-solution-plugin/common/constants'; import TestAgent from 'supertest/lib/agent'; import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces'; @@ -26,8 +23,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { it('Create a timeline just with a title', async () => { const titleToSaved = 'hello title'; const response = await createBasicTimeline(supertest, titleToSaved); - const { savedObjectId, title, version } = - response.body.data && response.body.data.persistTimeline.timeline; + const { savedObjectId, title, version } = response.body; expect(title).to.be(titleToSaved); expect(savedObjectId).to.not.be.empty(); @@ -152,8 +148,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { sort, title, version, - } = - response.body.data && omitTypenameInTimeline(response.body.data.persistTimeline.timeline); + } = response.body; expect(columns.map((col: { id: string }) => col.id)).to.eql( timelineObject.columns.map((col) => col.id) @@ -172,8 +167,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { it('Update a timeline with a new title', async () => { const titleToSaved = 'hello title'; const response = await createBasicTimeline(supertest, titleToSaved); - const { savedObjectId, version } = - response.body.data && response.body.data.persistTimeline.timeline; + const { savedObjectId, version } = response.body; const newTitle = 'new title'; @@ -187,11 +181,9 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { title: newTitle, }, }); - expect(responseToTest.body.data!.persistTimeline.timeline.savedObjectId).to.eql( - savedObjectId - ); - expect(responseToTest.body.data!.persistTimeline.timeline.title).to.be(newTitle); - expect(responseToTest.body.data!.persistTimeline.timeline.version).to.not.be.eql(version); + expect(responseToTest.body.savedObjectId).to.eql(savedObjectId); + expect(responseToTest.body.title).to.be(newTitle); + expect(responseToTest.body.version).to.not.be.eql(version); }); }); @@ -200,8 +192,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { const titleToSaved = 'hello title'; const response = await createBasicTimeline(supertest, titleToSaved); - const { savedObjectId, version } = - response.body.data && response.body.data.persistTimeline.timeline; + const { savedObjectId, version } = response.body; const responseToTest = await supertest .patch('/api/timeline/_favorite') @@ -213,14 +204,12 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { timelineType: TimelineTypeEnum.default, }); - expect(responseToTest.body.data!.persistFavorite.savedObjectId).to.be(savedObjectId); - expect(responseToTest.body.data!.persistFavorite.favorite.length).to.be(1); - expect(responseToTest.body.data!.persistFavorite.version).to.not.be.eql(version); - expect(responseToTest.body.data!.persistFavorite.templateTimelineId).to.be.eql(null); - expect(responseToTest.body.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); - expect(responseToTest.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineTypeEnum.default - ); + expect(responseToTest.body.savedObjectId).to.be(savedObjectId); + expect(responseToTest.body.favorite.length).to.be(1); + expect(responseToTest.body.version).to.not.be.eql(version); + expect(responseToTest.body.templateTimelineId).to.be.eql(null); + expect(responseToTest.body.templateTimelineVersion).to.be.eql(null); + expect(responseToTest.body.timelineType).to.be.eql(TimelineTypeEnum.default); }); it('to an existing timeline template', async () => { @@ -228,8 +217,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { const templateTimelineIdFromStore = 'f4a90a2d-365c-407b-9fef-c1dcb33a6ab3'; const templateTimelineVersionFromStore = 1; const response = await createBasicTimeline(supertest, titleToSaved); - const { savedObjectId, version } = - response.body.data && response.body.data.persistTimeline.timeline; + const { savedObjectId, version } = response.body; const responseToTest = await supertest .patch('/api/timeline/_favorite') @@ -240,25 +228,20 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { templateTimelineVersion: templateTimelineVersionFromStore, timelineType: TimelineTypeEnum.template, }); - expect(responseToTest.body.data!.persistFavorite.savedObjectId).to.be(savedObjectId); - expect(responseToTest.body.data!.persistFavorite.favorite.length).to.be(1); - expect(responseToTest.body.data!.persistFavorite.version).to.not.be.eql(version); - expect(responseToTest.body.data!.persistFavorite.templateTimelineId).to.be.eql( - templateTimelineIdFromStore - ); - expect(responseToTest.body.data!.persistFavorite.templateTimelineVersion).to.be.eql( + expect(responseToTest.body.savedObjectId).to.be(savedObjectId); + expect(responseToTest.body.favorite.length).to.be(1); + expect(responseToTest.body.version).to.not.be.eql(version); + expect(responseToTest.body.templateTimelineId).to.be.eql(templateTimelineIdFromStore); + expect(responseToTest.body.templateTimelineVersion).to.be.eql( templateTimelineVersionFromStore ); - expect(responseToTest.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineTypeEnum.template - ); + expect(responseToTest.body.timelineType).to.be.eql(TimelineTypeEnum.template); }); it('to Unfavorite an existing timeline', async () => { const titleToSaved = 'hello title'; const response = await createBasicTimeline(supertest, titleToSaved); - const { savedObjectId, version } = - response.body.data && response.body.data.persistTimeline.timeline; + const { savedObjectId, version } = response.body; await supertest.patch('/api/timeline/_favorite').set('kbn-xsrf', 'true').send({ timelineId: savedObjectId, @@ -277,14 +260,12 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { timelineType: TimelineTypeEnum.default, }); - expect(responseToTest.body.data!.persistFavorite.savedObjectId).to.be(savedObjectId); - expect(responseToTest.body.data!.persistFavorite.favorite).to.be.empty(); - expect(responseToTest.body.data!.persistFavorite.version).to.not.be.eql(version); - expect(responseToTest.body.data!.persistFavorite.templateTimelineId).to.be.eql(null); - expect(responseToTest.body.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); - expect(responseToTest.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineTypeEnum.default - ); + expect(responseToTest.body.savedObjectId).to.be(savedObjectId); + expect(responseToTest.body.favorite).to.be.empty(); + expect(responseToTest.body.version).to.not.be.eql(version); + expect(responseToTest.body.templateTimelineId).to.be.eql(null); + expect(responseToTest.body.templateTimelineVersion).to.be.eql(null); + expect(responseToTest.body.timelineType).to.be.eql(TimelineTypeEnum.default); }); it('to Unfavorite an existing timeline template', async () => { @@ -292,8 +273,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { const templateTimelineIdFromStore = 'f4a90a2d-365c-407b-9fef-c1dcb33a6ab3'; const templateTimelineVersionFromStore = 1; const response = await createBasicTimeline(supertest, titleToSaved); - const { savedObjectId, version } = - response.body.data && response.body.data.persistTimeline.timeline; + const { savedObjectId, version } = response.body; await supertest.patch('/api/timeline/_favorite').set('kbn-xsrf', 'true').send({ timelineId: savedObjectId, @@ -312,18 +292,14 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { timelineType: TimelineTypeEnum.template, }); - expect(responseToTest.body.data!.persistFavorite.savedObjectId).to.be(savedObjectId); - expect(responseToTest.body.data!.persistFavorite.favorite).to.be.empty(); - expect(responseToTest.body.data!.persistFavorite.version).to.not.be.eql(version); - expect(responseToTest.body.data!.persistFavorite.templateTimelineId).to.be.eql( - templateTimelineIdFromStore - ); - expect(responseToTest.body.data!.persistFavorite.templateTimelineVersion).to.be.eql( + expect(responseToTest.body.savedObjectId).to.be(savedObjectId); + expect(responseToTest.body.favorite).to.be.empty(); + expect(responseToTest.body.version).to.not.be.eql(version); + expect(responseToTest.body.templateTimelineId).to.be.eql(templateTimelineIdFromStore); + expect(responseToTest.body.templateTimelineVersion).to.be.eql( templateTimelineVersionFromStore ); - expect(responseToTest.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineTypeEnum.template - ); + expect(responseToTest.body.timelineType).to.be.eql(TimelineTypeEnum.template); }); it('to a timeline without a timelineId', async () => { @@ -337,14 +313,12 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { timelineType: TimelineTypeEnum.default, }); - expect(response.body.data!.persistFavorite.savedObjectId).to.not.be.empty(); - expect(response.body.data!.persistFavorite.favorite.length).to.be(1); - expect(response.body.data!.persistFavorite.version).to.not.be.empty(); - expect(response.body.data!.persistFavorite.templateTimelineId).to.be.eql(null); - expect(response.body.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); - expect(response.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineTypeEnum.default - ); + expect(response.body.savedObjectId).to.not.be.empty(); + expect(response.body.favorite.length).to.be(1); + expect(response.body.version).to.not.be.empty(); + expect(response.body.templateTimelineId).to.be.eql(null); + expect(response.body.templateTimelineVersion).to.be.eql(null); + expect(response.body.timelineType).to.be.eql(TimelineTypeEnum.default); }); it('to a timeline template without a timelineId', async () => { @@ -361,18 +335,12 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { timelineType: TimelineTypeEnum.template, }); - expect(response.body.data!.persistFavorite.savedObjectId).to.not.be.empty(); - expect(response.body.data!.persistFavorite.favorite.length).to.be(1); - expect(response.body.data!.persistFavorite.version).to.not.be.empty(); - expect(response.body.data!.persistFavorite.templateTimelineId).to.be.eql( - templateTimelineIdFromStore - ); - expect(response.body.data!.persistFavorite.templateTimelineVersion).to.be.eql( - templateTimelineVersionFromStore - ); - expect(response.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineTypeEnum.template - ); + expect(response.body.savedObjectId).to.not.be.empty(); + expect(response.body.favorite.length).to.be(1); + expect(response.body.version).to.not.be.empty(); + expect(response.body.templateTimelineId).to.be.eql(templateTimelineIdFromStore); + expect(response.body.templateTimelineVersion).to.be.eql(templateTimelineVersionFromStore); + expect(response.body.timelineType).to.be.eql(TimelineTypeEnum.template); }); }); @@ -380,7 +348,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { it('one timeline', async () => { const titleToSaved = 'hello title'; const response = await createBasicTimeline(supertest, titleToSaved); - const { savedObjectId } = response.body.data && response.body.data.persistTimeline.timeline; + const { savedObjectId } = response.body; const responseToTest = await supertest .delete(TIMELINE_URL) @@ -389,22 +357,16 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { savedObjectIds: [savedObjectId], }); - expect(responseToTest.body.data!.deleteTimeline).to.be(true); + expect(responseToTest.statusCode).to.be(200); }); it('multiple timelines', async () => { const titleToSaved = 'hello title'; const response1 = await createBasicTimeline(supertest, titleToSaved); - const savedObjectId1 = - response1.body.data && response1.body.data.persistTimeline.timeline - ? response1.body.data.persistTimeline.timeline.savedObjectId - : ''; + const savedObjectId1 = response1.body ? response1.body.savedObjectId : ''; const response2 = await createBasicTimeline(supertest, titleToSaved); - const savedObjectId2 = - response2.body.data && response2.body.data.persistTimeline.timeline - ? response2.body.data.persistTimeline.timeline.savedObjectId - : ''; + const savedObjectId2 = response2.body ? response2.body.savedObjectId : ''; const responseToTest = await supertest .delete(TIMELINE_URL) @@ -413,14 +375,8 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { savedObjectIds: [savedObjectId1, savedObjectId2], }); - expect(responseToTest.body.data!.deleteTimeline).to.be(true); + expect(responseToTest.status).to.be(200); }); }); }); } - -const omitTypename = (key: string, value: keyof TimelineResponse) => - key === '__typename' ? undefined : value; - -const omitTypenameInTimeline = (timeline: TimelineResponse) => - JSON.parse(JSON.stringify(timeline), omitTypename); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts index 0d14c693ea82..b337faad85f0 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContextWithSpaces } from '../../../../ftr_provider_context_with_spaces'; export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { - describe('@ess @serverless SecuritySolution Timeline', () => { + describe('@ess @serverless @serverlessQA SecuritySolution Timeline', () => { loadTestFile(require.resolve('./events')); loadTestFile(require.resolve('./timeline_details')); loadTestFile(require.resolve('./timeline')); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_migrations.ts index 2e8a8fd4aaa9..a3a2bc27932c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/tests/timeline_migrations.ts @@ -68,9 +68,9 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(resolveWithSpaceApi) .query({ id: '1e2e9850-25f8-11ec-a981-b77847c6ef30' }); - expect(resp.body.data.outcome).to.be('aliasMatch'); - expect(resp.body.data.alias_target_id).to.not.be(undefined); - expect(resp.body.data.timeline.title).to.be('An awesome timeline'); + expect(resp.body.outcome).to.be('aliasMatch'); + expect(resp.body.alias_target_id).to.not.be(undefined); + expect(resp.body.timeline.title).to.be('An awesome timeline'); }); describe('notes', () => { @@ -79,7 +79,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(resolveWithSpaceApi) .query({ id: '1e2e9850-25f8-11ec-a981-b77847c6ef30' }); - expect(resp.body.data.timeline.notes[0].eventId).to.be('StU_UXwBAowmaxx6YdiS'); + expect(resp.body.timeline.notes[0].eventId).to.be('StU_UXwBAowmaxx6YdiS'); }); it('should return notes with the timelineId matching the resolved timeline id', async () => { @@ -87,12 +87,8 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(resolveWithSpaceApi) .query({ id: '1e2e9850-25f8-11ec-a981-b77847c6ef30' }); - expect(resp.body.data.timeline.notes[0].timelineId).to.be( - resp.body.data.timeline.savedObjectId - ); - expect(resp.body.data.timeline.notes[1].timelineId).to.be( - resp.body.data.timeline.savedObjectId - ); + expect(resp.body.timeline.notes[0].timelineId).to.be(resp.body.timeline.savedObjectId); + expect(resp.body.timeline.notes[1].timelineId).to.be(resp.body.timeline.savedObjectId); }); }); @@ -102,7 +98,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(resolveWithSpaceApi) .query({ id: '1e2e9850-25f8-11ec-a981-b77847c6ef30' }); - expect(resp.body.data.timeline.pinnedEventsSaveObject[0].eventId).to.be( + expect(resp.body.timeline.pinnedEventsSaveObject[0].eventId).to.be( 'StU_UXwBAowmaxx6YdiS' ); }); @@ -112,8 +108,8 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(resolveWithSpaceApi) .query({ id: '1e2e9850-25f8-11ec-a981-b77847c6ef30' }); - expect(resp.body.data.timeline.pinnedEventsSaveObject[0].timelineId).to.be( - resp.body.data.timeline.savedObjectId + expect(resp.body.timeline.pinnedEventsSaveObject[0].timelineId).to.be( + resp.body.timeline.savedObjectId ); }); }); @@ -161,7 +157,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(TIMELINE_URL) .query({ id: '6484cc90-126e-11ec-83d2-db1096c73738' }); - expect(resp.body.data.getOneTimeline.notes[0].eventId).to.be('Edo00XsBEVtyvU-8LGNe'); + expect(resp.body.notes[0].eventId).to.be('Edo00XsBEVtyvU-8LGNe'); }); it('returns the timelineId in the response', async () => { @@ -169,12 +165,8 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(TIMELINE_URL) .query({ id: '6484cc90-126e-11ec-83d2-db1096c73738' }); - expect(resp.body.data.getOneTimeline.notes[0].timelineId).to.be( - '6484cc90-126e-11ec-83d2-db1096c73738' - ); - expect(resp.body.data.getOneTimeline.notes[1].timelineId).to.be( - '6484cc90-126e-11ec-83d2-db1096c73738' - ); + expect(resp.body.notes[0].timelineId).to.be('6484cc90-126e-11ec-83d2-db1096c73738'); + expect(resp.body.notes[1].timelineId).to.be('6484cc90-126e-11ec-83d2-db1096c73738'); }); }); @@ -198,7 +190,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(TIMELINE_URL) .query({ id: '8dc70950-1012-11ec-9ad3-2d7c6600c0f7' }); - expect(resp.body.data.getOneTimeline.title).to.be('Awesome Timeline'); + expect(resp.body.title).to.be('Awesome Timeline'); }); it('returns the savedQueryId in the response', async () => { @@ -206,7 +198,7 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(TIMELINE_URL) .query({ id: '8dc70950-1012-11ec-9ad3-2d7c6600c0f7' }); - expect(resp.body.data.getOneTimeline.savedQueryId).to.be("It's me"); + expect(resp.body.savedQueryId).to.be("It's me"); }); }); describe('pinned events timelineId', () => { @@ -238,12 +230,8 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(TIMELINE_URL) .query({ id: '6484cc90-126e-11ec-83d2-db1096c73738' }); - expect(resp.body.data.getOneTimeline.pinnedEventsSaveObject[0].eventId).to.be( - 'DNo00XsBEVtyvU-8LGNe' - ); - expect(resp.body.data.getOneTimeline.pinnedEventsSaveObject[1].eventId).to.be( - 'Edo00XsBEVtyvU-8LGNe' - ); + expect(resp.body.pinnedEventsSaveObject[0].eventId).to.be('DNo00XsBEVtyvU-8LGNe'); + expect(resp.body.pinnedEventsSaveObject[1].eventId).to.be('Edo00XsBEVtyvU-8LGNe'); }); it('returns the timelineId in the response', async () => { @@ -251,10 +239,10 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { .get(TIMELINE_URL) .query({ id: '6484cc90-126e-11ec-83d2-db1096c73738' }); - expect(resp.body.data.getOneTimeline.pinnedEventsSaveObject[0].timelineId).to.be( + expect(resp.body.pinnedEventsSaveObject[0].timelineId).to.be( '6484cc90-126e-11ec-83d2-db1096c73738' ); - expect(resp.body.data.getOneTimeline.pinnedEventsSaveObject[1].timelineId).to.be( + expect(resp.body.pinnedEventsSaveObject[1].timelineId).to.be( '6484cc90-126e-11ec-83d2-db1096c73738' ); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/callouts/missing_privileges_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/callouts/missing_privileges_callout.cy.ts index 69e6bb8b3525..501c8750b55f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/callouts/missing_privileges_callout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/callouts/missing_privileges_callout.cy.ts @@ -44,91 +44,96 @@ const waitForPageTitleToBeShown = () => { cy.get(PAGE_TITLE).should('be.visible'); }; -describe('Detections > Callouts', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { - before(() => { - // First, we have to open the app on behalf of a privileged user in order to initialize it. - // Otherwise the app will be disabled and show a "welcome"-like page. - login(); - visit(ALERTS_URL); - waitForPageTitleToBeShown(); - }); - - context('indicating read-only access to resources', () => { - context('On Detections home page', () => { - beforeEach(() => { - loadPageAsReadOnlyUser(ALERTS_URL); - }); +// FLAKY: https://github.com/elastic/kibana/issues/198628 +describe.skip( + 'Detections > Callouts', + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + before(() => { + // First, we have to open the app on behalf of a privileged user in order to initialize it. + // Otherwise the app will be disabled and show a "welcome"-like page. + login(); + visit(ALERTS_URL); + waitForPageTitleToBeShown(); + }); + + context('indicating read-only access to resources', () => { + context('On Detections home page', () => { + beforeEach(() => { + loadPageAsReadOnlyUser(ALERTS_URL); + }); - it('dismisses callout and persists its state', () => { - waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); + it('dismisses callout and persists its state', () => { + waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - dismissCallOut(MISSING_PRIVILEGES_CALLOUT); - reloadPage(); + dismissCallOut(MISSING_PRIVILEGES_CALLOUT); + reloadPage(); - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); - }); - // FYI: Rules Management check moved to ../detection_rules/all_rules_read_only.spec.ts + // FYI: Rules Management check moved to ../detection_rules/all_rules_read_only.spec.ts - context('On Rule Details page', () => { - beforeEach(() => { - createRule(getNewRule()).then((rule) => - loadPageAsReadOnlyUser(ruleDetailsUrl(rule.body.id)) - ); - }); + context('On Rule Details page', () => { + beforeEach(() => { + createRule(getNewRule()).then((rule) => + loadPageAsReadOnlyUser(ruleDetailsUrl(rule.body.id)) + ); + }); - afterEach(() => { - deleteCustomRule(); - }); + afterEach(() => { + deleteCustomRule(); + }); - it('dismisses callout and persists its state', () => { - waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); + it('dismisses callout and persists its state', () => { + waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); - dismissCallOut(MISSING_PRIVILEGES_CALLOUT); - reloadPage(); + dismissCallOut(MISSING_PRIVILEGES_CALLOUT); + reloadPage(); - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); }); - }); - context('indicating read-write access to resources', () => { - context('On Detections home page', () => { - beforeEach(() => { - loadPageAsPlatformEngineer(ALERTS_URL); - }); + context('indicating read-write access to resources', () => { + context('On Detections home page', () => { + beforeEach(() => { + loadPageAsPlatformEngineer(ALERTS_URL); + }); - it('We show no callout', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + it('We show no callout', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); - }); - context('On Rules Management page', () => { - beforeEach(() => { - login(ROLES.platform_engineer); - loadPageAsPlatformEngineer(RULES_MANAGEMENT_URL); - }); + context('On Rules Management page', () => { + beforeEach(() => { + login(ROLES.platform_engineer); + loadPageAsPlatformEngineer(RULES_MANAGEMENT_URL); + }); - it('We show no callout', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + it('We show no callout', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); - }); - context('On Rule Details page', () => { - beforeEach(() => { - createRule(getNewRule()).then((rule) => - loadPageAsPlatformEngineer(ruleDetailsUrl(rule.body.id)) - ); - }); + context('On Rule Details page', () => { + beforeEach(() => { + createRule(getNewRule()).then((rule) => + loadPageAsPlatformEngineer(ruleDetailsUrl(rule.body.id)) + ); + }); - afterEach(() => { - deleteCustomRule(); - }); + afterEach(() => { + deleteCustomRule(); + }); - it('We show no callouts', () => { - getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + it('We show no callouts', () => { + getCallOut(MISSING_PRIVILEGES_CALLOUT).should('not.exist'); + }); }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts index 438743dd01a7..f7f343c11b67 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts @@ -59,7 +59,7 @@ describe('Common rule creation flows', { tags: ['@ess', '@serverless'] }, () => deleteAlertsAndRules(); createTimeline() .then((response) => { - return response.body.data.persistTimeline.timeline.savedObjectId; + return response.body.savedObjectId; }) .as('timelineId'); visit(CREATE_RULE_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/eql_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/eql_query_rule.cy.ts index 994dbb2eb8ce..d7bd6d8ebce7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/eql_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/eql_query_rule.cy.ts @@ -15,7 +15,8 @@ import { } from '../../../../tasks/edit_rule'; import { login } from '../../../../tasks/login'; -describe('EQL query rules', { tags: ['@ess', '@serverless'] }, () => { +// Failing: See https://github.com/elastic/kibana/issues/201334 +describe.skip('EQL query rules', { tags: ['@ess', '@serverless'] }, () => { context('Editing rule with non-blocking query validation errors', () => { beforeEach(() => { login(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/common_flows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/common_flows.cy.ts index 6aded7a9a1f8..4ab4444ad968 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/common_flows.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/common_flows.cy.ts @@ -72,8 +72,8 @@ describe( tags: ruleFields.ruleTags, false_positives: ruleFields.falsePositives, note: ruleFields.investigationGuide, - timeline_id: response.body.data.persistTimeline.timeline.savedObjectId, - timeline_title: response.body.data.persistTimeline.timeline.title ?? '', + timeline_id: response.body.savedObjectId, + timeline_title: response.body.title ?? '', interval: ruleFields.ruleInterval, from: `now-1h`, query: ruleFields.ruleQuery, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/non_default_space.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/non_default_space.cy.ts index 6b7cb3309e92..bef28e2112a4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/non_default_space.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/non_default_space.cy.ts @@ -37,8 +37,8 @@ describe('Non-default space rule detail page', { tags: ['@ess'] }, function () { tags: ruleFields.ruleTags, false_positives: ruleFields.falsePositives, note: ruleFields.investigationGuide, - timeline_id: response.body.data.persistTimeline.timeline.savedObjectId, - timeline_title: response.body.data.persistTimeline.timeline.title ?? '', + timeline_id: response.body.savedObjectId, + timeline_title: response.body.title ?? '', interval: ruleFields.ruleInterval, from: `now-1h`, query: ruleFields.ruleQuery, diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_timeline.cy.ts index 68d14715d464..56eab1675ac8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_timeline.cy.ts @@ -28,7 +28,7 @@ describe('attach timeline to case', { tags: ['@ess', '@serverless'] }, () => { deleteTimelines(); deleteCases(); createTimeline().then((response) => { - cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline'); + cy.wrap(response.body).as('myTimeline'); }); }); @@ -63,9 +63,7 @@ describe('attach timeline to case', { tags: ['@ess', '@serverless'] }, () => { login(); deleteTimelines(); deleteCases(); - createTimeline().then((response) => - cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId') - ); + createTimeline().then((response) => cy.wrap(response.body.savedObjectId).as('timelineId')); createCase(getCase1()).then((response) => cy.wrap(response.body.id).as('caseId')); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts index 05d2cddc8eac..12cb30a5775a 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/creation.cy.ts @@ -67,7 +67,7 @@ describe('Cases', { tags: ['@ess', '@serverless'] }, () => { ...getCase1(), timeline: { ...getCase1().timeline, - id: response.body.data.persistTimeline.timeline.savedObjectId, + id: response.body.savedObjectId, }, }) .as('mycase') diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/inspect/inspect_button.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/inspect/inspect_button.cy.ts index a9025ba319ac..5f2b61b3de41 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/inspect/inspect_button.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/inspect/inspect_button.cy.ts @@ -44,8 +44,6 @@ describe('Inspect Explore pages', { tags: ['@ess', '@serverless'] }, () => { * Group all tests of a page into one "it" call to improve speed */ it(`inspect ${pageName} page`, () => { - login(); - visitWithTimeRange(url, { visitOptions: { onLoad: () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts index 1920bd01668f..eba2dbe770e4 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/navigation/navigation.cy.ts @@ -21,7 +21,6 @@ import { DETECTION_RESPONSE, DASHBOARDS, CSP_DASHBOARD, - KUBERNETES, INDICATORS, BLOCKLIST, CSP_BENCHMARKS, @@ -54,7 +53,6 @@ import { EXPLORE_URL, MANAGE_URL, CSP_DASHBOARD_URL, - KUBERNETES_URL, BLOCKLIST_URL, CSP_BENCHMARKS_URL, CSP_FINDINGS_URL, @@ -114,11 +112,6 @@ describe('top-level navigation common to all pages in the Security app', { tags: cy.url().should('include', ENTITY_ANALYTICS_URL); }); - it('navigates to the Kubernetes page', () => { - navigateFromHeaderTo(KUBERNETES); - cy.url().should('include', KUBERNETES_URL); - }); - it('navigates to the CSP dashboard page', () => { navigateFromHeaderTo(CSP_DASHBOARD); cy.url().should('include', CSP_DASHBOARD_URL); @@ -289,11 +282,6 @@ describe('Serverless side navigation links', { tags: '@serverless' }, () => { cy.url().should('include', ENTITY_ANALYTICS_URL); }); - it('navigates to the Kubernetes page', () => { - navigateFromHeaderTo(ServerlessHeaders.KUBERNETES, true); - cy.url().should('include', KUBERNETES_URL); - }); - it('navigates to the CSP dashboard page', () => { navigateFromHeaderTo(ServerlessHeaders.CSP_DASHBOARD, true); cy.url().should('include', CSP_DASHBOARD_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/overview/overview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/overview/overview.cy.ts index 78135fbd7723..0248f403b610 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/overview/overview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/overview/overview.cy.ts @@ -52,7 +52,7 @@ describe('Overview Page', { tags: ['@ess', '@serverless'] }, () => { describe('Favorite Timelines', { tags: ['@skipInServerless'] }, () => { it('should appear on overview page', () => { createTimeline() - .then((response) => response.body.data.persistTimeline.timeline.savedObjectId) + .then((response) => response.body.savedObjectId) .then((timelineId: string) => { favoriteTimeline({ timelineId, timelineType: 'default' }).then(() => { visitWithTimeRange(OVERVIEW_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts index 7531cf30a775..b5189b846225 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/urls/state.cy.ts @@ -304,7 +304,7 @@ describe('url state', { tags: ['@ess', '@skipInServerless'] }, () => { cy.wait('@timeline').then(({ response }) => { closeTimeline(); cy.wrap(response?.statusCode).should('eql', 200); - const timelineId = response?.body.data.persistTimeline.timeline.savedObjectId; + const timelineId = response?.body.savedObjectId; visitWithTimeRange('/app/home'); visitWithTimeRange(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`); cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('exist'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts index 107a170e9cc2..f1fcf7f76cba 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts @@ -47,57 +47,67 @@ describe('Alerts cell actions', { tags: ['@ess', '@serverless'] }, () => { waitForAlertsToPopulate(); }); - it('should filter in and out existing values', () => { - scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_SEVERITY_HEADER, () => { - cy.get(ALERT_TABLE_SEVERITY_VALUES) - .first() - .invoke('text') - .then((severityVal) => { - filterForAlertProperty(ALERT_TABLE_SEVERITY_VALUES, 0); - cy.get(FILTER_BADGE).first().should('have.text', `kibana.alert.severity: ${severityVal}`); - }); - removeKqlFilter(); - }); + // Flaky in Serverless MKI only + // https://github.com/elastic/kibana/issues/201117 + it( + 'should filter in and out existing values', + { + tags: ['@skipInServerlessMKI'], + }, + () => { + scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_SEVERITY_HEADER, () => { + cy.get(ALERT_TABLE_SEVERITY_VALUES) + .first() + .invoke('text') + .then((severityVal) => { + filterForAlertProperty(ALERT_TABLE_SEVERITY_VALUES, 0); + cy.get(FILTER_BADGE) + .first() + .should('have.text', `kibana.alert.severity: ${severityVal}`); + }); + removeKqlFilter(); + }); - cy.log('should work for empty properties'); - // add query condition to make sure the field is empty - fillKqlQueryBar('not file.name: *{enter}'); + cy.log('should work for empty properties'); + // add query condition to make sure the field is empty + fillKqlQueryBar('not file.name: *{enter}'); - scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_FILE_NAME_HEADER, () => { - cy.log('filter for alert property'); + scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_FILE_NAME_HEADER, () => { + cy.log('filter for alert property'); - filterForAlertProperty(ALERT_TABLE_FILE_NAME_VALUES, 0); + filterForAlertProperty(ALERT_TABLE_FILE_NAME_VALUES, 0); - cy.get(FILTER_BADGE).first().should('have.text', 'NOT file.name: exists'); - removeKqlFilter(); - }); + cy.get(FILTER_BADGE).first().should('have.text', 'NOT file.name: exists'); + removeKqlFilter(); + }); - cy.log('filter out alert property'); + cy.log('filter out alert property'); - scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_FILE_NAME_HEADER, () => { - cy.get(ALERT_TABLE_FILE_NAME_VALUES) - .first() - .then(() => { - filterOutAlertProperty(ALERT_TABLE_FILE_NAME_VALUES, 0); - cy.get(FILTER_BADGE).first().should('have.text', 'file.name: exists'); - }); - removeKqlFilter(); - }); - - cy.log('should filter out a non-empty property'); + scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_FILE_NAME_HEADER, () => { + cy.get(ALERT_TABLE_FILE_NAME_VALUES) + .first() + .then(() => { + filterOutAlertProperty(ALERT_TABLE_FILE_NAME_VALUES, 0); + cy.get(FILTER_BADGE).first().should('have.text', 'file.name: exists'); + }); + removeKqlFilter(); + }); - scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_SEVERITY_HEADER, () => { - cy.get(ALERT_TABLE_SEVERITY_VALUES) - .first() - .invoke('text') - .then((severityVal) => { - filterOutAlertProperty(ALERT_TABLE_SEVERITY_VALUES, 0); - cy.get(FILTER_BADGE) - .first() - .should('have.text', `NOT kibana.alert.severity: ${severityVal}`); - }); - }); - }); + cy.log('should filter out a non-empty property'); + + scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_SEVERITY_HEADER, () => { + cy.get(ALERT_TABLE_SEVERITY_VALUES) + .first() + .invoke('text') + .then((severityVal) => { + filterOutAlertProperty(ALERT_TABLE_SEVERITY_VALUES, 0); + cy.get(FILTER_BADGE) + .first() + .should('have.text', `NOT kibana.alert.severity: ${severityVal}`); + }); + }); + } + ); it('should allow copy paste', () => { scrollAlertTableColumnIntoViewAndTest(ALERT_TABLE_SEVERITY_HEADER, () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts index 509ba40e57fc..53db62751ebd 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts @@ -103,7 +103,8 @@ describe('KPI visualizations in Alerts Page', { tags: ['@ess', '@serverless'] }, }); }); - context('Histogram legend hover actions', () => { + // For some reason this suite is failing in CI while I cannot reproduce it locally + context.skip('Histogram legend hover actions', () => { it('should should add a filter in to KQL bar', () => { selectAlertsHistogram(); const expectedNumberOfAlerts = 1; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/ransomware_prevention.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/ransomware_prevention.cy.ts index c7078783466e..6fb8492cf57c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/ransomware_prevention.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/ransomware_prevention.cy.ts @@ -52,7 +52,7 @@ describe('Ransomware Prevention Alerts', { tags: ['@ess', '@serverless'] }, () = deleteTimelines(); login(); createTimeline({ ...getTimeline(), query: 'event.code: "ransomware"' }).then((response) => { - cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId'); + cy.wrap(response.body.savedObjectId).as('timelineId'); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts index 9d2e1ac2e11a..9d5b77919d54 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts @@ -100,11 +100,9 @@ describe('Timeline scope', { tags: ['@ess', '@serverless', '@skipInServerless'] beforeEach(() => { login(); deleteTimelines(); - createTimeline().then((response) => - cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId') - ); + createTimeline().then((response) => cy.wrap(response.body.savedObjectId).as('timelineId')); createTimeline(getTimelineModifiedSourcerer()).then((response) => - cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('auditbeatTimelineId') + cy.wrap(response.body.savedObjectId).as('auditbeatTimelineId') ); visitWithTimeRange(TIMELINES_URL); refreshUntilAlertsIndexExists(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts index 4e2da9bc9170..5c4ad0f8d471 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/creation.cy.ts @@ -83,7 +83,7 @@ describe.skip('Timeline Templates', { tags: ['@ess', '@serverless'] }, () => { closeTimeline(); cy.wait('@timeline').then(({ response }) => { - const { createdBy, savedObjectId } = response?.body.data.persistTimeline.timeline; + const { createdBy, savedObjectId } = response?.body; cy.log('Verify template shows on the table in the templates tab'); @@ -122,7 +122,7 @@ describe.skip('Timeline Templates', { tags: ['@ess', '@serverless'] }, () => { addNameToTimelineAndSave(savedName); cy.wait('@timeline').then(({ response }) => { - const { createdBy, savedObjectId } = response?.body.data.persistTimeline.timeline; + const { createdBy, savedObjectId } = response?.body; cy.log('Check that the template has been created correctly'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts index 3c407a750676..8c77cd61ebc2 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timeline_templates/export.cy.ts @@ -20,8 +20,8 @@ describe('Export timelines', { tags: ['@ess', '@serverless'] }, () => { deleteTimelines(); createTimelineTemplate().then((response) => { cy.wrap(response).as('templateResponse'); - cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('templateId'); - cy.wrap(response.body.data.persistTimeline.timeline.title).as('templateTitle'); + cy.wrap(response.body.savedObjectId).as('templateId'); + cy.wrap(response.body.title).as('templateTitle'); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/correlation_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/correlation_tab.cy.ts index 64ed8564626d..64ef95cd21b9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/correlation_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/correlation_tab.cy.ts @@ -26,7 +26,7 @@ describe('Correlation tab', { tags: ['@ess', '@serverless'] }, () => { cy.intercept('PATCH', '/api/timeline').as('updateTimeline'); createTimeline().then((response) => { visit(TIMELINES_URL); - openTimeline(response.body.data.persistTimeline.timeline.savedObjectId); + openTimeline(response.body.savedObjectId); addEqlToTimeline(eql); saveTimeline(); cy.wait('@updateTimeline'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/discover_timeline_state_integration.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/discover_timeline_state_integration.cy.ts index 75d0ddd63dc2..b4d0f1b0e023 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/discover_timeline_state_integration.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/esql/discover_timeline_state_integration.cy.ts @@ -53,8 +53,7 @@ const INITIAL_END_DATE = 'Jan 19, 2024 @ 20:33:29.186'; const TIMELINE_REQ_WITH_SAVED_SEARCH = 'TIMELINE_REQ_WITH_SAVED_SEARCH'; const TIMELINE_PATCH_REQ = 'TIMELINE_PATCH_REQ'; -const TIMELINE_RESPONSE_SAVED_OBJECT_ID_PATH = - 'response.body.data.persistTimeline.timeline.savedObjectId'; +const TIMELINE_RESPONSE_SAVED_OBJECT_ID_PATH = 'response.body.savedObjectId'; const esqlQuery = 'from auditbeat-* | where ecs.version == "8.0.0"'; const handleIntercepts = () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts index 0800c2b610a2..ba0ec381ddeb 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/export.cy.ts @@ -34,11 +34,11 @@ describe.skip('Export timelines', { tags: ['@ess', '@serverless'] }, () => { }).as('export'); createTimeline().then((response) => { cy.wrap(response).as('timelineResponse1'); - cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId1'); + cy.wrap(response.body.savedObjectId).as('timelineId1'); }); createTimeline().then((response) => { cy.wrap(response).as('timelineResponse2'); - cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId2'); + cy.wrap(response.body.savedObjectId).as('timelineId2'); }); visit(TIMELINES_URL); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/notes_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/notes_tab.cy.ts index 7eead20dcb8c..de32f2822321 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/notes_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/notes_tab.cy.ts @@ -33,7 +33,7 @@ describe('Timeline notes tab', { tags: ['@ess', '@serverless'] }, () => { deleteTimelines(); createTimeline(getTimelineNonValidQuery()) - .then((response) => response.body.data.persistTimeline.timeline.savedObjectId) + .then((response) => response.body.savedObjectId) .then((timelineId: string) => { login(); visitTimeline(timelineId); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/open_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/open_timeline.cy.ts index 73b6fff4fa8e..feee9a36168b 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/open_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/open_timeline.cy.ts @@ -35,7 +35,7 @@ describe('Open timeline modal', { tags: ['@serverless', '@ess'] }, () => { login(); visit(TIMELINES_URL); createTimeline() - .then((response) => response.body.data.persistTimeline.timeline.savedObjectId) + .then((response) => response.body.savedObjectId) .then((timelineId: string) => { refreshTimelinesUntilTimeLinePresent(timelineId) // This cy.wait is here because we cannot do a pipe on a timeline as that will introduce multiple URL diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts index ebc3591adfe1..9ae02b1223f0 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/row_renderers.cy.ts @@ -77,9 +77,7 @@ describe('Row renderers', { tags: ['@ess', '@serverless'] }, () => { addNameToTimelineAndSave('Test'); cy.wait('@excludedNetflow').then((interception) => { - expect( - interception?.response?.body.data.persistTimeline.timeline.excludedRowRendererIds - ).to.contain('netflow'); + expect(interception?.response?.body.excludedRowRendererIds).to.contain('netflow'); }); // open modal, filter and check @@ -93,9 +91,7 @@ describe('Row renderers', { tags: ['@ess', '@serverless'] }, () => { saveTimeline(); cy.wait('@includedNetflow').then((interception) => { - expect( - interception?.response?.body.data.persistTimeline.timeline.excludedRowRendererIds - ).not.to.contain('netflow'); + expect(interception?.response?.body.excludedRowRendererIds).not.to.contain('netflow'); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/search_or_filter.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/search_or_filter.cy.ts index 617bfce6b5ca..25a21ecde3b9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/search_or_filter.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/search_or_filter.cy.ts @@ -66,7 +66,7 @@ describe('Timeline search and filters', { tags: ['@ess', '@serverless'] }, () => addNameToTimelineAndSave('Test'); cy.wait('@update').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); - cy.wrap(response?.body.data.persistTimeline.timeline.kqlMode).should('eql', 'filter'); + cy.wrap(response?.body.kqlMode).should('eql', 'filter'); cy.get(ADD_FILTER).should('exist'); }); }); @@ -76,7 +76,7 @@ describe('Timeline search and filters', { tags: ['@ess', '@serverless'] }, () => addNameToTimelineAndSave('Test'); cy.wait('@update').then(({ response }) => { cy.wrap(response?.statusCode).should('eql', 200); - cy.wrap(response?.body.data.persistTimeline.timeline.kqlMode).should('eql', 'search'); + cy.wrap(response?.body.kqlMode).should('eql', 'search'); cy.get(ADD_FILTER).should('not.exist'); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/timelines_table.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/timelines_table.cy.ts index fe0e60afa785..24c4c5ded0e8 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/timelines_table.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/timelines_table.cy.ts @@ -40,7 +40,7 @@ describe.skip('timeline overview search', { tags: ['@ess', '@serverless'] }, () // create timeline and favorite it // we're doing it through the UI because doing it through the API currently has a problem on MKI environment createTimeline(mockFavoritedTimeline) - .then((response) => response.body.data.persistTimeline.timeline.savedObjectId) + .then((response) => response.body.savedObjectId) .then((timelineId) => { refreshTimelinesUntilTimeLinePresent(timelineId); openTimelineById(timelineId); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/url_state.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/url_state.cy.ts index df80bb4c5036..3fa1d2dc5902 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/url_state.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/url_state.cy.ts @@ -24,8 +24,8 @@ describe('Open timeline', { tags: ['@serverless', '@ess'] }, () => { deleteTimelines(); visit(TIMELINES_URL); createTimeline().then((response) => { - timelineSavedObjectId = response.body.data.persistTimeline.timeline.savedObjectId; - return response.body.data.persistTimeline.timeline.savedObjectId; + timelineSavedObjectId = response.body.savedObjectId; + return response.body.savedObjectId; }); createRule(getNewRule()); visitWithTimeRange(ALERTS_URL); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts index 1aea82de6612..aab0cbd06b54 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/timeline.ts @@ -72,7 +72,7 @@ export const expectedExportedTimelineTemplate = ( templateResponse: Cypress.Response<PersistTimelineResponse>, username: string ) => { - const timelineTemplateBody = templateResponse.body.data.persistTimeline.timeline; + const timelineTemplateBody = templateResponse.body; return { savedObjectId: timelineTemplateBody.savedObjectId, @@ -118,7 +118,7 @@ export const expectedExportedTimeline = ( timelineResponse: Cypress.Response<PersistTimelineResponse>, username: string ) => { - const timelineBody = timelineResponse.body.data.persistTimeline.timeline; + const timelineBody = timelineResponse.body; return { savedObjectId: timelineBody.savedObjectId, diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts index 055d98a2efcf..b33ed2e14235 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/timelines.ts @@ -6,7 +6,6 @@ */ import type { - DeleteTimelinesResponse, GetTimelinesResponse, PatchTimelineResponse, } from '@kbn/security-solution-plugin/common/api/timeline'; @@ -168,7 +167,7 @@ export const getAllTimelines = () => export const deleteTimelines = () => { getAllTimelines().then(($timelines) => { const savedObjectIds = $timelines.body.timeline.map((timeline) => timeline.savedObjectId); - rootRequest<DeleteTimelinesResponse>({ + rootRequest({ method: 'DELETE', url: 'api/timeline', body: { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts index 580b96bfd234..e40563c78c8a 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts @@ -86,21 +86,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); await pageObjects.timeline.navigateToTimelineList(); - await pageObjects.timeline.openTimelineById( - timeline.data.persistTimeline.timeline.savedObjectId - ); + await pageObjects.timeline.openTimelineById(timeline.savedObjectId); await pageObjects.timeline.setDateRange('Last 1 year'); await pageObjects.timeline.waitForEvents(60_000 * 2); }); after(async () => { if (timeline) { - log.info( - `Cleaning up created timeline [${timeline.data.persistTimeline.timeline.title} - ${timeline.data.persistTimeline.timeline.savedObjectId}]` - ); - await timelineTestService.deleteTimeline( - timeline.data.persistTimeline.timeline.savedObjectId - ); + log.info(`Cleaning up created timeline [${timeline.title} - ${timeline.savedObjectId}]`); + await timelineTestService.deleteTimeline(timeline.savedObjectId); } }); diff --git a/x-pack/test/security_solution_ftr/services/timeline/index.ts b/x-pack/test/security_solution_ftr/services/timeline/index.ts index 8195f63ffc24..4288e073519c 100644 --- a/x-pack/test/security_solution_ftr/services/timeline/index.ts +++ b/x-pack/test/security_solution_ftr/services/timeline/index.ts @@ -9,7 +9,6 @@ import { Response } from 'superagent'; import { EndpointError } from '@kbn/security-solution-plugin/common/endpoint/errors'; import { TIMELINE_DRAFT_URL, TIMELINE_URL } from '@kbn/security-solution-plugin/common/constants'; import { - DeleteTimelinesResponse, GetDraftTimelinesResponse, PatchTimelineResponse, SavedTimeline, @@ -58,15 +57,13 @@ export class TimelineTestService extends FtrService { */ async createTimeline(title: string): Promise<PatchTimelineResponse> { // Create a new timeline draft - const createdTimeline = ( - await this.supertest - .post(TIMELINE_DRAFT_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ timelineType: 'default' }) - .then(this.getHttpResponseFailureHandler()) - .then((response) => response.body as GetDraftTimelinesResponse) - ).data.persistTimeline.timeline; + const createdTimeline = await this.supertest + .post(TIMELINE_DRAFT_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ timelineType: 'default' }) + .then(this.getHttpResponseFailureHandler()) + .then((response) => response.body as GetDraftTimelinesResponse); this.log.info('Draft timeline:'); this.log.indent(4, () => { @@ -137,7 +134,7 @@ export class TimelineTestService extends FtrService { savedObjectIds: Array.isArray(id) ? id : [id], }) .then(this.getHttpResponseFailureHandler()) - .then((response) => response.body as DeleteTimelinesResponse); + .then((response) => response.body); } /** @@ -183,7 +180,7 @@ export class TimelineTestService extends FtrService { const { expression, esQuery } = this.getEndpointAlertsKqlQuery(endpointAgentId); const updatedTimeline = await this.updateTimeline( - newTimeline.data.persistTimeline.timeline.savedObjectId, + newTimeline.savedObjectId, { title, kqlQuery: { @@ -197,7 +194,7 @@ export class TimelineTestService extends FtrService { }, savedSearchId: null, }, - newTimeline.data.persistTimeline.timeline.version + newTimeline.version ); return updatedTimeline; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts index 7ee5513e1352..7bd4b7f3e7a2 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts @@ -6,42 +6,40 @@ */ export const mockAutoOpsResponse = { - metrics: { - ingest_rate: [ - { - name: 'metrics-system.cpu-default', - error: null, - data: [ - [1726858530000, 13756849], - [1726862130000, 14657904], - ], - }, - { - name: 'logs-nginx.access-default', - error: null, - data: [ - [1726858530000, 12894623], - [1726862130000, 14436905], - ], - }, - ], - storage_retained: [ - { - name: 'metrics-system.cpu-default', - error: null, - data: [ - [1726858530000, 12576413], - [1726862130000, 13956423], - ], - }, - { - name: 'logs-nginx.access-default', - error: null, - data: [ - [1726858530000, 12894623], - [1726862130000, 14436905], - ], - }, - ], - }, + ingest_rate: [ + { + name: 'metrics-system.cpu-default', + error: null, + data: [ + [1726858530000, 13756849], + [1726862130000, 14657904], + ], + }, + { + name: 'logs-nginx.access-default', + error: null, + data: [ + [1726858530000, 12894623], + [1726862130000, 14436905], + ], + }, + ], + storage_retained: [ + { + name: 'metrics-system.cpu-default', + error: null, + data: [ + [1726858530000, 12576413], + [1726862130000, 13956423], + ], + }, + { + name: 'logs-nginx.access-default', + error: null, + data: [ + [1726858530000, 12894623], + [1726862130000, 14436905], + ], + }, + ], }; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts index d805b8ccff6f..d26b73f8689c 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; import { DataStreamsResponseBodySchemaBody } from '@kbn/data-usage-plugin/common/rest_types'; import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '@kbn/data-usage-plugin/common'; +import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -33,7 +33,10 @@ export default function ({ getService }: FtrProviderContext) { await svlDatastreamsHelpers.deleteDataStream(testDataStreamName); }); - it('returns created data streams', async () => { + // skipped because we filter out data streams with 0 storage size, + // and metering api does not pick up indexed data here + // TODO: route should potentially not depend solely on metering API + it.skip('returns created data streams', async () => { const res = await supertestAdminWithCookieCredentials .get(DATA_USAGE_DATA_STREAMS_API_ROUTE) .set('elastic-api-version', '1'); @@ -43,14 +46,5 @@ export default function ({ getService }: FtrProviderContext) { expect(foundStream?.storageSizeBytes).to.be(0); expect(res.statusCode).to.be(200); }); - it('returns system indices', async () => { - const res = await supertestAdminWithCookieCredentials - .get(DATA_USAGE_DATA_STREAMS_API_ROUTE) - .set('elastic-api-version', '1'); - const dataStreams: DataStreamsResponseBodySchemaBody = res.body; - const systemDataStreams = dataStreams.filter((stream) => stream.name.startsWith('.')); - expect(systemDataStreams.length).to.be.greaterThan(0); - expect(res.statusCode).to.be(200); - }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts index 8985757ab1ca..5460b750a5b2 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts @@ -16,6 +16,12 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; import { setupMockServer } from '../mock_api'; import { mockAutoOpsResponse } from '../mock_data'; +const now = new Date(); +const to = now.toISOString(); // Current time in ISO format + +const nowMinus24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000); +const from = nowMinus24Hours.toISOString(); + export default function ({ getService }: FtrProviderContext) { const svlDatastreamsHelpers = getService('svlDatastreamsHelpers'); const roleScopedSupertest = getService('roleScopedSupertest'); @@ -46,8 +52,8 @@ export default function ({ getService }: FtrProviderContext) { after(async () => await svlDatastreamsHelpers.deleteDataStream(testDataStreamName)); it('returns 400 with non-existent data streams', async () => { const requestBody: UsageMetricsRequestBody = { - from: 'now-24h/h', - to: 'now', + from, + to, metricTypes: ['ingest_rate', 'storage_retained'], dataStreams: ['invalid-data-stream'], }; @@ -61,8 +67,8 @@ export default function ({ getService }: FtrProviderContext) { it('returns 400 when requesting no data streams', async () => { const requestBody = { - from: 'now-24h/h', - to: 'now', + from, + to, metricTypes: ['ingest_rate'], dataStreams: [], }; @@ -76,8 +82,8 @@ export default function ({ getService }: FtrProviderContext) { it('returns 400 when requesting an invalid metric type', async () => { const requestBody = { - from: 'now-24h/h', - to: 'now', + from, + to, metricTypes: [testDataStreamName], dataStreams: ['datastream'], }; @@ -93,8 +99,8 @@ export default function ({ getService }: FtrProviderContext) { it('returns 200 with valid request', async () => { const requestBody: UsageMetricsRequestBody = { - from: 'now-24h/h', - to: 'now', + from, + to, metricTypes: ['ingest_rate', 'storage_retained'], dataStreams: [testDataStreamName], }; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts index 385040bd9754..cd6ebf4923ab 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts @@ -63,8 +63,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, ]; - // FLAKY: https://github.com/elastic/kibana/issues/197175 - describe.skip('/internal/observability_ai_assistant/chat/complete', function () { + describe('/internal/observability_ai_assistant/chat/complete', function () { // TODO: https://github.com/elastic/kibana/issues/192751 this.tags(['skipMKI']); let proxy: LlmProxy; @@ -186,7 +185,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { const parsedEvents = decodeEvents(receivedChunks.join('')); - expect(parsedEvents.map((event) => event.type)).to.eql([ + expect( + parsedEvents + .map((event) => event.type) + .filter((eventType) => eventType !== StreamingChatResponseEventType.BufferFlush) + ).to.eql([ StreamingChatResponseEventType.MessageAdd, StreamingChatResponseEventType.MessageAdd, StreamingChatResponseEventType.ChatCompletionChunk, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/traces/critical_path.ts b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/traces/critical_path.ts deleted file mode 100644 index 50aab783b4cf..000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/traces/critical_path.ts +++ /dev/null @@ -1,99 +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 'expect'; -import { apm, ApmFields, SynthtraceGenerator, timerange } from '@kbn/apm-synthtrace-client'; -import { compact, uniq } from 'lodash'; -import { Readable } from 'stream'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../../shared/services'; -import { APMFtrContextProvider } from '../common/services'; - -export default function ({ getService }: APMFtrContextProvider) { - const apmApiClient = getService('apmApiClient'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); - const synthtrace = getService('synthtrace'); - - const start = new Date('2022-01-01T00:00:00.000Z').getTime(); - const end = new Date('2022-01-01T00:15:00.000Z').getTime() - 1; - - async function fetchAndBuildCriticalPathTree( - synthtraceEsClient: ApmSynthtraceEsClient, - options: { - fn: () => SynthtraceGenerator<ApmFields>; - roleAuthc: RoleCredentials; - internalReqHeader: InternalRequestHeader; - } & ({ serviceName: string; transactionName: string } | {}) - ) { - const { fn, roleAuthc, internalReqHeader } = options; - - const generator = fn(); - - const unserialized = Array.from(generator); - const serialized = unserialized.flatMap((event) => event.serialize()); - const traceIds = compact(uniq(serialized.map((event) => event['trace.id']))); - - await synthtraceEsClient.index(Readable.from(unserialized)); - - return apmApiClient.slsUser({ - endpoint: 'POST /internal/apm/traces/aggregated_critical_path', - params: { - body: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - traceIds, - serviceName: 'serviceName' in options ? options.serviceName : null, - transactionName: 'transactionName' in options ? options.transactionName : null, - }, - }, - roleAuthc, - internalReqHeader, - }); - } - - describe('APM Aggregated critical path', () => { - let roleAuthc: RoleCredentials; - let internalReqHeader: InternalRequestHeader; - let synthtraceEsClient: ApmSynthtraceEsClient; - - before(async () => { - synthtraceEsClient = await synthtrace.createSynthtraceEsClient(); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - - after(async () => { - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - return synthtraceEsClient.clean(); - }); - - it('returns service map elements', async () => { - const java = apm - .service({ name: 'java', environment: 'production', agentName: 'java' }) - .instance('java'); - - const duration = 1000; - const rate = 10; - - const response = await fetchAndBuildCriticalPathTree(synthtraceEsClient, { - fn: () => - timerange(start, end) - .interval('15m') - .rate(rate) - .generator((timestamp) => { - return java.transaction('GET /api').timestamp(timestamp).duration(duration); - }), - roleAuthc, - internalReqHeader, - }); - - expect(response.status).toBe(200); - expect(response.body.criticalPath).not.toBeUndefined(); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/config.ts b/x-pack/test_serverless/api_integration/test_suites/observability/config.ts index 97a30d0f340f..e92609cc66dd 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; import { services as apmServices } from './apm_api_integration/common/services'; import { services as datasetQualityServices } from './dataset_quality_api_integration/common/services'; @@ -27,11 +27,11 @@ export default createTestConfig({ // useful for testing (also enabled in MKI QA) '--coreApp.allowDynamicConfigOverrides=true', '--xpack.dataUsage.enabled=true', + '--xpack.dataUsage.enableExperimental=[]', // dataUsage.autoops* config is set in kibana controller '--xpack.dataUsage.autoops.enabled=true', '--xpack.dataUsage.autoops.api.url=http://localhost:9000', `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, - `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/index.ts b/x-pack/test_serverless/api_integration/test_suites/observability/index.ts index 89543982f2d4..d3d8d4805695 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/index.ts @@ -12,7 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { this.tags(['esGate']); loadTestFile(require.resolve('./apm_api_integration/feature_flags.ts')); - loadTestFile(require.resolve('./apm_api_integration/traces/critical_path')); loadTestFile(require.resolve('./cases')); loadTestFile(require.resolve('./synthetics')); loadTestFile(require.resolve('./dataset_quality_api_integration')); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts index 1e3ca0eecfaf..e49a9ed45871 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts @@ -10108,6 +10108,11 @@ export default function ({ getService }: FtrProviderContext) { "saved_object:uptime-synthetics-api-key/find", "saved_object:uptime-synthetics-api-key/open_point_in_time", "saved_object:uptime-synthetics-api-key/close_point_in_time", + "saved_object:synthetics-private-location/bulk_get", + "saved_object:synthetics-private-location/get", + "saved_object:synthetics-private-location/find", + "saved_object:synthetics-private-location/open_point_in_time", + "saved_object:synthetics-private-location/close_point_in_time", "saved_object:synthetics-privates-locations/bulk_get", "saved_object:synthetics-privates-locations/get", "saved_object:synthetics-privates-locations/find", @@ -10399,6 +10404,11 @@ export default function ({ getService }: FtrProviderContext) { "saved_object:uptime-synthetics-api-key/find", "saved_object:uptime-synthetics-api-key/open_point_in_time", "saved_object:uptime-synthetics-api-key/close_point_in_time", + "saved_object:synthetics-private-location/bulk_get", + "saved_object:synthetics-private-location/get", + "saved_object:synthetics-private-location/find", + "saved_object:synthetics-private-location/open_point_in_time", + "saved_object:synthetics-private-location/close_point_in_time", "saved_object:synthetics-privates-locations/bulk_get", "saved_object:synthetics-privates-locations/get", "saved_object:synthetics-privates-locations/find", diff --git a/x-pack/test_serverless/api_integration/test_suites/search/config.ts b/x-pack/test_serverless/api_integration/test_suites/search/config.ts index 9f02dc98b88c..b662b105b54b 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -23,11 +23,11 @@ export default createTestConfig({ // useful for testing (also enabled in MKI QA) '--coreApp.allowDynamicConfigOverrides=true', '--xpack.dataUsage.enabled=true', + '--xpack.dataUsage.enableExperimental=[]', // dataUsage.autoops* config is set in kibana controller '--xpack.dataUsage.autoops.enabled=true', '--xpack.dataUsage.autoops.api.url=http://localhost:9000', `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, - `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/security/config.ts b/x-pack/test_serverless/api_integration/test_suites/security/config.ts index 52b933a22b08..d5d816dfcdf1 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -27,11 +27,11 @@ export default createTestConfig({ `--xpack.securitySolutionServerless.cloudSecurityUsageReportingTaskInterval=5s`, `--xpack.securitySolutionServerless.usageApi.url=http://localhost:8081`, '--xpack.dataUsage.enabled=true', + '--xpack.dataUsage.enableExperimental=[]', // dataUsage.autoops* config is set in kibana controller '--xpack.dataUsage.autoops.enabled=true', '--xpack.dataUsage.autoops.api.url=http://localhost:9000', `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, - `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/functional/page_objects/index.ts b/x-pack/test_serverless/functional/page_objects/index.ts index 2c894b28b065..e10e98529b8b 100644 --- a/x-pack/test_serverless/functional/page_objects/index.ts +++ b/x-pack/test_serverless/functional/page_objects/index.ts @@ -25,6 +25,7 @@ import { SvlSearchIndexDetailPageProvider } from './svl_search_index_detail_page import { SvlSearchElasticsearchStartPageProvider } from './svl_search_elasticsearch_start_page'; import { SvlApiKeysProvider } from './svl_api_keys'; import { SvlSearchCreateIndexPageProvider } from './svl_search_create_index_page'; +import { SvlSearchInferenceManagementPageProvider } from './svl_search_inference_management_page'; export const pageObjects = { ...xpackFunctionalPageObjects, @@ -47,4 +48,5 @@ export const pageObjects = { svlSearchElasticsearchStartPage: SvlSearchElasticsearchStartPageProvider, svlApiKeys: SvlApiKeysProvider, svlSearchCreateIndexPage: SvlSearchCreateIndexPageProvider, + svlSearchInferenceManagementPage: SvlSearchInferenceManagementPageProvider, }; diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_inference_management_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_inference_management_page.ts new file mode 100644 index 000000000000..4424238a9c80 --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/svl_search_inference_management_page.ts @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export function SvlSearchInferenceManagementPageProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); + + return { + InferenceTabularPage: { + async expectHeaderToBeExist() { + await testSubjects.existOrFail('allInferenceEndpointsPage'); + await testSubjects.existOrFail('api-documentation'); + await testSubjects.existOrFail('view-your-models'); + }, + + async expectTabularViewToBeLoaded() { + await testSubjects.existOrFail('search-field-endpoints'); + await testSubjects.existOrFail('type-field-endpoints'); + await testSubjects.existOrFail('service-field-endpoints'); + + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + expect(rows.length).to.equal(2); + + const elserEndpointCell = await rows[0].findByTestSubject('endpointCell'); + const elserEndpointName = await elserEndpointCell.getVisibleText(); + expect(elserEndpointName).to.contain('.elser-2-elasticsearch'); + + const elserProviderCell = await rows[0].findByTestSubject('providerCell'); + const elserProviderName = await elserProviderCell.getVisibleText(); + expect(elserProviderName).to.contain('Elasticsearch'); + expect(elserProviderName).to.contain('.elser_model_2'); + + const elserTypeCell = await rows[0].findByTestSubject('typeCell'); + const elserTypeName = await elserTypeCell.getVisibleText(); + expect(elserTypeName).to.contain('sparse_embedding'); + + const e5EndpointCell = await rows[1].findByTestSubject('endpointCell'); + const e5EndpointName = await e5EndpointCell.getVisibleText(); + expect(e5EndpointName).to.contain('.multilingual-e5-small-elasticsearch'); + + const e5ProviderCell = await rows[1].findByTestSubject('providerCell'); + const e5ProviderName = await e5ProviderCell.getVisibleText(); + expect(e5ProviderName).to.contain('Elasticsearch'); + expect(e5ProviderName).to.contain('.multilingual-e5-small'); + + const e5TypeCell = await rows[1].findByTestSubject('typeCell'); + const e5TypeName = await e5TypeCell.getVisibleText(); + expect(e5TypeName).to.contain('text_embedding'); + }, + + async expectPreconfiguredEndpointsCannotBeDeleted() { + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + + const elserDeleteAction = await rows[0].findByTestSubject('inferenceUIDeleteAction'); + const e5DeleteAction = await rows[1].findByTestSubject('inferenceUIDeleteAction'); + + expect(await elserDeleteAction.isEnabled()).to.be(false); + expect(await e5DeleteAction.isEnabled()).to.be(false); + }, + + async expectEndpointWithoutUsageTobeDelete() { + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + + const userCreatedEndpoint = await rows[2].findByTestSubject('inferenceUIDeleteAction'); + + await userCreatedEndpoint.click(); + await testSubjects.existOrFail('deleteModalForInferenceUI'); + await testSubjects.existOrFail('deleteModalInferenceEndpointName'); + + await testSubjects.click('confirmModalConfirmButton'); + + await testSubjects.existOrFail('inferenceEndpointTable'); + }, + + async expectEndpointWithUsageTobeDelete() { + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + + const userCreatedEndpoint = await rows[2].findByTestSubject('inferenceUIDeleteAction'); + + await userCreatedEndpoint.click(); + await testSubjects.existOrFail('deleteModalForInferenceUI'); + await testSubjects.existOrFail('deleteModalInferenceEndpointName'); + + const items = await testSubjects.findAll('usageItem'); + expect(items.length).to.equal(2); + + const index = await items[0].getVisibleText(); + const pipeline = await items[1].getVisibleText(); + + expect(index.includes('elser_index')).to.be(true); + expect(pipeline.includes('endpoint-1')).to.be(true); + + expect(await testSubjects.isEnabled('confirmModalConfirmButton')).to.be(false); + + await testSubjects.click('warningCheckbox'); + + expect(await testSubjects.isEnabled('confirmModalConfirmButton')).to.be(true); + await testSubjects.click('confirmModalConfirmButton'); + + await testSubjects.existOrFail('inferenceEndpointTable'); + }, + + async expectToCopyEndpoint() { + const table = await testSubjects.find('inferenceEndpointTable'); + const rows = await table.findAllByClassName('euiTableRow'); + + const elserCopyEndpointId = await rows[0].findByTestSubject( + 'inference-endpoints-action-copy-id-label' + ); + + await elserCopyEndpointId.click(); + expect((await browser.getClipboardValue()).includes('.elser-2-elasticsearch')).to.be(true); + }, + }, + }; +} diff --git a/x-pack/test_serverless/functional/services/svl_search_navigation.ts b/x-pack/test_serverless/functional/services/svl_search_navigation.ts index 1f27cf18ec8c..434a5bd4e42a 100644 --- a/x-pack/test_serverless/functional/services/svl_search_navigation.ts +++ b/x-pack/test_serverless/functional/services/svl_search_navigation.ts @@ -47,5 +47,10 @@ export function SvlSearchNavigationServiceProvider({ }); await testSubjects.existOrFail('searchIndicesDetailsPage', { timeout: 2000 }); }, + async navigateToInferenceManagementPage(expectRedirect: boolean = false) { + await PageObjects.common.navigateToApp('searchInferenceEndpoints', { + shouldLoginIfPrompted: false, + }); + }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts b/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts index fa6e4199bdc0..0b59557229cd 100644 --- a/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts +++ b/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts @@ -24,8 +24,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.svlManagementPage.clickDataUsageManagementCard(); }); - after(async () => {}); - it('renders data usage page', async () => { await retry.waitFor('page to be visible', async () => { return await testSubjects.exists('DataUsagePage'); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_additional_cell_actions.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_additional_cell_actions.ts index 157100ccb903..0d39a9139e68 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_additional_cell_actions.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_additional_cell_actions.ts @@ -15,6 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'discover', 'header', 'unifiedFieldList', + 'context', 'svlCommonPage', ]); const dataViews = getService('dataViews'); @@ -37,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('example-data-source-action'); let alert = await browser.getAlert(); try { @@ -45,7 +46,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } finally { await alert?.dismiss(); } - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('another-example-data-source-action'); alert = await browser.getAlert(); try { @@ -65,7 +66,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 2); + await dataGrid.clickCellExpandButton(0, { columnName: 'message' }); expect(await dataGrid.cellExpandPopoverActionExists('example-data-source-action')).to.be( true ); @@ -84,7 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); expect(await dataGrid.cellExpandPopoverActionExists('example-data-source-action')).to.be( false ); @@ -94,8 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/193367 - describe.skip('data view mode', () => { + describe('data view mode', () => { it('should render additional cell actions for logs data source', async () => { await PageObjects.common.navigateToActualUrl('discover', undefined, { ensureCurrentUrl: false, @@ -103,7 +103,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataViews.switchTo('my-example-logs'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('example-data-source-action'); let alert = await browser.getAlert(); try { @@ -111,7 +111,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } finally { await alert?.dismiss(); } - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('another-example-data-source-action'); alert = await browser.getAlert(); try { @@ -127,7 +127,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.refresh(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await PageObjects.context.waitUntilContextLoadingHasFinished(); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('example-data-source-action'); alert = await browser.getAlert(); try { @@ -135,7 +136,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } finally { await alert?.dismiss(); } - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); await dataGrid.clickCellExpandPopoverAction('another-example-data-source-action'); alert = await browser.getAlert(); try { @@ -152,7 +153,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataViews.switchTo('my-example-logs'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 2); + await dataGrid.clickCellExpandButton(0, { columnName: 'message' }); expect(await dataGrid.cellExpandPopoverActionExists('example-data-source-action')).to.be( true ); @@ -168,7 +169,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataViews.switchTo('my-example-metrics'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await dataGrid.clickCellExpandButtonExcludingControlColumns(0, 0); + await dataGrid.clickCellExpandButton(0, { columnName: '@timestamp' }); expect(await dataGrid.cellExpandPopoverActionExists('example-data-source-action')).to.be( false ); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts index b4c73c56a484..dd975d5425de 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts @@ -38,9 +38,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; describe('discover esql view', function () { - // see details: https://github.com/elastic/kibana/issues/188816 - this.tags(['failsOnMKI']); - before(async () => { await kibanaServer.savedObjects.cleanStandardList(); log.debug('load kibana index with default index pattern'); @@ -141,8 +138,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should perform test query correctly', async function () { await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.selectTextBaseLang(); - const testQuery = `from logstash-* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const testQuery = `from logstash-* | sort @timestamp | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); @@ -157,7 +158,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render when switching to a time range with no data, then back to a time range with data', async () => { await PageObjects.discover.selectTextBaseLang(); - const testQuery = `from logstash-* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + const testQuery = `from logstash-* | sort @timestamp | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -183,7 +187,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - const testQuery = `from logstash* | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; + const testQuery = `from logstash* | sort @timestamp | limit 10 | stats countB = count(bytes) by geo.dest | sort countB`; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -256,8 +260,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/189636 - describe.skip('switch modal', () => { + describe('switch modal', () => { beforeEach(async () => { await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -302,6 +305,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.saveSearch('esql_test2'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); const testQuery = 'from logstash-* | limit 100 | drop @timestamp'; await monacoEditor.setCodeEditorValue(testQuery); await testSubjects.click('querySubmitButton'); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/group3/_request_counts.ts b/x-pack/test_serverless/functional/test_suites/common/discover/group3/_request_counts.ts index ff0f2eadf3e2..6402b5ce4737 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/group3/_request_counts.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/group3/_request_counts.ts @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { savedSearchesRequests?: number; setQuery: (query: string) => Promise<void>; }) => { - it('should send 2 search requests (documents + chart) on page load', async () => { + it('should send no more than 2 search requests (documents + chart) on page load', async () => { await browser.refresh(); await browser.execute(async () => { performance.setResourceTimingBufferSize(Number.MAX_SAFE_INTEGER); @@ -105,20 +105,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(searchCount).to.be(2); }); - it('should send 2 requests (documents + chart) when refreshing', async () => { + it('should send no more than 2 requests (documents + chart) when refreshing', async () => { await expectSearches(type, 2, async () => { await queryBar.clickQuerySubmitButton(); }); }); - it('should send 2 requests (documents + chart) when changing the query', async () => { + it('should send no more than 2 requests (documents + chart) when changing the query', async () => { await expectSearches(type, 2, async () => { await setQuery(query1); await queryBar.clickQuerySubmitButton(); }); }); - it('should send 2 requests (documents + chart) when changing the time range', async () => { + it('should send no more than 2 requests (documents + chart) when changing the time range', async () => { await expectSearches(type, 2, async () => { await PageObjects.timePicker.setAbsoluteRange( 'Sep 21, 2015 @ 06:31:44.000', @@ -127,7 +127,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should send 2 requests (documents + chart) when toggling the chart visibility', async () => { + it('should send no more than 2 requests (documents + chart) when toggling the chart visibility', async () => { await expectSearches(type, 2, async () => { await PageObjects.discover.toggleChartVisibility(); }); @@ -136,7 +136,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should send 2 requests for saved search changes', async () => { + it('should send no more than 2 requests for saved search changes', async () => { await setQuery(query1); await queryBar.clickQuerySubmitButton(); await PageObjects.timePicker.setAbsoluteRange( @@ -181,7 +181,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { setQuery: (query) => queryBar.setQuery(query), }); - it('should send 2 requests (documents + chart) when adding a filter', async () => { + it('should send no more than 2 requests (documents + chart) when adding a filter', async () => { await expectSearches(type, 2, async () => { await filterBar.addFilter({ field: 'extension', @@ -191,31 +191,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should send 2 requests (documents + chart) when sorting', async () => { + it('should send no more than 2 requests (documents + chart) when sorting', async () => { await expectSearches(type, 2, async () => { await PageObjects.discover.clickFieldSort('@timestamp', 'Sort Old-New'); }); }); - it('should send 2 requests (documents + chart) when changing to a breakdown field without an other bucket', async () => { + it('should send no more than 2 requests (documents + chart) when changing to a breakdown field without an other bucket', async () => { await expectSearches(type, 2, async () => { await PageObjects.discover.chooseBreakdownField('type'); }); }); - it('should send 3 requests (documents + chart + other bucket) when changing to a breakdown field with an other bucket', async () => { + it('should send no more than 3 requests (documents + chart + other bucket) when changing to a breakdown field with an other bucket', async () => { await expectSearches(type, 3, async () => { await PageObjects.discover.chooseBreakdownField('extension.raw'); }); }); - it('should send 2 requests (documents + chart) when changing the chart interval', async () => { + it('should send no more than 2 requests (documents + chart) when changing the chart interval', async () => { await expectSearches(type, 2, async () => { await PageObjects.discover.setChartInterval('Day'); }); }); - it('should send 2 requests (documents + chart) when changing the data view', async () => { + it('should send no more than 2 requests (documents + chart) when changing the data view', async () => { await expectSearches(type, 2, async () => { await dataViews.switchToAndValidate('long-window-logstash-*'); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/visualize_field.ts b/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/visualize_field.ts index 42d1ad33b30f..7d145542e588 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/visualize_field.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/visualize_field.ts @@ -48,7 +48,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { beforeEach(async () => { await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); await setDiscoverTimeRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); }); after(async () => { @@ -64,7 +68,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.unifiedFieldList.expectFieldListItemVisualize('bytes'); }); - it('visualizes field to Lens and loads fields to the dimesion editor', async () => { + it('visualizes field to Lens and loads fields to the dimension editor', async () => { await PageObjects.unifiedFieldList.findFieldByName('bytes'); await PageObjects.unifiedFieldList.clickFieldListItemVisualize('bytes'); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts index 0d8f09112362..a88d1977a0ab 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts @@ -15,6 +15,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const esDeleteAllIndices = getService('esDeleteAllIndices'); const testIndexName = `index-ftr-test-${Math.random()}`; const es = getService('es'); + const retry = getService('retry'); describe('Indices', function () { this.tags(['skipSvlSearch']); @@ -73,7 +74,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('can delete index', async () => { await pageObjects.indexManagement.confirmDeleteModalIsVisible(); - await pageObjects.indexManagement.expectIndexIsDeleted(testIndexName); + await retry.try(async () => { + await pageObjects.indexManagement.expectIndexIsDeleted(testIndexName); + }); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/dashboard.ts b/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/dashboard.ts index eff79084e9de..8b4a0019433b 100644 --- a/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/dashboard.ts +++ b/x-pack/test_serverless/functional/test_suites/common/visualizations/group3/open_in_lens/tsvb/dashboard.ts @@ -113,7 +113,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const titles = await dashboard.getPanelTitles(); expect(titles[0]).to.be(`${visTitle} (converted)`); - await panelActions.expectNotLinkedToLibrary(titles[0], true); + await panelActions.expectNotLinkedToLibrary(titles[0]); await dashboardBadgeActions.expectExistsTimeRangeBadgeAction(); await panelActions.removePanel(); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/config.ts b/x-pack/test_serverless/functional/test_suites/observability/config.ts index 9fffd5623f0a..40ca0c915d2a 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/config.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -20,11 +20,11 @@ export default createTestConfig({ esServerArgs: ['xpack.ml.dfa.enabled=false'], kbnServerArgs: [ '--xpack.dataUsage.enabled=true', + '--xpack.dataUsage.enableExperimental=[]', // dataUsage.autoops* config is set in kibana controller '--xpack.dataUsage.autoops.enabled=true', '--xpack.dataUsage.autoops.api.url=http://localhost:9000', `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, - `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts b/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts index bdd5d443b359..8073a7c5fcc7 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts @@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await ml.api.cleanMlIndices(); + await ml.api.cleanAnomalyDetection(); await ml.testResources.cleanMLSavedObjects(); await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.savedObjects.cleanStandardList(); diff --git a/x-pack/test_serverless/functional/test_suites/search/config.ts b/x-pack/test_serverless/functional/test_suites/search/config.ts index aef26951908d..dbf69f1c7091 100644 --- a/x-pack/test_serverless/functional/test_suites/search/config.ts +++ b/x-pack/test_serverless/functional/test_suites/search/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -23,12 +23,12 @@ export default createTestConfig({ `--xpack.cloud.serverless.project_name=ES3_FTR_TESTS`, `--xpack.cloud.deployment_url=/projects/elasticsearch/fakeprojectid`, '--xpack.dataUsage.enabled=true', + '--xpack.dataUsage.enableExperimental=[]', // dataUsage.autoops* config is set in kibana controller '--xpack.dataUsage.autoops.enabled=true', '--xpack.dataUsage.autoops.api.url=http://localhost:9000', `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, - `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], apps: { serverlessElasticsearch: { @@ -46,5 +46,8 @@ export default createTestConfig({ elasticsearchIndices: { pathname: '/app/elasticsearch/indices', }, + searchInferenceEndpoints: { + pathname: '/app/elasticsearch/relevance/inference_endpoints', + }, }, }); diff --git a/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts b/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts index 5ada9a50d45c..95b35381a3d1 100644 --- a/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts +++ b/x-pack/test_serverless/functional/test_suites/search/connectors/connectors_overview.ts @@ -18,7 +18,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]); const testSubjects = getService('testSubjects'); const browser = getService('browser'); - describe('connectors', function () { + // FLAKY: https://github.com/elastic/kibana/issues/197019 + describe.skip('connectors', function () { before(async () => { await pageObjects.svlCommonPage.loginWithRole('developer'); await pageObjects.svlCommonNavigation.sidenav.clickLink({ diff --git a/x-pack/test_serverless/functional/test_suites/search/dashboards/build_dashboard.ts b/x-pack/test_serverless/functional/test_suites/search/dashboards/build_dashboard.ts index 8f97f53c6275..aefd4c6da983 100644 --- a/x-pack/test_serverless/functional/test_suites/search/dashboards/build_dashboard.ts +++ b/x-pack/test_serverless/functional/test_suites/search/dashboards/build_dashboard.ts @@ -56,7 +56,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('can edit a Lens panel by value and save changes', async () => { await PageObjects.dashboard.waitForRenderComplete(); - await dashboardPanelActions.clickEdit(); + await dashboardPanelActions.navigateToEditorFromFlyout(); await PageObjects.lens.switchToVisualization('pie'); await PageObjects.lens.saveAndReturn(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/x-pack/test_serverless/functional/test_suites/search/index.ts b/x-pack/test_serverless/functional/test_suites/search/index.ts index 99190ae0cc07..dd7021aebe80 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.ts @@ -28,5 +28,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./search_playground/playground_overview')); loadTestFile(require.resolve('./ml')); loadTestFile(require.resolve('./custom_role_access')); + loadTestFile(require.resolve('./inference_management')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/inference_management.ts b/x-pack/test_serverless/functional/test_suites/search/inference_management.ts new file mode 100644 index 000000000000..5293655ef092 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/inference_management.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { testHasEmbeddedConsole } from './embedded_console'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects([ + 'svlCommonPage', + 'embeddedConsole', + 'svlSearchInferenceManagementPage', + 'header', + ]); + const svlSearchNavigation = getService('svlSearchNavigation'); + const browser = getService('browser'); + const ml = getService('ml'); + + describe('Serverless Inference Management UI', function () { + const endpoint = 'endpoint-1'; + const taskType = 'sparse_embedding'; + const modelConfig = { + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + }, + }; + + before(async () => { + await pageObjects.svlCommonPage.loginWithRole('developer'); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + beforeEach(async () => { + await svlSearchNavigation.navigateToInferenceManagementPage(); + }); + + describe('endpoint tabular view', () => { + it('is loaded successfully', async () => { + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectHeaderToBeExist(); + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectTabularViewToBeLoaded(); + }); + + it('preconfigured endpoints can not be deleted', async () => { + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectPreconfiguredEndpointsCannotBeDeleted(); + }); + }); + + describe('copy endpoint id action', () => { + it('can copy an endpoint id', async () => { + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectToCopyEndpoint(); + }); + }); + + describe('delete action', () => { + const usageIndex = 'elser_index'; + beforeEach(async () => { + await ml.api.createInferenceEndpoint(endpoint, taskType, modelConfig); + await browser.refresh(); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + after(async () => { + await ml.api.deleteIndices(usageIndex); + await ml.api.deleteIngestPipeline(endpoint); + }); + + it('deletes modal successfully without any usage', async () => { + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectEndpointWithoutUsageTobeDelete(); + }); + + it('deletes modal successfully with usage', async () => { + const indexMapping: estypes.MappingTypeMapping = { + properties: { + content: { + type: 'text', + }, + content_embedding: { + type: 'semantic_text', + inference_id: endpoint, + }, + }, + }; + await ml.api.createIngestPipeline(endpoint); + await ml.api.createIndex(usageIndex, indexMapping); + + await pageObjects.svlSearchInferenceManagementPage.InferenceTabularPage.expectEndpointWithUsageTobeDelete(); + }); + }); + + it('has embedded dev console', async () => { + await testHasEmbeddedConsole(pageObjects); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts index 24009866b2b1..74d1c59cbc8e 100644 --- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts @@ -95,17 +95,17 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); // check Relevance // > Inference Endpoints - // await solutionNavigation.sidenav.clickLink({ - // deepLinkId: 'searchInferenceEndpoints', - // }); - // await solutionNavigation.sidenav.expectLinkActive({ - // deepLinkId: 'searchInferenceEndpoints', - // }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Relevance' }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Inference Endpoints' }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ - // deepLinkId: 'searchInferenceEndpoints', - // }); + await solutionNavigation.sidenav.clickLink({ + deepLinkId: 'searchInferenceEndpoints', + }); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'searchInferenceEndpoints', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Relevance' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Inference Endpoints' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'searchInferenceEndpoints', + }); // check Analyze // > Discover @@ -245,8 +245,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await solutionNavigation.sidenav.expectLinkExists({ text: 'Build' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Dev Tools' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Playground' }); - // await solutionNavigation.sidenav.expectLinkExists({ text: 'Relevance' }); - // await solutionNavigation.sidenav.expectLinkExists({ text: 'Inference Endpoints' }); + await solutionNavigation.sidenav.expectLinkExists({ text: 'Relevance' }); + await solutionNavigation.sidenav.expectLinkExists({ text: 'Inference Endpoints' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Analyze' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Discover' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Dashboards' }); @@ -270,8 +270,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { 'build', 'dev_tools', 'searchPlayground', - // 'relevance', - // 'searchInferenceEndpoints', + 'relevance', + 'searchInferenceEndpoints', 'analyze', 'discover', 'dashboards', diff --git a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts index 946afe08a0b7..9e1e36d10b17 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts @@ -106,7 +106,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); describe('without any indices', () => { - it('hide no create index button when index added', async () => { + it('hide create index button when index added', async () => { await createIndex(); await pageObjects.searchPlayground.PlaygroundStartChatPage.expectOpenFlyoutAndSelectIndex(); }); @@ -121,6 +121,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { before(async () => { await createConnector(); await createIndex(); + }); + + beforeEach(async () => { await pageObjects.searchPlayground.session.setSession(); await browser.refresh(); }); @@ -129,6 +132,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.searchPlayground.PlaygroundStartChatPage.expectToSelectIndicesAndLoadChat(); }); + it('load start page after removing selected index', async () => { + await pageObjects.searchPlayground.PlaygroundStartChatPage.expectToSelectIndicesAndLoadChat(); + await esArchiver.unload(esArchiveIndex); + await browser.refresh(); + await pageObjects.searchPlayground.PlaygroundStartChatPage.expectCreateIndexButtonToExists(); + }); + after(async () => { await removeOpenAIConnector?.(); await esArchiver.unload(esArchiveIndex); diff --git a/x-pack/test_serverless/functional/test_suites/security/config.ts b/x-pack/test_serverless/functional/test_suites/security/config.ts index 1693a07b0e84..0a6a3a061d1f 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -20,11 +20,11 @@ export default createTestConfig({ esServerArgs: ['xpack.ml.nlp.enabled=true'], kbnServerArgs: [ '--xpack.dataUsage.enabled=true', + '--xpack.dataUsage.enableExperimental=[]', // dataUsage.autoops* config is set in kibana controller '--xpack.dataUsage.autoops.enabled=true', '--xpack.dataUsage.autoops.api.url=http://localhost:9000', `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, - `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts index 7611061398f1..017ef1b2fa82 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts @@ -78,12 +78,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.navigateToIntegrationCspList(); await pageObjects.header.waitUntilLoadingHasFinished(); - expect(await cisIntegration.getFirstCspmIntegrationPageIntegration()).to.be( + expect(await cisIntegration.getFirstCspmIntegrationPageAgentlessIntegration()).to.be( integrationPolicyName ); - expect(await cisIntegration.getFirstCspmIntegrationPageAgent()).to.be( - `Agentless policy for ${integrationPolicyName}` - ); + expect(await cisIntegration.getFirstCspmIntegrationPageAgentlessStatus()).to.be('Pending'); }); it(`should create default agent-based agent`, async () => { diff --git a/x-pack/test_serverless/functional/test_suites/security/ml/anomaly_detection_jobs_list.ts b/x-pack/test_serverless/functional/test_suites/security/ml/anomaly_detection_jobs_list.ts index 9e1154ea09bb..e8f3f5e1796f 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ml/anomaly_detection_jobs_list.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ml/anomaly_detection_jobs_list.ts @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await ml.api.cleanMlIndices(); + await ml.api.cleanAnomalyDetection(); await ml.testResources.cleanMLSavedObjects(); await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.savedObjects.cleanStandardList(); diff --git a/x-pack/test_serverless/functional/test_suites/security/ml/data_frame_analytics_jobs_list.ts b/x-pack/test_serverless/functional/test_suites/security/ml/data_frame_analytics_jobs_list.ts index d3caa3425f75..110cf64e07a1 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ml/data_frame_analytics_jobs_list.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ml/data_frame_analytics_jobs_list.ts @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { - await ml.api.cleanMlIndices(); + await ml.api.cleanAnomalyDetection(); await ml.testResources.cleanMLSavedObjects(); }); diff --git a/x-pack/test_serverless/kibana.jsonc b/x-pack/test_serverless/kibana.jsonc index 2ad994687ff9..a25f8d1fc141 100644 --- a/x-pack/test_serverless/kibana.jsonc +++ b/x-pack/test_serverless/kibana.jsonc @@ -2,5 +2,7 @@ "type": "test-helper", "id": "@kbn/test-suites-serverless", "owner": [], - "devOnly": true + "devOnly": true, + "group": "platform", + "visibility": "shared" } diff --git a/yarn.lock b/yarn.lock index 849a31b83316..61c6e6098f1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3492,13 +3492,6 @@ js-yaml "^3.13.1" resolve-from "^5.0.0" -"@istanbuljs/nyc-config-typescript@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz#1f5235b28540a07219ae0dd42014912a0b19cf89" - integrity sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - "@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" @@ -4613,6 +4606,10 @@ version "0.0.0" uid "" +"@kbn/core-http-server-utils@link:packages/core/http/core-http-server-utils": + version "0.0.0" + uid "" + "@kbn/core-http-server@link:packages/core/http/core-http-server": version "0.0.0" uid "" @@ -5301,6 +5298,10 @@ version "0.0.0" uid "" +"@kbn/dependency-usage@link:packages/kbn-dependency-usage": + version "0.0.0" + uid "" + "@kbn/dev-cli-errors@link:packages/kbn-dev-cli-errors": version "0.0.0" uid "" @@ -5433,6 +5434,10 @@ version "0.0.0" uid "" +"@kbn/entityManager-app-plugin@link:x-pack/plugins/observability_solution/entity_manager_app": + version "0.0.0" + uid "" + "@kbn/entityManager-plugin@link:x-pack/plugins/entity_manager": version "0.0.0" uid "" @@ -6917,6 +6922,10 @@ version "0.0.0" uid "" +"@kbn/search-navigation@link:x-pack/plugins/search_solution/search_navigation": + version "0.0.0" + uid "" + "@kbn/search-notebooks@link:x-pack/plugins/search_notebooks": version "0.0.0" uid "" @@ -7473,6 +7482,10 @@ version "0.0.0" uid "" +"@kbn/streams-app-plugin@link:x-pack/plugins/streams_app": + version "0.0.0" + uid "" + "@kbn/streams-plugin@link:x-pack/plugins/streams": version "0.0.0" uid "" @@ -10797,10 +10810,10 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@^6.5.0": - version "6.5.0" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz#50484da3f80fb222a853479f618a9ce5c47bfe54" - integrity sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA== +"@testing-library/jest-dom@^6.6.3": + version "6.6.3" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2" + integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== dependencies: "@adobe/css-tools" "^4.4.0" aria-query "^5.0.0" @@ -13102,11 +13115,23 @@ acorn-import-attributes@^1.9.5: resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== +acorn-jsx-walk@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz#a5ed648264e68282d7c2aead80216bfdf232573a" + integrity sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA== + acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-loose@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/acorn-loose/-/acorn-loose-8.4.0.tgz#26d3e219756d1e180d006f5bcc8d261a28530f55" + integrity sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ== + dependencies: + acorn "^8.11.0" + acorn-node@^1.6.1: version "1.8.2" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" @@ -13121,10 +13146,12 @@ acorn-walk@^7.0.0, acorn-walk@^7.2.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0, acorn-walk@^8.3.4: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" acorn@^6.4.1: version "6.4.2" @@ -13136,10 +13163,10 @@ acorn@^7.0.0, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.1.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.2, acorn@^8.9.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.11.0, acorn@^8.12.1, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.2, acorn@^8.9.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.13.0.tgz#2a30d670818ad16ddd6a35d3842dacec9e5d7ca3" + integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w== address@^1.0.1: version "1.1.2" @@ -13264,15 +13291,15 @@ ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.0.1, ajv@^8.12.0, ajv@^8.8.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" - integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== +ajv@^8.0.0, ajv@^8.0.1, ajv@^8.12.0, ajv@^8.17.1, ajv@^8.8.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: - fast-deep-equal "^3.1.1" + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.2.2" ansi-align@^3.0.0: version "3.0.1" @@ -13433,22 +13460,6 @@ arch@^2.2.0: resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== -archiver-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" - integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== - dependencies: - glob "^7.1.4" - graceful-fs "^4.2.0" - lazystream "^1.0.0" - lodash.defaults "^4.2.0" - lodash.difference "^4.5.0" - lodash.flatten "^4.4.0" - lodash.isplainobject "^4.0.6" - lodash.union "^4.6.0" - normalize-path "^3.0.0" - readable-stream "^2.0.0" - archiver-utils@^5.0.0, archiver-utils@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-5.0.2.tgz#63bc719d951803efc72cf961a56ef810760dd14d" @@ -13462,19 +13473,6 @@ archiver-utils@^5.0.0, archiver-utils@^5.0.2: normalize-path "^3.0.0" readable-stream "^4.0.0" -archiver@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.1.tgz#21e92811d6f09ecfce649fbefefe8c79e57cbbb6" - integrity sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w== - dependencies: - archiver-utils "^2.1.0" - async "^3.2.3" - buffer-crc32 "^0.2.1" - readable-stream "^3.6.0" - readdir-glob "^1.0.0" - tar-stream "^2.2.0" - zip-stream "^4.1.0" - archiver@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/archiver/-/archiver-7.0.1.tgz#c9d91c350362040b8927379c7aa69c0655122f61" @@ -14614,16 +14612,16 @@ buffer-builder@^0.2.0: resolved "https://registry.yarnpkg.com/buffer-builder/-/buffer-builder-0.2.0.tgz#3322cd307d8296dab1f604618593b261a3fade8f" integrity sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg== -buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - buffer-crc32@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-1.0.0.tgz#a10993b9055081d55304bd9feb4a072de179f405" integrity sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w== +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -15122,10 +15120,10 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^130.0.4: - version "130.0.4" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-130.0.4.tgz#55225ddfec428e306116507651f5a24fdb299bd6" - integrity sha512-lpR+PWXszij1k4Ig3t338Zvll9HtCTiwoLM7n4pCCswALHxzmgwaaIFBh3rt9+5wRk9D07oFblrazrBxwaYYAQ== +chromedriver@^131.0.0: + version "131.0.1" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-131.0.1.tgz#bfbf47f6c2ad7a65c154ff47d321bd8c33b52a77" + integrity sha512-LHRh+oaNU1WowJjAkWsviN8pTzQYJDbv/FvJyrQ7XhjKdIzVh/s3GV1iU7IjMTsxIQnBsTjx+9jWjzCWIXC7ug== dependencies: "@testim/chrome-version" "^1.1.4" axios "^1.7.4" @@ -15597,16 +15595,6 @@ component-emitter@^1.2.1, component-emitter@^1.3.0: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== -compress-commons@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" - integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== - dependencies: - buffer-crc32 "^0.2.13" - crc32-stream "^4.0.2" - normalize-path "^3.0.0" - readable-stream "^3.6.0" - compress-commons@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-6.0.2.tgz#26d31251a66b9d6ba23a84064ecd3a6a71d2609e" @@ -15893,14 +15881,6 @@ crc-32@^1.2.0: exit-on-epipe "~1.0.1" printj "~1.1.0" -crc32-stream@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" - integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== - dependencies: - crc-32 "^1.2.0" - readable-stream "^3.4.0" - crc32-stream@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-6.0.0.tgz#8529a3868f8b27abb915f6c3617c0fadedbf9430" @@ -17014,6 +16994,34 @@ dependency-check@^4.1.0: read-package-json "^2.0.10" resolve "^1.1.7" +dependency-cruiser@^16.4.2: + version "16.4.2" + resolved "https://registry.yarnpkg.com/dependency-cruiser/-/dependency-cruiser-16.4.2.tgz#586487e1ac355912a0ad2310b830b63054733e01" + integrity sha512-mQZM95WwIvKzYYdj+1RgIBuJ6qbr1cfyzTt62dDJVrWAShfhV9IEkG/Xv4S2iD5sT+Gt3oFWyZjwNufAhcbtWA== + dependencies: + acorn "^8.12.1" + acorn-jsx "^5.3.2" + acorn-jsx-walk "^2.0.0" + acorn-loose "^8.4.0" + acorn-walk "^8.3.4" + ajv "^8.17.1" + commander "^12.1.0" + enhanced-resolve "^5.17.1" + ignore "^6.0.2" + interpret "^3.1.1" + is-installed-globally "^1.0.0" + json5 "^2.2.3" + memoize "^10.0.0" + picocolors "^1.1.0" + picomatch "^4.0.2" + prompts "^2.4.2" + rechoir "^0.8.0" + safe-regex "^2.1.1" + semver "^7.6.3" + teamcity-service-messages "^0.1.14" + tsconfig-paths-webpack-plugin "^4.1.0" + watskeburt "^4.1.0" + dependency-tree@^10.0.9: version "10.0.9" resolved "https://registry.yarnpkg.com/dependency-tree/-/dependency-tree-10.0.9.tgz#0c6c0dbeb0c5ec2cf83bf755f30e9cb12e7b4ac7" @@ -17712,10 +17720,10 @@ enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" -enhanced-resolve@^5.14.1, enhanced-resolve@^5.16.0: - version "5.16.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787" - integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA== +enhanced-resolve@^5.14.1, enhanced-resolve@^5.16.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -18812,6 +18820,11 @@ fast-text-encoding@^1.0.0: resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== +fast-uri@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" + integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== + fast-xml-parser@4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" @@ -19821,6 +19834,13 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, gl once "^1.3.0" path-is-absolute "^1.0.0" +global-directory@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/global-directory/-/global-directory-4.0.1.tgz#4d7ac7cfd2cb73f304c53b8810891748df5e361e" + integrity sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q== + dependencies: + ini "4.1.1" + global-dirs@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" @@ -20831,6 +20851,11 @@ ignore@^5.0.5, ignore@^5.1.1, ignore@^5.2.0, ignore@^5.3.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== +ignore@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283" + integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -20927,6 +20952,11 @@ ini@2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== +ini@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" + integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== + ini@^1.3.5, ini@~1.3.0: version "1.3.7" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" @@ -21014,6 +21044,11 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + intl-messageformat@10.5.12: version "10.5.12" resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.12.tgz#a0c1a20da896b7a1f4ba1b59c8ba5d9943c29c3f" @@ -21054,11 +21089,6 @@ ip-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== -ip@^1.1.8: - version "1.1.9" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396" - integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== - ip@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" @@ -21343,6 +21373,14 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" integrity sha1-bghLvJIGH7sJcexYts5tQE4k2mk= +is-installed-globally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-1.0.0.tgz#08952c43758c33d815692392f7f8437b9e436d5a" + integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ== + dependencies: + global-directory "^4.0.1" + is-path-inside "^4.0.0" + is-installed-globally@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" @@ -21459,6 +21497,11 @@ is-path-inside@^3.0.2, is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-path-inside@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-4.0.0.tgz#805aeb62c47c1b12fc3fd13bfb3ed1e7430071db" + integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA== + is-plain-obj@2.1.0, is-plain-obj@^2.0.0, is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" @@ -23179,26 +23222,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= - -lodash.difference@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" - integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= - lodash.escape@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= - lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" @@ -23299,11 +23327,6 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= -lodash.union@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= - lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -23842,6 +23865,13 @@ memoize-one@^6.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== +memoize@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/memoize/-/memoize-10.0.0.tgz#43fa66b2022363c7c50cf5dfab732a808a3d7147" + integrity sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA== + dependencies: + mimic-function "^5.0.0" + memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" @@ -25687,12 +25717,11 @@ pac-proxy-agent@^7.0.1: socks-proxy-agent "^8.0.2" pac-resolver@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.0.tgz#79376f1ca26baf245b96b34c339d79bff25e900c" - integrity sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg== + version "7.0.1" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.1.tgz#54675558ea368b64d210fd9c92a640b5f3b8abb6" + integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== dependencies: degenerator "^5.0.0" - ip "^1.1.8" netmask "^2.0.2" package-hash@^4.0.0: @@ -26004,16 +26033,21 @@ picocolors@^0.2.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.0, picocolors@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + pidusage@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-3.0.2.tgz#6faa5402b2530b3af2cf93d13bcf202889724a53" @@ -26814,7 +26848,7 @@ promise.prototype.finally@^3.1.0: es-abstract "^1.9.0" function-bind "^1.1.1" -prompts@^2.0.1, prompts@^2.4.0, prompts@~2.4.2: +prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.2, prompts@~2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -27210,10 +27244,10 @@ re-resizable@^6.9.9: resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.9.tgz#99e8b31c67a62115dc9c5394b7e55892265be216" integrity sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA== -re2js@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/re2js/-/re2js-0.4.2.tgz#e344697e64d128ea65c121d6581e67ee5bfa5feb" - integrity sha512-wuv0p0BGbrVIkobV8zh82WjDurXko0QNCgaif6DdRAljgVm2iio4PVYCwjAxGaWen1/QZXWDM67dIslmz7AIbA== +re2js@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/re2js/-/re2js-0.4.3.tgz#1318cd0c12aa6ed3ba56d5e012311ffbfb2aef35" + integrity sha512-EuNmh7jurhHEE8Ge/lBo9JuMLb3qf866Xjjfyovw3wPc7+hlqDkZq4LwhrCQMEI+ARWfrKrHozEndzlpNT0WDg== react-clientside-effect@^1.2.6: version "1.2.6" @@ -27882,7 +27916,7 @@ readable-stream@~2.0.0: string_decoder "~0.10.x" util-deprecate "~1.0.1" -readdir-glob@^1.0.0, readdir-glob@^1.1.2: +readdir-glob@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== @@ -27936,6 +27970,13 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -28111,6 +28152,11 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexp-tree@~0.1.1: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" @@ -28765,6 +28811,13 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" +safe-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" + integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== + dependencies: + regexp-tree "~0.1.1" + safe-squel@^5.12.5: version "5.12.5" resolved "https://registry.yarnpkg.com/safe-squel/-/safe-squel-5.12.5.tgz#9597cec498dc184a15fe94082b7bcc80cb4d048b" @@ -30764,7 +30817,7 @@ tar-fs@^3.0.4, tar-fs@^3.0.6: bare-fs "^2.1.1" bare-path "^2.1.0" -tar-stream@^2.1.4, tar-stream@^2.2.0: +tar-stream@^2.1.4: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -30828,6 +30881,11 @@ tcp-port-used@^1.0.2: debug "4.3.1" is2 "^2.0.6" +teamcity-service-messages@^0.1.14: + version "0.1.14" + resolved "https://registry.yarnpkg.com/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz#193d420a5e4aef8e5e50b8c39e7865e08fbb5d8a" + integrity sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w== + teex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/teex/-/teex-1.0.1.tgz#b8fa7245ef8e8effa8078281946c85ab780a0b12" @@ -31327,6 +31385,15 @@ ts-pnp@^1.1.6: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== +tsconfig-paths-webpack-plugin@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz#3c6892c5e7319c146eee1e7302ed9e6f2be4f763" + integrity sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.7.0" + tsconfig-paths "^4.1.2" + tsconfig-paths@^3.14.2: version "3.14.2" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" @@ -31337,7 +31404,7 @@ tsconfig-paths@^3.14.2: minimist "^1.2.6" strip-bom "^3.0.0" -tsconfig-paths@^4.2.0: +tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== @@ -32689,6 +32756,11 @@ watchpack@^2.2.0, watchpack@^2.4.1: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +watskeburt@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/watskeburt/-/watskeburt-4.1.0.tgz#3c0227669be646a97424b631164b1afe3d4d5344" + integrity sha512-KkY5H51ajqy9HYYI+u9SIURcWnqeVVhdH0I+ab6aXPGHfZYxgRCwnR6Lm3+TYB6jJVt5jFqw4GAKmwf1zHmGQw== + wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" @@ -33569,15 +33641,6 @@ z-schema@^5.0.1: optionalDependencies: commander "^2.7.1" -zip-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" - integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== - dependencies: - archiver-utils "^2.1.0" - compress-commons "^4.1.0" - readable-stream "^3.6.0" - zip-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-6.0.1.tgz#e141b930ed60ccaf5d7fa9c8260e0d1748a2bbfb"