diff --git a/.buildkite/package-lock.json b/.buildkite/package-lock.json new file mode 100644 index 000000000000..cfbcb9ec1aae --- /dev/null +++ b/.buildkite/package-lock.json @@ -0,0 +1,915 @@ +{ + "name": "kibana-buildkite", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "kibana-buildkite", + "version": "1.0.0", + "dependencies": { + "kibana-buildkite-library": "git+https://git@github.com/elastic/kibana-buildkite-library#0f95579ace8100de9f1ad4cc16976b9ec6d5841e" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", + "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", + "dependencies": { + "@octokit/types": "^6.34.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", + "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", + "dependencies": { + "@octokit/types": "^6.34.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "dependencies": { + "@octokit/openapi-types": "^11.2.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kibana-buildkite-library": { + "version": "1.0.0", + "resolved": "git+https://git@github.com/elastic/kibana-buildkite-library.git#0f95579ace8100de9f1ad4cc16976b9ec6d5841e", + "integrity": "sha512-Ayiyy3rAE/jOWcR65vxiv4zacMlpxuRZ+WKvly6magfClWTWIUTcW1aiOH2/PYWP3faiCbIDHOyxLeGGajk5dQ==", + "license": "MIT", + "dependencies": { + "@octokit/rest": "^18.10.0", + "axios": "^0.21.4", + "globby": "^11.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + }, + "dependencies": { + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" + }, + "@octokit/plugin-paginate-rest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", + "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", + "requires": { + "@octokit/types": "^6.34.0" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "requires": {} + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", + "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", + "requires": { + "@octokit/types": "^6.34.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "requires": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "@octokit/types": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "requires": { + "@octokit/openapi-types": "^11.2.0" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, + "before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + } + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "kibana-buildkite-library": { + "version": "git+https://git@github.com/elastic/kibana-buildkite-library.git#0f95579ace8100de9f1ad4cc16976b9ec6d5841e", + "integrity": "sha512-Ayiyy3rAE/jOWcR65vxiv4zacMlpxuRZ+WKvly6magfClWTWIUTcW1aiOH2/PYWP3faiCbIDHOyxLeGGajk5dQ==", + "from": "kibana-buildkite-library@git+https://git@github.com/elastic/kibana-buildkite-library#0f95579ace8100de9f1ad4cc16976b9ec6d5841e", + "requires": { + "@octokit/rest": "^18.10.0", + "axios": "^0.21.4", + "globby": "^11.1.0", + "js-yaml": "^4.1.0" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/.buildkite/package.json b/.buildkite/package.json index 385c9f2429f7..551def1fa180 100644 --- a/.buildkite/package.json +++ b/.buildkite/package.json @@ -3,6 +3,6 @@ "version": "1.0.0", "private": true, "dependencies": { - "kibana-buildkite-library": "elastic/kibana-buildkite-library" + "kibana-buildkite-library": "git+https://git@github.com/elastic/kibana-buildkite-library#0f95579ace8100de9f1ad4cc16976b9ec6d5841e" } } diff --git a/.buildkite/pipelines/es_snapshots/verify.yml b/.buildkite/pipelines/es_snapshots/verify.yml index 23ec38085c3d..729d0d1b37b5 100755 --- a/.buildkite/pipelines/es_snapshots/verify.yml +++ b/.buildkite/pipelines/es_snapshots/verify.yml @@ -42,7 +42,7 @@ steps: - command: .buildkite/scripts/steps/functional/oss_cigroup.sh label: 'OSS CI Group' - parallelism: 11 + parallelism: 12 agents: queue: ci-group-4d depends_on: build @@ -53,13 +53,12 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/steps/test/jest_integration.sh - label: 'Jest Integration Tests' - parallelism: 3 + - command: .buildkite/scripts/steps/test/pick_jest_config_run_order.sh + label: 'Pick Jest Config Run Order' agents: - queue: n2-4 - timeout_in_minutes: 120 - key: jest-integration + queue: kibana-default + env: + FILTER_JEST_CONFIG_TYPE: integration retry: automatic: - exit_status: '*' diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index 8702493d9f4c..1c89c4e90893 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -66,7 +66,7 @@ steps: - command: .buildkite/scripts/steps/functional/oss_cigroup.sh label: 'OSS CI Group' - parallelism: 11 + parallelism: 12 agents: queue: n2-4-spot depends_on: build @@ -157,29 +157,14 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/steps/test/jest.sh - label: 'Jest Tests' - parallelism: 8 + - command: .buildkite/scripts/steps/test/pick_jest_config_run_order.sh + label: 'Pick Jest Config Run Order' agents: - queue: n2-4-spot - timeout_in_minutes: 90 - key: jest - retry: - automatic: - - exit_status: '-1' - limit: 3 - - - command: .buildkite/scripts/steps/test/jest_integration.sh - label: 'Jest Integration Tests' - parallelism: 3 - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - key: jest-integration + queue: kibana-default retry: automatic: - - exit_status: '-1' - limit: 3 + - exit_status: '*' + limit: 1 - command: .buildkite/scripts/steps/test/api_integration.sh label: 'API Integration Tests' diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index 658d855d86cf..4b4104f18c62 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -32,7 +32,7 @@ steps: - command: .buildkite/scripts/steps/functional/oss_cigroup.sh label: 'OSS CI Group' - parallelism: 11 + parallelism: 12 agents: queue: n2-4-spot depends_on: build @@ -123,29 +123,14 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/steps/test/jest.sh - label: 'Jest Tests' - parallelism: 8 + - command: .buildkite/scripts/steps/test/pick_jest_config_run_order.sh + label: 'Pick Jest Config Run Order' agents: - queue: n2-4-spot - timeout_in_minutes: 90 - key: jest - retry: - automatic: - - exit_status: '-1' - limit: 3 - - - command: .buildkite/scripts/steps/test/jest_integration.sh - label: 'Jest Integration Tests' - parallelism: 3 - agents: - queue: n2-4-spot - timeout_in_minutes: 120 - key: jest-integration + queue: kibana-default retry: automatic: - - exit_status: '-1' - limit: 3 + - exit_status: '*' + limit: 1 - command: .buildkite/scripts/steps/test/api_integration.sh label: 'API Integration Tests' diff --git a/.buildkite/pipelines/pull_request/response_ops.yml b/.buildkite/pipelines/pull_request/response_ops.yml index 846477170409..87f6a60c85c5 100644 --- a/.buildkite/pipelines/pull_request/response_ops.yml +++ b/.buildkite/pipelines/pull_request/response_ops.yml @@ -1,6 +1,6 @@ steps: - - command: .buildkite/scripts/steps/functional/response_ops_cases.sh - label: 'Cases Cypress Tests on Security Solution' + - command: .buildkite/scripts/steps/functional/response_ops.sh + label: 'Rules, Alerts and Exceptions ResponseOps Cypress Tests on Security Solution' agents: queue: ci-group-6 depends_on: build diff --git a/.buildkite/pipelines/pull_request/response_ops_cases.yml b/.buildkite/pipelines/pull_request/response_ops_cases.yml new file mode 100644 index 000000000000..846477170409 --- /dev/null +++ b/.buildkite/pipelines/pull_request/response_ops_cases.yml @@ -0,0 +1,11 @@ +steps: + - command: .buildkite/scripts/steps/functional/response_ops_cases.sh + label: 'Cases Cypress Tests on Security Solution' + agents: + queue: ci-group-6 + depends_on: build + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '*' + limit: 1 diff --git a/.buildkite/scripts/lifecycle/pre_command.sh b/.buildkite/scripts/lifecycle/pre_command.sh index e7a176a5c266..8f3776db3ca6 100755 --- a/.buildkite/scripts/lifecycle/pre_command.sh +++ b/.buildkite/scripts/lifecycle/pre_command.sh @@ -9,21 +9,7 @@ export BUILDKITE_TOKEN echo '--- Install buildkite dependencies' cd '.buildkite' - -# If this yarn install is terminated early, e.g. if the build is cancelled in buildkite, -# A node module could end up in a bad state that can cause all future builds to fail -# So, let's cache clean and try again to make sure that's not what caused the error -install_deps() { - yarn install --production --pure-lockfile - EXIT=$? - if [[ "$EXIT" != "0" ]]; then - yarn cache clean - fi - return $EXIT -} - -retry 5 15 install_deps - +retry 5 15 npm ci cd .. echo '--- Agent Debug/SSH Info' diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.js b/.buildkite/scripts/pipelines/pull_request/pipeline.js index fa167d9f324b..0f8e46842fd3 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.js +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.js @@ -64,12 +64,12 @@ const uploadPipeline = (pipelineContent) => { if ( (await doAnyChangesMatch([ - /^x-pack\/plugins\/security_solution/, /^x-pack\/plugins\/lists/, + /^x-pack\/plugins\/security_solution/, /^x-pack\/plugins\/timelines/, - /^x-pack\/test\/security_solution_cypress/, /^x-pack\/plugins\/triggers_actions_ui\/public\/application\/sections\/action_connector_form/, /^x-pack\/plugins\/triggers_actions_ui\/public\/application\/context\/actions_connectors_context\.tsx/, + /^x-pack\/test\/security_solution_cypress/, ])) || process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') ) { @@ -77,12 +77,26 @@ const uploadPipeline = (pipelineContent) => { } if ( - (await doAnyChangesMatch([/^x-pack\/plugins\/cases/])) || + (await doAnyChangesMatch([ + /^src\/plugins\/data/, + /^x-pack\/plugins\/actions/, + /^x-pack\/plugins\/alerting/, + /^x-pack\/plugins\/event_log/, + /^x-pack\/plugins\/rule_registry/, + /^x-pack\/plugins\/task_manager/, + ])) || process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') ) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/response_ops.yml')); } + if ( + (await doAnyChangesMatch([/^x-pack\/plugins\/cases/])) || + process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + ) { + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/response_ops_cases.yml')); + } + if ( (await doAnyChangesMatch([/^x-pack\/plugins\/apm/])) || process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') diff --git a/.buildkite/scripts/steps/checks.sh b/.buildkite/scripts/steps/checks.sh index cae019150b62..42de0f2cf239 100755 --- a/.buildkite/scripts/steps/checks.sh +++ b/.buildkite/scripts/steps/checks.sh @@ -8,6 +8,7 @@ export DISABLE_BOOTSTRAP_VALIDATION=false .buildkite/scripts/steps/checks/commit/commit.sh .buildkite/scripts/steps/checks/bazel_packages.sh .buildkite/scripts/steps/checks/telemetry.sh +.buildkite/scripts/steps/checks/validate_ci_groups.sh .buildkite/scripts/steps/checks/ts_projects.sh .buildkite/scripts/steps/checks/jest_configs.sh .buildkite/scripts/steps/checks/doc_api_changes.sh diff --git a/.buildkite/scripts/steps/checks/validate_ci_groups.sh b/.buildkite/scripts/steps/checks/validate_ci_groups.sh new file mode 100755 index 000000000000..1216ff0a55e1 --- /dev/null +++ b/.buildkite/scripts/steps/checks/validate_ci_groups.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +echo --- Ensure that all tests are in a CI Group +checks-reporter-with-killswitch "Ensure that all tests are in a CI Group" \ + node scripts/ensure_all_tests_in_ci_group diff --git a/.buildkite/scripts/steps/functional/response_ops.sh b/.buildkite/scripts/steps/functional/response_ops.sh new file mode 100755 index 000000000000..d8484854ed29 --- /dev/null +++ b/.buildkite/scripts/steps/functional/response_ops.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/steps/functional/common.sh + +export JOB=kibana-security-solution-chrome + +echo "--- Response Ops Cypress Tests on Security Solution" + +cd "$XPACK_DIR" + +checks-reporter-with-killswitch "Response Ops Cypress Tests on Security Solution" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ + --config test/security_solution_cypress/response_ops_cli_config.ts diff --git a/.buildkite/scripts/steps/test/jest_env.sh b/.buildkite/scripts/steps/test/jest_env.sh new file mode 100644 index 000000000000..80e88bebba18 --- /dev/null +++ b/.buildkite/scripts/steps/test/jest_env.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# keys used to associate test group data in ci-stats with Jest execution order +export TEST_GROUP_TYPE_UNIT="Jest Unit Tests" +export TEST_GROUP_TYPE_INTEGRATION="Jest Integration Tests" diff --git a/.buildkite/scripts/steps/test/jest_parallel.sh b/.buildkite/scripts/steps/test/jest_parallel.sh index 27eb9e921636..86c685d82c7e 100755 --- a/.buildkite/scripts/steps/test/jest_parallel.sh +++ b/.buildkite/scripts/steps/test/jest_parallel.sh @@ -2,36 +2,38 @@ set -uo pipefail -JOB=$BUILDKITE_PARALLEL_JOB -JOB_COUNT=$BUILDKITE_PARALLEL_JOB_COUNT +source .buildkite/scripts/steps/test/jest_env.sh -# a jest failure will result in the script returning an exit code of 10 +export JOB=$BUILDKITE_PARALLEL_JOB -i=0 +# a jest failure will result in the script returning an exit code of 10 exitCode=0 -# run unit tests in parallel if [[ "$1" == 'jest.config.js' ]]; then + # run unit tests in parallel parallelism="-w2" + TEST_TYPE="unit" else + # run integration tests in-band parallelism="--runInBand" + TEST_TYPE="integration" fi +export TEST_TYPE +echo "--- downloading integration test run order" +buildkite-agent artifact download jest_run_order.json . +configs=$(jq -r 'getpath([env.TEST_TYPE]) | .groups[env.JOB | tonumber].names | .[]' jest_run_order.json) + while read -r config; do - if [ "$((i % JOB_COUNT))" -eq "$JOB" ]; then - echo "--- $ node scripts/jest --config $config" - node --max-old-space-size=14336 ./scripts/jest --config="$config" "$parallelism" --coverage=false --passWithNoTests - lastCode=$? - - if [ $lastCode -ne 0 ]; then - exitCode=10 - echo "Jest exited with code $lastCode" - echo "^^^ +++" - fi + echo "--- $ node scripts/jest --config $config" + NODE_OPTIONS="--max-old-space-size=14336" node ./scripts/jest --config="$config" "$parallelism" --coverage=false --passWithNoTests + lastCode=$? + + if [ $lastCode -ne 0 ]; then + exitCode=10 + echo "Jest exited with code $lastCode" + echo "^^^ +++" fi - - ((i=i+1)) -# uses heredoc to avoid the while loop being in a sub-shell thus unable to overwrite exitCode -done <<< "$(find src x-pack packages -name "$1" -not -path "*/__fixtures__/*" | sort)" +done <<< "$configs" exit $exitCode diff --git a/.buildkite/scripts/steps/test/pick_jest_config_run_order.js b/.buildkite/scripts/steps/test/pick_jest_config_run_order.js new file mode 100644 index 000000000000..a5fa499419ab --- /dev/null +++ b/.buildkite/scripts/steps/test/pick_jest_config_run_order.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const { CiStats } = require('kibana-buildkite-library'); + +(async () => { + try { + await CiStats.pickTestGroupRunOrder(); + } catch (ex) { + console.error('CI Stats Error', ex.message); + if (ex.response) { + console.error('HTTP Error Response Status', ex.response.status); + console.error('HTTP Error Response Body', ex.response.data); + } + process.exit(1); + } +})(); diff --git a/.buildkite/scripts/steps/test/pick_jest_config_run_order.sh b/.buildkite/scripts/steps/test/pick_jest_config_run_order.sh new file mode 100644 index 000000000000..37d4e629c90b --- /dev/null +++ b/.buildkite/scripts/steps/test/pick_jest_config_run_order.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh +source .buildkite/scripts/steps/test/jest_env.sh + +echo '--- Pick Jest Config Run Order' +node "$(dirname "${0}")/pick_jest_config_run_order.js" diff --git a/.buildkite/yarn.lock b/.buildkite/yarn.lock deleted file mode 100644 index c5a4e404ba97..000000000000 --- a/.buildkite/yarn.lock +++ /dev/null @@ -1,180 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@octokit/auth-token@^2.4.4": - version "2.5.0" - resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz" - integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== - dependencies: - "@octokit/types" "^6.0.3" - -"@octokit/core@^3.5.1": - version "3.5.1" - resolved "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz" - integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== - dependencies: - "@octokit/auth-token" "^2.4.4" - "@octokit/graphql" "^4.5.8" - "@octokit/request" "^5.6.0" - "@octokit/request-error" "^2.0.5" - "@octokit/types" "^6.0.3" - before-after-hook "^2.2.0" - universal-user-agent "^6.0.0" - -"@octokit/endpoint@^6.0.1": - version "6.0.12" - resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz" - integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== - dependencies: - "@octokit/types" "^6.0.3" - is-plain-object "^5.0.0" - universal-user-agent "^6.0.0" - -"@octokit/graphql@^4.5.8": - version "4.8.0" - resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz" - integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== - dependencies: - "@octokit/request" "^5.6.0" - "@octokit/types" "^6.0.3" - universal-user-agent "^6.0.0" - -"@octokit/openapi-types@^11.2.0": - version "11.2.0" - resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz" - integrity sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA== - -"@octokit/plugin-paginate-rest@^2.16.8": - version "2.17.0" - resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz" - integrity sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw== - dependencies: - "@octokit/types" "^6.34.0" - -"@octokit/plugin-request-log@^1.0.4": - version "1.0.4" - resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz" - integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== - -"@octokit/plugin-rest-endpoint-methods@^5.12.0": - version "5.13.0" - resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz" - integrity sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA== - dependencies: - "@octokit/types" "^6.34.0" - deprecation "^2.3.1" - -"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": - version "2.1.0" - resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz" - integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== - dependencies: - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request@^5.6.0": - version "5.6.2" - resolved "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz" - integrity sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.1.0" - "@octokit/types" "^6.16.1" - is-plain-object "^5.0.0" - node-fetch "^2.6.1" - universal-user-agent "^6.0.0" - -"@octokit/rest@^18.10.0": - version "18.12.0" - resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz" - integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== - dependencies: - "@octokit/core" "^3.5.1" - "@octokit/plugin-paginate-rest" "^2.16.8" - "@octokit/plugin-request-log" "^1.0.4" - "@octokit/plugin-rest-endpoint-methods" "^5.12.0" - -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.34.0": - version "6.34.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz" - integrity sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw== - dependencies: - "@octokit/openapi-types" "^11.2.0" - -axios@^0.21.4: - version "0.21.4" - resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== - dependencies: - follow-redirects "^1.14.0" - -before-after-hook@^2.2.0: - version "2.2.2" - resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz" - integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== - -deprecation@^2.0.0, deprecation@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz" - integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== - -follow-redirects@^1.14.0: - version "1.14.5" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz" - integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - -kibana-buildkite-library@elastic/kibana-buildkite-library: - version "1.0.0" - resolved "https://codeload.github.com/elastic/kibana-buildkite-library/tar.gz/bd0bec4c7af5f64a12c781d03cedb9fb2386bfbd" - dependencies: - "@octokit/rest" "^18.10.0" - axios "^0.21.4" - -node-fetch@^2.6.1: - version "2.6.6" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz" - integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== - dependencies: - whatwg-url "^5.0.0" - -once@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= - -universal-user-agent@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz" - integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/.ci/ci_groups.yml b/.ci/ci_groups.yml index c3786f299d4c..508f118ce9dd 100644 --- a/.ci/ci_groups.yml +++ b/.ci/ci_groups.yml @@ -10,6 +10,7 @@ root: - ciGroup9 - ciGroup10 - ciGroup11 + - ciGroup12 xpack: - ciGroup1 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 869160bfa0fd..215514d58f60 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -415,6 +415,7 @@ /x-pack/plugins/security_solution/cypress/integration/urls @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/common/components/alerts_viewer @elastic/security-threat-hunting-investigations +/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_action @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/common/components/event_details @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/common/components/events_viewer @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/common/components/markdown_editor @elastic/security-threat-hunting-investigations diff --git a/.gitignore b/.gitignore index 7c20367dfe6d..fac261b2a97e 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ selenium *.swo *.out package-lock.json +!/.buildkite/package-lock.json .yo-rc.json .vscode *.sublime-* diff --git a/docs/api/machine-learning/sync.asciidoc b/docs/api/machine-learning/sync.asciidoc index ba7e98a12dde..d210a7826d16 100644 --- a/docs/api/machine-learning/sync.asciidoc +++ b/docs/api/machine-learning/sync.asciidoc @@ -47,9 +47,9 @@ it is added. This list contains the {dfeed} identifiers and indicates whether the synchronization was successful. `datafeedsRemoved`:: -(array) If saved objects exist for {dfeeds} that no longer exist, they are -deleted. This list contains the {dfeed} identifiers and indicates whether the -synchronization was successful. +(array) If a saved object for an anomaly detection job references a datafeed +that no longer exists, it is deleted. This list contains the {dfeed} identifiers +and indicates whether the synchronization was successful. `savedObjectsCreated`:: (array) If saved objects are missing for {ml} jobs or trained models, they are diff --git a/docs/management/connectors/action-types/email.asciidoc b/docs/management/connectors/action-types/email.asciidoc index af408fd36a23..3571185d0d51 100644 --- a/docs/management/connectors/action-types/email.asciidoc +++ b/docs/management/connectors/action-types/email.asciidoc @@ -7,9 +7,11 @@ The email connector uses the SMTP protocol to send mail messages, using an integration of https://nodemailer.com/[Nodemailer]. An exception is Microsoft Exchange, which uses HTTP protocol for sending emails, https://docs.microsoft.com/en-us/graph/api/user-sendmail[Send mail]. Email message text is sent as both plain text and html text. -NOTE: For emails to have a footer with a link back to {kib}, set the <> configuration setting. - -NOTE: When the <> configuration setting is used, the email addresses used for all of the Sender (from), To, CC, and BCC properties must have email domains specified in the configuration setting. +[NOTE] +==== +* For emails to have a footer with a link back to {kib}, set the <> configuration setting. +* When the <> configuration setting is used, the email addresses used for all of the Sender (from), To, CC, and BCC properties must have email domains specified in the configuration setting. +==== [float] [[email-connector-configuration]] diff --git a/docs/osquery/advanced-osquery.asciidoc b/docs/osquery/advanced-osquery.asciidoc deleted file mode 100644 index 4f03a6d5fe5e..000000000000 --- a/docs/osquery/advanced-osquery.asciidoc +++ /dev/null @@ -1,103 +0,0 @@ -[[advanced-osquery]] -== Advanced Osquery - -[float] -[[osquery-map-fields]] -=== Map result fields to ECS - -When you save queries or add queries to a pack, you can optionally map Osquery results or static values to fields in -the {ecs-ref}/ecs-reference.html[Elastic Common Schema] (ECS). -This standardizes your Osquery data for use across detections, machine learning, -and any other areas that rely on ECS-compliant data. -When the query is run, the results include the original `osquery.` -and the mapped ECS fields. For example, if you update a query to map `osquery.name` to `user.name`, the query results include both fields. - -. Edit saved queries or queries in a pack to map fields: - -* For *Saved queries*: Open the *Saved queries* tab, and then click the edit icon for the query that you want to map. - -* For *packs*: Open the *Packs* tab, edit a pack, and then click the edit icon for the query that you want to map. - -. In the **ECS mapping** section, select an **ECS field** to map. - -. In the **Value** column, use the dropdown on the left to choose what type of value to map to the ECS field: - -** **Osquery value**: Select an Osquery field. The fields available are based on the SQL query entered, and only include fields that the query returns. When the query runs, the ECS field is set dynamically to the value of the Osquery field selected. - -** **Static value**: Enter a static value. When the query runs, the ECS field is set to the value entered. For example, static fields can be used to apply `tags` or your preferred `event.category` to the query results. - -. Map more fields, as needed. To remove any mapped rows, click the delete icon. - -. Save your changes. - -[NOTE] -========================= - -* Some ECS fields are restricted and cannot be mapped. These are not available in the ECS dropdown. - -* Some ECS fields are restricted to a set of allowed values, like {ecs-ref}/ecs-event.html#field-event-category[event.category]. Use the {ecs-ref}/ecs-field-reference.html[ECS Field Reference] for help when mapping fields. - -* Osquery date fields have a variety of data types (including integer, text, or bigint). When mapping an Osquery date field to an ECS date field, you might need to use SQL operators in the query to get an {es}-compatible -{ref}/date.html[date] type. -========================= - - -[float] -[[osquery-extended-tables]] -=== Extended tables for Kubernetes queries -In addition to the Osquery schema, the Elastic-provided version of Osquery also includes the following tables to support Kubernetes containers. These can be queried with live or scheduled queries. - -* `host_users` - -* `host_groups` - -* `host_processes` - -When querying these tables, the expectation is that the `/etc/passwd`, `/etc/group`, and `/proc` are available in the container under `/hostfs` as: -`/hostfs/etc/passwd`, `/hostfs/etc/group`, and `/hostfs/proc`. For information about the fields available in these tables, see the -https://docs.elastic.co/en/integrations/osquery_manager#exported-fields[exported fields] reference. - -[float] -[[osquery-status]] -=== Osquery status - -A query can have the following status: - -[cols="2*<"] -|=== -| Successful | The query successfully completed. -| Failed | The query encountered a problem, such as an issue with the query or the agent was disconnected, and might have failed. -| Not yet responded | The query has not been sent to the agent. -| Expired | The action request timed out. The agent may be offline. -|=== - -NOTE: If an agent is offline, the request status remains **pending** as {kib} retries the request. -By default, a query request times out after five minutes. The time out applies to the time it takes -to deliver the action request to an agent to run a query. If the action completes after the timeout period, -the results are still returned. - - -[float] -[[osquery-results]] -=== Osquery results -When you run live or scheduled queries, the results are automatically -stored in an {es} index, so that you can search, analyze, and visualize this data in {kib}. -For a list of the Osquery fields that can be returned in query results, -refer to https://docs.elastic.co/en/integrations/osquery_manager#exported-fields[exported fields]. -Query results can also include ECS fields, if the query has a defined ECS mapping. - -Osquery responses include the following information: - -* Everything prefaced with `osquery.` is part of the query response. These fields are not mapped to ECS by default. - -* Results include some ECS fields by default, such as `host.*` and `agent.*`, which provide information about the host that was queried. - -* For live queries, the `action_data.query` is the query that was sent. - -* For scheduled queries in a pack, the `action_id` has the format `pack__`. You can use this information to look up the query that was run. - -* By default, all query results are https://osquery.readthedocs.io/en/stable/deployment/logging/#snapshot-logs[snapshot logs] -that represent a point in time with a set of results, with no -https://osquery.readthedocs.io/en/stable/deployment/logging/#differential-logs[differentials]. - -* Osquery data is stored in the `logs-osquery_manager.result-` datastream, and the result row data is under the `osquery` property in the document. diff --git a/docs/osquery/exported-fields-reference.asciidoc b/docs/osquery/exported-fields-reference.asciidoc index d359c484e443..600751620c6d 100644 --- a/docs/osquery/exported-fields-reference.asciidoc +++ b/docs/osquery/exported-fields-reference.asciidoc @@ -1,4 +1,6453 @@ [[exported-fields-osquery]] == Exported fields reference -_Content coming soon._ +The following fields can be returned in osquery results. Note the following about osquery fields: + +* Some fields list multiple descriptions because the one that applies depends on which table was queried. For example, a result stored in the `osquery.autoupdate` field may represent a response from the `firefox_addons` table or the `windows_security_center` table. +* In the cases where a field name is associated with more than one osquery table, we have made a best guess at what the data `type` should be. In the cases where it is unknown, the data type is set as a `keyword` object. + +For more information about osquery tables, see the https://osquery.io/schema[osquery schema documentation]. + +[float] +[[osquery-fields]] +=== Fields + +*UUID* - keyword, text.text + +* _system_extensions.UUID_ - Extension unique id + +*abi* - keyword, text.text + +* _elf_info.abi_ - Section type + +*action* - keyword, text.text + +* _disk_events.action_ - Appear or disappear +* _example.action_ - Action performed in generation +* _file_events.action_ - Change action (UPDATE, REMOVE, etc) +* _hardware_events.action_ - Remove, insert, change properties, etc +* _ntfs_journal_events.action_ - Change action (Write, Delete, etc) +* _scheduled_tasks.action_ - Actions executed by the scheduled task +* _socket_events.action_ - The socket action (bind, listen, close) +* _yara_events.action_ - Change action (UPDATE, REMOVE, etc) + +*activated* - keyword, number.long + +* _tpm_info.activated_ - TPM is activated + +*active* - keyword, number.long + +* _firefox_addons.active_ - 1 If the addon is active else 0 +* _memory_info.active_ - The total amount of buffer or page cache memory, in bytes, that is in active use +* _osquery_events.active_ - 1 if the publisher or subscriber is active else 0 +* _osquery_packs.active_ - Whether this pack is active (the version, platform and discovery queries match) yes=1, no=0. +* _osquery_registry.active_ - 1 If this plugin is active else 0 +* _virtual_memory_info.active_ - Total number of active pages. + +*active_disks* - keyword, number.long + +* _md_devices.active_disks_ - Number of active disks in array + +*active_state* - keyword, text.text + +* _systemd_units.active_state_ - The high-level unit activation state, i.e. generalization of SUB + +*actual* - keyword, number.long + +* _fan_speed_sensors.actual_ - Actual speed + +*additional_product_id* - keyword, text.text + +* _smart_drive_info.additional_product_id_ - An additional drive identifier if any + +*addr* - keyword, number.long + +* _elf_symbols.addr_ - Symbol address (value) + +*address* - keyword, text.text + +* _arp_cache.address_ - IPv4 address target +* _dns_resolvers.address_ - Resolver IP/IPv6 address +* _etc_hosts.address_ - IP address mapping +* _fbsd_kmods.address_ - Kernel module address +* _interface_addresses.address_ - Specific address for interface +* _kernel_modules.address_ - Kernel module address +* _listening_ports.address_ - Specific address for bind +* _platform_info.address_ - Relative address of firmware mapping +* _user_events.address_ - The Internet protocol address or family ID + +*address_width* - keyword, text.text + +* _cpu_info.address_width_ - The width of the CPU address bus. + +*algorithm* - keyword, text.text + +* _authorized_keys.algorithm_ - algorithm of key + +*alias* - keyword, text.text + +* _etc_protocols.alias_ - Protocol alias +* _time_machine_destinations.alias_ - Human readable name of drive + +*aliases* - keyword, text.text + +* _etc_services.aliases_ - Optional space separated list of other names for a service +* _lxd_images.aliases_ - Comma-separated list of image aliases + +*align* - keyword, number.long + +* _elf_sections.align_ - Segment alignment +* _elf_segments.align_ - Segment alignment + +*allow_maximum* - keyword, number.long + +* _shared_resources.allow_maximum_ - Number of concurrent users for this resource has been limited. If True, the value in the MaximumAllowed property is ignored. + +*allow_root* - keyword, text.text + +* _authorizations.allow_root_ - Label top-level key + +*allow_signed_enabled* - keyword, number.long + +* _alf.allow_signed_enabled_ - 1 If allow signed mode is enabled else 0 + +*ami_id* - keyword, text.text + +* _ec2_instance_metadata.ami_id_ - AMI ID used to launch this EC2 instance + +*amperage* - keyword, number.long + +* _battery.amperage_ - The battery's current amperage in mA + +*anonymous* - keyword, number.long + +* _virtual_memory_info.anonymous_ - Total number of anonymous pages. + +*antispyware* - keyword, text.text + +* _windows_security_center.antispyware_ - The health of the monitored Antispyware solution (see windows_security_products) + +*antivirus* - keyword, text.text + +* _windows_security_center.antivirus_ - The health of the monitored Antivirus solution (see windows_security_products) + +*api_version* - keyword, text.text + +* _docker_version.api_version_ - API version + +*apparmor* - keyword, text.text + +* _apparmor_events.apparmor_ - Apparmor Status like ALLOWED, DENIED etc. + +*applescript_enabled* - keyword, text.text + +* _apps.applescript_enabled_ - Info properties NSAppleScriptEnabled label + +*application* - keyword, text.text + +* _office_mru.application_ - Associated Office application + +*arch* - keyword, text.text + +* _deb_packages.arch_ - Package architecture +* _docker_version.arch_ - Hardware architecture +* _os_version.arch_ - OS Architecture +* _pkg_packages.arch_ - Architecture(s) supported +* _rpm_packages.arch_ - Architecture(s) supported +* _seccomp_events.arch_ - Information about the CPU architecture +* _signature.arch_ - If applicable, the arch of the signed code + +*architecture* - keyword, text.text + +* _docker_info.architecture_ - Hardware architecture +* _ec2_instance_metadata.architecture_ - Hardware architecture of this EC2 instance +* _lxd_images.architecture_ - Target architecture for the image +* _lxd_instances.architecture_ - Instance architecture + +*architectures* - keyword, text.text + +* _apt_sources.architectures_ - Repository architectures + +*args* - keyword, text.text + +* _startup_items.args_ - Arguments provided to startup executable + +*arguments* - keyword, text.text + +* _kernel_info.arguments_ - Kernel arguments + +*array_handle* - keyword, text.text + +* _memory_devices.array_handle_ - The memory array that the device is attached to + +*assessments_enabled* - keyword, number.long + +* _gatekeeper.assessments_enabled_ - 1 If a Gatekeeper is enabled else 0 + +*asset_tag* - keyword, text.text + +* _memory_devices.asset_tag_ - Manufacturer specific asset tag of memory device + +*ata_version* - keyword, text.text + +* _smart_drive_info.ata_version_ - ATA version of drive + +*atime* - keyword, number.long + +* _device_file.atime_ - Last access time +* _file.atime_ - Last access time +* _file_events.atime_ - Last access time +* _process_events.atime_ - File last access in UNIX time +* _shared_memory.atime_ - Attached time + +*attach* - keyword, text.text + +* _apparmor_profiles.attach_ - Which executable(s) a profile will attach to. + +*attached* - keyword, number.long + +* _shared_memory.attached_ - Number of attached processes + +*attributes* - keyword, text.text + +* _file.attributes_ - File attrib string. See: https://ss64.com/nt/attrib.html + +*audible_alarm* - keyword, text.text + +* _chassis_info.audible_alarm_ - If TRUE, the frame is equipped with an audible alarm. + +*auid* - keyword + +* _process_events.auid_ - Audit User ID at process start +* _process_file_events.auid_ - Audit user ID of the process using the file +* _seccomp_events.auid_ - Audit user ID (loginuid) of the user who started the analyzed process +* _socket_events.auid_ - Audit User ID +* _user_events.auid_ - Audit User ID + +*authenticate_user* - keyword, text.text + +* _authorizations.authenticate_user_ - Label top-level key + +*authentication_package* - keyword, text.text + +* _logon_sessions.authentication_package_ - The authentication package used to authenticate the owner of the logon session. + +*author* - keyword, text.text + +* _chocolatey_packages.author_ - Optional package author +* _chrome_extensions.author_ - Optional extension author +* _npm_packages.author_ - Package author name +* _python_packages.author_ - Optional package author +* _safari_extensions.author_ - Optional extension author + +*authority* - keyword, text.text + +* _signature.authority_ - Certificate Common Name + +*authority_key_id* - keyword, text.text + +* _certificates.authority_key_id_ - AKID an optionally included SHA1 + +*authority_key_identifier* - keyword, text.text + +* _curl_certificate.authority_key_identifier_ - Authority Key Identifier + +*authorizations* - keyword, text.text + +* _keychain_acls.authorizations_ - A space delimited set of authorization attributes + +*auto_login* - keyword, number.long + +* _wifi_networks.auto_login_ - 1 if auto login is enabled, 0 otherwise + +*auto_update* - keyword, number.long + +* _lxd_images.auto_update_ - Whether the image auto-updates (1) or not (0) + +*autoupdate* - keyword + +* _firefox_addons.autoupdate_ - 1 If the addon applies background updates else 0 +* _windows_security_center.autoupdate_ - The health of the Windows Autoupdate feature + +*availability* - keyword, text.text + +* _cpu_info.availability_ - The availability and status of the CPU. + +*availability_zone* - keyword, text.text + +* _ec2_instance_metadata.availability_zone_ - Availability zone in which this instance launched + +*average* - keyword, text.text + +* _load_average.average_ - Load average over the specified period. + +*average_memory* - keyword, number.long + +* _osquery_schedule.average_memory_ - Average private memory left after executing + +*avg_disk_bytes_per_read* - keyword, number.long + +* _physical_disk_performance.avg_disk_bytes_per_read_ - Average number of bytes transferred from the disk during read operations + +*avg_disk_bytes_per_write* - keyword, number.long + +* _physical_disk_performance.avg_disk_bytes_per_write_ - Average number of bytes transferred to the disk during write operations + +*avg_disk_read_queue_length* - keyword, number.long + +* _physical_disk_performance.avg_disk_read_queue_length_ - Average number of read requests that were queued for the selected disk during the sample interval + +*avg_disk_sec_per_read* - keyword, number.long + +* _physical_disk_performance.avg_disk_sec_per_read_ - Average time, in seconds, of a read operation of data from the disk + +*avg_disk_sec_per_write* - keyword, number.long + +* _physical_disk_performance.avg_disk_sec_per_write_ - Average time, in seconds, of a write operation of data to the disk + +*avg_disk_write_queue_length* - keyword, number.long + +* _physical_disk_performance.avg_disk_write_queue_length_ - Average number of write requests that were queued for the selected disk during the sample interval + +*backup_date* - keyword, number.long + +* _time_machine_backups.backup_date_ - Backup Date + +*bank_locator* - keyword, text.text + +* _memory_devices.bank_locator_ - String number of the string that identifies the physically-labeled bank where the memory device is located + +*base64* - keyword, number.long + +* _extended_attributes.base64_ - 1 if the value is base64 encoded else 0 + +*base_image* - keyword, text.text + +* _lxd_instances.base_image_ - ID of image used to launch this instance + +*base_uri* - keyword, text.text + +* _apt_sources.base_uri_ - Repository base URI + +*baseurl* - keyword, text.text + +* _yum_sources.baseurl_ - Repository base URL + +*basic_constraint* - keyword, text.text + +* _curl_certificate.basic_constraint_ - Basic Constraints + +*binary_queue* - keyword, number.long + +* _carbon_black_info.binary_queue_ - Size in bytes of binaries waiting to be sent to Carbon Black server + +*binding* - keyword, text.text + +* _elf_symbols.binding_ - Binding type + +*bitmap_chunk_size* - keyword, text.text + +* _md_devices.bitmap_chunk_size_ - Bitmap chunk size + +*bitmap_external_file* - keyword, text.text + +* _md_devices.bitmap_external_file_ - External referenced bitmap file + +*bitmap_on_mem* - keyword, text.text + +* _md_devices.bitmap_on_mem_ - Pages allocated in in-memory bitmap, if enabled + +*block* - keyword, text.text + +* _ssh_configs.block_ - The host or match block + +*block_size* - keyword, number.long + +* _block_devices.block_size_ - Block size in bytes +* _device_file.block_size_ - Block size of filesystem +* _file.block_size_ - Block size of filesystem + +*blocks* - keyword, number.long + +* _device_partitions.blocks_ - Number of blocks +* _mounts.blocks_ - Mounted device used blocks + +*blocks_available* - keyword, number.long + +* _mounts.blocks_available_ - Mounted device available blocks + +*blocks_free* - keyword, number.long + +* _mounts.blocks_free_ - Mounted device free blocks + +*blocks_size* - keyword, number.long + +* _device_partitions.blocks_size_ - Byte size of each block +* _mounts.blocks_size_ - Block size in bytes + +*bluetooth_sharing* - keyword, number.long + +* _sharing_preferences.bluetooth_sharing_ - 1 If bluetooth sharing is enabled for any user else 0 + +*board_model* - keyword, text.text + +* _system_info.board_model_ - Board model + +*board_serial* - keyword, text.text + +* _system_info.board_serial_ - Board serial number + +*board_vendor* - keyword, text.text + +* _system_info.board_vendor_ - Board vendor + +*board_version* - keyword, text.text + +* _system_info.board_version_ - Board version + +*boot_partition* - keyword, number.long + +* _logical_drives.boot_partition_ - True if Windows booted from this drive. + +*boot_uuid* - keyword, text.text + +* _ibridge_info.boot_uuid_ - Boot UUID of the iBridge controller + +*bp_microcode_disabled* - keyword, number.long + +* _kva_speculative_info.bp_microcode_disabled_ - Branch Predictions are disabled due to lack of microcode update. + +*bp_mitigations* - keyword, number.long + +* _kva_speculative_info.bp_mitigations_ - Branch Prediction mitigations are enabled. + +*bp_system_pol_disabled* - keyword, number.long + +* _kva_speculative_info.bp_system_pol_disabled_ - Branch Predictions are disabled via system policy. + +*breach_description* - keyword, text.text + +* _chassis_info.breach_description_ - If provided, gives a more detailed description of a detected security breach. + +*bridge_nf_ip6tables* - keyword, number.long + +* _docker_info.bridge_nf_ip6tables_ - 1 if bridge netfilter ip6tables is enabled. 0 otherwise + +*bridge_nf_iptables* - keyword, number.long + +* _docker_info.bridge_nf_iptables_ - 1 if bridge netfilter iptables is enabled. 0 otherwise + +*broadcast* - keyword, text.text + +* _interface_addresses.broadcast_ - Broadcast address for the interface + +*browser_type* - keyword, text.text + +* _chrome_extension_content_scripts.browser_type_ - The browser type (Valid values: chrome, chromium, opera, yandex, brave) +* _chrome_extensions.browser_type_ - The browser type (Valid values: chrome, chromium, opera, yandex, brave, edge, edge_beta) + +*bsd_flags* - keyword, text.text + +* _file.bsd_flags_ - The BSD file flags (chflags). Possible values: NODUMP, UF_IMMUTABLE, UF_APPEND, OPAQUE, HIDDEN, ARCHIVED, SF_IMMUTABLE, SF_APPEND + +*bssid* - keyword, text.text + +* _wifi_status.bssid_ - The current basic service set identifier +* _wifi_survey.bssid_ - The current basic service set identifier + +*btime* - keyword, number.long + +* _file.btime_ - (B)irth or (cr)eate time +* _process_events.btime_ - File creation in UNIX time + +*buffers* - keyword, number.long + +* _memory_info.buffers_ - The amount of physical RAM, in bytes, used for file buffers + +*build* - keyword, text.text + +* _os_version.build_ - Optional build-specific or variant string + +*build_distro* - keyword, text.text + +* _osquery_info.build_distro_ - osquery toolkit platform distribution name (os version) + +*build_id* - keyword, text.text + +* _sandboxes.build_id_ - Sandbox-specific identifier + +*build_number* - keyword, number.long + +* _windows_crashes.build_number_ - Windows build number of the crashing machine + +*build_platform* - keyword, text.text + +* _osquery_info.build_platform_ - osquery toolkit build platform + +*build_time* - keyword, text.text + +* _docker_version.build_time_ - Build time +* _portage_packages.build_time_ - Unix time when package was built + +*bundle_executable* - keyword, text.text + +* _apps.bundle_executable_ - Info properties CFBundleExecutable label + +*bundle_identifier* - keyword, text.text + +* _apps.bundle_identifier_ - Info properties CFBundleIdentifier label +* _running_apps.bundle_identifier_ - The bundle identifier of the application + +*bundle_name* - keyword, text.text + +* _apps.bundle_name_ - Info properties CFBundleName label + +*bundle_package_type* - keyword, text.text + +* _apps.bundle_package_type_ - Info properties CFBundlePackageType label + +*bundle_path* - keyword, text.text + +* _sandboxes.bundle_path_ - Application bundle used by the sandbox +* _system_extensions.bundle_path_ - System extension bundle path + +*bundle_short_version* - keyword, text.text + +* _apps.bundle_short_version_ - Info properties CFBundleShortVersionString label + +*bundle_version* - keyword, text.text + +* _apps.bundle_version_ - Info properties CFBundleVersion label + +*busy_state* - keyword, number.long + +* _iokit_devicetree.busy_state_ - 1 if the device is in a busy state else 0 +* _iokit_registry.busy_state_ - 1 if the node is in a busy state else 0 + +*bytes* - keyword, number.long + +* _curl.bytes_ - Number of bytes in the response +* _iptables.bytes_ - Number of matching bytes for this rule. + +*bytes_available* - keyword, number.long + +* _time_machine_destinations.bytes_available_ - Bytes available on volume + +*bytes_received* - keyword, number.long + +* _lxd_networks.bytes_received_ - Number of bytes received on this network + +*bytes_sent* - keyword, number.long + +* _lxd_networks.bytes_sent_ - Number of bytes sent on this network + +*bytes_used* - keyword, number.long + +* _time_machine_destinations.bytes_used_ - Bytes used on volume + +*ca* - keyword, number.long + +* _certificates.ca_ - 1 if CA: true (certificate is an authority) else 0 + +*cache_path* - keyword, text.text + +* _quicklook_cache.cache_path_ - Path to cache data + +*cached* - keyword, number.long + +* _lxd_images.cached_ - Whether image is cached (1) or not (0) +* _memory_info.cached_ - The amount of physical RAM, in bytes, used as cache memory + +*capability* - keyword, number.long + +* _apparmor_events.capability_ - Capability number + +*capname* - keyword, text.text + +* _apparmor_events.capname_ - Capability requested by the process + +*caption* - keyword, text.text + +* _patches.caption_ - Short description of the patch. +* _windows_optional_features.caption_ - Caption of feature in settings UI + +*captive_portal* - keyword, number.long + +* _wifi_networks.captive_portal_ - 1 if this network has a captive portal, 0 otherwise + +*carve* - keyword, number.long + +* _carves.carve_ - Set this value to '1' to start a file carve + +*carve_guid* - keyword, text.text + +* _carves.carve_guid_ - Identifying value of the carve session + +*category* - keyword, text.text + +* _apps.category_ - The UTI that categorizes the app for the App Store +* _file_events.category_ - The category of the file defined in the config +* _ntfs_journal_events.category_ - The category that the event originated from +* _power_sensors.category_ - The sensor category: currents, voltage, wattage +* _system_extensions.category_ - System extension category +* _yara_events.category_ - The category of the file + +*cdhash* - keyword, text.text + +* _es_process_events.cdhash_ - Codesigning hash of the process +* _signature.cdhash_ - Hash of the application Code Directory + +*celsius* - keyword, number.double + +* _temperature_sensors.celsius_ - Temperature in Celsius + +*certificate* - keyword, text.text + +* _lxd_certificates.certificate_ - Certificate content + +*cgroup_driver* - keyword, text.text + +* _docker_info.cgroup_driver_ - Control groups driver + +*cgroup_namespace* - keyword, text.text + +* _docker_containers.cgroup_namespace_ - cgroup namespace +* _process_namespaces.cgroup_namespace_ - cgroup namespace inode + +*chain* - keyword, text.text + +* _iptables.chain_ - Size of module content. + +*change_type* - keyword, text.text + +* _docker_container_fs_changes.change_type_ - Type of change: C:Modified, A:Added, D:Deleted + +*channel* - keyword + +* _wifi_status.channel_ - Channel number +* _wifi_survey.channel_ - Channel number +* _windows_eventlog.channel_ - Source or channel of the event + +*channel_band* - keyword, number.long + +* _wifi_status.channel_band_ - Channel band +* _wifi_survey.channel_band_ - Channel band + +*channel_width* - keyword, number.long + +* _wifi_status.channel_width_ - Channel width +* _wifi_survey.channel_width_ - Channel width + +*charged* - keyword, number.long + +* _battery.charged_ - 1 if the battery is currently completely charged. 0 otherwise + +*charging* - keyword, number.long + +* _battery.charging_ - 1 if the battery is currently being charged by a power source. 0 otherwise + +*chassis_bridge_capability_available* - keyword, number.long + +* _lldp_neighbors.chassis_bridge_capability_available_ - Chassis bridge capability availability + +*chassis_bridge_capability_enabled* - keyword, number.long + +* _lldp_neighbors.chassis_bridge_capability_enabled_ - Is chassis bridge capability enabled. + +*chassis_docsis_capability_available* - keyword, number.long + +* _lldp_neighbors.chassis_docsis_capability_available_ - Chassis DOCSIS capability availability + +*chassis_docsis_capability_enabled* - keyword, number.long + +* _lldp_neighbors.chassis_docsis_capability_enabled_ - Chassis DOCSIS capability enabled + +*chassis_id* - keyword, text.text + +* _lldp_neighbors.chassis_id_ - Neighbor chassis ID value + +*chassis_id_type* - keyword, text.text + +* _lldp_neighbors.chassis_id_type_ - Neighbor chassis ID type + +*chassis_mgmt_ips* - keyword, text.text + +* _lldp_neighbors.chassis_mgmt_ips_ - Comma delimited list of chassis management IPS + +*chassis_other_capability_available* - keyword, number.long + +* _lldp_neighbors.chassis_other_capability_available_ - Chassis other capability availability + +*chassis_other_capability_enabled* - keyword, number.long + +* _lldp_neighbors.chassis_other_capability_enabled_ - Chassis other capability enabled + +*chassis_repeater_capability_available* - keyword, number.long + +* _lldp_neighbors.chassis_repeater_capability_available_ - Chassis repeater capability availability + +*chassis_repeater_capability_enabled* - keyword, number.long + +* _lldp_neighbors.chassis_repeater_capability_enabled_ - Chassis repeater capability enabled + +*chassis_router_capability_available* - keyword, number.long + +* _lldp_neighbors.chassis_router_capability_available_ - Chassis router capability availability + +*chassis_router_capability_enabled* - keyword, number.long + +* _lldp_neighbors.chassis_router_capability_enabled_ - Chassis router capability enabled + +*chassis_station_capability_available* - keyword, number.long + +* _lldp_neighbors.chassis_station_capability_available_ - Chassis station capability availability + +*chassis_station_capability_enabled* - keyword, number.long + +* _lldp_neighbors.chassis_station_capability_enabled_ - Chassis station capability enabled + +*chassis_sys_description* - keyword, number.long + +* _lldp_neighbors.chassis_sys_description_ - Max number of CPU physical cores + +*chassis_sysname* - keyword, text.text + +* _lldp_neighbors.chassis_sysname_ - CPU brand string, contains vendor and model + +*chassis_tel_capability_available* - keyword, number.long + +* _lldp_neighbors.chassis_tel_capability_available_ - Chassis telephone capability availability + +*chassis_tel_capability_enabled* - keyword, number.long + +* _lldp_neighbors.chassis_tel_capability_enabled_ - Chassis telephone capability enabled + +*chassis_types* - keyword, text.text + +* _chassis_info.chassis_types_ - A comma-separated list of chassis types, such as Desktop or Laptop. + +*chassis_wlan_capability_available* - keyword, number.long + +* _lldp_neighbors.chassis_wlan_capability_available_ - Chassis wlan capability availability + +*chassis_wlan_capability_enabled* - keyword, number.long + +* _lldp_neighbors.chassis_wlan_capability_enabled_ - Chassis wlan capability enabled + +*check_array_finish* - keyword, text.text + +* _md_devices.check_array_finish_ - Estimated duration of the check array activity + +*check_array_progress* - keyword, text.text + +* _md_devices.check_array_progress_ - Progress of the check array activity + +*check_array_speed* - keyword, text.text + +* _md_devices.check_array_speed_ - Speed of the check array activity + +*checksum* - keyword, text.text + +* _disk_events.checksum_ - UDIF Master checksum if available (CRC32) + +*child_pid* - keyword, number.long + +* _es_process_events.child_pid_ - Process ID of a child process in case of a fork event + +*chunk_size* - keyword, number.long + +* _md_devices.chunk_size_ - chunk size in bytes + +*cid* - keyword, number.long + +* _bpf_process_events.cid_ - Cgroup ID +* _bpf_socket_events.cid_ - Cgroup ID + +*class* - keyword, text.text + +* _authorizations.class_ - Label top-level key +* _drivers.class_ - Device/driver class name +* _elf_dynamic.class_ - Class (32 or 64) +* _elf_info.class_ - Class type, 32 or 64bit +* _iokit_devicetree.class_ - Best matching device class (most-specific category) +* _iokit_registry.class_ - Best matching device class (most-specific category) +* _usb_devices.class_ - USB Device class +* _wmi_cli_event_consumers.class_ - The name of the class. +* _wmi_event_filters.class_ - The name of the class. +* _wmi_filter_consumer_binding.class_ - The name of the class. +* _wmi_script_event_consumers.class_ - The name of the class. + +*client_site_name* - keyword, text.text + +* _ntdomains.client_site_name_ - The name of the site where the domain controller is configured. + +*cmdline* - keyword, text.text + +* _bpf_process_events.cmdline_ - Command line arguments +* _docker_container_processes.cmdline_ - Complete argv +* _es_process_events.cmdline_ - Command line arguments (argv) +* _process_events.cmdline_ - Command line arguments (argv) +* _processes.cmdline_ - Complete argv + +*cmdline_count* - keyword, number.long + +* _es_process_events.cmdline_count_ - Number of command line arguments + +*cmdline_size* - keyword, number.long + +* _process_events.cmdline_size_ - Actual size (bytes) of command line arguments + +*code* - keyword, text.text + +* _seccomp_events.code_ - The seccomp action + +*code_integrity_policy_enforcement_status* - keyword, text.text + +* _hvci_status.code_integrity_policy_enforcement_status_ - The status of the code integrity policy enforcement settings. Returns UNKNOWN if an error is encountered. + +*codename* - keyword, text.text + +* _os_version.codename_ - OS version codename + +*collect_cross_processes* - keyword, number.long + +* _carbon_black_info.collect_cross_processes_ - If the sensor is configured to cross process events + +*collect_data_file_writes* - keyword, number.long + +* _carbon_black_info.collect_data_file_writes_ - If the sensor is configured to collect non binary file writes + +*collect_emet_events* - keyword, number.long + +* _carbon_black_info.collect_emet_events_ - If the sensor is configured to EMET events + +*collect_file_mods* - keyword, number.long + +* _carbon_black_info.collect_file_mods_ - If the sensor is configured to collect file modification events + +*collect_module_info* - keyword, number.long + +* _carbon_black_info.collect_module_info_ - If the sensor is configured to collect metadata of binaries + +*collect_module_loads* - keyword, number.long + +* _carbon_black_info.collect_module_loads_ - If the sensor is configured to capture module loads + +*collect_net_conns* - keyword, number.long + +* _carbon_black_info.collect_net_conns_ - If the sensor is configured to collect network connections + +*collect_process_user_context* - keyword, number.long + +* _carbon_black_info.collect_process_user_context_ - If the sensor is configured to collect the user running a process + +*collect_processes* - keyword, number.long + +* _carbon_black_info.collect_processes_ - If the sensor is configured to process events + +*collect_reg_mods* - keyword, number.long + +* _carbon_black_info.collect_reg_mods_ - If the sensor is configured to collect registry modification events + +*collect_sensor_operations* - keyword, number.long + +* _carbon_black_info.collect_sensor_operations_ - Unknown + +*collect_store_files* - keyword, number.long + +* _carbon_black_info.collect_store_files_ - If the sensor is configured to send back binaries to the Carbon Black server + +*collisions* - keyword, number.long + +* _interface_details.collisions_ - Packet Collisions detected + +*color_depth* - keyword, number.long + +* _video_info.color_depth_ - The amount of bits per pixel to represent color. + +*comm* - keyword, text.text + +* _apparmor_events.comm_ - Command-line name of the command that was used to invoke the analyzed process +* _seccomp_events.comm_ - Command-line name of the command that was used to invoke the analyzed process + +*command* - keyword, text.text + +* _crontab.command_ - Raw command string +* _docker_containers.command_ - Command with arguments +* _shell_history.command_ - Unparsed date/line/command history line + +*command_args* - keyword, text.text + +* _shortcut_files.command_args_ - Command args passed to lnk file. + +*command_line* - keyword, text.text + +* _windows_crashes.command_line_ - Command-line string passed to the crashed process + +*command_line_template* - keyword, text.text + +* _wmi_cli_event_consumers.command_line_template_ - Standard string template that specifies the process to be started. This property can be NULL, and the ExecutablePath property is used as the command line. + +*comment* - keyword, text.text + +* _authorizations.comment_ - Label top-level key +* _docker_image_history.comment_ - Instruction comment +* _etc_protocols.comment_ - Comment with protocol description +* _etc_services.comment_ - Optional comment for a service. +* _groups.comment_ - Remarks or comments associated with the group +* _keychain_items.comment_ - Optional keychain comment + +*common_name* - keyword, text.text + +* _certificates.common_name_ - Certificate CommonName +* _curl_certificate.common_name_ - Common name of company issued to + +*common_path* - keyword, text.text + +* _shortcut_files.common_path_ - Common system path to target file. + +*compat* - keyword, number.long + +* _seccomp_events.compat_ - Is system call in compatibility mode + +*compiler* - keyword, text.text + +* _apps.compiler_ - Info properties DTCompiler label + +*completed_time* - keyword, number.long + +* _cups_jobs.completed_time_ - When the job completed printing + +*components* - keyword, text.text + +* _apt_sources.components_ - Repository components + +*compressed* - keyword, number.long + +* _virtual_memory_info.compressed_ - The total number of pages that have been compressed by the VM compressor. + +*compressor* - keyword, number.long + +* _virtual_memory_info.compressor_ - The number of pages used to store compressed VM pages. + +*computer_name* - keyword, text.text + +* _system_info.computer_name_ - Friendly computer name (optional) +* _windows_eventlog.computer_name_ - Hostname of system where event was generated +* _windows_events.computer_name_ - Hostname of system where event was generated + +*condition* - keyword, text.text + +* _battery.condition_ - One of the following: "Normal" indicates the condition of the battery is within normal tolerances, "Service Needed" indicates that the battery should be checked out by a licensed Mac repair service, "Permanent Failure" indicates the battery needs replacement + +*config_entrypoint* - keyword, text.text + +* _docker_containers.config_entrypoint_ - Container entrypoint(s) + +*config_flag* - keyword, text.text + +* _sip_config.config_flag_ - The System Integrity Protection config flag + +*config_hash* - keyword, text.text + +* _osquery_info.config_hash_ - Hash of the working configuration state + +*config_name* - keyword, text.text + +* _carbon_black_info.config_name_ - Sensor group + +*config_valid* - keyword, number.long + +* _osquery_info.config_valid_ - 1 if the config was loaded and considered valid, else 0 + +*config_value* - keyword, text.text + +* _system_controls.config_value_ - The MIB value set in /etc/sysctl.conf + +*configured_clock_speed* - keyword, number.long + +* _memory_devices.configured_clock_speed_ - Configured speed of memory device in megatransfers per second (MT/s) + +*configured_voltage* - keyword, number.long + +* _memory_devices.configured_voltage_ - Configured operating voltage of device in millivolts + +*connection_id* - keyword, text.text + +* _interface_details.connection_id_ - Name of the network connection as it appears in the Network Connections Control Panel program. + +*connection_status* - keyword, text.text + +* _interface_details.connection_status_ - State of the network adapter connection to the network. + +*consistency_scan_date* - keyword, number.long + +* _time_machine_destinations.consistency_scan_date_ - Consistency scan date + +*consumer* - keyword, text.text + +* _wmi_filter_consumer_binding.consumer_ - Reference to an instance of __EventConsumer that represents the object path to a logical consumer, the recipient of an event. + +*containers* - keyword, number.long + +* _docker_info.containers_ - Total number of containers + +*containers_paused* - keyword, number.long + +* _docker_info.containers_paused_ - Number of containers in paused state + +*containers_running* - keyword, number.long + +* _docker_info.containers_running_ - Number of containers currently running + +*containers_stopped* - keyword, number.long + +* _docker_info.containers_stopped_ - Number of containers in stopped state + +*content* - keyword, text.text + +* _disk_events.content_ - Disk event content + +*content_caching* - keyword, number.long + +* _sharing_preferences.content_caching_ - 1 If content caching is enabled else 0 + +*content_type* - keyword, text.text + +* _package_install_history.content_type_ - Package content_type (optional) + +*conversion_status* - keyword, number.long + +* _bitlocker_info.conversion_status_ - The bitlocker conversion status of the drive. + +*coprocessor_version* - keyword, text.text + +* _ibridge_info.coprocessor_version_ - The manufacturer and chip version + +*copy* - keyword, number.long + +* _virtual_memory_info.copy_ - Total number of copy-on-write pages. + +*copyright* - keyword, text.text + +* _apps.copyright_ - Info properties NSHumanReadableCopyright label + +*core* - keyword, number.long + +* _cpu_time.core_ - Name of the cpu (core) + +*cosine_similarity* - keyword, number.double + +* _powershell_events.cosine_similarity_ - How similar the Powershell script is to a provided 'normal' character frequency + +*count* - keyword, number.long + +* _userassist.count_ - Number of times the application has been executed. +* _yara.count_ - Number of YARA matches +* _yara_events.count_ - Number of YARA matches + +*country_code* - keyword, text.text + +* _wifi_status.country_code_ - The country code (ISO/IEC 3166-1:1997) for the network +* _wifi_survey.country_code_ - The country code (ISO/IEC 3166-1:1997) for the network + +*cpu* - keyword, number.double + +* _docker_container_processes.cpu_ - CPU utilization as percentage + +*cpu_brand* - keyword, text.text + +* _system_info.cpu_brand_ - CPU brand string, contains vendor and model + +*cpu_cfs_period* - keyword, number.long + +* _docker_info.cpu_cfs_period_ - 1 if CPU Completely Fair Scheduler (CFS) period support is enabled. 0 otherwise + +*cpu_cfs_quota* - keyword, number.long + +* _docker_info.cpu_cfs_quota_ - 1 if CPU Completely Fair Scheduler (CFS) quota support is enabled. 0 otherwise + +*cpu_kernelmode_usage* - keyword, number.long + +* _docker_container_stats.cpu_kernelmode_usage_ - CPU kernel mode usage + +*cpu_logical_cores* - keyword, number.long + +* _system_info.cpu_logical_cores_ - Number of logical CPU cores available to the system + +*cpu_microcode* - keyword, text.text + +* _system_info.cpu_microcode_ - Microcode version + +*cpu_physical_cores* - keyword, number.long + +* _system_info.cpu_physical_cores_ - Number of physical CPU cores in to the system + +*cpu_pred_cmd_supported* - keyword, number.long + +* _kva_speculative_info.cpu_pred_cmd_supported_ - PRED_CMD MSR supported by CPU Microcode. + +*cpu_set* - keyword, number.long + +* _docker_info.cpu_set_ - 1 if CPU set selection support is enabled. 0 otherwise + +*cpu_shares* - keyword, number.long + +* _docker_info.cpu_shares_ - 1 if CPU share weighting support is enabled. 0 otherwise + +*cpu_spec_ctrl_supported* - keyword, number.long + +* _kva_speculative_info.cpu_spec_ctrl_supported_ - SPEC_CTRL MSR supported by CPU Microcode. + +*cpu_status* - keyword, number.long + +* _cpu_info.cpu_status_ - The current operating status of the CPU. + +*cpu_subtype* - keyword + +* _processes.cpu_subtype_ - Indicates the specific processor on which an entry may be used. +* _system_info.cpu_subtype_ - CPU subtype + +*cpu_total_usage* - keyword, number.long + +* _docker_container_stats.cpu_total_usage_ - Total CPU usage + +*cpu_type* - keyword + +* _processes.cpu_type_ - Indicates the specific processor designed for installation. +* _system_info.cpu_type_ - CPU type + +*cpu_usermode_usage* - keyword, number.long + +* _docker_container_stats.cpu_usermode_usage_ - CPU user mode usage + +*cpus* - keyword, number.long + +* _docker_info.cpus_ - Number of CPUs + +*crash_path* - keyword, text.text + +* _crashes.crash_path_ - Location of log file +* _windows_crashes.crash_path_ - Path of the log file + +*crashed_thread* - keyword, number.long + +* _crashes.crashed_thread_ - Thread ID which crashed + +*created* - keyword, text.text + +* _authorizations.created_ - Label top-level key +* _docker_containers.created_ - Time of creation as UNIX time +* _docker_image_history.created_ - Time of creation as UNIX time +* _docker_images.created_ - Time of creation as UNIX time +* _docker_networks.created_ - Time of creation as UNIX time +* _keychain_items.created_ - Data item was created + +*created_at* - keyword, text.text + +* _lxd_images.created_at_ - ISO time of image creation +* _lxd_instances.created_at_ - ISO time of creation + +*created_by* - keyword, text.text + +* _docker_image_history.created_by_ - Created by instruction + +*created_time* - keyword, number.long + +* _shellbags.created_time_ - Directory Created time. + +*creation_time* - keyword + +* _account_policy_data.creation_time_ - When the account was first created +* _cups_jobs.creation_time_ - When the print request was initiated + +*creator* - keyword, text.text + +* _firefox_addons.creator_ - Addon-supported creator string + +*creator_pid* - keyword, number.long + +* _shared_memory.creator_pid_ - Process ID that created the segment + +*creator_uid* - keyword, number.long + +* _shared_memory.creator_uid_ - User ID of creator process + +*csname* - keyword, text.text + +* _patches.csname_ - The name of the host the patch is installed on. + +*ctime* - keyword + +* _device_file.ctime_ - Creation time +* _file.ctime_ - Last status change time +* _file_events.ctime_ - Last status change time +* _gatekeeper_approved_apps.ctime_ - Last change time +* _process_events.ctime_ - File last metadata change in UNIX time +* _shared_memory.ctime_ - Changed time + +*current_capacity* - keyword, number.long + +* _battery.current_capacity_ - The battery's current charged capacity in mAh + +*current_clock_speed* - keyword, number.long + +* _cpu_info.current_clock_speed_ - The current frequency of the CPU. + +*current_directory* - keyword, text.text + +* _windows_crashes.current_directory_ - Current working directory of the crashed process + +*current_disk_queue_length* - keyword, number.long + +* _physical_disk_performance.current_disk_queue_length_ - Number of requests outstanding on the disk at the time the performance data is collected + +*current_locale* - keyword, text.text + +* _chrome_extensions.current_locale_ - Current locale supported by extension + +*current_value* - keyword, text.text + +* _system_controls.current_value_ - Value of setting + +*cwd* - keyword, text.text + +* _bpf_process_events.cwd_ - Current working directory +* _es_process_events.cwd_ - The process current working directory +* _process_events.cwd_ - The process current working directory +* _process_file_events.cwd_ - The current working directory of the process +* _processes.cwd_ - Process current working directory + +*cycle_count* - keyword, number.long + +* _battery.cycle_count_ - The number of charge/discharge cycles + +*data* - keyword, text.text + +* _magic.data_ - Magic number data from libmagic +* _registry.data_ - Data content of registry value +* _windows_eventlog.data_ - Data associated with the event +* _windows_events.data_ - Data associated with the event + +*data_width* - keyword, number.long + +* _memory_devices.data_width_ - Data width, in bits, of this memory device + +*database* - keyword, number.long + +* _lxd_cluster_members.database_ - Whether the server is a database node (1) or not (0) + +*date* - keyword + +* _drivers.date_ - Driver date +* _platform_info.date_ - Self-reported platform code update date + +*datetime* - keyword, text.text + +* _crashes.datetime_ - Date/Time at which the crash occurred +* _powershell_events.datetime_ - System time at which the Powershell script event occurred +* _syslog_events.datetime_ - Time known to syslog +* _time.datetime_ - Current date and time (ISO format) in the system +* _windows_crashes.datetime_ - Timestamp (log format) of the crash +* _windows_eventlog.datetime_ - System time at which the event occurred +* _windows_events.datetime_ - System time at which the event occurred + +*day* - keyword, number.long + +* _time.day_ - Current day in the system + +*day_of_month* - keyword, text.text + +* _crontab.day_of_month_ - The day of the month for the job + +*day_of_week* - keyword, text.text + +* _crontab.day_of_week_ - The day of the week for the job + +*days* - keyword, number.long + +* _uptime.days_ - Days of uptime + +*dc_site_name* - keyword, text.text + +* _ntdomains.dc_site_name_ - The name of the site where the domain controller is located. + +*decompressed* - keyword, number.long + +* _virtual_memory_info.decompressed_ - The total number of pages that have been decompressed by the VM compressor. + +*default_locale* - keyword, text.text + +* _chrome_extensions.default_locale_ - Default locale supported by extension + +*default_value* - keyword, text.text + +* _osquery_flags.default_value_ - Flag default value + +*denied_mask* - keyword, text.text + +* _apparmor_events.denied_mask_ - Denied permissions for the process + +*denylisted* - keyword, number.long + +* _osquery_schedule.denylisted_ - 1 if the query is denylisted else 0 + +*dependencies* - keyword, text.text + +* _kernel_panics.dependencies_ - Module dependencies existing in crashed module's backtrace + +*depth* - keyword, number.long + +* _iokit_devicetree.depth_ - Device nested depth +* _iokit_registry.depth_ - Node nested depth + +*description* - keyword, text.text + +* _appcompat_shims.description_ - Description of the SDB. +* _atom_packages.description_ - Package supplied description +* _browser_plugins.description_ - Plugin description text +* _chassis_info.description_ - An extended description of the chassis if available. +* _chrome_extensions.description_ - Extension-optional description +* _disk_info.description_ - The OS's description of the disk. +* _drivers.description_ - Driver description +* _firefox_addons.description_ - Addon-supplied description string +* _interface_details.description_ - Short description of the object a one-line string. +* _keychain_acls.description_ - The description included with the ACL entry +* _keychain_items.description_ - Optional item description +* _logical_drives.description_ - The canonical description of the drive, e.g. 'Logical Fixed Disk', 'CD-ROM Disk'. +* _lxd_images.description_ - Image description +* _lxd_instances.description_ - Instance description +* _npm_packages.description_ - Package supplied description +* _osquery_flags.description_ - Flag description +* _patches.description_ - Fuller description of the patch. +* _safari_extensions.description_ - Optional extension description text +* _services.description_ - Service Description +* _shared_resources.description_ - A textual description of the object +* _shortcut_files.description_ - Lnk file description. +* _smbios_tables.description_ - Table entry description +* _systemd_units.description_ - Unit description +* _users.description_ - Optional user description +* _ycloud_instance_metadata.description_ - Description of the VM + +*designed_capacity* - keyword, number.long + +* _battery.designed_capacity_ - The battery's designed capacity in mAh + +*dest_path* - keyword, text.text + +* _process_file_events.dest_path_ - The canonical path associated with the event + +*destination* - keyword, text.text + +* _cups_jobs.destination_ - The printer the job was sent to +* _docker_container_mounts.destination_ - Destination path inside container +* _routes.destination_ - Destination IP address + +*destination_id* - keyword, text.text + +* _time_machine_backups.destination_id_ - Time Machine destination ID +* _time_machine_destinations.destination_id_ - Time Machine destination ID + +*dev_id_enabled* - keyword, number.long + +* _gatekeeper.dev_id_enabled_ - 1 If a Gatekeeper allows execution from identified developers else 0 + +*developer_id* - keyword, text.text + +* _safari_extensions.developer_id_ - Optional developer identifier +* _xprotect_meta.developer_id_ - Developer identity (SHA1) of extension + +*development_region* - keyword, text.text + +* _apps.development_region_ - Info properties CFBundleDevelopmentRegion label +* _browser_plugins.development_region_ - Plugin language-localization + +*device* - keyword, text.text + +* _device_file.device_ - Absolute file path to device node +* _device_firmware.device_ - The device name +* _device_hash.device_ - Absolute file path to device node +* _device_partitions.device_ - Absolute file path to device node +* _disk_events.device_ - Disk event BSD name +* _file.device_ - Device ID (optional) +* _kernel_info.device_ - Kernel device identifier +* _lxd_instance_devices.device_ - Name of the device +* _mounts.device_ - Mounted device +* _process_memory_map.device_ - MA:MI Major/minor device ID + +*device_alias* - keyword, text.text + +* _mounts.device_alias_ - Mounted device alias + +*device_error_address* - keyword, text.text + +* _memory_error_info.device_error_address_ - 32 bit physical address of the error relative to the start of the failing memory address, in bytes + +*device_id* - keyword, text.text + +* _bitlocker_info.device_id_ - ID of the encrypted drive. +* _cpu_info.device_id_ - The DeviceID of the CPU. +* _drivers.device_id_ - Device ID +* _logical_drives.device_id_ - The drive id, usually the drive name, e.g., 'C:'. + +*device_locator* - keyword, text.text + +* _memory_devices.device_locator_ - String number of the string that identifies the physically-labeled socket or board position where the memory device is located + +*device_model* - keyword, text.text + +* _smart_drive_info.device_model_ - Device Model + +*device_name* - keyword, text.text + +* _drivers.device_name_ - Device name +* _md_devices.device_name_ - md device name +* _smart_drive_info.device_name_ - Name of block device + +*device_path* - keyword, text.text + +* _iokit_devicetree.device_path_ - Device tree path + +*device_type* - keyword, text.text + +* _lxd_instance_devices.device_type_ - Device type +* _shortcut_files.device_type_ - Device containing the target file. + +*dhcp_enabled* - keyword, number.long + +* _interface_details.dhcp_enabled_ - If TRUE, the dynamic host configuration protocol (DHCP) server automatically assigns an IP address to the computer system when establishing a network connection. + +*dhcp_lease_expires* - keyword, text.text + +* _interface_details.dhcp_lease_expires_ - Expiration date and time for a leased IP address that was assigned to the computer by the dynamic host configuration protocol (DHCP) server. + +*dhcp_lease_obtained* - keyword, text.text + +* _interface_details.dhcp_lease_obtained_ - Date and time the lease was obtained for the IP address assigned to the computer by the dynamic host configuration protocol (DHCP) server. + +*dhcp_server* - keyword, text.text + +* _interface_details.dhcp_server_ - IP address of the dynamic host configuration protocol (DHCP) server. + +*directory* - keyword, text.text + +* _extended_attributes.directory_ - Directory of file(s) +* _file.directory_ - Directory of file(s) +* _hash.directory_ - Must provide a path or directory +* _npm_packages.directory_ - Node module's directory where this package is located +* _python_packages.directory_ - Directory where Python modules are located +* _users.directory_ - User's home directory + +*disabled* - keyword + +* _browser_plugins.disabled_ - Is the plugin disabled. 1 = Disabled +* _firefox_addons.disabled_ - 1 If the addon is application-disabled else 0 +* _launchd.disabled_ - Skip loading this daemon or agent on boot +* _wifi_networks.disabled_ - 1 if this network is disabled, 0 otherwise + +*disc_sharing* - keyword, number.long + +* _sharing_preferences.disc_sharing_ - 1 If CD or DVD sharing is enabled else 0 + +*disconnected* - keyword, number.long + +* _connectivity.disconnected_ - True if the all interfaces are not connected to any network + +*discovery_cache_hits* - keyword, number.long + +* _osquery_packs.discovery_cache_hits_ - The number of times that the discovery query used cached values since the last time the config was reloaded + +*discovery_executions* - keyword, number.long + +* _osquery_packs.discovery_executions_ - The number of times that the discovery queries have been executed since the last time the config was reloaded + +*disk_bytes_read* - keyword, number.long + +* _processes.disk_bytes_read_ - Bytes read from disk + +*disk_bytes_written* - keyword, number.long + +* _processes.disk_bytes_written_ - Bytes written to disk + +*disk_id* - keyword, number.long + +* _smart_drive_info.disk_id_ - Physical slot number of device, only exists when hardware storage controller exists + +*disk_index* - keyword, number.long + +* _disk_info.disk_index_ - Physical drive number of the disk. + +*disk_read* - keyword, number.long + +* _docker_container_stats.disk_read_ - Total disk read bytes + +*disk_size* - keyword, number.long + +* _disk_info.disk_size_ - Size of the disk. + +*disk_write* - keyword, number.long + +* _docker_container_stats.disk_write_ - Total disk write bytes + +*display_name* - keyword, text.text + +* _apps.display_name_ - Info properties CFBundleDisplayName label +* _services.display_name_ - Service Display name + +*dns_domain* - keyword, text.text + +* _interface_details.dns_domain_ - Organization name followed by a period and an extension that indicates the type of organization, such as 'microsoft.com'. + +*dns_domain_name* - keyword, text.text + +* _logon_sessions.dns_domain_name_ - The DNS name for the owner of the logon session. + +*dns_domain_suffix_search_order* - keyword, text.text + +* _interface_details.dns_domain_suffix_search_order_ - Array of DNS domain suffixes to be appended to the end of host names during name resolution. + +*dns_forest_name* - keyword, text.text + +* _ntdomains.dns_forest_name_ - The name of the root of the DNS tree. + +*dns_host_name* - keyword, text.text + +* _interface_details.dns_host_name_ - Host name used to identify the local computer for authentication by some utilities. + +*dns_server_search_order* - keyword, text.text + +* _interface_details.dns_server_search_order_ - Array of server IP addresses to be used in querying for DNS servers. + +*domain* - keyword, text.text + +* _ad_config.domain_ - Active Directory trust domain +* _managed_policies.domain_ - System or manager-chosen domain key +* _preferences.domain_ - Application ID usually in com.name.product format + +*domain_controller_address* - keyword, text.text + +* _ntdomains.domain_controller_address_ - The IP Address of the discovered domain controller.. + +*domain_controller_name* - keyword, text.text + +* _ntdomains.domain_controller_name_ - The name of the discovered domain controller. + +*domain_name* - keyword, text.text + +* _ntdomains.domain_name_ - The name of the domain. + +*drive_letter* - keyword, text.text + +* _bitlocker_info.drive_letter_ - Drive letter of the encrypted drive. +* _ntfs_journal_events.drive_letter_ - The drive letter identifying the source journal + +*drive_name* - keyword, text.text + +* _md_drives.drive_name_ - Drive device name + +*driver* - keyword, text.text + +* _docker_container_mounts.driver_ - Driver providing the mount +* _docker_networks.driver_ - Network driver +* _docker_volumes.driver_ - Volume driver +* _hardware_events.driver_ - Driver claiming the device +* _lxd_storage_pools.driver_ - Storage driver +* _pci_devices.driver_ - PCI Device used driver +* _video_info.driver_ - The driver of the device. + +*driver_date* - keyword, number.long + +* _video_info.driver_date_ - The date listed on the installed driver. + +*driver_key* - keyword, text.text + +* _drivers.driver_key_ - Driver key + +*driver_type* - keyword, text.text + +* _smart_drive_info.driver_type_ - The explicit device type used to retrieve the SMART information + +*driver_version* - keyword, text.text + +* _video_info.driver_version_ - The version of the installed driver. + +*dst_ip* - keyword, text.text + +* _iptables.dst_ip_ - Destination IP address. + +*dst_mask* - keyword, text.text + +* _iptables.dst_mask_ - Destination IP address mask. + +*dst_port* - keyword, text.text + +* _iptables.dst_port_ - Protocol destination port(s). + +*dtime* - keyword, number.long + +* _shared_memory.dtime_ - Detached time + +*dump_certificate* - keyword, number.long + +* _curl_certificate.dump_certificate_ - Set this value to '1' to dump certificate + +*duration* - keyword, number.long + +* _bpf_process_events.duration_ - How much time was spent inside the syscall (nsecs) +* _bpf_socket_events.duration_ - How much time was spent inside the syscall (nsecs) + +*eapi* - keyword, number.long + +* _portage_packages.eapi_ - The eapi for the ebuild + +*egid* - keyword + +* _docker_container_processes.egid_ - Effective group ID +* _es_process_events.egid_ - Effective Group ID of the process +* _process_events.egid_ - Effective group ID at process start +* _process_file_events.egid_ - Effective group ID of the process using the file +* _processes.egid_ - Unsigned effective group ID + +*eid* - keyword, text.text + +* _apparmor_events.eid_ - Event ID +* _bpf_process_events.eid_ - Event ID +* _bpf_socket_events.eid_ - Event ID +* _disk_events.eid_ - Event ID +* _es_process_events.eid_ - Event ID +* _file_events.eid_ - Event ID +* _hardware_events.eid_ - Event ID +* _ntfs_journal_events.eid_ - Event ID +* _process_events.eid_ - Event ID +* _process_file_events.eid_ - Event ID +* _selinux_events.eid_ - Event ID +* _socket_events.eid_ - Event ID +* _syslog_events.eid_ - Event ID +* _user_events.eid_ - Event ID +* _windows_events.eid_ - Event ID +* _yara_events.eid_ - Event ID + +*ejectable* - keyword, number.long + +* _disk_events.ejectable_ - 1 if ejectable, 0 if not + +*elapsed_time* - keyword, number.long + +* _processes.elapsed_time_ - Elapsed time in seconds this process has been running. + +*element* - keyword, text.text + +* _apps.element_ - Does the app identify as a background agent + +*elevated_token* - keyword, number.long + +* _processes.elevated_token_ - Process uses elevated token yes=1, no=0 + +*enable_ipv6* - keyword, number.long + +* _docker_networks.enable_ipv6_ - 1 if IPv6 is enabled on this network. 0 otherwise + +*enabled* - keyword + +* _app_schemes.enabled_ - 1 if this handler is the OS default, else 0 +* _event_taps.enabled_ - Is the Event Tap enabled +* _interface_details.enabled_ - Indicates whether the adapter is enabled or not. +* _location_services.enabled_ - 1 if Location Services are enabled, else 0 +* _lxd_cluster.enabled_ - Whether clustering enabled (1) or not (0) on this node +* _sandboxes.enabled_ - Application sandboxings enabled on container +* _scheduled_tasks.enabled_ - Whether or not the scheduled task is enabled +* _screenlock.enabled_ - 1 If a password is required after sleep or the screensaver begins; else 0 +* _sip_config.enabled_ - 1 if this configuration is enabled, otherwise 0 +* _tpm_info.enabled_ - TPM is enabled +* _yum_sources.enabled_ - Whether the repository is used + +*enabled_nvram* - keyword, number.long + +* _sip_config.enabled_nvram_ - 1 if this configuration is enabled, otherwise 0 + +*encrypted* - keyword, number.long + +* _disk_encryption.encrypted_ - 1 If encrypted: true (disk is encrypted), else 0 +* _user_ssh_keys.encrypted_ - 1 if key is encrypted, 0 otherwise + +*encryption* - keyword, text.text + +* _time_machine_destinations.encryption_ - Last known encrypted state + +*encryption_method* - keyword, text.text + +* _bitlocker_info.encryption_method_ - The encryption type of the device. + +*encryption_status* - keyword, text.text + +* _disk_encryption.encryption_status_ - Disk encryption status with one of following values: encrypted | not encrypted | undefined + +*end* - keyword, text.text + +* _memory_map.end_ - End address of memory region +* _process_memory_map.end_ - Virtual end address (hex) + +*ending_address* - keyword, text.text + +* _memory_array_mapped_addresses.ending_address_ - Physical ending address of last kilobyte of a range of memory mapped to physical memory array +* _memory_device_mapped_addresses.ending_address_ - Physical ending address of last kilobyte of a range of memory mapped to physical memory array + +*endpoint_id* - keyword, text.text + +* _docker_container_networks.endpoint_id_ - Endpoint ID + +*entry* - keyword, text.text + +* _authorization_mechanisms.entry_ - The whole string entry +* _elf_info.entry_ - Entry point address +* _shimcache.entry_ - Execution order. + +*env* - keyword, text.text + +* _es_process_events.env_ - Environment variables delimited by spaces +* _process_events.env_ - Environment variables delimited by spaces + +*env_count* - keyword, number.long + +* _es_process_events.env_count_ - Number of environment variables +* _process_events.env_count_ - Number of environment variables + +*env_size* - keyword, number.long + +* _process_events.env_size_ - Actual size (bytes) of environment list + +*env_variables* - keyword, text.text + +* _docker_containers.env_variables_ - Container environmental variables + +*environment* - keyword, text.text + +* _apps.environment_ - Application-set environment variables + +*ephemeral* - keyword, number.long + +* _lxd_instances.ephemeral_ - Whether the instance is ephemeral(1) or not(0) + +*epoch* - keyword, number.long + +* _rpm_packages.epoch_ - Package epoch value + +*error* - keyword, text.text + +* _apparmor_events.error_ - Error information + +*error_granularity* - keyword, text.text + +* _memory_error_info.error_granularity_ - Granularity to which the error can be resolved + +*error_operation* - keyword, text.text + +* _memory_error_info.error_operation_ - Memory access operation that caused the error + +*error_resolution* - keyword, text.text + +* _memory_error_info.error_resolution_ - Range, in bytes, within which this error can be determined, when an error address is given + +*error_type* - keyword, text.text + +* _memory_error_info.error_type_ - type of error associated with current error status for array or device + +*euid* - keyword + +* _docker_container_processes.euid_ - Effective user ID +* _es_process_events.euid_ - Effective User ID of the process +* _process_events.euid_ - Effective user ID at process start +* _process_file_events.euid_ - Effective user ID of the process using the file +* _processes.euid_ - Unsigned effective user ID + +*event* - keyword, text.text + +* _crontab.event_ - The job @event name (rare) + +*event_queue* - keyword, number.long + +* _carbon_black_info.event_queue_ - Size in bytes of Carbon Black event files on disk + +*event_tap_id* - keyword, number.long + +* _event_taps.event_tap_id_ - Unique ID for the Tap + +*event_tapped* - keyword, text.text + +* _event_taps.event_tapped_ - The mask that identifies the set of events to be observed. + +*event_type* - keyword, text.text + +* _es_process_events.event_type_ - Type of EndpointSecurity event + +*eventid* - keyword, number.long + +* _windows_eventlog.eventid_ - Event ID of the event +* _windows_events.eventid_ - Event ID of the event + +*events* - keyword, number.long + +* _osquery_events.events_ - Number of events emitted or received since osquery started + +*exception_address* - keyword, text.text + +* _windows_crashes.exception_address_ - Address (in hex) where the exception occurred + +*exception_code* - keyword, text.text + +* _windows_crashes.exception_code_ - The Windows exception code + +*exception_codes* - keyword, text.text + +* _crashes.exception_codes_ - Exception codes from the crash + +*exception_message* - keyword, text.text + +* _windows_crashes.exception_message_ - The NTSTATUS error message associated with the exception code + +*exception_notes* - keyword, text.text + +* _crashes.exception_notes_ - Exception notes from the crash + +*exception_type* - keyword, text.text + +* _crashes.exception_type_ - Exception type of the crash + +*exe* - keyword, text.text + +* _seccomp_events.exe_ - The path to the executable that was used to invoke the analyzed process + +*executable* - keyword, text.text + +* _appcompat_shims.executable_ - Name of the executable that is being shimmed. This is pulled from the registry. +* _process_file_events.executable_ - The executable path + +*executable_path* - keyword, text.text + +* _wmi_cli_event_consumers.executable_path_ - Module to execute. The string can specify the full path and file name of the module to execute, or it can specify a partial name. If a partial name is specified, the current drive and current directory are assumed. + +*execution_flag* - keyword, number.long + +* _shimcache.execution_flag_ - Boolean Execution flag, 1 for execution, 0 for no execution, -1 for missing (this flag does not exist on Windows 10 and higher). + +*executions* - keyword, number.long + +* _osquery_schedule.executions_ - Number of times the query was executed + +*exit_code* - keyword, text.text + +* _bpf_process_events.exit_code_ - Exit code of the system call +* _bpf_socket_events.exit_code_ - Exit code of the system call +* _es_process_events.exit_code_ - Exit code of a process in case of an exit event + +*expand* - keyword, number.long + +* _default_environment.expand_ - 1 if the variable needs expanding, 0 otherwise + +*expire* - keyword, number.long + +* _shadow.expire_ - Number of days since UNIX epoch date until account is disabled + +*expires_at* - keyword, text.text + +* _lxd_images.expires_at_ - ISO time of image expiration + +*extended_key_usage* - keyword, text.text + +* _curl_certificate.extended_key_usage_ - Extended usage of key in certificate + +*extensions* - keyword, text.text + +* _osquery_info.extensions_ - osquery extensions status + +*external* - keyword, number.long + +* _app_schemes.external_ - 1 if this handler does NOT exist on OS X by default, else 0 + +*extra* - keyword, text.text + +* _asl.extra_ - Extra columns, in JSON format. Queries against this column are performed entirely in SQLite, so do not benefit from efficient querying via asl.h. +* _platform_info.extra_ - Platform-specific additional information + +*facility* - keyword, text.text + +* _asl.facility_ - Sender's facility. Default is 'user'. +* _syslog_events.facility_ - Syslog facility + +*fahrenheit* - keyword, number.double + +* _temperature_sensors.fahrenheit_ - Temperature in Fahrenheit + +*failed_disks* - keyword, number.long + +* _md_devices.failed_disks_ - Number of failed disks in array + +*failed_login_count* - keyword, number.long + +* _account_policy_data.failed_login_count_ - The number of failed login attempts using an incorrect password. Count resets after a correct password is entered. + +*failed_login_timestamp* - keyword, number.double + +* _account_policy_data.failed_login_timestamp_ - The time of the last failed login attempt. Resets after a correct password is entered + +*family* - keyword, number.long + +* _bpf_socket_events.family_ - The Internet protocol family ID +* _listening_ports.family_ - Network protocol (IPv4, IPv6) +* _process_open_sockets.family_ - Network protocol (IPv4, IPv6) +* _socket_events.family_ - The Internet protocol family ID + +*fan* - keyword, text.text + +* _fan_speed_sensors.fan_ - Fan number + +*faults* - keyword, number.long + +* _virtual_memory_info.faults_ - Total number of calls to vm_faults. + +*fd* - keyword, text.text + +* _bpf_socket_events.fd_ - The file description for the process socket +* _listening_ports.fd_ - Socket file descriptor number +* _process_open_files.fd_ - Process-specific file descriptor number +* _process_open_pipes.fd_ - File descriptor +* _process_open_sockets.fd_ - Socket file descriptor number +* _socket_events.fd_ - The file description for the process socket + +*feature* - keyword, text.text + +* _cpuid.feature_ - Present feature flags + +*feature_control* - keyword, number.long + +* _msr.feature_control_ - Bitfield controlling enabled features. + +*field_name* - keyword, text.text + +* _system_controls.field_name_ - Specific attribute of opaque type + +*file_attributes* - keyword, text.text + +* _ntfs_journal_events.file_attributes_ - File attributes + +*file_backed* - keyword, number.long + +* _virtual_memory_info.file_backed_ - Total number of file backed pages. + +*file_id* - keyword, text.text + +* _file.file_id_ - file ID + +*file_sharing* - keyword, number.long + +* _sharing_preferences.file_sharing_ - 1 If file sharing is enabled else 0 + +*file_system* - keyword, text.text + +* _logical_drives.file_system_ - The file system of the drive. + +*file_version* - keyword, text.text + +* _file.file_version_ - File version + +*filename* - keyword, text.text + +* _device_file.filename_ - Name portion of file path +* _file.filename_ - Name portion of file path +* _lxd_images.filename_ - Filename of the image file +* _prefetch.filename_ - Executable filename. +* _xprotect_entries.filename_ - Use this file name to match + +*filepath* - keyword, text.text + +* _package_bom.filepath_ - Package file or directory + +*filesystem* - keyword, text.text + +* _disk_events.filesystem_ - Filesystem if available + +*filetype* - keyword, text.text + +* _xprotect_entries.filetype_ - Use this file type to match + +*filevault_status* - keyword, text.text + +* _disk_encryption.filevault_status_ - FileVault status with one of following values: on | off | unknown + +*filter* - keyword, text.text + +* _wmi_filter_consumer_binding.filter_ - Reference to an instance of __EventFilter that represents the object path to an event filter which is a query that specifies the type of event to be received. + +*filter_name* - keyword, text.text + +* _iptables.filter_name_ - Packet matching filter table name. + +*fingerprint* - keyword, text.text + +* _lxd_certificates.fingerprint_ - SHA256 hash of the certificate + +*finished_at* - keyword, text.text + +* _docker_containers.finished_at_ - Container finish time as string + +*firewall* - keyword, text.text + +* _windows_security_center.firewall_ - The health of the monitored Firewall (see windows_security_products) + +*firewall_unload* - keyword, number.long + +* _alf.firewall_unload_ - 1 If firewall unloading enabled else 0 + +*firmware_version* - keyword, text.text + +* _ibridge_info.firmware_version_ - The build version of the firmware +* _smart_drive_info.firmware_version_ - Drive firmware version + +*fix_comments* - keyword, text.text + +* _patches.fix_comments_ - Additional comments about the patch. + +*flag* - keyword, number.long + +* _shadow.flag_ - Reserved + +*flags* - keyword + +* _device_partitions.flags_ - +* _dns_cache.flags_ - DNS record flags +* _elf_info.flags_ - ELF header flags +* _elf_sections.flags_ - Section attributes +* _elf_segments.flags_ - Segment attributes +* _interface_details.flags_ - Flags (netdevice) for the device +* _mounts.flags_ - Mounted device flags +* _pipes.flags_ - The flags indicating whether this pipe connection is a server or client end, and if the pipe for sending messages or bytes +* _routes.flags_ - Flags to describe route + +*flatsize* - keyword, number.long + +* _pkg_packages.flatsize_ - Package size in bytes + +*folder_id* - keyword, text.text + +* _ycloud_instance_metadata.folder_id_ - Folder identifier for the VM + +*following* - keyword, text.text + +* _systemd_units.following_ - The name of another unit that this unit follows in state + +*forced* - keyword, number.long + +* _preferences.forced_ - 1 if the value is forced/managed, else 0 + +*form_factor* - keyword, text.text + +* _memory_devices.form_factor_ - Implementation form factor for this memory device +* _smart_drive_info.form_factor_ - Form factor if reported + +*format* - keyword, text.text + +* _cups_jobs.format_ - The format of the print job + +*forwarding_enabled* - keyword, number.long + +* _interface_ipv6.forwarding_enabled_ - Enable IP forwarding + +*fragment_path* - keyword, text.text + +* _systemd_units.fragment_path_ - The unit file path this unit was read from, if there is any + +*frame_backtrace* - keyword, text.text + +* _kernel_panics.frame_backtrace_ - Backtrace of the crashed module + +*free* - keyword, number.long + +* _virtual_memory_info.free_ - Total number of free pages. + +*free_space* - keyword, number.long + +* _logical_drives.free_space_ - The amount of free space, in bytes, of the drive (-1 on failure). + +*friendly_name* - keyword, text.text + +* _interface_addresses.friendly_name_ - The friendly display name of the interface. +* _interface_details.friendly_name_ - The friendly display name of the interface. + +*from_webstore* - keyword, text.text + +* _chrome_extensions.from_webstore_ - True if this extension was installed from the web store + +*fs_id* - keyword, text.text + +* _quicklook_cache.fs_id_ - Quicklook file fs_id key + +*fsgid* - keyword + +* _process_events.fsgid_ - Filesystem group ID at process start +* _process_file_events.fsgid_ - Filesystem group ID of the process using the file + +*fsuid* - keyword + +* _apparmor_events.fsuid_ - Filesystem user ID +* _process_events.fsuid_ - Filesystem user ID at process start +* _process_file_events.fsuid_ - Filesystem user ID of the process using the file + +*gateway* - keyword, text.text + +* _docker_container_networks.gateway_ - Gateway +* _docker_networks.gateway_ - Network gateway +* _routes.gateway_ - Route gateway + +*gid* - keyword + +* _asl.gid_ - GID that sent the log message (set by the server). +* _bpf_process_events.gid_ - Group ID +* _bpf_socket_events.gid_ - Group ID +* _device_file.gid_ - Owning group ID +* _docker_container_processes.gid_ - Group ID +* _es_process_events.gid_ - Group ID of the process +* _file.gid_ - Owning group ID +* _file_events.gid_ - Owning group ID +* _groups.gid_ - Unsigned int64 group ID +* _package_bom.gid_ - Expected group of file or directory +* _process_events.gid_ - Group ID at process start +* _process_file_events.gid_ - The gid of the process performing the action +* _processes.gid_ - Unsigned group ID +* _seccomp_events.gid_ - Group ID of the user who started the analyzed process +* _user_groups.gid_ - Group ID +* _users.gid_ - Group ID (unsigned) + +*gid_signed* - keyword, number.long + +* _groups.gid_signed_ - A signed int64 version of gid +* _users.gid_signed_ - Default group ID as int64 signed (Apple) + +*git_commit* - keyword, text.text + +* _docker_version.git_commit_ - Docker build git commit + +*global_seq_num* - keyword, number.long + +* _es_process_events.global_seq_num_ - Global sequence number + +*global_state* - keyword, number.long + +* _alf.global_state_ - 1 If the firewall is enabled with exceptions, 2 if the firewall is configured to block all incoming connections, else 0 + +*go_version* - keyword, text.text + +* _docker_version.go_version_ - Go version + +*gpgcheck* - keyword, text.text + +* _yum_sources.gpgcheck_ - Whether packages are GPG checked + +*gpgkey* - keyword, text.text + +* _yum_sources.gpgkey_ - URL to GPG key + +*grace_period* - keyword, number.long + +* _screenlock.grace_period_ - The amount of time in seconds the screen must be asleep or the screensaver on before a password is required on-wake. 0 = immediately; -1 = no password is required on-wake + +*group_sid* - keyword, text.text + +* _groups.group_sid_ - Unique group ID + +*groupname* - keyword, text.text + +* _groups.groupname_ - Canonical local group name +* _launchd.groupname_ - Run this daemon or agent as this group +* _rpm_package_files.groupname_ - File default groupname from info DB +* _suid_bin.groupname_ - Binary owner group + +*guest* - keyword, number.long + +* _cpu_time.guest_ - Time spent running a virtual CPU for a guest OS under the control of the Linux kernel + +*guest_nice* - keyword, number.long + +* _cpu_time.guest_nice_ - Time spent running a niced guest + +*handle* - keyword, text.text + +* _memory_array_mapped_addresses.handle_ - Handle, or instance number, associated with the structure +* _memory_arrays.handle_ - Handle, or instance number, associated with the array +* _memory_device_mapped_addresses.handle_ - Handle, or instance number, associated with the structure +* _memory_devices.handle_ - Handle, or instance number, associated with the structure in SMBIOS +* _memory_error_info.handle_ - Handle, or instance number, associated with the structure +* _oem_strings.handle_ - Handle, or instance number, associated with the Type 11 structure +* _smbios_tables.handle_ - Table entry handle + +*handle_count* - keyword, number.long + +* _processes.handle_count_ - Total number of handles that the process has open. This number is the sum of the handles currently opened by each thread in the process. + +*handler* - keyword, text.text + +* _app_schemes.handler_ - Application label for the handler + +*hard_limit* - keyword, text.text + +* _ulimit_info.hard_limit_ - Maximum limit value + +*hard_links* - keyword, number.long + +* _device_file.hard_links_ - Number of hard links +* _file.hard_links_ - Number of hard links + +*hardware_model* - keyword, text.text + +* _disk_info.hardware_model_ - Hard drive model. +* _system_info.hardware_model_ - Hardware model + +*hardware_serial* - keyword, text.text + +* _system_info.hardware_serial_ - Device serial number + +*hardware_vendor* - keyword, text.text + +* _system_info.hardware_vendor_ - Hardware vendor + +*hardware_version* - keyword, text.text + +* _system_info.hardware_version_ - Hardware version + +*has_expired* - keyword, number.long + +* _curl_certificate.has_expired_ - 1 if the certificate has expired, 0 otherwise + +*hash* - keyword, text.text + +* _prefetch.hash_ - Prefetch CRC hash. + +*hash_alg* - keyword, text.text + +* _shadow.hash_alg_ - Password hashing algorithm + +*hash_resources* - keyword, number.long + +* _signature.hash_resources_ - Set to 1 to also hash resources, or 0 otherwise. Default is 1 + +*hashed* - keyword, number.long + +* _file_events.hashed_ - 1 if the file was hashed, 0 if not, -1 if hashing failed + +*header* - keyword, text.text + +* _sudoers.header_ - Symbol for given rule + +*header_size* - keyword, number.long + +* _smbios_tables.header_size_ - Header size in bytes + +*health* - keyword, text.text + +* _battery.health_ - One of the following: "Good" describes a well-performing battery, "Fair" describes a functional battery with limited capacity, or "Poor" describes a battery that's not capable of providing power + +*hidden* - keyword, number.long + +* _scheduled_tasks.hidden_ - Whether or not the task is visible in the UI +* _smc_keys.hidden_ - 1 if this key is normally hidden, otherwise 0 + +*history_file* - keyword, text.text + +* _shell_history.history_file_ - Path to the .*_history for this user + +*hit_count* - keyword, text.text + +* _quicklook_cache.hit_count_ - Number of cache hits on thumbnail + +*home_directory* - keyword, text.text + +* _logon_sessions.home_directory_ - The home directory for the logon session. + +*home_directory_drive* - keyword, text.text + +* _logon_sessions.home_directory_drive_ - The drive location of the home directory of the logon session. + +*homepage* - keyword, text.text + +* _atom_packages.homepage_ - Package supplied homepage + +*hop_limit* - keyword, number.long + +* _interface_ipv6.hop_limit_ - Current Hop Limit + +*hopcount* - keyword, number.long + +* _routes.hopcount_ - Max hops expected + +*host* - keyword, text.text + +* _asl.host_ - Sender's address (set by the server). +* _last.host_ - Entry hostname +* _logged_in_users.host_ - Remote hostname +* _preferences.host_ - 'current' or 'any' host, where 'current' takes precedence +* _syslog_events.host_ - Hostname configured for syslog + +*host_ip* - keyword, text.text + +* _docker_container_ports.host_ip_ - Host IP address on which public port is listening + +*host_port* - keyword, number.long + +* _docker_container_ports.host_port_ - Host port + +*hostname* - keyword, text.text + +* _curl_certificate.hostname_ - Hostname (domain[:port]) to CURL +* _shortcut_files.hostname_ - Optional hostname of the target file. +* _system_info.hostname_ - Network hostname including domain +* _ycloud_instance_metadata.hostname_ - Hostname of the VM + +*hostnames* - keyword, text.text + +* _etc_hosts.hostnames_ - Raw hosts mapping + +*hotfix_id* - keyword, text.text + +* _patches.hotfix_id_ - The KB ID of the patch. + +*hour* - keyword, text.text + +* _crontab.hour_ - The hour of the day for the job +* _time.hour_ - Current hour in the system + +*hours* - keyword, number.long + +* _uptime.hours_ - Hours of uptime + +*http_proxy* - keyword, text.text + +* _docker_info.http_proxy_ - HTTP proxy + +*https_proxy* - keyword, text.text + +* _docker_info.https_proxy_ - HTTPS proxy + +*hwaddr* - keyword, text.text + +* _lxd_networks.hwaddr_ - Hardware address for this network + +*iam_arn* - keyword, text.text + +* _ec2_instance_metadata.iam_arn_ - If there is an IAM role associated with the instance, contains instance profile ARN + +*ibrs_support_enabled* - keyword, number.long + +* _kva_speculative_info.ibrs_support_enabled_ - Windows uses IBRS. + +*ibytes* - keyword, number.long + +* _interface_details.ibytes_ - Input bytes + +*icon_mode* - keyword, number.long + +* _quicklook_cache.icon_mode_ - Thumbnail icon mode + +*icon_path* - keyword, text.text + +* _shortcut_files.icon_path_ - Lnk file icon location. + +*id* - keyword, text.text + +* _disk_info.id_ - The unique identifier of the drive on the system. +* _dns_resolvers.id_ - Address type index or order +* _docker_container_fs_changes.id_ - Container ID +* _docker_container_labels.id_ - Container ID +* _docker_container_mounts.id_ - Container ID +* _docker_container_networks.id_ - Container ID +* _docker_container_ports.id_ - Container ID +* _docker_container_processes.id_ - Container ID +* _docker_container_stats.id_ - Container ID +* _docker_containers.id_ - Container ID +* _docker_image_history.id_ - Image ID +* _docker_image_labels.id_ - Image ID +* _docker_image_layers.id_ - Image ID +* _docker_images.id_ - Image ID +* _docker_info.id_ - Docker system ID +* _docker_network_labels.id_ - Network ID +* _docker_networks.id_ - Network ID +* _example.id_ - An index of some sort +* _iokit_devicetree.id_ - IOKit internal registry ID +* _iokit_registry.id_ - IOKit internal registry ID +* _lxd_images.id_ - Image ID +* _systemd_units.id_ - Unique unit identifier + +*identifier* - keyword, text.text + +* _browser_plugins.identifier_ - Plugin identifier +* _chrome_extension_content_scripts.identifier_ - Extension identifier +* _chrome_extensions.identifier_ - Extension identifier, computed from its manifest. Empty in case of error. +* _crashes.identifier_ - Identifier of the crashed process +* _firefox_addons.identifier_ - Addon identifier +* _safari_extensions.identifier_ - Extension identifier +* _signature.identifier_ - The signing identifier sealed into the signature +* _system_extensions.identifier_ - Identifier name +* _xprotect_meta.identifier_ - Browser plugin or extension identifier + +*identifying_number* - keyword, text.text + +* _programs.identifying_number_ - Product identification such as a serial number on software, or a die number on a hardware chip. + +*identity* - keyword, text.text + +* _xprotect_entries.identity_ - XProtect identity (SHA1) of content + +*idle* - keyword, number.long + +* _cpu_time.idle_ - Time spent in the idle task + +*idrops* - keyword, number.long + +* _interface_details.idrops_ - Input drops + +*idx* - keyword, number.long + +* _kernel_extensions.idx_ - Extension load tag or index + +*ierrors* - keyword, number.long + +* _interface_details.ierrors_ - Input errors + +*image* - keyword, text.text + +* _docker_containers.image_ - Docker image (name) used to launch this container +* _drivers.image_ - Path to driver image file + +*image_id* - keyword, text.text + +* _docker_containers.image_id_ - Docker image ID + +*images* - keyword, number.long + +* _docker_info.images_ - Number of images + +*in_smartctl_db* - keyword, number.long + +* _smart_drive_info.in_smartctl_db_ - Boolean value for if drive is recognized + +*inactive* - keyword, number.long + +* _memory_info.inactive_ - The total amount of buffer or page cache memory, in bytes, that are free and available +* _shadow.inactive_ - Number of days after password expires until account is blocked +* _virtual_memory_info.inactive_ - Total number of inactive pages. + +*inetd_compatibility* - keyword, text.text + +* _launchd.inetd_compatibility_ - Run this daemon or agent as it was launched from inetd + +*inf* - keyword, text.text + +* _drivers.inf_ - Associated inf file + +*info* - keyword, text.text + +* _apparmor_events.info_ - Additional information + +*info_access* - keyword, text.text + +* _curl_certificate.info_access_ - Authority Information Access + +*info_string* - keyword, text.text + +* _apps.info_string_ - Info properties CFBundleGetInfoString label + +*inherited_from* - keyword, text.text + +* _ntfs_acl_permissions.inherited_from_ - The inheritance policy of the ACE. + +*iniface* - keyword, text.text + +* _iptables.iniface_ - Input interface for the rule. + +*iniface_mask* - keyword, text.text + +* _iptables.iniface_mask_ - Input interface mask for the rule. + +*inode* - keyword, number.long + +* _device_file.inode_ - Filesystem inode number +* _device_hash.inode_ - Filesystem inode number +* _file.inode_ - Filesystem inode number +* _file_events.inode_ - Filesystem inode number +* _process_memory_map.inode_ - Mapped path inode, 0 means uninitialized (BSS) +* _process_open_pipes.inode_ - Pipe inode number +* _quicklook_cache.inode_ - Parsed file ID (inode) from fs_id + +*inodes* - keyword, number.long + +* _device_partitions.inodes_ - Number of meta nodes +* _mounts.inodes_ - Mounted device used inodes + +*inodes_free* - keyword, number.long + +* _mounts.inodes_free_ - Mounted device free inodes + +*inodes_total* - keyword, number.long + +* _lxd_storage_pools.inodes_total_ - Total number of inodes available in this storage pool + +*inodes_used* - keyword, number.long + +* _lxd_storage_pools.inodes_used_ - Number of inodes used + +*input_eax* - keyword, text.text + +* _cpuid.input_eax_ - Value of EAX used + +*install_date* - keyword + +* _os_version.install_date_ - The install date of the OS. +* _patches.install_date_ - Indicates when the patch was installed. Lack of a value does not indicate that the patch was not installed. +* _programs.install_date_ - Date that this product was installed on the system. +* _shared_resources.install_date_ - Indicates when the object was installed. Lack of a value does not indicate that the object is not installed. + +*install_location* - keyword, text.text + +* _programs.install_location_ - The installation location directory of the product. + +*install_source* - keyword, text.text + +* _programs.install_source_ - The installation source of the product. + +*install_time* - keyword + +* _appcompat_shims.install_time_ - Install time of the SDB +* _chrome_extensions.install_time_ - Extension install time, in its original Webkit format +* _package_receipts.install_time_ - Timestamp of install time +* _rpm_packages.install_time_ - When the package was installed + +*install_timestamp* - keyword, number.long + +* _chrome_extensions.install_timestamp_ - Extension install time, converted to unix time + +*installed_by* - keyword, text.text + +* _patches.installed_by_ - The system context in which the patch as installed. + +*installed_on* - keyword, text.text + +* _patches.installed_on_ - The date when the patch was installed. + +*installer_name* - keyword, text.text + +* _package_receipts.installer_name_ - Name of installer process + +*instance_id* - keyword, text.text + +* _ec2_instance_metadata.instance_id_ - EC2 instance ID +* _ec2_instance_tags.instance_id_ - EC2 instance ID +* _osquery_info.instance_id_ - Unique, long-lived ID per instance of osquery +* _ycloud_instance_metadata.instance_id_ - Unique identifier for the VM + +*instance_identifier* - keyword, text.text + +* _hvci_status.instance_identifier_ - The instance ID of Device Guard. + +*instance_type* - keyword, text.text + +* _ec2_instance_metadata.instance_type_ - EC2 instance type + +*instances* - keyword, number.long + +* _pipes.instances_ - Number of instances of the named pipe + +*interface* - keyword, text.text + +* _arp_cache.interface_ - Interface of the network for the MAC +* _interface_addresses.interface_ - Interface name +* _interface_details.interface_ - Interface name +* _interface_ipv6.interface_ - Interface name +* _lldp_neighbors.interface_ - Interface name +* _routes.interface_ - Route local interface +* _wifi_status.interface_ - Name of the interface +* _wifi_survey.interface_ - Name of the interface + +*interleave_data_depth* - keyword, number.long + +* _memory_device_mapped_addresses.interleave_data_depth_ - The max number of consecutive rows from memory device that are accessed in a single interleave transfer; 0 indicates device is non-interleave + +*interleave_position* - keyword, number.long + +* _memory_device_mapped_addresses.interleave_position_ - The position of the device in a interleave, i.e. 0 indicates non-interleave, 1 indicates 1st interleave, 2 indicates 2nd interleave, etc. + +*internal* - keyword, number.long + +* _osquery_registry.internal_ - 1 If the plugin is internal else 0 + +*internet_settings* - keyword, text.text + +* _windows_security_center.internet_settings_ - The health of the Internet Settings + +*internet_sharing* - keyword, number.long + +* _sharing_preferences.internet_sharing_ - 1 If internet sharing is enabled else 0 + +*interval* - keyword, number.long + +* _docker_container_stats.interval_ - Difference between read and preread in nano-seconds +* _osquery_schedule.interval_ - The interval in seconds to run this query, not an exact interval + +*iowait* - keyword, number.long + +* _cpu_time.iowait_ - Time spent waiting for I/O to complete + +*ip* - keyword, text.text + +* _seccomp_events.ip_ - Instruction pointer value + +*ip_address* - keyword, text.text + +* _docker_container_networks.ip_address_ - IP address + +*ip_prefix_len* - keyword, number.long + +* _docker_container_networks.ip_prefix_len_ - IP subnet prefix length + +*ipackets* - keyword, number.long + +* _interface_details.ipackets_ - Input packets + +*ipc_namespace* - keyword, text.text + +* _docker_containers.ipc_namespace_ - IPC namespace +* _process_namespaces.ipc_namespace_ - ipc namespace inode + +*ipv4_address* - keyword, text.text + +* _lxd_networks.ipv4_address_ - IPv4 address + +*ipv4_forwarding* - keyword, number.long + +* _docker_info.ipv4_forwarding_ - 1 if IPv4 forwarding is enabled. 0 otherwise + +*ipv4_internet* - keyword, number.long + +* _connectivity.ipv4_internet_ - True if any interface is connected to the Internet via IPv4 + +*ipv4_local_network* - keyword, number.long + +* _connectivity.ipv4_local_network_ - True if any interface is connected to a routed network via IPv4 + +*ipv4_no_traffic* - keyword, number.long + +* _connectivity.ipv4_no_traffic_ - True if any interface is connected via IPv4, but has seen no traffic + +*ipv4_subnet* - keyword, number.long + +* _connectivity.ipv4_subnet_ - True if any interface is connected to the local subnet via IPv4 + +*ipv6_address* - keyword, text.text + +* _docker_container_networks.ipv6_address_ - IPv6 address +* _lxd_networks.ipv6_address_ - IPv6 address + +*ipv6_gateway* - keyword, text.text + +* _docker_container_networks.ipv6_gateway_ - IPv6 gateway + +*ipv6_internet* - keyword, number.long + +* _connectivity.ipv6_internet_ - True if any interface is connected to the Internet via IPv6 + +*ipv6_local_network* - keyword, number.long + +* _connectivity.ipv6_local_network_ - True if any interface is connected to a routed network via IPv6 + +*ipv6_no_traffic* - keyword, number.long + +* _connectivity.ipv6_no_traffic_ - True if any interface is connected via IPv6, but has seen no traffic + +*ipv6_prefix_len* - keyword, number.long + +* _docker_container_networks.ipv6_prefix_len_ - IPv6 subnet prefix length + +*ipv6_subnet* - keyword, number.long + +* _connectivity.ipv6_subnet_ - True if any interface is connected to the local subnet via IPv6 + +*irq* - keyword, number.long + +* _cpu_time.irq_ - Time spent servicing interrupts + +*is_active* - keyword, number.long + +* _running_apps.is_active_ - 1 if the application is in focus, 0 otherwise + +*is_hidden* - keyword, number.long + +* _groups.is_hidden_ - IsHidden attribute set in OpenDirectory +* _users.is_hidden_ - IsHidden attribute set in OpenDirectory + +*iso_8601* - keyword, text.text + +* _time.iso_8601_ - Current time (ISO format) in the system + +*issuer* - keyword, text.text + +* _certificates.issuer_ - Certificate issuer distinguished name + +*issuer_alternative_names* - keyword, text.text + +* _curl_certificate.issuer_alternative_names_ - Issuer Alternative Name + +*issuer_common_name* - keyword, text.text + +* _curl_certificate.issuer_common_name_ - Issuer common name + +*issuer_name* - keyword, text.text + +* _authenticode.issuer_name_ - The certificate issuer name + +*issuer_organization* - keyword, text.text + +* _curl_certificate.issuer_organization_ - Issuer organization + +*issuer_organization_unit* - keyword, text.text + +* _curl_certificate.issuer_organization_unit_ - Issuer organization unit + +*job_id* - keyword, number.long + +* _systemd_units.job_id_ - Next queued job id + +*job_path* - keyword, text.text + +* _systemd_units.job_path_ - The object path for the job + +*job_type* - keyword, text.text + +* _systemd_units.job_type_ - Job type + +*json_cmdline* - keyword, text.text + +* _bpf_process_events.json_cmdline_ - Command line arguments, in JSON format + +*keep_alive* - keyword, text.text + +* _launchd.keep_alive_ - Should the process be restarted if killed + +*kernel_memory* - keyword, number.long + +* _docker_info.kernel_memory_ - 1 if kernel memory limit support is enabled. 0 otherwise + +*kernel_version* - keyword, text.text + +* _docker_info.kernel_version_ - Kernel version +* _docker_version.kernel_version_ - Kernel version +* _kernel_panics.kernel_version_ - Version of the system kernel + +*key* - keyword, text.text + +* _authorized_keys.key_ - parsed authorized keys line +* _azure_instance_tags.key_ - The tag key +* _chrome_extensions.key_ - The extension key, from the manifest file +* _docker_container_labels.key_ - Label key +* _docker_image_labels.key_ - Label key +* _docker_network_labels.key_ - Label key +* _docker_volume_labels.key_ - Label key +* _ec2_instance_tags.key_ - Tag key +* _extended_attributes.key_ - Name of the value generated from the extended attribute +* _known_hosts.key_ - parsed authorized keys line +* _launchd_overrides.key_ - Name of the override key +* _lxd_instance_config.key_ - Configuration parameter name +* _lxd_instance_devices.key_ - Device info param name +* _mdls.key_ - Name of the metadata key +* _plist.key_ - Preference top-level key +* _power_sensors.key_ - The SMC key on OS X +* _preferences.key_ - Preference top-level key +* _process_envs.key_ - Environment variable name +* _registry.key_ - Name of the key to search for +* _selinux_settings.key_ - Key or class name. +* _smc_keys.key_ - 4-character key +* _temperature_sensors.key_ - The SMC key on OS X + +*key_algorithm* - keyword, text.text + +* _certificates.key_algorithm_ - Key algorithm used + +*key_file* - keyword, text.text + +* _authorized_keys.key_file_ - Path to the authorized_keys file +* _known_hosts.key_file_ - Path to known_hosts file + +*key_strength* - keyword, text.text + +* _certificates.key_strength_ - Key size used for RSA/DSA, or curve name + +*key_type* - keyword, text.text + +* _user_ssh_keys.key_type_ - The type of the private key. One of [rsa, dsa, dh, ec, hmac, cmac], or the empty string. + +*key_usage* - keyword, text.text + +* _certificates.key_usage_ - Certificate key usage and extended key usage +* _curl_certificate.key_usage_ - Usage of key in certificate + +*keychain_path* - keyword, text.text + +* _keychain_acls.keychain_path_ - The path of the keychain + +*keyword* - keyword, text.text + +* _portage_keywords.keyword_ - The keyword applied to the package + +*keywords* - keyword, text.text + +* _windows_eventlog.keywords_ - A bitmask of the keywords defined in the event +* _windows_events.keywords_ - A bitmask of the keywords defined in the event + +*kva_shadow_enabled* - keyword, number.long + +* _kva_speculative_info.kva_shadow_enabled_ - Kernel Virtual Address shadowing is enabled. + +*kva_shadow_inv_pcid* - keyword, number.long + +* _kva_speculative_info.kva_shadow_inv_pcid_ - Kernel VA INVPCID is enabled. + +*kva_shadow_pcid* - keyword, number.long + +* _kva_speculative_info.kva_shadow_pcid_ - Kernel VA PCID flushing optimization is enabled. + +*kva_shadow_user_global* - keyword, number.long + +* _kva_speculative_info.kva_shadow_user_global_ - User pages are marked as global. + +*label* - keyword, text.text + +* _apparmor_events.label_ - AppArmor label +* _augeas.label_ - The label of the configuration item +* _authorization_mechanisms.label_ - Label of the authorization right +* _authorizations.label_ - Item name, usually in reverse domain format +* _block_devices.label_ - Block device label string +* _device_partitions.label_ - +* _keychain_acls.label_ - An optional label tag that may be included with the keychain entry +* _keychain_items.label_ - Generic item name +* _launchd.label_ - Daemon or agent service name +* _launchd_overrides.label_ - Daemon or agent service name +* _quicklook_cache.label_ - Parsed version 'gen' field +* _sandboxes.label_ - UTI-format bundle or label ID + +*language* - keyword, text.text + +* _programs.language_ - The language of the product. + +*last_change* - keyword, number.long + +* _interface_details.last_change_ - Time of last device modification (optional) +* _shadow.last_change_ - Date of last password change (starting from UNIX epoch date) + +*last_connected* - keyword, number.long + +* _wifi_networks.last_connected_ - Last time this netword was connected to as a unix_time + +*last_executed* - keyword, number.long + +* _osquery_schedule.last_executed_ - UNIX time stamp in seconds of the last completed execution + +*last_execution_time* - keyword, number.long + +* _background_activities_moderator.last_execution_time_ - Most recent time application was executed. +* _userassist.last_execution_time_ - Most recent time application was executed. + +*last_hit_date* - keyword, number.long + +* _quicklook_cache.last_hit_date_ - Apple date format for last thumbnail cache hit + +*last_loaded* - keyword, text.text + +* _kernel_panics.last_loaded_ - Last loaded module before panic + +*last_opened_time* - keyword + +* _apps.last_opened_time_ - The time that the app was last used +* _office_mru.last_opened_time_ - Most recent opened time file was opened + +*last_run_code* - keyword, text.text + +* _scheduled_tasks.last_run_code_ - Exit status code of the last task run + +*last_run_message* - keyword, text.text + +* _scheduled_tasks.last_run_message_ - Exit status message of the last task run + +*last_run_time* - keyword, number.long + +* _prefetch.last_run_time_ - Most recent time application was run. +* _scheduled_tasks.last_run_time_ - Timestamp the task last ran + +*last_unloaded* - keyword, text.text + +* _kernel_panics.last_unloaded_ - Last unloaded module before panic + +*last_used_at* - keyword, text.text + +* _lxd_images.last_used_at_ - ISO time for the most recent use of this image in terms of container spawn + +*launch_type* - keyword, text.text + +* _xprotect_entries.launch_type_ - Launch services content type + +*layer_id* - keyword, text.text + +* _docker_image_layers.layer_id_ - Layer ID + +*layer_order* - keyword, number.long + +* _docker_image_layers.layer_order_ - Layer Order (1 = base layer) + +*level* - keyword, number.long + +* _asl.level_ - Log level number. See levels in asl.h. +* _windows_eventlog.level_ - Severity level associated with the event +* _windows_events.level_ - The severity level associated with the event + +*license* - keyword, text.text + +* _atom_packages.license_ - License for package +* _chocolatey_packages.license_ - License under which package is launched +* _npm_packages.license_ - License for package +* _python_packages.license_ - License under which package is launched + +*link* - keyword, text.text + +* _elf_sections.link_ - Link to other section + +*link_speed* - keyword, number.long + +* _interface_details.link_speed_ - Interface speed in Mb/s + +*linked_against* - keyword, text.text + +* _kernel_extensions.linked_against_ - Indexes of extensions this extension is linked against + +*load_state* - keyword, text.text + +* _systemd_units.load_state_ - Reflects whether the unit definition was properly loaded + +*local_address* - keyword, text.text + +* _bpf_socket_events.local_address_ - Local address associated with socket +* _process_open_sockets.local_address_ - Socket local address +* _socket_events.local_address_ - Local address associated with socket + +*local_hostname* - keyword, text.text + +* _ec2_instance_metadata.local_hostname_ - Private IPv4 DNS hostname of the first interface of this instance +* _system_info.local_hostname_ - Local hostname (optional) + +*local_ipv4* - keyword, text.text + +* _ec2_instance_metadata.local_ipv4_ - Private IPv4 address of the first interface of this instance + +*local_path* - keyword, text.text + +* _shortcut_files.local_path_ - Local system path to target file. + +*local_port* - keyword, number.long + +* _bpf_socket_events.local_port_ - Local network protocol port number +* _process_open_sockets.local_port_ - Socket local port +* _socket_events.local_port_ - Local network protocol port number + +*local_time* - keyword, number.long + +* _time.local_time_ - Current local UNIX time in the system + +*local_timezone* - keyword, text.text + +* _time.local_timezone_ - Current local timezone in the system + +*location* - keyword, text.text + +* _azure_instance_metadata.location_ - Azure Region the VM is running in +* _firefox_addons.location_ - Global, profile location +* _memory_arrays.location_ - Physical location of the memory array +* _package_receipts.location_ - Optional relative install path on volume + +*lock* - keyword, text.text + +* _chassis_info.lock_ - If TRUE, the frame is equipped with a lock. + +*lock_status* - keyword, number.long + +* _bitlocker_info.lock_status_ - The accessibility status of the drive from Windows. + +*locked* - keyword, number.long + +* _shared_memory.locked_ - 1 if segment is locked else 0 + +*log_file_disk_quota_mb* - keyword, number.long + +* _carbon_black_info.log_file_disk_quota_mb_ - Event file disk quota in MB + +*log_file_disk_quota_percentage* - keyword, number.long + +* _carbon_black_info.log_file_disk_quota_percentage_ - Event file disk quota in a percentage + +*logging_driver* - keyword, text.text + +* _docker_info.logging_driver_ - Logging driver + +*logging_enabled* - keyword, number.long + +* _alf.logging_enabled_ - 1 If logging mode is enabled else 0 + +*logging_option* - keyword, number.long + +* _alf.logging_option_ - Firewall logging option + +*logical_processors* - keyword, number.long + +* _cpu_info.logical_processors_ - The number of logical processors of the CPU. + +*logon_domain* - keyword, text.text + +* _logon_sessions.logon_domain_ - The name of the domain used to authenticate the owner of the logon session. + +*logon_id* - keyword, number.long + +* _logon_sessions.logon_id_ - A locally unique identifier (LUID) that identifies a logon session. + +*logon_script* - keyword, text.text + +* _logon_sessions.logon_script_ - The script used for logging on. + +*logon_server* - keyword, text.text + +* _logon_sessions.logon_server_ - The name of the server used to authenticate the owner of the logon session. + +*logon_sid* - keyword, text.text + +* _logon_sessions.logon_sid_ - The user's security identifier (SID). + +*logon_time* - keyword, number.long + +* _logon_sessions.logon_time_ - The time the session owner logged on. + +*logon_type* - keyword, text.text + +* _logon_sessions.logon_type_ - The logon method. + +*lu_wwn_device_id* - keyword, text.text + +* _smart_drive_info.lu_wwn_device_id_ - Device Identifier + +*mac* - keyword, text.text + +* _arp_cache.mac_ - MAC address of broadcasted address +* _ec2_instance_metadata.mac_ - MAC address for the first network interface of this EC2 instance +* _interface_details.mac_ - MAC of interface (optional) + +*mac_address* - keyword, text.text + +* _docker_container_networks.mac_address_ - MAC address + +*machine* - keyword, number.long + +* _elf_info.machine_ - Machine type + +*machine_name* - keyword, text.text + +* _windows_crashes.machine_name_ - Name of the machine where the crash happened + +*magic_db_files* - keyword, text.text + +* _magic.magic_db_files_ - Colon(:) separated list of files where the magic db file can be found. By default one of the following is used: /usr/share/file/magic/magic, /usr/share/misc/magic or /usr/share/misc/magic.mgc + +*maintainer* - keyword, text.text + +* _apt_sources.maintainer_ - Repository maintainer +* _deb_packages.maintainer_ - Package maintainer + +*major* - keyword, number.long + +* _os_version.major_ - Major release version + +*major_version* - keyword, number.long + +* _windows_crashes.major_version_ - Windows major version of the machine + +*managed* - keyword, number.long + +* _lxd_networks.managed_ - 1 if network created by LXD, 0 otherwise + +*manifest_hash* - keyword, text.text + +* _chrome_extensions.manifest_hash_ - The SHA256 hash of the manifest.json file + +*manifest_json* - keyword, text.text + +* _chrome_extensions.manifest_json_ - The manifest file of the extension + +*manual* - keyword, number.long + +* _managed_policies.manual_ - 1 if policy was loaded manually, otherwise 0 + +*manufacture_date* - keyword, number.long + +* _battery.manufacture_date_ - The date the battery was manufactured UNIX Epoch + +*manufacturer* - keyword, text.text + +* _battery.manufacturer_ - The battery manufacturer's name +* _chassis_info.manufacturer_ - The manufacturer of the chassis. +* _cpu_info.manufacturer_ - The manufacturer of the CPU. +* _disk_info.manufacturer_ - The manufacturer of the disk. +* _drivers.manufacturer_ - Device manufacturer +* _interface_details.manufacturer_ - Name of the network adapter's manufacturer. +* _memory_devices.manufacturer_ - Manufacturer ID string +* _video_info.manufacturer_ - The manufacturer of the gpu. + +*manufacturer_id* - keyword, number.long + +* _tpm_info.manufacturer_id_ - TPM manufacturers ID + +*manufacturer_name* - keyword, text.text + +* _tpm_info.manufacturer_name_ - TPM manufacturers name + +*manufacturer_version* - keyword, text.text + +* _tpm_info.manufacturer_version_ - TPM version + +*mask* - keyword, text.text + +* _interface_addresses.mask_ - Interface netmask +* _portage_keywords.mask_ - If the package is masked + +*match* - keyword, text.text + +* _chrome_extension_content_scripts.match_ - The pattern that the script is matched against +* _iptables.match_ - Matching rule that applies. + +*matches* - keyword, text.text + +* _yara.matches_ - List of YARA matches +* _yara_events.matches_ - List of YARA matches + +*max* - keyword, number.long + +* _fan_speed_sensors.max_ - Maximum speed +* _shadow.max_ - Maximum number of days between password changes + +*max_capacity* - keyword, number.long + +* _battery.max_capacity_ - The battery's actual capacity when it is fully charged in mAh +* _memory_arrays.max_capacity_ - Maximum capacity of array in gigabytes + +*max_clock_speed* - keyword, number.long + +* _cpu_info.max_clock_speed_ - The maximum possible frequency of the CPU. + +*max_instances* - keyword, number.long + +* _pipes.max_instances_ - The maximum number of instances creatable for this pipe + +*max_speed* - keyword, number.long + +* _memory_devices.max_speed_ - Max speed of memory device in megatransfers per second (MT/s) + +*max_voltage* - keyword, number.long + +* _memory_devices.max_voltage_ - Maximum operating voltage of device in millivolts + +*maximum_allowed* - keyword, number.long + +* _shared_resources.maximum_allowed_ - Limit on the maximum number of users allowed to use this resource concurrently. The value is only valid if the AllowMaximum property is set to FALSE. + +*md5* - keyword, text.text + +* _acpi_tables.md5_ - MD5 hash of table content +* _device_hash.md5_ - MD5 hash of provided inode data +* _file_events.md5_ - The MD5 of the file after change +* _hash.md5_ - MD5 hash of provided filesystem data +* _smbios_tables.md5_ - MD5 hash of table entry + +*md_device_name* - keyword, text.text + +* _md_drives.md_device_name_ - md device name + +*mdm_managed* - keyword, number.long + +* _system_extensions.mdm_managed_ - 1 if managed by MDM system extension payload configuration, 0 otherwise + +*mechanism* - keyword, text.text + +* _authorization_mechanisms.mechanism_ - Name of the mechanism that will be called + +*med_capability_capabilities* - keyword, number.long + +* _lldp_neighbors.med_capability_capabilities_ - Is MED capabilities enabled + +*med_capability_inventory* - keyword, number.long + +* _lldp_neighbors.med_capability_inventory_ - Is MED inventory capability enabled + +*med_capability_location* - keyword, number.long + +* _lldp_neighbors.med_capability_location_ - Is MED location capability enabled + +*med_capability_mdi_pd* - keyword, number.long + +* _lldp_neighbors.med_capability_mdi_pd_ - Is MED MDI PD capability enabled + +*med_capability_mdi_pse* - keyword, number.long + +* _lldp_neighbors.med_capability_mdi_pse_ - Is MED MDI PSE capability enabled + +*med_capability_policy* - keyword, number.long + +* _lldp_neighbors.med_capability_policy_ - Is MED policy capability enabled + +*med_device_type* - keyword, text.text + +* _lldp_neighbors.med_device_type_ - Chassis MED type + +*med_policies* - keyword, text.text + +* _lldp_neighbors.med_policies_ - Comma delimited list of MED policies + +*media_name* - keyword, text.text + +* _disk_events.media_name_ - Disk event media name string + +*mem* - keyword, number.double + +* _docker_container_processes.mem_ - Memory utilization as percentage + +*member_config_description* - keyword, text.text + +* _lxd_cluster.member_config_description_ - Config description + +*member_config_entity* - keyword, text.text + +* _lxd_cluster.member_config_entity_ - Type of configuration parameter for this node + +*member_config_key* - keyword, text.text + +* _lxd_cluster.member_config_key_ - Config key + +*member_config_name* - keyword, text.text + +* _lxd_cluster.member_config_name_ - Name of configuration parameter + +*member_config_value* - keyword, text.text + +* _lxd_cluster.member_config_value_ - Config value + +*memory* - keyword, number.long + +* _docker_info.memory_ - Total memory + +*memory_array_error_address* - keyword, text.text + +* _memory_error_info.memory_array_error_address_ - 32 bit physical address of the error based on the addressing of the bus to which the memory array is connected + +*memory_array_handle* - keyword, text.text + +* _memory_array_mapped_addresses.memory_array_handle_ - Handle of the memory array associated with this structure + +*memory_array_mapped_address_handle* - keyword, text.text + +* _memory_device_mapped_addresses.memory_array_mapped_address_handle_ - Handle of the memory array mapped address to which this device range is mapped to + +*memory_device_handle* - keyword, text.text + +* _memory_device_mapped_addresses.memory_device_handle_ - Handle of the memory device structure associated with this structure + +*memory_error_correction* - keyword, text.text + +* _memory_arrays.memory_error_correction_ - Primary hardware error correction or detection method supported + +*memory_error_info_handle* - keyword, text.text + +* _memory_arrays.memory_error_info_handle_ - Handle, or instance number, associated with any error that was detected for the array + +*memory_free* - keyword, number.long + +* _memory_info.memory_free_ - The amount of physical RAM, in bytes, left unused by the system + +*memory_limit* - keyword, number.long + +* _docker_container_stats.memory_limit_ - Memory limit +* _docker_info.memory_limit_ - 1 if memory limit support is enabled. 0 otherwise + +*memory_max_usage* - keyword, number.long + +* _docker_container_stats.memory_max_usage_ - Memory maximum usage + +*memory_total* - keyword, number.long + +* _memory_info.memory_total_ - Total amount of physical RAM, in bytes + +*memory_type* - keyword, text.text + +* _memory_devices.memory_type_ - Type of memory used + +*memory_type_details* - keyword, text.text + +* _memory_devices.memory_type_details_ - Additional details for memory device + +*memory_usage* - keyword, number.long + +* _docker_container_stats.memory_usage_ - Memory usage + +*message* - keyword, text.text + +* _apparmor_events.message_ - Raw audit message +* _asl.message_ - Message text. +* _lxd_cluster_members.message_ - Message from the node (Online/Offline) +* _selinux_events.message_ - Message +* _syslog_events.message_ - The syslog message +* _user_events.message_ - Message from the event + +*metadata_endpoint* - keyword, text.text + +* _ycloud_instance_metadata.metadata_endpoint_ - Endpoint used to fetch VM metadata + +*method* - keyword, text.text + +* _curl.method_ - The HTTP method for the request + +*metric* - keyword, number.long + +* _interface_details.metric_ - Metric based on the speed of the interface +* _routes.metric_ - Cost of route. Lowest is preferred + +*metric_name* - keyword, text.text + +* _prometheus_metrics.metric_name_ - Name of collected Prometheus metric + +*metric_value* - keyword, number.double + +* _prometheus_metrics.metric_value_ - Value of collected Prometheus metric + +*mft_entry* - keyword, number.long + +* _shellbags.mft_entry_ - Directory master file table entry. +* _shortcut_files.mft_entry_ - Target mft entry. + +*mft_sequence* - keyword, number.long + +* _shellbags.mft_sequence_ - Directory master file table sequence. +* _shortcut_files.mft_sequence_ - Target mft sequence. + +*mime_encoding* - keyword, text.text + +* _magic.mime_encoding_ - MIME encoding data from libmagic + +*mime_type* - keyword, text.text + +* _magic.mime_type_ - MIME type data from libmagic + +*min* - keyword, number.long + +* _fan_speed_sensors.min_ - Minimum speed +* _shadow.min_ - Minimal number of days between password changes + +*min_api_version* - keyword, text.text + +* _docker_version.min_api_version_ - Minimum API version supported + +*min_version* - keyword, text.text + +* _xprotect_meta.min_version_ - The minimum allowed plugin version. + +*min_voltage* - keyword, number.long + +* _memory_devices.min_voltage_ - Minimum operating voltage of device in millivolts + +*minimum_system_version* - keyword, text.text + +* _apps.minimum_system_version_ - Minimum version of OS X required for the app to run + +*minor* - keyword, number.long + +* _os_version.minor_ - Minor release version + +*minor_version* - keyword, number.long + +* _windows_crashes.minor_version_ - Windows minor version of the machine + +*minute* - keyword, text.text + +* _crontab.minute_ - The exact minute for the job + +*minutes* - keyword, number.long + +* _time.minutes_ - Current minutes in the system +* _uptime.minutes_ - Minutes of uptime + +*minutes_to_full_charge* - keyword, number.long + +* _battery.minutes_to_full_charge_ - The number of minutes until the battery is fully charged. This value is -1 if this time is still being calculated + +*minutes_until_empty* - keyword, number.long + +* _battery.minutes_until_empty_ - The number of minutes until the battery is fully depleted. This value is -1 if this time is still being calculated + +*mnt_namespace* - keyword, text.text + +* _docker_containers.mnt_namespace_ - Mount namespace +* _process_namespaces.mnt_namespace_ - mnt namespace inode + +*mode* - keyword, text.text + +* _apparmor_profiles.mode_ - How the policy is applied. +* _device_file.mode_ - Permission bits +* _docker_container_mounts.mode_ - Mount options (rw, ro) +* _file.mode_ - Permission bits +* _file_events.mode_ - Permission bits +* _package_bom.mode_ - Expected permissions +* _process_events.mode_ - File mode permissions +* _process_open_pipes.mode_ - Pipe open mode (r/w) +* _rpm_package_files.mode_ - File permissions mode from info DB +* _wifi_status.mode_ - The current operating mode for the Wi-Fi interface + +*model* - keyword, text.text + +* _battery.model_ - The battery's model number +* _block_devices.model_ - Block device model string identifier +* _chassis_info.model_ - The model of the chassis. +* _cpu_info.model_ - The model of the CPU. +* _hardware_events.model_ - Hardware device model +* _pci_devices.model_ - PCI Device model +* _usb_devices.model_ - USB Device model string +* _video_info.model_ - The model of the gpu. + +*model_family* - keyword, text.text + +* _smart_drive_info.model_family_ - Drive model family + +*model_id* - keyword, text.text + +* _hardware_events.model_id_ - Hex encoded Hardware model identifier +* _pci_devices.model_id_ - Hex encoded PCI Device model identifier +* _usb_devices.model_id_ - Hex encoded USB Device model identifier + +*modified* - keyword, text.text + +* _authorizations.modified_ - Label top-level key +* _keychain_items.modified_ - Date of last modification + +*modified_time* - keyword, number.long + +* _package_bom.modified_time_ - Timestamp the file was installed +* _shellbags.modified_time_ - Directory Modified time. +* _shimcache.modified_time_ - File Modified time. + +*module* - keyword, text.text + +* _windows_crashes.module_ - Path of the crashed module within the process + +*module_backtrace* - keyword, text.text + +* _kernel_panics.module_backtrace_ - Modules appearing in the crashed module's backtrace + +*module_path* - keyword, text.text + +* _services.module_path_ - Path to ServiceDll + +*month* - keyword, text.text + +* _crontab.month_ - The month of the year for the job +* _time.month_ - Current month in the system + +*mount_namespace_id* - keyword, text.text + +* _deb_packages.mount_namespace_id_ - Mount namespace id +* _file.mount_namespace_id_ - Mount namespace id +* _hash.mount_namespace_id_ - Mount namespace id +* _npm_packages.mount_namespace_id_ - Mount namespace id +* _os_version.mount_namespace_id_ - Mount namespace id +* _rpm_packages.mount_namespace_id_ - Mount namespace id + +*mount_point* - keyword, text.text + +* _docker_volumes.mount_point_ - Mount point + +*mountable* - keyword, number.long + +* _disk_events.mountable_ - 1 if mountable, 0 if not + +*msize* - keyword, number.long + +* _elf_segments.msize_ - Segment offset in memory + +*mtime* - keyword + +* _device_file.mtime_ - Last modification time +* _file.mtime_ - Last modification time +* _file_events.mtime_ - Last modification time +* _gatekeeper_approved_apps.mtime_ - Last modification time +* _process_events.mtime_ - File modification in UNIX time +* _quicklook_cache.mtime_ - Parsed version date field +* _registry.mtime_ - timestamp of the most recent registry write + +*mtu* - keyword, number.long + +* _interface_details.mtu_ - Network MTU +* _lxd_networks.mtu_ - MTU size +* _routes.mtu_ - Maximum Transmission Unit for the route + +*name* - keyword, text.text + +* _acpi_tables.name_ - ACPI table name +* _ad_config.name_ - The OS X-specific configuration name +* _apparmor_events.name_ - Process name +* _apparmor_profiles.name_ - Policy name. +* _apps.name_ - Name of the Name.app folder +* _apt_sources.name_ - Repository name +* _atom_packages.name_ - Package display name +* _autoexec.name_ - Name of the program +* _azure_instance_metadata.name_ - Name of the VM +* _block_devices.name_ - Block device name +* _browser_plugins.name_ - Plugin display name +* _chocolatey_packages.name_ - Package display name +* _chrome_extensions.name_ - Extension display name +* _cups_destinations.name_ - Name of the printer +* _deb_packages.name_ - Package name +* _disk_encryption.name_ - Disk name +* _disk_events.name_ - Disk event name +* _disk_info.name_ - The label of the disk object. +* _dns_cache.name_ - DNS record name +* _docker_container_mounts.name_ - Optional mount name +* _docker_container_networks.name_ - Network name +* _docker_container_processes.name_ - The process path or shorthand argv[0] +* _docker_container_stats.name_ - Container name +* _docker_containers.name_ - Container name +* _docker_info.name_ - Name of the docker host +* _docker_networks.name_ - Network name +* _docker_volume_labels.name_ - Volume name +* _docker_volumes.name_ - Volume name +* _elf_sections.name_ - Section name +* _elf_segments.name_ - Segment type/name +* _elf_symbols.name_ - Symbol name +* _etc_protocols.name_ - Protocol name +* _etc_services.name_ - Service name +* _example.name_ - Description for name column +* _fan_speed_sensors.name_ - Fan name +* _fbsd_kmods.name_ - Module name +* _firefox_addons.name_ - Addon display name +* _homebrew_packages.name_ - Package name +* _ie_extensions.name_ - Extension display name +* _iokit_devicetree.name_ - Device node name +* _iokit_registry.name_ - Default name of the node +* _kernel_extensions.name_ - Extension label +* _kernel_modules.name_ - Module name +* _kernel_panics.name_ - Process name corresponding to crashed thread +* _launchd.name_ - File name of plist (used by launchd) +* _lxd_certificates.name_ - Name of the certificate +* _lxd_instance_config.name_ - Instance name +* _lxd_instance_devices.name_ - Instance name +* _lxd_instances.name_ - Instance name +* _lxd_networks.name_ - Name of the network +* _lxd_storage_pools.name_ - Name of the storage pool +* _managed_policies.name_ - Policy key name +* _md_personalities.name_ - Name of personality supported by kernel +* _memory_map.name_ - Region name +* _npm_packages.name_ - Package display name +* _ntdomains.name_ - The label by which the object is known. +* _nvram.name_ - Variable name +* _os_version.name_ - Distribution or product name +* _osquery_events.name_ - Event publisher or subscriber name +* _osquery_extensions.name_ - Extension's name +* _osquery_flags.name_ - Flag name +* _osquery_packs.name_ - The given name for this query pack +* _osquery_registry.name_ - Name of the plugin item +* _osquery_schedule.name_ - The given name for this query +* _package_install_history.name_ - Package display name +* _physical_disk_performance.name_ - Name of the physical disk +* _pipes.name_ - Name of the pipe +* _pkg_packages.name_ - Package name +* _power_sensors.name_ - Name of power source +* _processes.name_ - The process path or shorthand argv[0] +* _programs.name_ - Commonly used product name. +* _python_packages.name_ - Package display name +* _registry.name_ - Name of the registry value entry +* _rpm_packages.name_ - RPM package name +* _safari_extensions.name_ - Extension display name +* _scheduled_tasks.name_ - Name of the scheduled task +* _services.name_ - Service name +* _shared_folders.name_ - The shared name of the folder as it appears to other users +* _shared_resources.name_ - Alias given to a path set up as a share on a computer system running Windows. +* _startup_items.name_ - Name of startup item +* _system_controls.name_ - Full sysctl MIB name +* _temperature_sensors.name_ - Name of temperature source +* _windows_optional_features.name_ - Name of the feature +* _windows_security_products.name_ - Name of product +* _wmi_bios_info.name_ - Name of the Bios setting +* _wmi_cli_event_consumers.name_ - Unique name of a consumer. +* _wmi_event_filters.name_ - Unique identifier of an event filter. +* _wmi_script_event_consumers.name_ - Unique identifier for the event consumer. +* _xprotect_entries.name_ - Description of XProtected malware +* _xprotect_reports.name_ - Description of XProtected malware +* _ycloud_instance_metadata.name_ - Name of the VM +* _yum_sources.name_ - Repository name + +*name_constraints* - keyword, text.text + +* _curl_certificate.name_constraints_ - Name Constraints + +*namespace* - keyword, text.text + +* _apparmor_events.namespace_ - AppArmor namespace + +*native* - keyword, number.long + +* _browser_plugins.native_ - Plugin requires native execution +* _firefox_addons.native_ - 1 If the addon includes binary components else 0 + +*net_namespace* - keyword, text.text + +* _docker_containers.net_namespace_ - Network namespace +* _listening_ports.net_namespace_ - The inode number of the network namespace +* _process_namespaces.net_namespace_ - net namespace inode +* _process_open_sockets.net_namespace_ - The inode number of the network namespace + +*netmask* - keyword, text.text + +* _dns_resolvers.netmask_ - Address (sortlist) netmask length +* _routes.netmask_ - Netmask length + +*network_id* - keyword, text.text + +* _docker_container_networks.network_id_ - Network ID + +*network_name* - keyword, text.text + +* _wifi_networks.network_name_ - Name of the network +* _wifi_status.network_name_ - Name of the network +* _wifi_survey.network_name_ - Name of the network + +*network_rx_bytes* - keyword, number.long + +* _docker_container_stats.network_rx_bytes_ - Total network bytes read + +*network_tx_bytes* - keyword, number.long + +* _docker_container_stats.network_tx_bytes_ - Total network bytes transmitted + +*next_run_time* - keyword, number.long + +* _scheduled_tasks.next_run_time_ - Timestamp the task is scheduled to run next + +*nice* - keyword, number.long + +* _cpu_time.nice_ - Time spent in user mode with low priority (nice) +* _docker_container_processes.nice_ - Process nice level (-20 to 20, default 0) +* _processes.nice_ - Process nice level (-20 to 20, default 0) + +*no_proxy* - keyword, text.text + +* _docker_info.no_proxy_ - Comma-separated list of domain extensions proxy should not be used for + +*node* - keyword, text.text + +* _augeas.node_ - The node path of the configuration item + +*node_ref_number* - keyword, text.text + +* _ntfs_journal_events.node_ref_number_ - The ordinal that associates a journal record with a filename + +*noise* - keyword, number.long + +* _wifi_status.noise_ - The current noise measurement (dBm) +* _wifi_survey.noise_ - The current noise measurement (dBm) + +*not_valid_after* - keyword, text.text + +* _certificates.not_valid_after_ - Certificate expiration data + +*not_valid_before* - keyword, text.text + +* _certificates.not_valid_before_ - Lower bound of valid date + +*nr_raid_disks* - keyword, number.long + +* _md_devices.nr_raid_disks_ - Number of partitions or disk devices to comprise the array + +*ntime* - keyword, text.text + +* _bpf_process_events.ntime_ - The nsecs uptime timestamp as obtained from BPF +* _bpf_socket_events.ntime_ - The nsecs uptime timestamp as obtained from BPF + +*num_procs* - keyword, number.long + +* _docker_container_stats.num_procs_ - Number of processors + +*number* - keyword, number.long + +* _etc_protocols.number_ - Protocol number +* _oem_strings.number_ - The string index of the structure +* _smbios_tables.number_ - Table entry number + +*number_memory_devices* - keyword, number.long + +* _memory_arrays.number_memory_devices_ - Number of memory devices on array + +*number_of_cores* - keyword, text.text + +* _cpu_info.number_of_cores_ - The number of cores of the CPU. + +*object_name* - keyword, text.text + +* _winbaseobj.object_name_ - Object Name + +*object_path* - keyword, text.text + +* _systemd_units.object_path_ - The object path for this unit + +*object_type* - keyword, text.text + +* _winbaseobj.object_type_ - Object Type + +*obytes* - keyword, number.long + +* _interface_details.obytes_ - Output bytes + +*odrops* - keyword, number.long + +* _interface_details.odrops_ - Output drops + +*oerrors* - keyword, number.long + +* _interface_details.oerrors_ - Output errors + +*offer* - keyword, text.text + +* _azure_instance_metadata.offer_ - Offer information for the VM image (Azure image gallery VMs only) + +*offset* - keyword, number.long + +* _device_partitions.offset_ - +* _elf_sections.offset_ - Offset of section in file +* _elf_segments.offset_ - Segment offset in file +* _elf_symbols.offset_ - Section table index +* _process_memory_map.offset_ - Offset into mapped path + +*oid* - keyword, text.text + +* _system_controls.oid_ - Control MIB + +*old_path* - keyword, text.text + +* _ntfs_journal_events.old_path_ - Old path (renames only) + +*on_demand* - keyword, text.text + +* _launchd.on_demand_ - Deprecated key, replaced by keep_alive + +*on_disk* - keyword, number.long + +* _processes.on_disk_ - The process path exists yes=1, no=0, unknown=-1 + +*online_cpus* - keyword, number.long + +* _docker_container_stats.online_cpus_ - Online CPUs + +*oom_kill_disable* - keyword, number.long + +* _docker_info.oom_kill_disable_ - 1 if Out-of-memory kill is disabled. 0 otherwise + +*opackets* - keyword, number.long + +* _interface_details.opackets_ - Output packets + +*opaque_version* - keyword, text.text + +* _gatekeeper.opaque_version_ - Version of Gatekeeper's gkopaque.bundle + +*operation* - keyword, text.text + +* _apparmor_events.operation_ - Permission requested by the process +* _process_file_events.operation_ - Operation type + +*option* - keyword, text.text + +* _ad_config.option_ - Canonical name of option +* _ssh_configs.option_ - The option and value + +*option_name* - keyword, text.text + +* _cups_destinations.option_name_ - Option name + +*option_value* - keyword, text.text + +* _cups_destinations.option_value_ - Option value + +*optional* - keyword, number.long + +* _xprotect_entries.optional_ - Match any of the identities/patterns for this XProtect name + +*optional_permissions* - keyword, text.text + +* _chrome_extensions.optional_permissions_ - The permissions optionally required by the extensions + +*optional_permissions_json* - keyword, text.text + +* _chrome_extensions.optional_permissions_json_ - The JSON-encoded permissions optionally required by the extensions + +*options* - keyword + +* _dns_resolvers.options_ - Resolver options +* _nfs_shares.options_ - Options string set on the export share + +*organization* - keyword, text.text + +* _curl_certificate.organization_ - Organization issued to + +*organization_unit* - keyword, text.text + +* _curl_certificate.organization_unit_ - Organization unit issued to + +*original_parent* - keyword, number.long + +* _es_process_events.original_parent_ - Original parent process ID in case of reparenting + +*original_program_name* - keyword, text.text + +* _authenticode.original_program_name_ - The original program name that the publisher has signed + +*os* - keyword, text.text + +* _docker_info.os_ - Operating system +* _docker_version.os_ - Operating system +* _lxd_images.os_ - OS on which image is based +* _lxd_instances.os_ - The OS of this instance + +*os_type* - keyword, text.text + +* _azure_instance_metadata.os_type_ - Linux or Windows +* _docker_info.os_type_ - Operating system type + +*os_version* - keyword, text.text + +* _kernel_panics.os_version_ - Version of the operating system + +*other* - keyword, text.text + +* _md_devices.other_ - Other information associated with array from /proc/mdstat + +*other_run_times* - keyword, text.text + +* _prefetch.other_run_times_ - Other execution times in prefetch file. + +*ouid* - keyword, number.long + +* _apparmor_events.ouid_ - Object owner's user ID + +*outiface* - keyword, text.text + +* _iptables.outiface_ - Output interface for the rule. + +*outiface_mask* - keyword, text.text + +* _iptables.outiface_mask_ - Output interface mask for the rule. + +*output_bit* - keyword, number.long + +* _cpuid.output_bit_ - Bit in register value for feature value + +*output_register* - keyword, text.text + +* _cpuid.output_register_ - Register used to for feature value + +*output_size* - keyword, number.long + +* _osquery_schedule.output_size_ - Total number of bytes generated by the query + +*overflows* - keyword, text.text + +* _process_events.overflows_ - List of structures that overflowed + +*owned* - keyword, number.long + +* _tpm_info.owned_ - TPM is ownned + +*owner_gid* - keyword, number.long + +* _process_events.owner_gid_ - File owner group ID + +*owner_uid* - keyword, number.long + +* _process_events.owner_uid_ - File owner user ID +* _shared_memory.owner_uid_ - User ID of owning process + +*owner_uuid* - keyword, number.long + +* _osquery_registry.owner_uuid_ - Extension route UUID (0 for core) + +*package* - keyword, text.text + +* _portage_keywords.package_ - Package name +* _portage_packages.package_ - Package name +* _portage_use.package_ - Package name +* _rpm_package_files.package_ - RPM package name + +*package_filename* - keyword, text.text + +* _package_receipts.package_filename_ - Filename of original .pkg file + +*package_group* - keyword, text.text + +* _rpm_packages.package_group_ - Package group + +*package_id* - keyword, text.text + +* _package_install_history.package_id_ - Label packageIdentifiers +* _package_receipts.package_id_ - Package domain identifier + +*packet_device_type* - keyword, text.text + +* _smart_drive_info.packet_device_type_ - Packet device type + +*packets* - keyword, number.long + +* _iptables.packets_ - Number of matching packets for this rule. + +*packets_received* - keyword, number.long + +* _lxd_networks.packets_received_ - Number of packets received on this network + +*packets_sent* - keyword, number.long + +* _lxd_networks.packets_sent_ - Number of packets sent on this network + +*page_ins* - keyword, number.long + +* _virtual_memory_info.page_ins_ - The total number of requests for pages from a pager. + +*page_outs* - keyword, number.long + +* _virtual_memory_info.page_outs_ - Total number of pages paged out. + +*parent* - keyword + +* _apparmor_events.parent_ - Parent process PID +* _block_devices.parent_ - Block device parent name +* _bpf_process_events.parent_ - Parent process ID +* _bpf_socket_events.parent_ - Parent process ID +* _crashes.parent_ - Parent PID of the crashed process +* _docker_container_processes.parent_ - Process parent's PID +* _es_process_events.parent_ - Parent process ID +* _iokit_devicetree.parent_ - Parent device registry ID +* _iokit_registry.parent_ - Parent registry ID +* _process_events.parent_ - Process parent's PID, or -1 if cannot be determined. +* _processes.parent_ - Process parent's PID + +*parent_ref_number* - keyword, text.text + +* _ntfs_journal_events.parent_ref_number_ - The ordinal that associates a journal record with a filename's parent directory + +*part_number* - keyword, text.text + +* _memory_devices.part_number_ - Manufacturer specific serial number of memory device + +*partial* - keyword + +* _ntfs_journal_events.partial_ - Set to 1 if either path or old_path only contains the file or folder name +* _process_file_events.partial_ - True if this is a partial event (i.e.: this process existed before we started osquery) + +*partition* - keyword, text.text + +* _device_file.partition_ - A partition number +* _device_hash.partition_ - A partition number +* _device_partitions.partition_ - A partition number or description + +*partition_row_position* - keyword, number.long + +* _memory_device_mapped_addresses.partition_row_position_ - Identifies the position of the referenced memory device in a row of the address partition + +*partition_width* - keyword, number.long + +* _memory_array_mapped_addresses.partition_width_ - Number of memory devices that form a single row of memory for the address partition of this structure + +*partitions* - keyword, number.long + +* _disk_info.partitions_ - Number of detected partitions on disk. + +*partner_fd* - keyword, number.long + +* _process_open_pipes.partner_fd_ - File descriptor of shared pipe at partner's end + +*partner_mode* - keyword, text.text + +* _process_open_pipes.partner_mode_ - Mode of shared pipe at partner's end + +*partner_pid* - keyword, number.long + +* _process_open_pipes.partner_pid_ - Process ID of partner process sharing a particular pipe + +*passpoint* - keyword, number.long + +* _wifi_networks.passpoint_ - 1 if Passpoint is supported, 0 otherwise + +*password_last_set_time* - keyword, number.double + +* _account_policy_data.password_last_set_time_ - The time the password was last changed + +*password_status* - keyword, text.text + +* _shadow.password_status_ - Password status + +*patch* - keyword, number.long + +* _os_version.patch_ - Optional patch release + +*path* - keyword, text.text + +* _alf_exceptions.path_ - Path to the executable that is excepted +* _apparmor_profiles.path_ - Unique, aa-status compatible, policy identifier. +* _appcompat_shims.path_ - This is the path to the SDB database. +* _apps.path_ - Absolute and full Name.app path +* _atom_packages.path_ - Package's package.json path +* _augeas.path_ - The path to the configuration file +* _authenticode.path_ - Must provide a path or directory +* _autoexec.path_ - Path to the executable +* _background_activities_moderator.path_ - Application file path. +* _bpf_process_events.path_ - Binary path +* _bpf_socket_events.path_ - Path of executed file +* _browser_plugins.path_ - Path to plugin bundle +* _carves.path_ - The path of the requested carve +* _certificates.path_ - Path to Keychain or PEM bundle +* _chocolatey_packages.path_ - Path at which this package resides +* _chrome_extension_content_scripts.path_ - Path to extension folder +* _chrome_extensions.path_ - Path to extension folder +* _crashes.path_ - Path to the crashed process +* _crontab.path_ - File parsed +* _device_file.path_ - A logical path within the device node +* _disk_events.path_ - Path of the DMG file accessed +* _docker_container_fs_changes.path_ - FIle or directory path relative to rootfs +* _docker_containers.path_ - Container path +* _elf_dynamic.path_ - Path to ELF file +* _elf_info.path_ - Path to ELF file +* _elf_sections.path_ - Path to ELF file +* _elf_segments.path_ - Path to ELF file +* _elf_symbols.path_ - Path to ELF file +* _es_process_events.path_ - Path of executed file +* _example.path_ - Path of example +* _extended_attributes.path_ - Absolute file path +* _file.path_ - Absolute file path +* _firefox_addons.path_ - Path to plugin bundle +* _gatekeeper_approved_apps.path_ - Path of executable allowed to run +* _hardware_events.path_ - Local device path assigned (optional) +* _hash.path_ - Must provide a path or directory +* _homebrew_packages.path_ - Package install path +* _ie_extensions.path_ - Path to executable +* _kernel_extensions.path_ - Optional path to extension bundle +* _kernel_info.path_ - Kernel path +* _kernel_panics.path_ - Location of log file +* _keychain_acls.path_ - The path of the authorized application +* _keychain_items.path_ - Path to keychain containing item +* _launchd.path_ - Path to daemon or agent plist +* _launchd_overrides.path_ - Path to daemon or agent plist +* _listening_ports.path_ - Path for UNIX domain sockets +* _magic.path_ - Absolute path to target file +* _mdfind.path_ - Path of the file returned from spotlight +* _mdls.path_ - Path of the file +* _mounts.path_ - Mounted device path +* _npm_packages.path_ - Module's package.json path +* _ntfs_acl_permissions.path_ - Path to the file or directory. +* _ntfs_journal_events.path_ - Path +* _office_mru.path_ - File path +* _osquery_extensions.path_ - Path of the extension's Thrift connection or library path +* _package_bom.path_ - Path of package bom +* _package_receipts.path_ - Path of receipt plist +* _plist.path_ - (required) read preferences from a plist +* _prefetch.path_ - Prefetch file path. +* _process_events.path_ - Path of executed file +* _process_file_events.path_ - The path associated with the event +* _process_memory_map.path_ - Path to mapped file or mapped type +* _process_open_files.path_ - Filesystem path of descriptor +* _process_open_sockets.path_ - For UNIX sockets (family=AF_UNIX), the domain path +* _processes.path_ - Path to executed binary +* _python_packages.path_ - Path at which this module resides +* _quicklook_cache.path_ - Path of file +* _registry.path_ - Full path to the value +* _rpm_package_files.path_ - File path within the package +* _safari_extensions.path_ - Path to extension XAR bundle +* _sandboxes.path_ - Path to sandbox container directory +* _scheduled_tasks.path_ - Path to the executable to be run +* _services.path_ - Path to Service Executable +* _shared_folders.path_ - Absolute path of shared folder on the local system +* _shared_resources.path_ - Local path of the Windows share. +* _shellbags.path_ - Directory name. +* _shimcache.path_ - This is the path to the executed file. +* _shortcut_files.path_ - Directory name. +* _signature.path_ - Must provide a path or directory +* _socket_events.path_ - Path of executed file +* _startup_items.path_ - Path of startup item +* _suid_bin.path_ - Binary path +* _system_extensions.path_ - Original path of system extension +* _user_events.path_ - Supplied path from event +* _user_ssh_keys.path_ - Path to key file +* _userassist.path_ - Application file path. +* _windows_crashes.path_ - Path of the executable file for the crashed process +* _yara.path_ - The path scanned + +*pci_class* - keyword, text.text + +* _pci_devices.pci_class_ - PCI Device class + +*pci_class_id* - keyword, text.text + +* _pci_devices.pci_class_id_ - PCI Device class ID in hex format + +*pci_slot* - keyword, text.text + +* _interface_details.pci_slot_ - PCI slot number +* _pci_devices.pci_slot_ - PCI Device used slot + +*pci_subclass* - keyword, text.text + +* _pci_devices.pci_subclass_ - PCI Device subclass + +*pci_subclass_id* - keyword, text.text + +* _pci_devices.pci_subclass_id_ - PCI Device subclass in hex format + +*pem* - keyword, text.text + +* _curl_certificate.pem_ - Certificate PEM format + +*percent_disk_read_time* - keyword, number.long + +* _physical_disk_performance.percent_disk_read_time_ - Percentage of elapsed time that the selected disk drive is busy servicing read requests + +*percent_disk_time* - keyword, number.long + +* _physical_disk_performance.percent_disk_time_ - Percentage of elapsed time that the selected disk drive is busy servicing read or write requests + +*percent_disk_write_time* - keyword, number.long + +* _physical_disk_performance.percent_disk_write_time_ - Percentage of elapsed time that the selected disk drive is busy servicing write requests + +*percent_idle_time* - keyword, number.long + +* _physical_disk_performance.percent_idle_time_ - Percentage of time during the sample interval that the disk was idle + +*percent_processor_time* - keyword, number.long + +* _processes.percent_processor_time_ - Returns elapsed time that all of the threads of this process used the processor to execute instructions in 100 nanoseconds ticks. + +*percent_remaining* - keyword, number.long + +* _battery.percent_remaining_ - The percentage of battery remaining before it is drained + +*percentage_encrypted* - keyword, number.long + +* _bitlocker_info.percentage_encrypted_ - The percentage of the drive that is encrypted. + +*perf_ctl* - keyword, number.long + +* _msr.perf_ctl_ - Performance setting for the processor. + +*perf_status* - keyword, number.long + +* _msr.perf_status_ - Performance status for the processor. + +*period* - keyword, text.text + +* _load_average.period_ - Period over which the average is calculated. + +*permanent* - keyword, text.text + +* _arp_cache.permanent_ - 1 for true, 0 for false + +*permissions* - keyword, text.text + +* _chrome_extensions.permissions_ - The permissions required by the extension +* _process_memory_map.permissions_ - r=read, w=write, x=execute, p=private (cow) +* _shared_memory.permissions_ - Memory segment permissions +* _suid_bin.permissions_ - Binary permissions + +*permissions_json* - keyword, text.text + +* _chrome_extensions.permissions_json_ - The JSON-encoded permissions required by the extension + +*persistent* - keyword, number.long + +* _chrome_extensions.persistent_ - 1 If extension is persistent across all tabs else 0 + +*persistent_volume_id* - keyword, text.text + +* _bitlocker_info.persistent_volume_id_ - Persistent ID of the drive. + +*pgroup* - keyword, number.long + +* _docker_container_processes.pgroup_ - Process group +* _processes.pgroup_ - Process group + +*physical_adapter* - keyword, number.long + +* _interface_details.physical_adapter_ - Indicates whether the adapter is a physical or a logical adapter. + +*physical_memory* - keyword, number.long + +* _system_info.physical_memory_ - Total physical memory in bytes + +*physical_presence_version* - keyword, text.text + +* _tpm_info.physical_presence_version_ - Version of the Physical Presence Interface + +*pid* - keyword, number.long + +* _apparmor_events.pid_ - Process ID +* _asl.pid_ - Sending process ID encoded as a string. Set automatically. +* _bpf_process_events.pid_ - Process ID +* _bpf_socket_events.pid_ - Process ID +* _crashes.pid_ - Process (or thread) ID of the crashed process +* _docker_container_processes.pid_ - Process ID +* _docker_containers.pid_ - Identifier of the initial process +* _es_process_events.pid_ - Process (or thread) ID +* _last.pid_ - Process (or thread) ID +* _listening_ports.pid_ - Process (or thread) ID +* _logged_in_users.pid_ - Process (or thread) ID +* _lxd_instances.pid_ - Instance's process ID +* _osquery_info.pid_ - Process (or thread/handle) ID +* _pipes.pid_ - Process ID of the process to which the pipe belongs +* _process_envs.pid_ - Process (or thread) ID +* _process_events.pid_ - Process (or thread) ID +* _process_file_events.pid_ - Process ID +* _process_memory_map.pid_ - Process (or thread) ID +* _process_namespaces.pid_ - Process (or thread) ID +* _process_open_files.pid_ - Process (or thread) ID +* _process_open_pipes.pid_ - Process ID +* _process_open_sockets.pid_ - Process (or thread) ID +* _processes.pid_ - Process (or thread) ID +* _running_apps.pid_ - The pid of the application +* _seccomp_events.pid_ - Process ID +* _services.pid_ - the Process ID of the service +* _shared_memory.pid_ - Process ID to last use the segment +* _socket_events.pid_ - Process (or thread) ID +* _user_events.pid_ - Process (or thread) ID +* _windows_crashes.pid_ - Process ID of the crashed process +* _windows_eventlog.pid_ - Process ID which emitted the event record + +*pid_namespace* - keyword, text.text + +* _docker_containers.pid_namespace_ - PID namespace +* _process_namespaces.pid_namespace_ - pid namespace inode + +*pid_with_namespace* - keyword, number.long + +* _apt_sources.pid_with_namespace_ - Pids that contain a namespace +* _authorized_keys.pid_with_namespace_ - Pids that contain a namespace +* _crontab.pid_with_namespace_ - Pids that contain a namespace +* _deb_packages.pid_with_namespace_ - Pids that contain a namespace +* _dns_resolvers.pid_with_namespace_ - Pids that contain a namespace +* _etc_hosts.pid_with_namespace_ - Pids that contain a namespace +* _file.pid_with_namespace_ - Pids that contain a namespace +* _groups.pid_with_namespace_ - Pids that contain a namespace +* _hash.pid_with_namespace_ - Pids that contain a namespace +* _npm_packages.pid_with_namespace_ - Pids that contain a namespace +* _os_version.pid_with_namespace_ - Pids that contain a namespace +* _python_packages.pid_with_namespace_ - Pids that contain a namespace +* _rpm_packages.pid_with_namespace_ - Pids that contain a namespace +* _suid_bin.pid_with_namespace_ - Pids that contain a namespace +* _user_ssh_keys.pid_with_namespace_ - Pids that contain a namespace +* _users.pid_with_namespace_ - Pids that contain a namespace +* _yum_sources.pid_with_namespace_ - Pids that contain a namespace + +*pids* - keyword + +* _docker_container_stats.pids_ - Number of processes +* _lldp_neighbors.pids_ - Comma delimited list of PIDs + +*placement_group_id* - keyword, text.text + +* _azure_instance_metadata.placement_group_id_ - Placement group for the VM scale set + +*platform* - keyword, text.text + +* _os_version.platform_ - OS Platform or ID +* _osquery_packs.platform_ - Platforms this query is supported on + +*platform_binary* - keyword, number.long + +* _es_process_events.platform_binary_ - Indicates if the binary is Apple signed binary (1) or not (0) + +*platform_fault_domain* - keyword, text.text + +* _azure_instance_metadata.platform_fault_domain_ - Fault domain the VM is running in + +*platform_info* - keyword, number.long + +* _msr.platform_info_ - Platform information. + +*platform_like* - keyword, text.text + +* _os_version.platform_like_ - Closely related platforms + +*platform_mask* - keyword, number.long + +* _osquery_info.platform_mask_ - The osquery platform bitmask + +*platform_update_domain* - keyword, text.text + +* _azure_instance_metadata.platform_update_domain_ - Update domain the VM is running in + +*plugin* - keyword, text.text + +* _authorization_mechanisms.plugin_ - Authorization plugin name + +*pnp_device_id* - keyword, text.text + +* _disk_info.pnp_device_id_ - The unique identifier of the drive on the system. + +*point_to_point* - keyword, text.text + +* _interface_addresses.point_to_point_ - PtP address for the interface + +*points* - keyword, number.long + +* _example.points_ - This is a signed SQLite int column + +*policies* - keyword, text.text + +* _curl_certificate.policies_ - Certificate Policies + +*policy* - keyword, text.text + +* _iptables.policy_ - Policy that applies for this rule. + +*policy_constraints* - keyword, text.text + +* _curl_certificate.policy_constraints_ - Policy Constraints + +*policy_mappings* - keyword, text.text + +* _curl_certificate.policy_mappings_ - Policy Mappings + +*port* - keyword, number.long + +* _docker_container_ports.port_ - Port inside the container +* _etc_services.port_ - Service port number +* _listening_ports.port_ - Transport layer port + +*port_aggregation_id* - keyword, text.text + +* _lldp_neighbors.port_aggregation_id_ - Port aggregation ID + +*port_autoneg_1000baset_fd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_1000baset_fd_enabled_ - 1000Base-T FD auto negotiation enabled + +*port_autoneg_1000baset_hd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_1000baset_hd_enabled_ - 1000Base-T HD auto negotiation enabled + +*port_autoneg_1000basex_fd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_1000basex_fd_enabled_ - 1000Base-X FD auto negotiation enabled + +*port_autoneg_1000basex_hd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_1000basex_hd_enabled_ - 1000Base-X HD auto negotiation enabled + +*port_autoneg_100baset2_fd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_100baset2_fd_enabled_ - 100Base-T2 FD auto negotiation enabled + +*port_autoneg_100baset2_hd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_100baset2_hd_enabled_ - 100Base-T2 HD auto negotiation enabled + +*port_autoneg_100baset4_fd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_100baset4_fd_enabled_ - 100Base-T4 FD auto negotiation enabled + +*port_autoneg_100baset4_hd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_100baset4_hd_enabled_ - 100Base-T4 HD auto negotiation enabled + +*port_autoneg_100basetx_fd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_100basetx_fd_enabled_ - 100Base-TX FD auto negotiation enabled + +*port_autoneg_100basetx_hd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_100basetx_hd_enabled_ - 100Base-TX HD auto negotiation enabled + +*port_autoneg_10baset_fd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_10baset_fd_enabled_ - 10Base-T FD auto negotiation enabled + +*port_autoneg_10baset_hd_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_10baset_hd_enabled_ - 10Base-T HD auto negotiation enabled + +*port_autoneg_enabled* - keyword, number.long + +* _lldp_neighbors.port_autoneg_enabled_ - Is auto negotiation enabled + +*port_autoneg_supported* - keyword, number.long + +* _lldp_neighbors.port_autoneg_supported_ - Auto negotiation supported + +*port_description* - keyword, text.text + +* _lldp_neighbors.port_description_ - Port description + +*port_id* - keyword, text.text + +* _lldp_neighbors.port_id_ - Port ID value + +*port_id_type* - keyword, text.text + +* _lldp_neighbors.port_id_type_ - Port ID type + +*port_mau_type* - keyword, text.text + +* _lldp_neighbors.port_mau_type_ - MAU type + +*port_mfs* - keyword, number.long + +* _lldp_neighbors.port_mfs_ - Port max frame size + +*port_ttl* - keyword, number.long + +* _lldp_neighbors.port_ttl_ - Age of neighbor port + +*possibly_hidden* - keyword, number.long + +* _wifi_networks.possibly_hidden_ - 1 if network is possibly a hidden network, 0 otherwise + +*power_8023at_enabled* - keyword, number.long + +* _lldp_neighbors.power_8023at_enabled_ - Is 802.3at enabled + +*power_8023at_power_allocated* - keyword, text.text + +* _lldp_neighbors.power_8023at_power_allocated_ - 802.3at power allocated + +*power_8023at_power_priority* - keyword, text.text + +* _lldp_neighbors.power_8023at_power_priority_ - 802.3at power priority + +*power_8023at_power_requested* - keyword, text.text + +* _lldp_neighbors.power_8023at_power_requested_ - 802.3at power requested + +*power_8023at_power_source* - keyword, text.text + +* _lldp_neighbors.power_8023at_power_source_ - 802.3at power source + +*power_8023at_power_type* - keyword, text.text + +* _lldp_neighbors.power_8023at_power_type_ - 802.3at power type + +*power_class* - keyword, text.text + +* _lldp_neighbors.power_class_ - Power class + +*power_device_type* - keyword, text.text + +* _lldp_neighbors.power_device_type_ - Dot3 power device type + +*power_mdi_enabled* - keyword, number.long + +* _lldp_neighbors.power_mdi_enabled_ - Is MDI power enabled + +*power_mdi_supported* - keyword, number.long + +* _lldp_neighbors.power_mdi_supported_ - MDI power supported + +*power_mode* - keyword, text.text + +* _smart_drive_info.power_mode_ - Device power mode + +*power_paircontrol_enabled* - keyword, number.long + +* _lldp_neighbors.power_paircontrol_enabled_ - Is power pair control enabled + +*power_pairs* - keyword, text.text + +* _lldp_neighbors.power_pairs_ - Dot3 power pairs + +*ppid* - keyword, number.long + +* _process_file_events.ppid_ - Parent process ID + +*ppvids_enabled* - keyword, text.text + +* _lldp_neighbors.ppvids_enabled_ - Comma delimited list of enabled PPVIDs + +*ppvids_supported* - keyword, text.text + +* _lldp_neighbors.ppvids_supported_ - Comma delimited list of supported PPVIDs + +*pre_cpu_kernelmode_usage* - keyword, number.long + +* _docker_container_stats.pre_cpu_kernelmode_usage_ - Last read CPU kernel mode usage + +*pre_cpu_total_usage* - keyword, number.long + +* _docker_container_stats.pre_cpu_total_usage_ - Last read total CPU usage + +*pre_cpu_usermode_usage* - keyword, number.long + +* _docker_container_stats.pre_cpu_usermode_usage_ - Last read CPU user mode usage + +*pre_online_cpus* - keyword, number.long + +* _docker_container_stats.pre_online_cpus_ - Last read online CPUs + +*pre_system_cpu_usage* - keyword, number.long + +* _docker_container_stats.pre_system_cpu_usage_ - Last read CPU system usage + +*prefix* - keyword, text.text + +* _homebrew_packages.prefix_ - Homebrew install prefix + +*preread* - keyword, number.long + +* _docker_container_stats.preread_ - UNIX time when stats were last read + +*principal* - keyword, text.text + +* _ntfs_acl_permissions.principal_ - User or group to which the ACE applies. + +*printer_sharing* - keyword, number.long + +* _sharing_preferences.printer_sharing_ - 1 If printer sharing is enabled else 0 + +*priority* - keyword, text.text + +* _deb_packages.priority_ - Package priority + +*privileged* - keyword, text.text + +* _authorization_mechanisms.privileged_ - If privileged it will run as root, else as an anonymous user +* _docker_containers.privileged_ - Is the container privileged + +*probe_error* - keyword, number.long + +* _bpf_process_events.probe_error_ - Set to 1 if one or more buffers could not be captured +* _bpf_socket_events.probe_error_ - Set to 1 if one or more buffers could not be captured + +*process* - keyword, text.text + +* _alf_explicit_auths.process_ - Process name explicitly allowed + +*process_being_tapped* - keyword, number.long + +* _event_taps.process_being_tapped_ - The process ID of the target application + +*process_type* - keyword, text.text + +* _launchd.process_type_ - Key describes the intended purpose of the job + +*process_uptime* - keyword, number.long + +* _windows_crashes.process_uptime_ - Uptime of the process in seconds + +*processes* - keyword, number.long + +* _lxd_instances.processes_ - Number of processes running inside this instance + +*processing_time* - keyword, number.long + +* _cups_jobs.processing_time_ - How long the job took to process + +*processor_number* - keyword, number.long + +* _msr.processor_number_ - The processor number as reported in /proc/cpuinfo + +*processor_type* - keyword, text.text + +* _cpu_info.processor_type_ - The processor type, such as Central, Math, or Video. + +*product_name* - keyword, text.text + +* _tpm_info.product_name_ - Product name of the TPM + +*product_version* - keyword, text.text + +* _file.product_version_ - File product version + +*profile* - keyword, text.text + +* _apparmor_events.profile_ - Apparmor profile name +* _chrome_extensions.profile_ - The name of the Chrome profile that contains this extension + +*profile_path* - keyword, text.text + +* _chrome_extension_content_scripts.profile_path_ - The profile path +* _chrome_extensions.profile_path_ - The profile path +* _logon_sessions.profile_path_ - The home directory for the logon session. + +*program* - keyword, text.text + +* _launchd.program_ - Path to target program + +*program_arguments* - keyword, text.text + +* _launchd.program_arguments_ - Command line arguments passed to program + +*propagation* - keyword, text.text + +* _docker_container_mounts.propagation_ - Mount propagation + +*protected* - keyword, number.long + +* _app_schemes.protected_ - 1 if this handler is protected (reserved) by OS X, else 0 + +*protection_disabled* - keyword, number.long + +* _carbon_black_info.protection_disabled_ - If the sensor is configured to report tamper events + +*protection_status* - keyword, number.long + +* _bitlocker_info.protection_status_ - The bitlocker protection status of the drive. + +*protection_type* - keyword, text.text + +* _processes.protection_type_ - The protection type of the process + +*protocol* - keyword + +* _bpf_socket_events.protocol_ - The network protocol ID +* _etc_services.protocol_ - Transport protocol (TCP/UDP) +* _iptables.protocol_ - Protocol number identification. +* _listening_ports.protocol_ - Transport protocol (TCP/UDP) +* _process_open_sockets.protocol_ - Transport protocol (TCP/UDP) +* _socket_events.protocol_ - The network protocol ID +* _usb_devices.protocol_ - USB Device protocol + +*provider* - keyword, text.text + +* _drivers.provider_ - Driver provider + +*provider_guid* - keyword, text.text + +* _windows_eventlog.provider_guid_ - Provider guid of the event +* _windows_events.provider_guid_ - Provider guid of the event + +*provider_name* - keyword, text.text + +* _windows_eventlog.provider_name_ - Provider name of the event +* _windows_events.provider_name_ - Provider name of the event + +*pseudo* - keyword, number.long + +* _process_memory_map.pseudo_ - 1 If path is a pseudo path, else 0 + +*psize* - keyword, number.long + +* _elf_segments.psize_ - Size of segment in file + +*public* - keyword, number.long + +* _lxd_images.public_ - Whether image is public (1) or not (0) + +*publisher* - keyword, text.text + +* _azure_instance_metadata.publisher_ - Publisher of the VM image +* _osquery_events.publisher_ - Name of the associated publisher +* _programs.publisher_ - Name of the product supplier. + +*purgeable* - keyword, number.long + +* _virtual_memory_info.purgeable_ - Total number of purgeable pages. + +*purged* - keyword, number.long + +* _virtual_memory_info.purged_ - Total number of purged pages. + +*pvid* - keyword, text.text + +* _lldp_neighbors.pvid_ - Primary VLAN id + +*query* - keyword, text.text + +* _mdfind.query_ - The query that was run to find the file +* _osquery_schedule.query_ - The exact query to run +* _wmi_event_filters.query_ - Windows Management Instrumentation Query Language (WQL) event query that specifies the set of events for consumer notification, and the specific conditions for notification. + +*query_language* - keyword, text.text + +* _wmi_event_filters.query_language_ - Query language that the query is written in. + +*queue_directories* - keyword, text.text + +* _launchd.queue_directories_ - Similar to watch_paths but only with non-empty directories + +*raid_disks* - keyword, number.long + +* _md_devices.raid_disks_ - Number of configured RAID disks in array + +*raid_level* - keyword, number.long + +* _md_devices.raid_level_ - Current raid level of the array + +*rapl_energy_status* - keyword, number.long + +* _msr.rapl_energy_status_ - Run Time Average Power Limiting energy status. + +*rapl_power_limit* - keyword, number.long + +* _msr.rapl_power_limit_ - Run Time Average Power Limiting power limit. + +*rapl_power_units* - keyword, number.long + +* _msr.rapl_power_units_ - Run Time Average Power Limiting power units. + +*reactivated* - keyword, number.long + +* _virtual_memory_info.reactivated_ - Total number of reactivated pages. + +*read* - keyword, number.long + +* _docker_container_stats.read_ - UNIX time when stats were read + +*read_device_identity_failure* - keyword, text.text + +* _smart_drive_info.read_device_identity_failure_ - Error string for device id read, if any + +*readonly* - keyword, number.long + +* _nfs_shares.readonly_ - 1 if the share is exported readonly else 0 + +*readonly_rootfs* - keyword, number.long + +* _docker_containers.readonly_rootfs_ - Is the root filesystem mounted as read only + +*record_timestamp* - keyword, text.text + +* _ntfs_journal_events.record_timestamp_ - Journal record timestamp + +*record_usn* - keyword, text.text + +* _ntfs_journal_events.record_usn_ - The update sequence number that identifies the journal record + +*recovery_finish* - keyword, text.text + +* _md_devices.recovery_finish_ - Estimated duration of recovery activity + +*recovery_progress* - keyword, text.text + +* _md_devices.recovery_progress_ - Progress of the recovery activity + +*recovery_speed* - keyword, text.text + +* _md_devices.recovery_speed_ - Speed of recovery activity + +*redirect_accept* - keyword, number.long + +* _interface_ipv6.redirect_accept_ - Accept ICMP redirect messages + +*ref_pid* - keyword, number.long + +* _asl.ref_pid_ - Reference PID for messages proxied by launchd + +*ref_proc* - keyword, text.text + +* _asl.ref_proc_ - Reference process for messages proxied by launchd + +*referenced* - keyword, number.long + +* _chrome_extension_content_scripts.referenced_ - 1 if this extension is referenced by the Preferences file of the profile +* _chrome_extensions.referenced_ - 1 if this extension is referenced by the Preferences file of the profile + +*referenced_identifier* - keyword, text.text + +* _chrome_extensions.referenced_identifier_ - Extension identifier, as specified by the preferences file. Empty if the extension is not in the profile. + +*refreshes* - keyword, number.long + +* _osquery_events.refreshes_ - Publisher only: number of runloop restarts + +*refs* - keyword, number.long + +* _fbsd_kmods.refs_ - Module reverse dependencies +* _kernel_extensions.refs_ - Reference count + +*region* - keyword, text.text + +* _ec2_instance_metadata.region_ - AWS region in which this instance launched + +*registers* - keyword, text.text + +* _crashes.registers_ - The value of the system registers +* _kernel_panics.registers_ - A space delimited line of register:value pairs +* _windows_crashes.registers_ - The values of the system registers + +*registry* - keyword, text.text + +* _osquery_registry.registry_ - Name of the osquery registry + +*registry_hive* - keyword, text.text + +* _logged_in_users.registry_hive_ - HKEY_USERS registry hive + +*registry_path* - keyword, text.text + +* _ie_extensions.registry_path_ - Extension identifier + +*relative_path* - keyword, text.text + +* _shortcut_files.relative_path_ - Relative path to target file from lnk file. +* _wmi_cli_event_consumers.relative_path_ - Relative path to the class or instance. +* _wmi_event_filters.relative_path_ - Relative path to the class or instance. +* _wmi_filter_consumer_binding.relative_path_ - Relative path to the class or instance. +* _wmi_script_event_consumers.relative_path_ - Relative path to the class or instance. + +*release* - keyword, text.text + +* _apt_sources.release_ - Release name +* _lxd_images.release_ - OS release version on which the image is based +* _rpm_packages.release_ - Package release + +*remediation_path* - keyword, text.text + +* _windows_security_products.remediation_path_ - Remediation path + +*remote_address* - keyword, text.text + +* _bpf_socket_events.remote_address_ - Remote address associated with socket +* _process_open_sockets.remote_address_ - Socket remote address +* _socket_events.remote_address_ - Remote address associated with socket + +*remote_apple_events* - keyword, number.long + +* _sharing_preferences.remote_apple_events_ - 1 If remote apple events are enabled else 0 + +*remote_login* - keyword, number.long + +* _sharing_preferences.remote_login_ - 1 If remote login is enabled else 0 + +*remote_management* - keyword, number.long + +* _sharing_preferences.remote_management_ - 1 If remote management is enabled else 0 + +*remote_port* - keyword, number.long + +* _bpf_socket_events.remote_port_ - Remote network protocol port number +* _process_open_sockets.remote_port_ - Socket remote port +* _socket_events.remote_port_ - Remote network protocol port number + +*removable* - keyword, number.long + +* _usb_devices.removable_ - 1 If USB device is removable else 0 + +*repository* - keyword, text.text + +* _portage_packages.repository_ - From which repository the ebuild was used + +*request_id* - keyword, text.text + +* _carves.request_id_ - Identifying value of the carve request (e.g., scheduled query name, distributed request, etc) + +*requested_mask* - keyword, text.text + +* _apparmor_events.requested_mask_ - Requested access mask + +*requirement* - keyword, text.text + +* _gatekeeper_approved_apps.requirement_ - Code signing requirement language + +*reservation_id* - keyword, text.text + +* _ec2_instance_metadata.reservation_id_ - ID of the reservation + +*reshape_finish* - keyword, text.text + +* _md_devices.reshape_finish_ - Estimated duration of reshape activity + +*reshape_progress* - keyword, text.text + +* _md_devices.reshape_progress_ - Progress of the reshape activity + +*reshape_speed* - keyword, text.text + +* _md_devices.reshape_speed_ - Speed of reshape activity + +*resident_size* - keyword, number.long + +* _docker_container_processes.resident_size_ - Bytes of private memory used by process +* _processes.resident_size_ - Bytes of private memory used by process + +*resource_group_name* - keyword, text.text + +* _azure_instance_metadata.resource_group_name_ - Resource group for the VM + +*response_code* - keyword, number.long + +* _curl.response_code_ - The HTTP status code for the response + +*responsible* - keyword, text.text + +* _crashes.responsible_ - Process responsible for the crashed process + +*result* - keyword, text.text + +* _authenticode.result_ - The signature check result +* _curl.result_ - The HTTP response body + +*resync_finish* - keyword, text.text + +* _md_devices.resync_finish_ - Estimated duration of resync activity + +*resync_progress* - keyword, text.text + +* _md_devices.resync_progress_ - Progress of the resync activity + +*resync_speed* - keyword, text.text + +* _md_devices.resync_speed_ - Speed of resync activity + +*retain_count* - keyword, number.long + +* _iokit_devicetree.retain_count_ - The device reference count +* _iokit_registry.retain_count_ - The node reference count + +*revision* - keyword, text.text + +* _deb_packages.revision_ - Package revision +* _hardware_events.revision_ - Device revision (optional) +* _platform_info.revision_ - BIOS major and minor revision + +*rid* - keyword, number.long + +* _lldp_neighbors.rid_ - Neighbor chassis index + +*roaming* - keyword, number.long + +* _wifi_networks.roaming_ - 1 if roaming is supported, 0 otherwise + +*roaming_profile* - keyword, text.text + +* _wifi_networks.roaming_profile_ - Describe the roaming profile, usually one of Single, Dual or Multi + +*root* - keyword, text.text + +* _processes.root_ - Process virtual root directory + +*root_dir* - keyword, text.text + +* _docker_info.root_dir_ - Docker root directory + +*root_directory* - keyword, text.text + +* _launchd.root_directory_ - Key used to specify a directory to chroot to before launch + +*root_volume_uuid* - keyword, text.text + +* _time_machine_destinations.root_volume_uuid_ - Root UUID of backup volume + +*rotation_rate* - keyword, text.text + +* _smart_drive_info.rotation_rate_ - Drive RPM + +*round_trip_time* - keyword, number.long + +* _curl.round_trip_time_ - Time taken to complete the request + +*rowid* - keyword, number.long + +* _quicklook_cache.rowid_ - Quicklook file rowid key + +*rssi* - keyword, number.long + +* _wifi_status.rssi_ - The current received signal strength indication (dbm) +* _wifi_survey.rssi_ - The current received signal strength indication (dbm) + +*rtadv_accept* - keyword, number.long + +* _interface_ipv6.rtadv_accept_ - Accept ICMP Router Advertisement + +*rule_details* - keyword, text.text + +* _sudoers.rule_details_ - Rule definition + +*run_at_load* - keyword, text.text + +* _launchd.run_at_load_ - Should the program run on launch load + +*run_count* - keyword, number.long + +* _prefetch.run_count_ - Number of times the application has been run. + +*rw* - keyword, number.long + +* _docker_container_mounts.rw_ - 1 if read/write. 0 otherwise + +*sata_version* - keyword, text.text + +* _smart_drive_info.sata_version_ - SATA version, if any + +*scheme* - keyword, text.text + +* _app_schemes.scheme_ - Name of the scheme/protocol + +*scope* - keyword, text.text + +* _selinux_settings.scope_ - Where the key is located inside the SELinuxFS mount point. + +*screen_sharing* - keyword, number.long + +* _sharing_preferences.screen_sharing_ - 1 If screen sharing is enabled else 0 + +*script* - keyword, text.text + +* _chrome_extension_content_scripts.script_ - The content script used by the extension + +*script_block_count* - keyword, number.long + +* _powershell_events.script_block_count_ - The total number of script blocks for this script + +*script_block_id* - keyword, text.text + +* _powershell_events.script_block_id_ - The unique GUID of the powershell script to which this block belongs + +*script_file_name* - keyword, text.text + +* _wmi_script_event_consumers.script_file_name_ - Name of the file from which the script text is read, intended as an alternative to specifying the text of the script in the ScriptText property. + +*script_name* - keyword, text.text + +* _powershell_events.script_name_ - The name of the Powershell script + +*script_path* - keyword, text.text + +* _powershell_events.script_path_ - The path for the Powershell script + +*script_text* - keyword, text.text + +* _powershell_events.script_text_ - The text content of the Powershell script +* _wmi_script_event_consumers.script_text_ - Text of the script that is expressed in a language known to the scripting engine. This property must be NULL if the ScriptFileName property is not NULL. + +*scripting_engine* - keyword, text.text + +* _wmi_script_event_consumers.scripting_engine_ - Name of the scripting engine to use, for example, 'VBScript'. This property cannot be NULL. + +*sdb_id* - keyword, text.text + +* _appcompat_shims.sdb_id_ - Unique GUID of the SDB. + +*sdk* - keyword, text.text + +* _browser_plugins.sdk_ - Build SDK used to compile plugin +* _safari_extensions.sdk_ - Bundle SDK used to compile extension + +*sdk_version* - keyword, text.text + +* _osquery_extensions.sdk_version_ - osquery SDK version used to build the extension + +*seconds* - keyword, number.long + +* _time.seconds_ - Current seconds in the system +* _uptime.seconds_ - Seconds of uptime + +*section* - keyword, text.text + +* _deb_packages.section_ - Package section + +*sector_sizes* - keyword, text.text + +* _smart_drive_info.sector_sizes_ - Bytes of drive sector sizes + +*secure_boot* - keyword, number.long + +* _secureboot.secure_boot_ - Whether secure boot is enabled + +*secure_process* - keyword, number.long + +* _processes.secure_process_ - Process is secure (IUM) yes=1, no=0 + +*security_breach* - keyword, text.text + +* _chassis_info.security_breach_ - The physical status of the chassis such as Breach Successful, Breach Attempted, etc. + +*security_groups* - keyword, text.text + +* _ec2_instance_metadata.security_groups_ - Comma separated list of security group names + +*security_options* - keyword, text.text + +* _docker_containers.security_options_ - List of container security options + +*security_type* - keyword, text.text + +* _wifi_networks.security_type_ - Type of security on this network +* _wifi_status.security_type_ - Type of security on this network + +*self_signed* - keyword, number.long + +* _certificates.self_signed_ - 1 if self-signed, else 0 + +*sender* - keyword, text.text + +* _asl.sender_ - Sender's identification string. Default is process name. + +*sensor_backend_server* - keyword, text.text + +* _carbon_black_info.sensor_backend_server_ - Carbon Black server + +*sensor_id* - keyword, number.long + +* _carbon_black_info.sensor_id_ - Sensor ID of the Carbon Black sensor + +*sensor_ip_addr* - keyword, text.text + +* _carbon_black_info.sensor_ip_addr_ - IP address of the sensor + +*seq_num* - keyword, number.long + +* _es_process_events.seq_num_ - Per event sequence number + +*serial* - keyword, text.text + +* _certificates.serial_ - Certificate serial number +* _chassis_info.serial_ - The serial number of the chassis. +* _disk_info.serial_ - The serial number of the disk. +* _hardware_events.serial_ - Device serial (optional) +* _usb_devices.serial_ - USB Device serial connection + +*serial_number* - keyword, text.text + +* _authenticode.serial_number_ - The certificate serial number +* _battery.serial_number_ - The battery's unique serial number +* _curl_certificate.serial_number_ - Certificate serial number +* _memory_devices.serial_number_ - Serial number of memory device +* _smart_drive_info.serial_number_ - Device serial number + +*serial_port_enabled* - keyword, text.text + +* _ycloud_instance_metadata.serial_port_enabled_ - Indicates if serial port is enabled for the VM + +*series* - keyword, text.text + +* _video_info.series_ - The series of the gpu. + +*server_name* - keyword, text.text + +* _lxd_cluster.server_name_ - Name of the LXD server node +* _lxd_cluster_members.server_name_ - Name of the LXD server node + +*server_version* - keyword, text.text + +* _docker_info.server_version_ - Server version + +*service* - keyword, text.text + +* _drivers.service_ - Driver service name, if one exists +* _interface_details.service_ - The name of the service the network adapter uses. +* _iokit_devicetree.service_ - 1 if the device conforms to IOService else 0 + +*service_exit_code* - keyword, number.long + +* _services.service_exit_code_ - The service-specific error code that the service returns when an error occurs while the service is starting or stopping + +*service_key* - keyword, text.text + +* _drivers.service_key_ - Driver service registry key + +*service_type* - keyword, text.text + +* _services.service_type_ - Service Type: OWN_PROCESS, SHARE_PROCESS and maybe Interactive (can interact with the desktop) + +*ses* - keyword, number.long + +* _seccomp_events.ses_ - Session ID of the session from which the analyzed process was invoked + +*session_id* - keyword, number.long + +* _logon_sessions.session_id_ - The Terminal Services session identifier. +* _winbaseobj.session_id_ - Terminal Services Session Id + +*session_owner* - keyword, text.text + +* _authorizations.session_owner_ - Label top-level key + +*set* - keyword, number.long + +* _memory_devices.set_ - Identifies if memory device is one of a set of devices. A value of 0 indicates no set affiliation. + +*setup_mode* - keyword, number.long + +* _secureboot.setup_mode_ - Whether setup mode is enabled + +*severity* - keyword, number.long + +* _syslog_events.severity_ - Syslog severity + +*sgid* - keyword + +* _docker_container_processes.sgid_ - Saved group ID +* _process_events.sgid_ - Saved group ID at process start +* _process_file_events.sgid_ - Saved group ID of the process using the file +* _processes.sgid_ - Unsigned saved group ID + +*sha1* - keyword, text.text + +* _apparmor_profiles.sha1_ - A unique hash that identifies this policy. +* _certificates.sha1_ - SHA1 hash of the raw certificate contents +* _device_hash.sha1_ - SHA1 hash of provided inode data +* _file_events.sha1_ - The SHA1 of the file after change +* _hash.sha1_ - SHA1 hash of provided filesystem data +* _rpm_packages.sha1_ - SHA1 hash of the package contents + +*sha1_fingerprint* - keyword, text.text + +* _curl_certificate.sha1_fingerprint_ - SHA1 fingerprint + +*sha256* - keyword, text.text + +* _carves.sha256_ - A SHA256 sum of the carved archive +* _device_hash.sha256_ - SHA256 hash of provided inode data +* _file_events.sha256_ - The SHA256 of the file after change +* _hash.sha256_ - SHA256 hash of provided filesystem data +* _rpm_package_files.sha256_ - SHA256 file digest from RPM info DB + +*sha256_fingerprint* - keyword, text.text + +* _curl_certificate.sha256_fingerprint_ - SHA-256 fingerprint + +*shard* - keyword, number.long + +* _osquery_packs.shard_ - Shard restriction limit, 1-100, 0 meaning no restriction + +*share* - keyword, text.text + +* _nfs_shares.share_ - Filesystem path to the share + +*share_name* - keyword, text.text + +* _shortcut_files.share_name_ - Share name of the target file. + +*shared* - keyword, text.text + +* _authorizations.shared_ - Label top-level key + +*shell* - keyword, text.text + +* _users.shell_ - User's configured default shell + +*shell_only* - keyword, number.long + +* _osquery_flags.shell_only_ - Is the flag shell only? + +*shmid* - keyword, number.long + +* _shared_memory.shmid_ - Shared memory segment ID + +*sid* - keyword, text.text + +* _background_activities_moderator.sid_ - User SID. +* _certificates.sid_ - SID +* _logged_in_users.sid_ - The user's unique security identifier +* _office_mru.sid_ - User SID +* _shellbags.sid_ - User SID +* _userassist.sid_ - User SID. + +*sig* - keyword, number.long + +* _seccomp_events.sig_ - Signal value sent to process by seccomp + +*sig_group* - keyword, text.text + +* _yara.sig_group_ - Signature group used + +*sigfile* - keyword, text.text + +* _yara.sigfile_ - Signature file used + +*signature* - keyword, text.text + +* _curl_certificate.signature_ - Signature + +*signature_algorithm* - keyword, text.text + +* _curl_certificate.signature_algorithm_ - Signature Algorithm + +*signatures_up_to_date* - keyword, number.long + +* _windows_security_products.signatures_up_to_date_ - 1 if product signatures are up to date, else 0 + +*signed* - keyword, number.long + +* _drivers.signed_ - Whether the driver is signed or not +* _signature.signed_ - 1 If the file is signed else 0 + +*signing_algorithm* - keyword, text.text + +* _certificates.signing_algorithm_ - Signing algorithm used + +*signing_id* - keyword, text.text + +* _es_process_events.signing_id_ - Signature identifier of the process + +*sigrule* - keyword, text.text + +* _yara.sigrule_ - Signature strings used + +*sigurl* - keyword, text.text + +* _yara.sigurl_ - Signature url + +*size* - keyword + +* _acpi_tables.size_ - Size of compiled table data +* _block_devices.size_ - Block device size in blocks +* _carves.size_ - Size of the carved archive +* _cups_jobs.size_ - The size of the print job +* _deb_packages.size_ - Package size in bytes +* _device_file.size_ - Size of file in bytes +* _disk_events.size_ - Size of partition in bytes +* _docker_image_history.size_ - Size of instruction in bytes +* _elf_sections.size_ - Size of section +* _elf_symbols.size_ - Size of object +* _example.size_ - This is a signed SQLite bigint column +* _fbsd_kmods.size_ - Size of module content +* _file.size_ - Size of file in bytes +* _file_events.size_ - Size of file in bytes +* _kernel_extensions.size_ - Bytes of wired memory used by extension +* _kernel_modules.size_ - Size of module content +* _logical_drives.size_ - The total amount of space, in bytes, of the drive (-1 on failure). +* _lxd_images.size_ - Size of image in bytes +* _lxd_storage_pools.size_ - Size of the storage pool +* _md_devices.size_ - size of the array in blocks +* _memory_devices.size_ - Size of memory device in Megabyte +* _package_bom.size_ - Expected file size +* _platform_info.size_ - Size in bytes of firmware +* _portage_packages.size_ - The size of the package +* _prefetch.size_ - Application file size. +* _quicklook_cache.size_ - Parsed version size field +* _rpm_package_files.size_ - Expected file size in bytes from RPM info DB +* _rpm_packages.size_ - Package size in bytes +* _shared_memory.size_ - Size in bytes +* _smbios_tables.size_ - Table entry size in bytes +* _smc_keys.size_ - Reported size of data in bytes + +*size_bytes* - keyword, number.long + +* _docker_images.size_bytes_ - Size of image in bytes + +*sku* - keyword, text.text + +* _azure_instance_metadata.sku_ - SKU for the VM image +* _chassis_info.sku_ - The Stock Keeping Unit number if available. + +*slot* - keyword + +* _md_drives.slot_ - Slot position of disk +* _portage_packages.slot_ - The slot used by package + +*smart_enabled* - keyword, text.text + +* _smart_drive_info.smart_enabled_ - SMART enabled status + +*smart_supported* - keyword, text.text + +* _smart_drive_info.smart_supported_ - SMART support status + +*smbios_tag* - keyword, text.text + +* _chassis_info.smbios_tag_ - The assigned asset tag number of the chassis. + +*socket* - keyword + +* _listening_ports.socket_ - Socket handle or inode number +* _process_open_sockets.socket_ - Socket handle or inode number +* _socket_events.socket_ - The local path (UNIX domain socket only) + +*socket_designation* - keyword, text.text + +* _cpu_info.socket_designation_ - The assigned socket on the board for the given CPU. + +*soft_limit* - keyword, text.text + +* _ulimit_info.soft_limit_ - Current limit value + +*softirq* - keyword, number.long + +* _cpu_time.softirq_ - Time spent servicing softirqs + +*source* - keyword, text.text + +* _apt_sources.source_ - Source file +* _autoexec.source_ - Source table of the autoexec item +* _deb_packages.source_ - Package source +* _docker_container_mounts.source_ - Source path on host +* _lxd_storage_pools.source_ - Storage pool source +* _package_install_history.source_ - Install source: usually the installer process name +* _routes.source_ - Route source +* _rpm_packages.source_ - Source RPM package name (optional) +* _shellbags.source_ - Shellbags source Registry file +* _startup_items.source_ - Directory or plist containing startup item +* _sudoers.source_ - Source file containing the given rule +* _windows_events.source_ - Source or channel of the event + +*source_path* - keyword, text.text + +* _systemd_units.source_path_ - Path to the (possibly generated) unit configuration file + +*source_url* - keyword, text.text + +* _firefox_addons.source_url_ - URL that installed the addon + +*space_total* - keyword, number.long + +* _lxd_storage_pools.space_total_ - Total available storage space in bytes for this storage pool + +*space_used* - keyword, number.long + +* _lxd_storage_pools.space_used_ - Storage space used in bytes + +*spare_disks* - keyword, number.long + +* _md_devices.spare_disks_ - Number of idle disks in array + +*spec_version* - keyword, text.text + +* _tpm_info.spec_version_ - Trusted Computing Group specification that the TPM supports + +*speculative* - keyword, number.long + +* _virtual_memory_info.speculative_ - Total number of speculative pages. + +*speed* - keyword, number.long + +* _interface_details.speed_ - Estimate of the current bandwidth in bits per second. + +*src_ip* - keyword, text.text + +* _iptables.src_ip_ - Source IP address. + +*src_mask* - keyword, text.text + +* _iptables.src_mask_ - Source IP address mask. + +*src_port* - keyword, text.text + +* _iptables.src_port_ - Protocol source port(s). + +*ssdeep* - keyword, text.text + +* _hash.ssdeep_ - ssdeep hash of provided filesystem data + +*ssh_config_file* - keyword, text.text + +* _ssh_configs.ssh_config_file_ - Path to the ssh_config file + +*ssh_public_key* - keyword, text.text + +* _ec2_instance_metadata.ssh_public_key_ - SSH public key. Only available if supplied at instance launch time +* _ycloud_instance_metadata.ssh_public_key_ - SSH public key. Only available if supplied at instance launch time + +*ssid* - keyword, text.text + +* _wifi_networks.ssid_ - SSID octets of the network +* _wifi_status.ssid_ - SSID octets of the network +* _wifi_survey.ssid_ - SSID octets of the network + +*stack_trace* - keyword, text.text + +* _crashes.stack_trace_ - Most recent frame from the stack trace +* _windows_crashes.stack_trace_ - Multiple stack frames from the stack trace + +*start* - keyword, text.text + +* _memory_map.start_ - Start address of memory region +* _process_memory_map.start_ - Virtual start address (hex) + +*start_interval* - keyword, text.text + +* _launchd.start_interval_ - Frequency to run in seconds + +*start_on_mount* - keyword, text.text + +* _launchd.start_on_mount_ - Run daemon or agent every time a filesystem is mounted + +*start_time* - keyword, number.long + +* _docker_container_processes.start_time_ - Process start in seconds since boot (non-sleeping) +* _osquery_info.start_time_ - UNIX time in seconds when the process started +* _processes.start_time_ - Process start time in seconds since Epoch, in case of error -1 + +*start_type* - keyword, text.text + +* _services.start_type_ - Service start type: BOOT_START, SYSTEM_START, AUTO_START, DEMAND_START, DISABLED + +*started_at* - keyword, text.text + +* _docker_containers.started_at_ - Container start time as string + +*starting_address* - keyword, text.text + +* _memory_array_mapped_addresses.starting_address_ - Physical stating address, in kilobytes, of a range of memory mapped to physical memory array +* _memory_device_mapped_addresses.starting_address_ - Physical stating address, in kilobytes, of a range of memory mapped to physical memory array + +*state* - keyword + +* _alf_exceptions.state_ - Firewall exception state +* _battery.state_ - One of the following: "AC Power" indicates the battery is connected to an external power source, "Battery Power" indicates that the battery is drawing internal power, "Off Line" indicates the battery is off-line or no longer connected +* _chrome_extensions.state_ - 1 if this extension is enabled +* _docker_container_processes.state_ - Process state +* _docker_containers.state_ - Container state (created, restarting, running, removing, paused, exited, dead) +* _lxd_networks.state_ - Network status +* _md_drives.state_ - State of the drive +* _process_open_sockets.state_ - TCP socket state +* _processes.state_ - Process state +* _scheduled_tasks.state_ - State of the scheduled task +* _system_extensions.state_ - System extension state +* _windows_optional_features.state_ - Installation state value. 1 == Enabled, 2 == Disabled, 3 == Absent +* _windows_security_products.state_ - State of protection + +*state_timestamp* - keyword, text.text + +* _windows_security_products.state_timestamp_ - Timestamp for the product state + +*stateful* - keyword, number.long + +* _lxd_instances.stateful_ - Whether the instance is stateful(1) or not(0) + +*statename* - keyword, text.text + +* _windows_optional_features.statename_ - Installation state name. 'Enabled','Disabled','Absent' + +*status* - keyword, text.text + +* _carves.status_ - Status of the carve, can be STARTING, PENDING, SUCCESS, or FAILED +* _chassis_info.status_ - If available, gives various operational or nonoperational statuses such as OK, Degraded, and Pred Fail. +* _deb_packages.status_ - Package status +* _docker_containers.status_ - Container status information +* _kernel_modules.status_ - Kernel module status +* _lxd_cluster_members.status_ - Status of the node (Online/Offline) +* _lxd_instances.status_ - Instance state (running, stopped, etc.) +* _md_devices.status_ - Current state of the array +* _ntdomains.status_ - The current status of the domain object. +* _process_events.status_ - OpenBSM Attribute: Status of the process +* _services.status_ - Service Current status: STOPPED, START_PENDING, STOP_PENDING, RUNNING, CONTINUE_PENDING, PAUSE_PENDING, PAUSED +* _shared_memory.status_ - Destination/attach status +* _shared_resources.status_ - String that indicates the current status of the object. +* _socket_events.status_ - Either 'succeeded', 'failed', 'in_progress' (connect() on non-blocking socket) or 'no_client' (null accept() on non-blocking socket) +* _startup_items.status_ - Startup status; either enabled or disabled + +*stderr_path* - keyword, text.text + +* _launchd.stderr_path_ - Pipe stderr to a target path + +*stdout_path* - keyword, text.text + +* _launchd.stdout_path_ - Pipe stdout to a target path + +*steal* - keyword, number.long + +* _cpu_time.steal_ - Time spent in other operating systems when running in a virtualized environment + +*stealth_enabled* - keyword, number.long + +* _alf.stealth_enabled_ - 1 If stealth mode is enabled else 0 + +*stibp_support_enabled* - keyword, number.long + +* _kva_speculative_info.stibp_support_enabled_ - Windows uses STIBP. + +*storage_driver* - keyword, text.text + +* _docker_info.storage_driver_ - Storage driver + +*store* - keyword, text.text + +* _certificates.store_ - Certificate system store + +*store_id* - keyword, text.text + +* _certificates.store_id_ - Exists for service/user stores. Contains raw store id provided by WinAPI. + +*store_location* - keyword, text.text + +* _certificates.store_location_ - Certificate system store location + +*strings* - keyword, text.text + +* _yara.strings_ - Matching strings +* _yara_events.strings_ - Matching strings + +*sub_state* - keyword, text.text + +* _systemd_units.sub_state_ - The low-level unit activation state, values depend on unit type + +*subclass* - keyword, text.text + +* _usb_devices.subclass_ - USB Device subclass + +*subject* - keyword, text.text + +* _certificates.subject_ - Certificate distinguished name + +*subject_alternative_names* - keyword, text.text + +* _curl_certificate.subject_alternative_names_ - Subject Alternative Name + +*subject_info_access* - keyword, text.text + +* _curl_certificate.subject_info_access_ - Subject Information Access + +*subject_key_id* - keyword, text.text + +* _certificates.subject_key_id_ - SKID an optionally included SHA1 + +*subject_key_identifier* - keyword, text.text + +* _curl_certificate.subject_key_identifier_ - Subject Key Identifier + +*subject_name* - keyword, text.text + +* _authenticode.subject_name_ - The certificate subject name + +*subkey* - keyword, text.text + +* _plist.subkey_ - Intermediate key path, includes lists/dicts +* _preferences.subkey_ - Intemediate key path, includes lists/dicts + +*subnet* - keyword, text.text + +* _docker_networks.subnet_ - Network subnet + +*subscription_id* - keyword, text.text + +* _azure_instance_metadata.subscription_id_ - Azure subscription for the VM + +*subscriptions* - keyword, number.long + +* _osquery_events.subscriptions_ - Number of subscriptions the publisher received or subscriber used + +*subsystem* - keyword, text.text + +* _system_controls.subsystem_ - Subsystem ID, control type + +*subsystem_model* - keyword, text.text + +* _pci_devices.subsystem_model_ - Device description of PCI device subsystem + +*subsystem_model_id* - keyword, text.text + +* _pci_devices.subsystem_model_id_ - Model ID of PCI device subsystem + +*subsystem_vendor* - keyword, text.text + +* _pci_devices.subsystem_vendor_ - Vendor of PCI device subsystem + +*subsystem_vendor_id* - keyword, text.text + +* _pci_devices.subsystem_vendor_id_ - Vendor ID of PCI device subsystem + +*success* - keyword, number.long + +* _socket_events.success_ - Deprecated. Use the 'status' column instead + +*suid* - keyword + +* _docker_container_processes.suid_ - Saved user ID +* _process_events.suid_ - Saved user ID at process start +* _process_file_events.suid_ - Saved user ID of the process using the file +* _processes.suid_ - Unsigned saved user ID + +*summary* - keyword, text.text + +* _chocolatey_packages.summary_ - Package-supplied summary +* _python_packages.summary_ - Package-supplied summary + +*superblock_state* - keyword, text.text + +* _md_devices.superblock_state_ - State of the superblock + +*superblock_update_time* - keyword, number.long + +* _md_devices.superblock_update_time_ - Unix timestamp of last update + +*superblock_version* - keyword, text.text + +* _md_devices.superblock_version_ - Version of the superblock + +*swap_cached* - keyword, number.long + +* _memory_info.swap_cached_ - The amount of swap, in bytes, used as cache memory + +*swap_free* - keyword, number.long + +* _memory_info.swap_free_ - The total amount of swap free, in bytes + +*swap_ins* - keyword, number.long + +* _virtual_memory_info.swap_ins_ - The total number of compressed pages that have been swapped out to disk. + +*swap_limit* - keyword, number.long + +* _docker_info.swap_limit_ - 1 if swap limit support is enabled. 0 otherwise + +*swap_outs* - keyword, number.long + +* _virtual_memory_info.swap_outs_ - The total number of compressed pages that have been swapped back in from disk. + +*swap_total* - keyword, number.long + +* _memory_info.swap_total_ - The total amount of swap available, in bytes + +*symlink* - keyword, number.long + +* _file.symlink_ - 1 if the path is a symlink, otherwise 0 + +*syscall* - keyword, text.text + +* _bpf_process_events.syscall_ - System call name +* _bpf_socket_events.syscall_ - System call name +* _process_events.syscall_ - Syscall name: fork, vfork, clone, execve, execveat +* _seccomp_events.syscall_ - Type of the system call + +*system* - keyword, number.long + +* _cpu_time.system_ - Time spent in system mode + +*system_cpu_usage* - keyword, number.long + +* _docker_container_stats.system_cpu_usage_ - CPU system usage + +*system_model* - keyword, text.text + +* _kernel_panics.system_model_ - Physical system model, for example 'MacBookPro12,1 (Mac-E43C1C25D4880AD6)' + +*system_time* - keyword, number.long + +* _osquery_schedule.system_time_ - Total system time spent executing +* _processes.system_time_ - CPU time in milliseconds spent in kernel space + +*table* - keyword, text.text + +* _elf_symbols.table_ - Table name containing symbol + +*tag* - keyword + +* _elf_dynamic.tag_ - Tag ID +* _syslog_events.tag_ - The syslog tag + +*tags* - keyword, text.text + +* _docker_image_history.tags_ - Comma-separated list of tags +* _docker_images.tags_ - Comma-separated list of repository tags +* _yara.tags_ - Matching tags +* _yara_events.tags_ - Matching tags + +*tapping_process* - keyword, number.long + +* _event_taps.tapping_process_ - The process ID of the application that created the event tap. + +*target* - keyword + +* _fan_speed_sensors.target_ - Target speed +* _iptables.target_ - Target that applies for this rule. + +*target_accessed* - keyword, number.long + +* _shortcut_files.target_accessed_ - Target Accessed time. + +*target_created* - keyword, number.long + +* _shortcut_files.target_created_ - Target Created time. + +*target_modified* - keyword, number.long + +* _shortcut_files.target_modified_ - Target Modified time. + +*target_name* - keyword, text.text + +* _prometheus_metrics.target_name_ - Address of prometheus target + +*target_path* - keyword, text.text + +* _file_events.target_path_ - The path associated with the event +* _shortcut_files.target_path_ - Target file path +* _yara_events.target_path_ - The path scanned + +*target_size* - keyword, number.long + +* _shortcut_files.target_size_ - Size of target file. + +*task* - keyword, number.long + +* _windows_eventlog.task_ - Task value associated with the event +* _windows_events.task_ - Task value associated with the event + +*team* - keyword, text.text + +* _system_extensions.team_ - Signing team ID + +*team_id* - keyword, text.text + +* _es_process_events.team_id_ - Team identifier of thd process + +*team_identifier* - keyword, text.text + +* _signature.team_identifier_ - The team signing identifier sealed into the signature + +*temporarily_disabled* - keyword, number.long + +* _wifi_networks.temporarily_disabled_ - 1 if this network is temporarily disabled, 0 otherwise + +*terminal* - keyword, text.text + +* _user_events.terminal_ - The network protocol ID + +*threads* - keyword, number.long + +* _docker_container_processes.threads_ - Number of threads used by process +* _processes.threads_ - Number of threads used by process + +*throttled* - keyword, number.long + +* _virtual_memory_info.throttled_ - Total number of throttled pages. + +*tid* - keyword, number.long + +* _bpf_process_events.tid_ - Thread ID +* _bpf_socket_events.tid_ - Thread ID +* _windows_crashes.tid_ - Thread ID of the crashed thread +* _windows_eventlog.tid_ - Thread ID which emitted the event record + +*time* - keyword + +* _apparmor_events.time_ - Time of execution in UNIX time +* _asl.time_ - Unix timestamp. Set automatically +* _bpf_process_events.time_ - Time of execution in UNIX time +* _bpf_socket_events.time_ - Time of execution in UNIX time +* _carves.time_ - Time at which the carve was kicked off +* _disk_events.time_ - Time of appearance/disappearance in UNIX time +* _docker_container_processes.time_ - Cumulative CPU time. [DD-]HH:MM:SS format +* _es_process_events.time_ - Time of execution in UNIX time +* _file_events.time_ - Time of file event +* _hardware_events.time_ - Time of hardware event +* _kernel_panics.time_ - Formatted time of the event +* _last.time_ - Entry timestamp +* _logged_in_users.time_ - Time entry was made +* _ntfs_journal_events.time_ - Time of file event +* _package_install_history.time_ - Label date as UNIX timestamp +* _powershell_events.time_ - Timestamp the event was received by the osquery event publisher +* _process_events.time_ - Time of execution in UNIX time +* _process_file_events.time_ - Time of execution in UNIX time +* _seccomp_events.time_ - Time of execution in UNIX time +* _selinux_events.time_ - Time of execution in UNIX time +* _shell_history.time_ - Entry timestamp. It could be absent, default value is 0. +* _socket_events.time_ - Time of execution in UNIX time +* _syslog_events.time_ - Current unix epoch time +* _user_events.time_ - Time of execution in UNIX time +* _user_interaction_events.time_ - Time +* _windows_events.time_ - Timestamp the event was received +* _xprotect_reports.time_ - Quarantine alert time +* _yara_events.time_ - Time of the scan + +*time_nano_sec* - keyword, number.long + +* _asl.time_nano_sec_ - Nanosecond time. + +*time_range* - keyword, text.text + +* _windows_eventlog.time_range_ - System time to selectively filter the events + +*timeout* - keyword, text.text + +* _authorizations.timeout_ - Label top-level key +* _curl_certificate.timeout_ - Set this value to the timeout in seconds to complete the TLS handshake (default 4s, use 0 for no timeout) + +*timestamp* - keyword, text.text + +* _time.timestamp_ - Current timestamp (log format) in the system +* _windows_eventlog.timestamp_ - Timestamp to selectively filter the events + +*timestamp_ms* - keyword, number.long + +* _prometheus_metrics.timestamp_ms_ - Unix timestamp of collected data in MS + +*timezone* - keyword, text.text + +* _time.timezone_ - Current timezone in the system + +*title* - keyword, text.text + +* _cups_jobs.title_ - Title of the printed job + +*total_seconds* - keyword, number.long + +* _uptime.total_seconds_ - Total uptime seconds + +*total_size* - keyword, number.long + +* _docker_container_processes.total_size_ - Total virtual memory size +* _processes.total_size_ - Total virtual memory size + +*total_width* - keyword, number.long + +* _memory_devices.total_width_ - Total width, in bits, of this memory device, including any check or error-correction bits + +*transaction_id* - keyword, number.long + +* _file_events.transaction_id_ - ID used during bulk update +* _yara_events.transaction_id_ - ID used during bulk update + +*transmit_rate* - keyword, text.text + +* _wifi_status.transmit_rate_ - The current transmit rate + +*transport_type* - keyword, text.text + +* _smart_drive_info.transport_type_ - Drive transport type + +*tries* - keyword, text.text + +* _authorizations.tries_ - Label top-level key + +*tty* - keyword, text.text + +* _last.tty_ - Entry terminal +* _logged_in_users.tty_ - Device name + +*turbo_disabled* - keyword, number.long + +* _msr.turbo_disabled_ - Whether the turbo feature is disabled. + +*turbo_ratio_limit* - keyword, number.long + +* _msr.turbo_ratio_limit_ - The turbo feature ratio limit. + +*type* - keyword, text.text + +* _apparmor_events.type_ - Event type +* _appcompat_shims.type_ - Type of the SDB database. +* _block_devices.type_ - Block device type string +* _bpf_socket_events.type_ - The socket type +* _crashes.type_ - Type of crash log +* _device_file.type_ - File status +* _device_firmware.type_ - Type of device +* _device_partitions.type_ - +* _disk_encryption.type_ - Description of cipher type and mode if available +* _disk_info.type_ - The interface type of the disk. +* _dns_cache.type_ - DNS record type +* _dns_resolvers.type_ - Address type: sortlist, nameserver, search +* _docker_container_mounts.type_ - Type of mount (bind, volume) +* _docker_container_ports.type_ - Protocol (tcp, udp) +* _docker_volumes.type_ - Volume type +* _elf_info.type_ - Offset of section in file +* _elf_sections.type_ - Section type +* _elf_symbols.type_ - Symbol type +* _file.type_ - File status +* _firefox_addons.type_ - Extension, addon, webapp +* _hardware_events.type_ - Type of hardware and hardware event +* _interface_addresses.type_ - Type of address. One of dhcp, manual, auto, other, unknown +* _interface_details.type_ - Interface type (includes virtual) +* _keychain_items.type_ - Keychain item type (class) +* _last.type_ - Entry type, according to ut_type types (utmp.h) +* _logged_in_users.type_ - Login type +* _logical_drives.type_ - Deprecated (always 'Unknown'). +* _lxd_certificates.type_ - Type of the certificate +* _lxd_networks.type_ - Type of network +* _mounts.type_ - Mounted device type +* _ntfs_acl_permissions.type_ - Type of access mode for the access control entry. +* _nvram.type_ - Data type (CFData, CFString, etc) +* _osquery_events.type_ - Either publisher or subscriber +* _osquery_extensions.type_ - SDK extension type: extension or module +* _osquery_flags.type_ - Flag type +* _process_open_pipes.type_ - Pipe Type: named vs unnamed/anonymous +* _registry.type_ - Type of the registry value, or 'subkey' if item is a subkey +* _routes.type_ - Type of route +* _selinux_events.type_ - Event type +* _shared_resources.type_ - Type of resource being shared. Types include: disk drives, print queues, interprocess communications (IPC), and general devices. +* _smbios_tables.type_ - Table entry type +* _smc_keys.type_ - SMC-reported type literal type +* _startup_items.type_ - Startup Item or Login Item +* _system_controls.type_ - Data type +* _ulimit_info.type_ - System resource to be limited +* _user_events.type_ - The file description for the process socket +* _users.type_ - Whether the account is roaming (domain), local, or a system profile +* _windows_crashes.type_ - Type of crash log +* _windows_security_products.type_ - Type of security product +* _xprotect_meta.type_ - Either plugin or extension + +*type_name* - keyword, text.text + +* _last.type_name_ - Entry type name, according to ut_type types (utmp.h) + +*uid* - keyword + +* _account_policy_data.uid_ - User ID +* _asl.uid_ - UID that sent the log message (set by the server). +* _atom_packages.uid_ - The local user that owns the plugin +* _authorized_keys.uid_ - The local owner of authorized_keys file +* _bpf_process_events.uid_ - User ID +* _bpf_socket_events.uid_ - User ID +* _browser_plugins.uid_ - The local user that owns the plugin +* _chrome_extension_content_scripts.uid_ - The local user that owns the extension +* _chrome_extensions.uid_ - The local user that owns the extension +* _crashes.uid_ - User ID of the crashed process +* _device_file.uid_ - Owning user ID +* _disk_encryption.uid_ - Currently authenticated user if available +* _docker_container_processes.uid_ - User ID +* _es_process_events.uid_ - User ID of the process +* _file.uid_ - Owning user ID +* _file_events.uid_ - Owning user ID +* _firefox_addons.uid_ - The local user that owns the addon +* _known_hosts.uid_ - The local user that owns the known_hosts file +* _launchd_overrides.uid_ - User ID applied to the override, 0 applies to all +* _package_bom.uid_ - Expected user of file or directory +* _process_events.uid_ - User ID at process start +* _process_file_events.uid_ - The uid of the process performing the action +* _processes.uid_ - Unsigned user ID +* _safari_extensions.uid_ - The local user that owns the extension +* _seccomp_events.uid_ - User ID of the user who started the analyzed process +* _shell_history.uid_ - Shell history owner +* _ssh_configs.uid_ - The local owner of the ssh_config file +* _user_events.uid_ - User ID +* _user_groups.uid_ - User ID +* _user_ssh_keys.uid_ - The local user that owns the key file +* _users.uid_ - User ID + +*uid_signed* - keyword, number.long + +* _users.uid_signed_ - User ID as int64 signed (Apple) + +*umci_policy_status* - keyword, text.text + +* _hvci_status.umci_policy_status_ - The status of the User Mode Code Integrity security settings. Returns UNKNOWN if an error is encountered. + +*uncompressed* - keyword, number.long + +* _virtual_memory_info.uncompressed_ - Total number of uncompressed pages. + +*uninstall_string* - keyword, text.text + +* _programs.uninstall_string_ - Path and filename of the uninstaller. + +*unique_chip_id* - keyword, text.text + +* _ibridge_info.unique_chip_id_ - Unique id of the iBridge controller + +*unix_time* - keyword, number.long + +* _time.unix_time_ - Current UNIX time in the system, converted to UTC if --utc enabled + +*unmask* - keyword, number.long + +* _portage_keywords.unmask_ - If the package is unmasked + +*unused_devices* - keyword, text.text + +* _md_devices.unused_devices_ - Unused devices + +*update_source_alias* - keyword, text.text + +* _lxd_images.update_source_alias_ - Alias of image at update source server + +*update_source_certificate* - keyword, text.text + +* _lxd_images.update_source_certificate_ - Certificate for update source server + +*update_source_protocol* - keyword, text.text + +* _lxd_images.update_source_protocol_ - Protocol used for image information update and image import from source server + +*update_source_server* - keyword, text.text + +* _lxd_images.update_source_server_ - Server for image update + +*update_url* - keyword, text.text + +* _chrome_extensions.update_url_ - Extension-supplied update URI +* _safari_extensions.update_url_ - Extension-supplied update URI + +*upid* - keyword, number.long + +* _processes.upid_ - A 64bit pid that is never reused. Returns -1 if we couldn't gather them from the system. + +*uploaded_at* - keyword, text.text + +* _lxd_images.uploaded_at_ - ISO time of image upload + +*upn* - keyword, text.text + +* _logon_sessions.upn_ - The user principal name (UPN) for the owner of the logon session. + +*uppid* - keyword, number.long + +* _processes.uppid_ - The 64bit parent pid that is never reused. Returns -1 if we couldn't gather them from the system. + +*uptime* - keyword, number.long + +* _apparmor_events.uptime_ - Time of execution in system uptime +* _kernel_panics.uptime_ - System uptime at kernel panic in nanoseconds +* _process_events.uptime_ - Time of execution in system uptime +* _process_file_events.uptime_ - Time of execution in system uptime +* _seccomp_events.uptime_ - Time of execution in system uptime +* _selinux_events.uptime_ - Time of execution in system uptime +* _socket_events.uptime_ - Time of execution in system uptime +* _user_events.uptime_ - Time of execution in system uptime + +*url* - keyword, text.text + +* _curl.url_ - The url for the request +* _lxd_cluster_members.url_ - URL of the node + +*usb_address* - keyword, number.long + +* _usb_devices.usb_address_ - USB Device used address + +*usb_port* - keyword, number.long + +* _usb_devices.usb_port_ - USB Device used port + +*use* - keyword, text.text + +* _memory_arrays.use_ - Function for which the array is used +* _portage_use.use_ - USE flag which has been enabled for package + +*used_by* - keyword, text.text + +* _kernel_modules.used_by_ - Module reverse dependencies +* _lxd_networks.used_by_ - URLs for containers using this network + +*user* - keyword + +* _cpu_time.user_ - Time spent in user mode +* _cups_jobs.user_ - The user who printed the job +* _docker_container_processes.user_ - User name +* _logged_in_users.user_ - User login name +* _logon_sessions.user_ - The account name of the security principal that owns the logon session. +* _sandboxes.user_ - Sandbox owner +* _systemd_units.user_ - The configured user, if any + +*user_account* - keyword, text.text + +* _services.user_account_ - The name of the account that the service process will be logged on as when it runs. This name can be of the form Domain\UserName. If the account belongs to the built-in domain, the name can be of the form .\UserName. + +*user_account_control* - keyword, text.text + +* _windows_security_center.user_account_control_ - The health of the User Account Control (UAC) capability in Windows + +*user_action* - keyword, text.text + +* _xprotect_reports.user_action_ - Action taken by user after prompted + +*user_agent* - keyword, text.text + +* _curl.user_agent_ - The user-agent string to use for the request + +*user_capacity* - keyword, text.text + +* _smart_drive_info.user_capacity_ - Bytes of drive capacity + +*user_namespace* - keyword, text.text + +* _docker_containers.user_namespace_ - User namespace +* _process_namespaces.user_namespace_ - user namespace inode + +*user_time* - keyword, number.long + +* _osquery_schedule.user_time_ - Total user time spent executing +* _processes.user_time_ - CPU time in milliseconds spent in user space + +*user_uuid* - keyword, text.text + +* _disk_encryption.user_uuid_ - UUID of authenticated user if available + +*username* - keyword, text.text + +* _certificates.username_ - Username +* _es_process_events.username_ - Username +* _last.username_ - Entry username +* _launchd.username_ - Run this daemon or agent as this username +* _managed_policies.username_ - Policy applies only this user +* _preferences.username_ - (optional) read preferences for a specific user +* _rpm_package_files.username_ - File default username from info DB +* _shadow.username_ - Username +* _startup_items.username_ - The user associated with the startup item +* _suid_bin.username_ - Binary owner username +* _users.username_ - Username +* _windows_crashes.username_ - Username of the user who ran the crashed process + +*uses_pattern* - keyword, number.long + +* _xprotect_entries.uses_pattern_ - Uses a match pattern instead of identity + +*uts_namespace* - keyword, text.text + +* _docker_containers.uts_namespace_ - UTS namespace +* _process_namespaces.uts_namespace_ - uts namespace inode + +*uuid* - keyword, text.text + +* _block_devices.uuid_ - Block device Universally Unique Identifier +* _disk_encryption.uuid_ - Disk Universally Unique Identifier +* _disk_events.uuid_ - UUID of the volume inside DMG if available +* _managed_policies.uuid_ - Optional UUID assigned to policy set +* _osquery_extensions.uuid_ - The transient ID assigned for communication +* _osquery_info.uuid_ - Unique ID provided by the system +* _system_info.uuid_ - Unique ID provided by the system +* _users.uuid_ - User's UUID (Apple) or SID (Windows) + +*vaddr* - keyword, number.long + +* _elf_sections.vaddr_ - Section virtual address in memory +* _elf_segments.vaddr_ - Segment virtual address in memory + +*valid_from* - keyword, text.text + +* _curl_certificate.valid_from_ - Period of validity start date + +*valid_to* - keyword, text.text + +* _curl_certificate.valid_to_ - Period of validity end date + +*value* - keyword, text.text + +* _ad_config.value_ - Variable typed option value +* _augeas.value_ - The value of the configuration item +* _azure_instance_tags.value_ - The tag value +* _cpuid.value_ - Bit value or string +* _default_environment.value_ - Value of the environment variable +* _docker_container_labels.value_ - Optional label value +* _docker_image_labels.value_ - Optional label value +* _docker_network_labels.value_ - Optional label value +* _docker_volume_labels.value_ - Optional label value +* _ec2_instance_tags.value_ - Tag value +* _elf_dynamic.value_ - Tag value +* _extended_attributes.value_ - The parsed information from the attribute +* _launchd_overrides.value_ - Overridden value +* _lxd_instance_config.value_ - Configuration parameter value +* _lxd_instance_devices.value_ - Device info param value +* _managed_policies.value_ - Policy value +* _mdls.value_ - Value stored in the metadata key +* _nvram.value_ - Raw variable data +* _oem_strings.value_ - The value of the OEM string +* _osquery_flags.value_ - Flag value +* _plist.value_ - String value of most CF types +* _power_sensors.value_ - Power in Watts +* _preferences.value_ - String value of most CF types +* _process_envs.value_ - Environment variable value +* _selinux_settings.value_ - Active value. +* _smc_keys.value_ - A type-encoded representation of the key value +* _wmi_bios_info.value_ - Value of the Bios setting + +*valuetype* - keyword, text.text + +* _mdls.valuetype_ - CoreFoundation type of data stored in value + +*variable* - keyword, text.text + +* _default_environment.variable_ - Name of the environment variable + +*vbs_status* - keyword, text.text + +* _hvci_status.vbs_status_ - The status of the virtualization based security settings. Returns UNKNOWN if an error is encountered. + +*vendor* - keyword, text.text + +* _block_devices.vendor_ - Block device vendor string +* _disk_events.vendor_ - Disk event vendor string +* _hardware_events.vendor_ - Hardware device vendor +* _pci_devices.vendor_ - PCI Device vendor +* _platform_info.vendor_ - Platform code vendor +* _rpm_packages.vendor_ - Package vendor +* _usb_devices.vendor_ - USB Device vendor string + +*vendor_id* - keyword, text.text + +* _hardware_events.vendor_id_ - Hex encoded Hardware vendor identifier +* _pci_devices.vendor_id_ - Hex encoded PCI Device vendor identifier +* _usb_devices.vendor_id_ - Hex encoded USB Device vendor identifier + +*vendor_syndrome* - keyword, text.text + +* _memory_error_info.vendor_syndrome_ - Vendor specific ECC syndrome or CRC data associated with the erroneous access + +*version* - keyword, text.text + +* _alf.version_ - Application Layer Firewall version +* _apt_sources.version_ - Repository source version +* _atom_packages.version_ - Package supplied version +* _authorizations.version_ - Label top-level key +* _azure_instance_metadata.version_ - Version of the VM image +* _bitlocker_info.version_ - The FVE metadata version of the drive. +* _browser_plugins.version_ - Plugin short version +* _chocolatey_packages.version_ - Package-supplied version +* _chrome_extension_content_scripts.version_ - Extension-supplied version +* _chrome_extensions.version_ - Extension-supplied version +* _crashes.version_ - Version info of the crashed process +* _curl_certificate.version_ - Version Number +* _deb_packages.version_ - Package version +* _device_firmware.version_ - Firmware version +* _docker_version.version_ - Docker version +* _drivers.version_ - Driver version +* _elf_info.version_ - Object file version +* _es_process_events.version_ - Version of EndpointSecurity event +* _firefox_addons.version_ - Addon-supplied version string +* _gatekeeper.version_ - Version of Gatekeeper's gke.bundle +* _homebrew_packages.version_ - Current 'linked' version +* _hvci_status.version_ - The version number of the Device Guard build. +* _ie_extensions.version_ - Version of the executable +* _intel_me_info.version_ - Intel ME version +* _kernel_extensions.version_ - Extension version +* _kernel_info.version_ - Kernel version +* _npm_packages.version_ - Package supplied version +* _office_mru.version_ - Office application version number +* _os_version.version_ - Pretty, suitable for presentation, OS version +* _osquery_extensions.version_ - Extension's version +* _osquery_info.version_ - osquery toolkit version +* _osquery_packs.version_ - Minimum osquery version that this query will run on +* _package_install_history.version_ - Package display version +* _package_receipts.version_ - Installed package version +* _pkg_packages.version_ - Package version +* _platform_info.version_ - Platform code version +* _portage_keywords.version_ - The version which are affected by the use flags, empty means all +* _portage_packages.version_ - The version which are affected by the use flags, empty means all +* _portage_use.version_ - The version of the installed package +* _programs.version_ - Product version information. +* _python_packages.version_ - Package-supplied version +* _rpm_packages.version_ - Package version +* _safari_extensions.version_ - Extension long version +* _system_extensions.version_ - System extension version +* _usb_devices.version_ - USB Device version number +* _windows_crashes.version_ - File version info of the crashed process + +*video_mode* - keyword, text.text + +* _video_info.video_mode_ - The current resolution of the display. + +*virtual_process* - keyword, number.long + +* _processes.virtual_process_ - Process is virtual (e.g. System, Registry, vmmem) yes=1, no=0 + +*visible* - keyword, number.long + +* _firefox_addons.visible_ - 1 If the addon is shown in browser else 0 + +*visible_alarm* - keyword, text.text + +* _chassis_info.visible_alarm_ - If TRUE, the frame is equipped with a visual alarm. + +*vlans* - keyword, text.text + +* _lldp_neighbors.vlans_ - Comma delimited list of vlan ids + +*vm_id* - keyword, text.text + +* _azure_instance_metadata.vm_id_ - Unique identifier for the VM +* _azure_instance_tags.vm_id_ - Unique identifier for the VM + +*vm_scale_set_name* - keyword, text.text + +* _azure_instance_metadata.vm_scale_set_name_ - VM scale set name + +*vm_size* - keyword, text.text + +* _azure_instance_metadata.vm_size_ - VM size + +*voltage* - keyword, number.long + +* _battery.voltage_ - The battery's current voltage in mV + +*volume_creation* - keyword, text.text + +* _prefetch.volume_creation_ - Volume creation time. + +*volume_id* - keyword, number.long + +* _quicklook_cache.volume_id_ - Parsed volume ID from fs_id + +*volume_serial* - keyword, text.text + +* _file.volume_serial_ - Volume serial number +* _prefetch.volume_serial_ - Volume serial number. +* _shortcut_files.volume_serial_ - Volume serial number. + +*volume_size* - keyword, number.long + +* _platform_info.volume_size_ - (Optional) size of firmware volume + +*wall_time* - keyword, number.long + +* _osquery_schedule.wall_time_ - Total wall time spent executing + +*warning* - keyword, number.long + +* _shadow.warning_ - Number of days before password expires to warn user about it + +*warnings* - keyword, text.text + +* _smart_drive_info.warnings_ - Warning messages from SMART controller + +*watch_paths* - keyword, text.text + +* _launchd.watch_paths_ - Key that launches daemon or agent if path is modified + +*watcher* - keyword, number.long + +* _osquery_info.watcher_ - Process (or thread/handle) ID of optional watcher process + +*weekday* - keyword, text.text + +* _time.weekday_ - Current weekday in the system + +*win32_exit_code* - keyword, number.long + +* _services.win32_exit_code_ - The error code that the service uses to report an error that occurs when it is starting or stopping + +*win_timestamp* - keyword, number.long + +* _time.win_timestamp_ - Timestamp value in 100 nanosecond units. + +*windows_security_center_service* - keyword, text.text + +* _windows_security_center.windows_security_center_service_ - The health of the Windows Security Center Service + +*wired* - keyword, number.long + +* _virtual_memory_info.wired_ - Total number of wired down pages. + +*wired_size* - keyword, number.long + +* _docker_container_processes.wired_size_ - Bytes of unpageable memory used by process +* _processes.wired_size_ - Bytes of unpageable memory used by process + +*working_directory* - keyword, text.text + +* _launchd.working_directory_ - Key used to specify a directory to chdir to before launch + +*working_disks* - keyword, number.long + +* _md_devices.working_disks_ - Number of working disks in array + +*working_path* - keyword, text.text + +* _shortcut_files.working_path_ - Target file directory. + +*world* - keyword, number.long + +* _portage_packages.world_ - If package is in the world file + +*writable* - keyword, number.long + +* _disk_events.writable_ - 1 if writable, 0 if not + +*xpath* - keyword, text.text + +* _windows_eventlog.xpath_ - The custom query to filter events + +*year* - keyword, number.long + +* _time.year_ - Current year in the system + +*zero_fill* - keyword, number.long + +* _virtual_memory_info.zero_fill_ - Total number of zero filled pages. + +*zone* - keyword, text.text + +* _azure_instance_metadata.zone_ - Availability zone of the VM +* _ycloud_instance_metadata.zone_ - Availability zone of the VM + diff --git a/docs/osquery/osquery.asciidoc b/docs/osquery/osquery.asciidoc index ab8058027934..e450bcbcb7d6 100644 --- a/docs/osquery/osquery.asciidoc +++ b/docs/osquery/osquery.asciidoc @@ -16,7 +16,7 @@ With Osquery in {kib}, you can: * View a history of past queries and their results * Save queries and build a library of queries for specific use cases -Osquery is powered by the *Osquery Manager* integration. +Osquery in {kib} is powered by the *Osquery Manager* integration. For information on how to set up *Osquery Manager*, refer to <>. [float] @@ -119,6 +119,51 @@ image::images/scheduled-pack.png[Shows queries in the pack and details about eac . View scheduled query results in <> or the drag-and-drop <> editor. +[float] +[[osquery-prebuilt-packs]] +== Prebuilt Elastic packs + +The Osquery Manager integration includes a set of prebuilt Osquery packs that you can optionally load. Once added, you can then activate and schedule the packs. + +You can modify the scheduled agent policies for a prebuilt pack, but you cannot edit queries in the pack. To edit the queries, you must first create a copy of the pack. + +[float] +[[load-prebuilt-packs]] +=== Load and activate prebuilt Elastic packs + +. Go to *Packs*, and then click *Load Elastic prebuilt packs*. ++ +NOTE: This option is only available if new or updated prebuilt packs are available. + +. For each pack that you want to schedule: + +* Enable the option to make the pack *Active*. + +* Click the pack name, then *Edit*. + +* Update the *Scheduled agent policies* to specify the policies where this pack should run. + +. Click *Update pack*. + +[float] +[[copy-prebuilt-packs]] +=== Copy prebuilt Elastic packs + +To modify queries in prebuilt packs, you must first make a copy of the pack. + +. Go to *Stack Management* -> *Saved Objects*. + +. Search for the Osquery packs you want to modify by name. + +. Select the checkboxes of the packs to export. + +. Click *Export x objects*. + +. Click *Import*. + +. Select the import option *Create new objects with random IDs*, then click *Import* to import the pack. This creates a copy of the pack that you can edit. + + [float] [[osquery-manage-query]] == Save queries @@ -157,9 +202,110 @@ To add or edit saved queries from the *Saved queries* tab: . Click *Save* or *Update*. --- -include::advanced-osquery.asciidoc[] +[float] +[[osquery-map-fields]] +== Map result fields to ECS + +When you save queries or add queries to a pack, you can optionally map Osquery results or static values to fields in +the {ecs-ref}/ecs-reference.html[Elastic Common Schema] (ECS). +This standardizes your Osquery data for use across detections, machine learning, +and any other areas that rely on ECS-compliant data. +When the query is run, the results include the original `osquery.` +and the mapped ECS fields. For example, if you update a query to map `osquery.name` to `user.name`, the query results include both fields. + +. Edit saved queries or queries in a pack to map fields: + +* For *Saved queries*: Open the *Saved queries* tab, and then click the edit icon for the query that you want to map. + +* For *packs*: Open the *Packs* tab, edit a pack, and then click the edit icon for the query that you want to map. + +. In the **ECS mapping** section, select an **ECS field** to map. + +. In the **Value** column, use the dropdown on the left to choose what type of value to map to the ECS field: + +** **Osquery value**: Select an Osquery field. The fields available are based on the SQL query entered, and only include fields that the query returns. When the query runs, the ECS field is set dynamically to the value of the Osquery field selected. + +** **Static value**: Enter a static value. When the query runs, the ECS field is set to the value entered. For example, static fields can be used to apply `tags` or your preferred `event.category` to the query results. + +. Map more fields, as needed. To remove any mapped rows, click the delete icon. + +. Save your changes. + +[NOTE] +========================= + +* Some ECS fields are restricted and cannot be mapped. These are not available in the ECS dropdown. + +* Some ECS fields are restricted to a set of allowed values, like {ecs-ref}/ecs-event.html#field-event-category[event.category]. Use the {ecs-ref}/ecs-field-reference.html[ECS Field Reference] for help when mapping fields. + +* Osquery date fields have a variety of data types (including integer, text, or bigint). When mapping an Osquery date field to an ECS date field, you might need to use SQL operators in the query to get an {es}-compatible +{ref}/date.html[date] type. +========================= + + +[float] +[[osquery-extended-tables]] +== Extended tables for Kubernetes queries +In addition to the Osquery schema, the Elastic-provided version of Osquery also includes the following tables to support Kubernetes containers. These can be queried with live or scheduled queries. + +* `host_users` + +* `host_groups` + +* `host_processes` + +When querying these tables, the expectation is that the `/etc/passwd`, `/etc/group`, and `/proc` are available in the container under `/hostfs` as: +`/hostfs/etc/passwd`, `/hostfs/etc/group`, and `/hostfs/proc`. For information about the fields available in these tables, see the +https://docs.elastic.co/en/integrations/osquery_manager#exported-fields[exported fields] reference. + +[float] +[[osquery-status]] +== Osquery status + +A query can have the following status: + +[cols="2*<"] +|=== +| Successful | The query successfully completed. +| Failed | The query encountered a problem, such as an issue with the query or the agent was disconnected, and might have failed. +| Not yet responded | The query has not been sent to the agent. +| Expired | The action request timed out. The agent may be offline. +|=== + +NOTE: If an agent is offline, the request status remains **pending** as {kib} retries the request. +By default, a query request times out after five minutes. The time out applies to the time it takes +to deliver the action request to an agent to run a query. If the action completes after the timeout period, +the results are still returned. + + +[float] +[[osquery-results]] +== Osquery results +When you run live or scheduled queries, the results are automatically +stored in an {es} index, so that you can search, analyze, and visualize this data in {kib}. +For a list of the Osquery fields that can be returned in query results, +refer to https://docs.elastic.co/en/integrations/osquery_manager#exported-fields[exported fields]. +Query results can also include ECS fields, if the query has a defined ECS mapping. + +Osquery responses include the following information: + +* Everything prefaced with `osquery.` is part of the query response. These fields are not mapped to ECS by default. + +* Results include some ECS fields by default, such as `host.*` and `agent.*`, which provide information about the host that was queried. + +* For live queries, the `action_data.query` is the query that was sent. + +* For scheduled queries in a pack, the `action_id` has the format `pack__`. You can use this information to look up the query that was run. + +* By default, all query results are https://osquery.readthedocs.io/en/stable/deployment/logging/#snapshot-logs[snapshot logs] +that represent a point in time with a set of results, with no +https://osquery.readthedocs.io/en/stable/deployment/logging/#differential-logs[differentials]. + +* Osquery data is stored in the `logs-osquery_manager.result-` datastream, and the result row data is under the `osquery` property in the document. + + +-- include::manage-integration.asciidoc[] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 9a96eec55db3..934ebfc3e4b0 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -410,3 +410,8 @@ This page has been deleted. Refer to <>. == View a document This page has been deleted. Refer to <>. + +[role="exclude",id="advanced-osquery"] +== Advanced Osquery + +This page has been deleted. Refer to <>. diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index 7579ec207c83..33c97373a76c 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -126,9 +126,9 @@ into a single string. This configuration can be used for environments where the files cannot be made available. [[action-config-email-domain-allowlist]] `xpack.actions.email.domain_allowlist` {ess-icon}:: -A list of allowed email domains which can be used with the email connector. When this setting is not used, all email domains are allowed. When this setting is used, if any email is attempted to be sent that includes an addressee with an email domain that is not in the allowlist, or the from address domain is not in the allowlist, the run of the connector will fail with a message indicating the emails not allowed. +A list of allowed email domains which can be used with the email connector. When this setting is not used, all email domains are allowed. When this setting is used, if any email is attempted to be sent that (a) includes an addressee with an email domain that is not in the allowlist, or (b) includes a from address domain that is not in the allowlist, it will fail with a message indicating the email is not allowed. -WARNING: This feature is available in {kib} 7.17.4 and 8.3.0 onwards but is not supported in {kib} 8.0, 8.1 or 8.2. As such this settings should be removed before upgrading from 7.17 to 8.0, 8.1 or 8.2. It is possible to configure the settings in 7.17.4 and then upgrade to 8.3.0 directly. +WARNING: This feature is available in {kib} 7.17.4 and 8.3.0 onwards but is not supported in {kib} 8.0, 8.1 or 8.2. As such, this setting should be removed before upgrading from 7.17 to 8.0, 8.1 or 8.2. It is possible to configure the settings in 7.17.4 and then upgrade to 8.3.0 directly. `xpack.actions.enabledActionTypes` {ess-icon}:: A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.xmatters`, and `.webhook`. An empty list `[]` will disable all action types. diff --git a/docs/settings/general-infra-logs-ui-settings.asciidoc b/docs/settings/general-infra-logs-ui-settings.asciidoc index 91a6c2ef91a2..af84b92829df 100644 --- a/docs/settings/general-infra-logs-ui-settings.asciidoc +++ b/docs/settings/general-infra-logs-ui-settings.asciidoc @@ -1,28 +1,7 @@ -`xpack.infra.sources.default.logAlias`:: -Index pattern for matching indices that contain log data. Defaults to `filebeat-*,kibana_sample_data_logs*`. To match multiple wildcard patterns, use a comma to separate the names, with no space after the comma. For example, `logstash-app1-*,default-logs-*`. - -`xpack.infra.sources.default.metricAlias`:: -Index pattern for matching indices that contain Metricbeat data. Defaults to `metricbeat-*`. To match multiple wildcard patterns, use a comma to separate the names, with no space after the comma. For example, `logstash-app1-*,default-logs-*`. - -`xpack.infra.sources.default.fields.timestamp`:: -Timestamp used to sort log entries. Defaults to `@timestamp`. - `xpack.infra.sources.default.fields.message`:: Fields used to display messages in the Logs app. Defaults to `['message', '@message']`. -`xpack.infra.sources.default.fields.tiebreaker`:: -Field used to break ties between two entries with the same timestamp. Defaults to `_doc`. - -`xpack.infra.sources.default.fields.host`:: -Field used to identify hosts. Defaults to `host.name`. - -`xpack.infra.sources.default.fields.container`:: -Field used to identify Docker containers. Defaults to `container.id`. - -`xpack.infra.sources.default.fields.pod`:: -Field used to identify Kubernetes pods. Defaults to `kubernetes.pod.uid`. - `xpack.infra.alerting.inventory_threshold.group_by_page_size`:: Controls the size of the composite aggregations used by the Inventory Threshold to retrieve all the hosts. Defaults to `10_000`. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 2d52abc1f013..79421e1ac90b 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -500,7 +500,7 @@ send on all responses to the client from the {kib} server. *Default: `{}`* |[[server-host]] `server.host:` | This setting specifies the host of the -back end server. To allow remote users to connect, set the value to the IP address or DNS name of the {kib} server. *Default: `"localhost"`* +back end server. To allow remote users to connect, set the value to the IP address or DNS name of the {kib} server. Use `0.0.0.0` to make Kibana listen on all IPs (public and private). *Default: `"localhost"`* | `server.keepaliveTimeout:` | The number of milliseconds to wait for additional data before restarting @@ -543,10 +543,6 @@ inactive socket. *Default: `"120000"`* | Paths to a PEM-encoded X.509 server certificate and its corresponding private key. These are used by {kib} to establish trust when receiving inbound SSL/TLS connections from users. -|[[server-uuid]] `server.uuid:` - | The unique identifier for this {kib} instance. - - |=== [NOTE] @@ -627,6 +623,9 @@ all http requests to https over the port configured as <=2.0.0 <4.0.0", - "@types/selenium-webdriver": "^4.0.18", + "@types/selenium-webdriver": "^4.0.19", "@types/semver": "^7", "@types/set-value": "^2.0.0", "@types/sinon": "^7.0.13", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 567e5dd65d66..43c80fc88215 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -26,7 +26,6 @@ filegroup( "//packages/kbn-babel-preset:build", "//packages/kbn-bazel-packages:build", "//packages/kbn-bazel-runner:build", - "//packages/kbn-ci-stats-client:build", "//packages/kbn-ci-stats-core:build", "//packages/kbn-ci-stats-reporter:build", "//packages/kbn-cli-dev-mode:build", @@ -126,7 +125,6 @@ filegroup( "//packages/kbn-axe-config:build_types", "//packages/kbn-bazel-packages:build_types", "//packages/kbn-bazel-runner:build_types", - "//packages/kbn-ci-stats-client:build_types", "//packages/kbn-ci-stats-core:build_types", "//packages/kbn-ci-stats-reporter:build_types", "//packages/kbn-cli-dev-mode:build_types", diff --git a/packages/kbn-bazel-runner/jest.config.js b/packages/kbn-bazel-runner/jest.config.js deleted file mode 100644 index 4d4f77a8f43f..000000000000 --- a/packages/kbn-bazel-runner/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-bazel-runner'], -}; diff --git a/packages/kbn-ci-stats-client/BUILD.bazel b/packages/kbn-ci-stats-client/BUILD.bazel deleted file mode 100644 index 7017adc60441..000000000000 --- a/packages/kbn-ci-stats-client/BUILD.bazel +++ /dev/null @@ -1,120 +0,0 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library") -load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") - -PKG_DIRNAME = "kbn-ci-stats-client" -PKG_REQUIRE_NAME = "@kbn/ci-stats-client" - -SOURCE_FILES = glob( - [ - "src/**/*.ts", - ], - exclude = [ - "**/*.test.*", - ], -) - -SRCS = SOURCE_FILES - -filegroup( - name = "srcs", - srcs = SRCS, -) - -NPM_MODULE_EXTRA_FILES = [ - "package.json", -] - -# In this array place runtime dependencies, including other packages and NPM packages -# which must be available for this code to run. -# -# To reference other packages use: -# "//repo/relative/path/to/package" -# eg. "//packages/kbn-utils" -# -# To reference a NPM package use: -# "@npm//name-of-package" -# eg. "@npm//lodash" -RUNTIME_DEPS = [ - "@npm//axios", - "//packages/kbn-ci-stats-core", - "//packages/kbn-tooling-log", -] - -# In this array place dependencies necessary to build the types, which will include the -# :npm_module_types target of other packages and packages from NPM, including @types/* -# packages. -# -# To reference the types for another package use: -# "//repo/relative/path/to/package:npm_module_types" -# eg. "//packages/kbn-utils:npm_module_types" -# -# References to NPM packages work the same as RUNTIME_DEPS -TYPES_DEPS = [ - "@npm//@types/node", - "@npm//@types/jest", - "@npm//axios", - "//packages/kbn-ci-stats-core:npm_module_types", - "//packages/kbn-tooling-log:npm_module_types", -] - -jsts_transpiler( - name = "target_node", - srcs = SRCS, - build_pkg_name = package_name(), -) - -ts_config( - name = "tsconfig", - src = "tsconfig.json", - deps = [ - "//:tsconfig.base.json", - "//:tsconfig.bazel.json", - ], -) - -ts_project( - name = "tsc_types", - args = ['--pretty'], - srcs = SRCS, - deps = TYPES_DEPS, - declaration = True, - emit_declaration_only = True, - out_dir = "target_types", - root_dir = "src", - tsconfig = ":tsconfig", -) - -js_library( - name = PKG_DIRNAME, - srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node"], - package_name = PKG_REQUIRE_NAME, - visibility = ["//visibility:public"], -) - -pkg_npm( - name = "npm_module", - deps = [":" + PKG_DIRNAME], -) - -filegroup( - name = "build", - srcs = [":npm_module"], - visibility = ["//visibility:public"], -) - -pkg_npm_types( - name = "npm_module_types", - srcs = SRCS, - deps = [":tsc_types"], - package_name = PKG_REQUIRE_NAME, - tsconfig = ":tsconfig", - visibility = ["//visibility:public"], -) - -filegroup( - name = "build_types", - srcs = [":npm_module_types"], - visibility = ["//visibility:public"], -) diff --git a/packages/kbn-ci-stats-client/README.md b/packages/kbn-ci-stats-client/README.md deleted file mode 100644 index d1f6c59e978c..000000000000 --- a/packages/kbn-ci-stats-client/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @kbn/ci-stats-client - -Client for reading data stored at https://ci-stats.kibana.dev \ No newline at end of file diff --git a/packages/kbn-ci-stats-client/package.json b/packages/kbn-ci-stats-client/package.json deleted file mode 100644 index 709f6a3454d5..000000000000 --- a/packages/kbn-ci-stats-client/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "@kbn/ci-stats-client", - "private": true, - "version": "1.0.0", - "main": "./target_node/index.js", - "license": "SSPL-1.0 OR Elastic License 2.0", - "kibana": { - "devOnly": true - } -} diff --git a/packages/kbn-ci-stats-client/src/ci_stats_client.ts b/packages/kbn-ci-stats-client/src/ci_stats_client.ts deleted file mode 100644 index a7ab6f1cc4cb..000000000000 --- a/packages/kbn-ci-stats-client/src/ci_stats_client.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Axios from 'axios'; -import { ToolingLog } from '@kbn/tooling-log'; - -import { parseConfig, Config, CiStatsMetadata } from '@kbn/ci-stats-core'; - -interface LatestTestGroupStatsOptions { - /** The Kibana branch to get stats for, eg "main" */ - branch: string; - /** The CI job names to filter builds by, eg "kibana-hourly" */ - ciJobNames: string[]; - /** Filter test groups by group type */ - testGroupType?: string; -} - -interface CompleteSuccessBuildSource { - jobName: string; - jobRunner: string; - completedAt: string; - commit: string; - startedAt: string; - branch: string; - result: 'SUCCESS'; - jobId: string; - targetBranch: string | null; - fromKibanaCiProduction: boolean; - requiresValidMetrics: boolean | null; - jobUrl: string; - mergeBase: string | null; -} - -interface TestGroupSource { - '@timestamp': string; - buildId: string; - name: string; - type: string; - startTime: string; - durationMs: number; - meta: CiStatsMetadata; -} - -interface LatestTestGroupStatsResp { - build: CompleteSuccessBuildSource & { id: string }; - testGroups: Array; -} - -export class CiStatsClient { - /** - * Create a CiStatsReporter by inspecting the ENV for the necessary config - */ - static fromEnv(log: ToolingLog) { - return new CiStatsClient(parseConfig(log)); - } - - constructor(private readonly config?: Config) {} - - isEnabled() { - return !!this.config?.apiToken; - } - - async getLatestTestGroupStats(options: LatestTestGroupStatsOptions) { - if (!this.config || !this.config.apiToken) { - throw new Error('No ciStats config available, call `isEnabled()` before using the client'); - } - - const resp = await Axios.request({ - baseURL: 'https://ci-stats.kibana.dev', - url: '/v1/test_group_stats', - params: { - branch: options.branch, - ci_job_name: options.ciJobNames.join(','), - test_group_type: options.testGroupType, - }, - headers: { - Authentication: `token ${this.config.apiToken}`, - }, - }); - - return resp.data; - } -} diff --git a/packages/kbn-ci-stats-client/src/index.ts b/packages/kbn-ci-stats-client/src/index.ts deleted file mode 100644 index ac32c69b9f7b..000000000000 --- a/packages/kbn-ci-stats-client/src/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { CiStatsClient } from './ci_stats_client'; diff --git a/packages/kbn-ci-stats-client/tsconfig.json b/packages/kbn-ci-stats-client/tsconfig.json deleted file mode 100644 index a8cfc2cceb08..000000000000 --- a/packages/kbn-ci-stats-client/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../tsconfig.bazel.json", - "compilerOptions": { - "declaration": true, - "emitDeclarationOnly": true, - "outDir": "target_types", - "rootDir": "src", - "stripInternal": false, - "types": [ - "jest", - "node" - ] - }, - "include": [ - "src/**/*" - ] -} diff --git a/packages/kbn-ci-stats-core/README.md b/packages/kbn-ci-stats-core/README.md index b2e34a492b74..741728394aa8 100644 --- a/packages/kbn-ci-stats-core/README.md +++ b/packages/kbn-ci-stats-core/README.md @@ -1,3 +1,3 @@ # @kbn/ci-stats-core -Config and types used by `@kbn/ci-stats-client` and `@kbn/ci-stats-reporter`. \ No newline at end of file +Config and types used by `@kbn/ci-stats-reporter`. \ No newline at end of file diff --git a/packages/kbn-ci-stats-core/jest.config.js b/packages/kbn-ci-stats-core/jest.config.js deleted file mode 100644 index 0feb7b4e1b87..000000000000 --- a/packages/kbn-ci-stats-core/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-ci-stats-core'], -}; diff --git a/packages/kbn-ci-stats-reporter/jest.config.js b/packages/kbn-ci-stats-reporter/jest.config.js deleted file mode 100644 index bf58324f440a..000000000000 --- a/packages/kbn-ci-stats-reporter/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-ci-stats-reporter'], -}; diff --git a/packages/kbn-ci-stats-reporter/src/ci_stats_reporter.ts b/packages/kbn-ci-stats-reporter/src/ci_stats_reporter.ts index d709927787b0..73eecbda2ff3 100644 --- a/packages/kbn-ci-stats-reporter/src/ci_stats_reporter.ts +++ b/packages/kbn-ci-stats-reporter/src/ci_stats_reporter.ts @@ -23,6 +23,18 @@ import type { CiStatsTestGroupInfo, CiStatsTestRun } from './ci_stats_test_group const BASE_URL = 'https://ci-stats.kibana.dev'; +function limitMetaStrings(meta: CiStatsMetadata) { + return Object.fromEntries( + Object.entries(meta).map(([key, value]) => { + if (typeof value === 'string' && value.length > 2000) { + return [key, value.slice(0, 2000)]; + } + + return [key, value]; + }) + ); +} + /** A ci-stats metric record */ export interface CiStatsMetric { /** Top-level categorization for the metric, e.g. "page load bundle size" */ @@ -84,10 +96,8 @@ export interface CiStatsReportTestsOptions { } /* @internal */ -interface ReportTestsResponse { - buildId: string; +interface ReportTestGroupResponse { groupId: string; - testRunCount: number; } /* @internal */ @@ -138,7 +148,14 @@ export class CiStatsReporter { } const buildId = this.config?.buildId; - const timings = options.timings; + const timings = options.timings.map((timing) => + timing.meta + ? { + ...timing, + meta: limitMetaStrings(timing.meta), + } + : timing + ); const upstreamBranch = options.upstreamBranch ?? this.getUpstreamBranch(); const kibanaUuid = options.kibanaUuid === undefined ? this.getKibanaUuid() : options.kibanaUuid; let email; @@ -238,18 +255,51 @@ export class CiStatsReporter { ); } - return await this.req({ + const groupResp = await this.req({ auth: true, - path: '/v1/test_group', + path: '/v2/test_group', query: { buildId: this.config?.buildId, }, - bodyDesc: `[${group.name}/${group.type}] test groups with ${testRuns.length} tests`, - body: [ - JSON.stringify({ group }), - ...testRuns.map((testRun) => JSON.stringify({ testRun })), - ].join('\n'), + bodyDesc: `[${group.name}/${group.type}] test group`, + body: group, }); + + if (!groupResp) { + return; + } + + let bufferBytes = 0; + const buffer: string[] = []; + const flushBuffer = async () => { + await this.req<{ testRunCount: number }>({ + auth: true, + path: '/v2/test_runs', + query: { + buildId: this.config?.buildId, + groupId: groupResp.groupId, + groupType: group.type, + }, + bodyDesc: `[${group.name}/${group.type}] Chunk of ${bufferBytes} bytes`, + body: buffer.join('\n'), + }); + buffer.length = 0; + bufferBytes = 0; + }; + + // send test runs in chunks of ~500kb + for (const testRun of testRuns) { + const json = JSON.stringify(testRun); + bufferBytes += json.length; + buffer.push(json); + if (bufferBytes >= 450000) { + await flushBuffer(); + } + } + + if (bufferBytes) { + await flushBuffer(); + } } /** diff --git a/packages/kbn-ci-stats-reporter/src/ci_stats_test_group_types.ts b/packages/kbn-ci-stats-reporter/src/ci_stats_test_group_types.ts index 223273ca82cd..298b46498aff 100644 --- a/packages/kbn-ci-stats-reporter/src/ci_stats_test_group_types.ts +++ b/packages/kbn-ci-stats-reporter/src/ci_stats_test_group_types.ts @@ -83,6 +83,10 @@ export interface CiStatsTestGroupInfo { * The name of this specific group (within the "type") */ name: string; + /** + * Overall result of this test group + */ + result: 'fail' | 'pass' | 'skip'; /** * Arbitrary metadata associated with this group. We currently look for a ciGroup metadata property for highlighting that when appropriate */ diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index a661707bfa56..74549f4e32b5 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -459,6 +459,9 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { monitorLogstash: `${ELASTIC_WEBSITE_URL}guide/en/logstash/${DOC_LINK_VERSION}/monitoring-with-metricbeat.html`, troubleshootKibana: `${KIBANA_DOCS}monitor-troubleshooting.html`, }, + reporting: { + cloudMinimumRequirements: `${KIBANA_DOCS}reporting-getting-started.html#reporting-on-cloud-resource-requirements`, + }, security: { apiKeyServiceSettings: `${ELASTICSEARCH_DOCS}security-settings.html#api-key-service-settings`, clusterPrivileges: `${ELASTICSEARCH_DOCS}security-privileges.html#privileges-list-cluster`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index a2d05fbbe699..bf378c4cf2dd 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -310,6 +310,9 @@ export interface DocLinks { gdalTutorial: string; }>; readonly monitoring: Record; + readonly reporting: Readonly<{ + cloudMinimumRequirements: string; + }>; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; diff --git a/packages/kbn-eslint-plugin-imports/jest.integration.config.js b/packages/kbn-eslint-plugin-imports/jest.integration.config.js deleted file mode 100644 index e7d05a6222c8..000000000000 --- a/packages/kbn-eslint-plugin-imports/jest.integration.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_integration_node', - rootDir: '../..', - roots: ['/packages/kbn-eslint-plugin-imports'], -}; diff --git a/packages/kbn-generate/jest.config.js b/packages/kbn-generate/jest.config.js deleted file mode 100644 index b72f891cfb1a..000000000000 --- a/packages/kbn-generate/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-generate'], -}; diff --git a/packages/kbn-jest-serializers/jest.config.js b/packages/kbn-jest-serializers/jest.config.js deleted file mode 100644 index 23fad67c028b..000000000000 --- a/packages/kbn-jest-serializers/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-jest-serializers'], -}; diff --git a/packages/kbn-kibana-json-schema/jest.config.js b/packages/kbn-kibana-json-schema/jest.config.js deleted file mode 100644 index 00bc8f55adc5..000000000000 --- a/packages/kbn-kibana-json-schema/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-kibana-json-schema'], -}; diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/find_node_libs_browser_polyfills_in_entry_bundles.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/find_node_libs_browser_polyfills_in_entry_bundles.ts new file mode 100644 index 000000000000..06ad13da2b2f --- /dev/null +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/find_node_libs_browser_polyfills_in_entry_bundles.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; + +import { run } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; + +import { OptimizerConfig } from '../optimizer'; +import { parseStats, inAnyEntryChunk } from './parse_stats'; + +export async function runFindNodeLibsBrowserPolyfillsInEntryBundlesCli() { + run(async ({ log }) => { + const config = OptimizerConfig.create({ + includeCoreBundle: true, + repoRoot: REPO_ROOT, + }); + + const paths = config.bundles.map((b) => Path.resolve(b.outputDir, 'stats.json')); + + log.info('analyzing', paths.length, 'stats files'); + log.verbose(paths); + + const imports = new Set(); + for (const path of paths) { + const stats = parseStats(path); + + for (const module of stats.modules) { + if (!inAnyEntryChunk(stats, module)) { + continue; + } + + // Relying on module name instead of actual imports because these are usual polyfills that assume the global + // Node.js environment when development (i.e.: Buffer doesn't require an import to be used). + if (module.name.includes('node-libs-browser/node_modules/')) { + imports.add(module.name); + } + } + } + + log.success('found', imports.size, 'node-libs-browser/* imports in entry bundles'); + log.write( + Array.from(imports, (i) => `'${i}',`) + .sort() + .join('\n') + ); + }); +} diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts index 58a3ddf263a1..3a7987f867bc 100644 --- a/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/index.ts @@ -7,3 +7,4 @@ */ export * from './find_babel_runtime_helpers_in_entry_bundles'; +export * from './find_node_libs_browser_polyfills_in_entry_bundles'; diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts index fac0b099b519..9b9ba11f90c9 100644 --- a/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/parse_stats.ts @@ -20,6 +20,7 @@ const partialObject =

(props: P) => { export type Module = TypeOf; const moduleSchema = partialObject({ identifier: schema.string(), + name: schema.string(), chunks: schema.arrayOf(schema.oneOf([schema.string(), schema.number()])), reasons: schema.arrayOf( partialObject({ diff --git a/packages/kbn-plugin-discovery/jest.config.js b/packages/kbn-plugin-discovery/jest.config.js deleted file mode 100644 index 37d7a4f2b63a..000000000000 --- a/packages/kbn-plugin-discovery/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-plugin-discovery'], -}; diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 32171e030d91..b0aabefb1b0f 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -1580,8 +1580,19 @@ var _ciStatsCore = __webpack_require__("../../node_modules/@kbn/ci-stats-core/ta */ // @ts-expect-error not "public", but necessary to prevent Jest shimming from breaking things const BASE_URL = 'https://ci-stats.kibana.dev'; + +function limitMetaStrings(meta) { + return Object.fromEntries(Object.entries(meta).map(([key, value]) => { + if (typeof value === 'string' && value.length > 2000) { + return [key, value.slice(0, 2000)]; + } + + return [key, value]; + })); +} /** A ci-stats metric record */ + /** Object that helps report data to the ci-stats service */ class CiStatsReporter { /** @@ -1631,7 +1642,9 @@ class CiStatsReporter { } const buildId = (_this$config3 = this.config) === null || _this$config3 === void 0 ? void 0 : _this$config3.buildId; - const timings = options.timings; + const timings = options.timings.map(timing => timing.meta ? { ...timing, + meta: limitMetaStrings(timing.meta) + } : timing); const upstreamBranch = (_options$upstreamBran = options.upstreamBranch) !== null && _options$upstreamBran !== void 0 ? _options$upstreamBran : this.getUpstreamBranch(); const kibanaUuid = options.kibanaUuid === undefined ? this.getKibanaUuid() : options.kibanaUuid; let email; @@ -1740,19 +1753,55 @@ class CiStatsReporter { throw new Error('unable to report tests unless buildId is configured and auth config available'); } - return await this.req({ + const groupResp = await this.req({ auth: true, - path: '/v1/test_group', + path: '/v2/test_group', query: { buildId: (_this$config7 = this.config) === null || _this$config7 === void 0 ? void 0 : _this$config7.buildId }, - bodyDesc: `[${group.name}/${group.type}] test groups with ${testRuns.length} tests`, - body: [JSON.stringify({ - group - }), ...testRuns.map(testRun => JSON.stringify({ - testRun - }))].join('\n') + bodyDesc: `[${group.name}/${group.type}] test group`, + body: group }); + + if (!groupResp) { + return; + } + + let bufferBytes = 0; + const buffer = []; + + const flushBuffer = async () => { + var _this$config8; + + await this.req({ + auth: true, + path: '/v2/test_runs', + query: { + buildId: (_this$config8 = this.config) === null || _this$config8 === void 0 ? void 0 : _this$config8.buildId, + groupId: groupResp.groupId, + groupType: group.type + }, + bodyDesc: `[${group.name}/${group.type}] Chunk of ${bufferBytes} bytes`, + body: buffer.join('\n') + }); + buffer.length = 0; + bufferBytes = 0; + }; // send test runs in chunks of ~500kb + + + for (const testRun of testRuns) { + const json = JSON.stringify(testRun); + bufferBytes += json.length; + buffer.push(json); + + if (bufferBytes >= 450000) { + await flushBuffer(); + } + } + + if (bufferBytes) { + await flushBuffer(); + } } /** * In order to allow this code to run before @kbn/utils is built, @kbn/pm will pass diff --git a/packages/kbn-shared-ux-components/src/empty_state/index.tsx b/packages/kbn-shared-ux-components/src/empty_state/index.ts similarity index 87% rename from packages/kbn-shared-ux-components/src/empty_state/index.tsx rename to packages/kbn-shared-ux-components/src/empty_state/index.ts index 902a9cd3614d..68defa526934 100644 --- a/packages/kbn-shared-ux-components/src/empty_state/index.tsx +++ b/packages/kbn-shared-ux-components/src/empty_state/index.ts @@ -7,3 +7,4 @@ */ export { NoDataViews, NoDataViewsComponent } from './no_data_views'; +export { KibanaNoDataPage } from './kibana_no_data_page'; diff --git a/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.mdx b/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.mdx new file mode 100644 index 000000000000..c3571d1dd9d8 --- /dev/null +++ b/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.mdx @@ -0,0 +1,23 @@ +--- +id: sharedUX/Components/KibanaNoDataPage +slug: /shared-ux-components/kibana_no_data_page +title: Kibana No Data Page +summary: A page to be displayed when there is no data in Elasticsearch, or no data views +tags: ['shared-ux', 'component'] +date: 2022-04-20 +--- + +## Description + +Many plugins display "no data" page, either when there is no data in Elasticsearch, or there haven't been any data views created yet. This component is meant +to be used in those scenarios. It displays an appropriate message to the user and facilitate addition of integrations and creation of data views. + +## Component: `KibanaNoDataPage` + +- uses `hasUserDataView` and `hasData` API from `HasData` service in `data_views` plugin to check for existence of data / data views +- uses `onDataViewCreated` callback to be called once the data view has been created +- receives (noDataConfig)[https://github.com/elastic/kibana/blob/main/packages/kbn-shared-ux-components/src/page_template/no_data_page/types.ts] as configuration for the page in case of no data +- needs to be wrapped in `ServicesContext` provided by the start contract of the `shared_ux` plugin to be used + +## EUI Promotion Status +This component is not currently considered for promotion to EUI. \ No newline at end of file diff --git a/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.stories.tsx b/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.stories.tsx new file mode 100644 index 000000000000..6d0c240dd4c0 --- /dev/null +++ b/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.stories.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { action } from '@storybook/addon-actions'; +import React from 'react'; +import { servicesFactory, DataServiceFactoryConfig } from '@kbn/shared-ux-storybook'; +import { SharedUxServicesProvider } from '@kbn/shared-ux-services'; +import mdx from './kibana_no_data_page.mdx'; +import { NoDataPageProps } from '../page_template'; +import { KibanaNoDataPage } from './kibana_no_data_page'; + +export default { + title: 'No Data/Kibana No Data Page', + description: 'A component to display when there is no data available', + parameters: { + docs: { + page: mdx, + }, + }, +}; + +const noDataConfig = { + solution: 'Analytics', + logo: 'logoKibana', + action: { + elasticAgent: { + title: 'Add Integrations', + }, + }, + docsLink: 'http://www.docs.com', +}; + +type Params = Pick & DataServiceFactoryConfig; + +export const PureComponent = (params: Params) => { + const { solution, logo, hasESData, hasUserDataView } = params; + const serviceParams = { hasESData, hasUserDataView, hasDataViews: false }; + const services = servicesFactory(serviceParams); + return ( + + + + ); +}; + +PureComponent.argTypes = { + solution: { + control: 'text', + defaultValue: 'Observability', + }, + logo: { + control: { type: 'radio' }, + options: ['logoElastic', 'logoKibana', 'logoCloud', undefined], + defaultValue: undefined, + }, + hasESData: { + control: 'boolean', + defaultValue: false, + }, + hasUserDataView: { + control: 'boolean', + defaultValue: false, + }, +}; diff --git a/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.test.tsx b/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.test.tsx new file mode 100644 index 000000000000..82fbd222b364 --- /dev/null +++ b/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.test.tsx @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { SharedUxServicesProvider, mockServicesFactory } from '@kbn/shared-ux-services'; + +import { KibanaNoDataPage } from './kibana_no_data_page'; +import { NoDataConfigPage } from '../page_template'; +import { NoDataViews } from './no_data_views'; + +describe('Kibana No Data Page', () => { + const noDataConfig = { + solution: 'Analytics', + pageTitle: 'Analytics', + logo: 'logoKibana', + action: { + elasticAgent: { + title: 'Add Integrations', + }, + }, + docsLink: 'http://www.docs.com', + }; + const onDataViewCreated = jest.fn(); + const config = { + hasESData: false, + hasDataView: false, + hasUserDataView: false, + }; + + afterEach(() => { + jest.resetAllMocks(); + }); + + test('renders NoDataConfigPage', async () => { + const services = mockServicesFactory({ config: { ...config, hasESData: false } }); + const component = mountWithIntl( + + + + ); + + await act(() => new Promise(setImmediate)); + component.update(); + + expect(component.find(NoDataConfigPage).length).toBe(1); + expect(component.find(NoDataViews).length).toBe(0); + }); + + test('renders NoDataViews', async () => { + const services = mockServicesFactory({ config: { ...config, hasESData: true } }); + const component = mountWithIntl( + + + + ); + + await act(() => new Promise(setImmediate)); + component.update(); + + expect(component.find(NoDataViews).length).toBe(1); + expect(component.find(NoDataConfigPage).length).toBe(0); + }); +}); diff --git a/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.tsx b/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.tsx new file mode 100644 index 000000000000..2e54d0d9f6a6 --- /dev/null +++ b/packages/kbn-shared-ux-components/src/empty_state/kibana_no_data_page.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { useEffect, useState } from 'react'; +import { useData } from '@kbn/shared-ux-services'; +import { NoDataConfigPage, NoDataPageProps } from '../page_template'; +import { NoDataViews } from './no_data_views'; + +export interface Props { + onDataViewCreated: (dataView: unknown) => void; + noDataConfig: NoDataPageProps; +} + +export const KibanaNoDataPage = ({ onDataViewCreated, noDataConfig }: Props) => { + const { hasESData, hasUserDataView } = useData(); + const [dataExists, setDataExists] = useState(false); + const [hasUserDataViews, setHasUserDataViews] = useState(false); + + useEffect(() => { + const checkData = async () => { + setDataExists(await hasESData()); + setHasUserDataViews(await hasUserDataView()); + }; + // TODO: add error handling + // https://github.com/elastic/kibana/issues/130913 + checkData().catch(() => {}); + }, [hasESData, hasUserDataView]); + + if (!dataExists) { + return ; + } + + if (!hasUserDataViews) { + return ; + } + + return null; +}; diff --git a/packages/kbn-shared-ux-components/src/empty_state/no_data_views/no_data_views.stories.tsx b/packages/kbn-shared-ux-components/src/empty_state/no_data_views/no_data_views.stories.tsx index f40a04d8d4d2..bee7c87d2841 100644 --- a/packages/kbn-shared-ux-components/src/empty_state/no_data_views/no_data_views.stories.tsx +++ b/packages/kbn-shared-ux-components/src/empty_state/no_data_views/no_data_views.stories.tsx @@ -18,7 +18,7 @@ import mdx from './no_data_views.mdx'; const services = servicesFactory({}); export default { - title: 'No Data Views', + title: 'No Data/No Data Views', description: 'A component to display when there are no user-created data views available.', parameters: { docs: { diff --git a/packages/kbn-shared-ux-components/src/index.ts b/packages/kbn-shared-ux-components/src/index.ts index 98bd3ad1ac7e..05afc94f782c 100644 --- a/packages/kbn-shared-ux-components/src/index.ts +++ b/packages/kbn-shared-ux-components/src/index.ts @@ -30,53 +30,36 @@ export const ToolbarButton = withSuspense(LazyToolbarButton); export { AddFromLibraryButton, ToolbarPopover } from './toolbar'; /** - * The Lazily-loaded `NoDataViews` component. Consumers should use `React.Suspense` or the + * The Lazily-loaded `IconButtonGroup` component. Consumers should use `React.Suspense` or the * `withSuspense` HOC to load this component. */ -export const LazyNoDataViews = React.lazy(() => - import('./empty_state/no_data_views').then(({ NoDataViews }) => ({ - default: NoDataViews, +export const LazyIconButtonGroup = React.lazy(() => + import('./toolbar').then(({ IconButtonGroup }) => ({ + default: IconButtonGroup, })) ); /** - * A `NoDataViews` component that is wrapped by the `withSuspense` HOC. This component can - * be used directly by consumers and will load the `LazyNoDataViews` component lazily with - * a predefined fallback and error boundary. + * The IconButtonGroup component that is wrapped by the `withSuspence` HOC. */ -export const NoDataViews = withSuspense(LazyNoDataViews); +export const IconButtonGroup = withSuspense(LazyIconButtonGroup); /** - * A pure `NoDataViews` component, with no services hooks. Consumers should use `React.Suspense` or the + * A `KibanaNoDataPage` component, with service hooks. Consumers should use `React.Suspennse` or the * `withSuspense` HOC to load this component. */ -export const LazyNoDataViewsComponent = React.lazy(() => - import('./empty_state/no_data_views').then(({ NoDataViewsComponent }) => ({ - default: NoDataViewsComponent, +export const KibanaNoDataPageLazy = React.lazy(() => + import('./empty_state').then(({ KibanaNoDataPage }) => ({ + default: KibanaNoDataPage, })) ); /** - * A pure `NoDataViews` component, with no services hooks. The component is wrapped by the `withSuspense` HOC. - * This component can be used directly by consumers and will load the `LazyNoDataViewsComponent` lazily with + * A `KibanaNoDataPage` component. The component is wrapped by the `withSuspense` HOC. + * This component can be used directly by consumers and will load the `KibanaNoDataPageLazy` lazily with * a predefined fallback and error boundary. */ -export const NoDataViewsComponent = withSuspense(LazyNoDataViewsComponent); - -/** - * The Lazily-loaded `IconButtonGroup` component. Consumers should use `React.Suspense` or the - * `withSuspense` HOC to load this component. - */ -export const LazyIconButtonGroup = React.lazy(() => - import('./toolbar').then(({ IconButtonGroup }) => ({ - default: IconButtonGroup, - })) -); - -/** - * The IconButtonGroup component that is wrapped by the `withSuspence` HOC. - */ -export const IconButtonGroup = withSuspense(LazyIconButtonGroup); +export const KibanaNoDataPage = withSuspense(KibanaNoDataPageLazy); /** * The lazily loaded `KibanaPageTemplate` component that is wrapped by the `withSuspense` HOC. Consumers should use @@ -95,6 +78,11 @@ export const KibanaPageTemplateLazy = React.lazy(() => */ export const KibanaPageTemplate = withSuspense(KibanaPageTemplateLazy); +/** + * A `KibanaPageTemplateProps` type. + */ +export type { KibanaPageTemplateProps } from './page_template'; + /** * The lazily loaded `KibanaPageTemplateSolutionNav` component that is wrapped by the `withSuspense` HOC. Consumers should use * `React.Suspense` or `withSuspense` HOC to load this component. @@ -128,3 +116,37 @@ export const KibanaSolutionAvatarLazy = React.lazy(() => * a predefined fallback and error boundary. */ export const KibanaSolutionAvatar = withSuspense(KibanaSolutionAvatarLazy); + +/** + * The Lazily-loaded `NoDataViews` component. Consumers should use `React.Suspennse` or the + * `withSuspense` HOC to load this component. + */ +export const NoDataViewsLazy = React.lazy(() => + import('./empty_state/no_data_views').then(({ NoDataViews }) => ({ + default: NoDataViews, + })) +); + +/** + * A `NoDataViews` component that is wrapped by the `withSuspense` HOC. This component can + * be used directly by consumers and will load the `LazyNoDataViews` component lazily with + * a predefined fallback and error boundary. + */ +export const NoDataViews = withSuspense(NoDataViewsLazy); + +/** + * A pure `NoDataViews` component, with no services hooks. Consumers should use `React.Suspennse` or the + * `withSuspense` HOC to load this component. + */ +export const NoDataViewsComponentLazy = React.lazy(() => + import('./empty_state/no_data_views').then(({ NoDataViewsComponent }) => ({ + default: NoDataViewsComponent, + })) +); + +/** + * A pure `NoDataViews` component, with no services hooks. The component is wrapped by the `withSuspense` HOC. + * This component can be used directly by consumers and will load the `LazyNoDataViewsComponent` lazily with + * a predefined fallback and error boundary. + */ +export const NoDataViewsComponent = withSuspense(NoDataViewsComponentLazy); diff --git a/packages/kbn-shared-ux-components/src/page_template/index.ts b/packages/kbn-shared-ux-components/src/page_template/index.ts index caed703e5d65..671f720972fc 100644 --- a/packages/kbn-shared-ux-components/src/page_template/index.ts +++ b/packages/kbn-shared-ux-components/src/page_template/index.ts @@ -5,8 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - -export { NoDataCard, ElasticAgentCard } from './no_data_page'; -export { NoDataPage } from './no_data_page'; +export { NoDataCard, ElasticAgentCard, NoDataPage, NoDataConfigPage } from './no_data_page'; export { KibanaPageTemplate } from './page_template'; export type { KibanaPageTemplateProps } from './types'; +export type { NoDataPageProps } from './no_data_page'; diff --git a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap index cac139bbb903..79c0ea245b6c 100644 --- a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap +++ b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/__snapshots__/elastic_agent_card.test.tsx.snap @@ -8,6 +8,13 @@ exports[`ElasticAgentCard renders 1`] = ` "navigateToUrl": [Function], } } + data={ + Object { + "hasDataView": [Function], + "hasESData": [Function], + "hasUserDataView": [Function], + } + } docLinks={ Object { "dataViewsDocLink": "dummy link", @@ -20,7 +27,19 @@ exports[`ElasticAgentCard renders 1`] = ` } http={ Object { - "addBasePath": [MockFunction], + "addBasePath": [MockFunction] { + "calls": Array [ + Array [ + "/app/integrations/browse", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": "/app/integrations/browse", + }, + ], + }, } } intl={ diff --git a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/elastic_agent_card.stories.tsx b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/elastic_agent_card.stories.tsx index ea890c265b5c..77c41cddde6d 100644 --- a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/elastic_agent_card.stories.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/elastic_agent_card.stories.tsx @@ -15,7 +15,7 @@ import { } from './elastic_agent_card.component'; export default { - title: 'Page Template/No Data Page/Elastic Agent Data Card', + title: 'Page Template/No Data/Elastic Agent Data Card', description: 'A solution-specific wrapper around NoDataCard, to be used on NoData page', }; diff --git a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/elastic_agent_card.tsx b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/elastic_agent_card.tsx index d917514a96cc..42d42dd80565 100644 --- a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/elastic_agent_card.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/elastic_agent_card.tsx @@ -27,7 +27,7 @@ export const ElasticAgentCard = (props: ElasticAgentCardProps) => { if (category) { return addBasePath(`${prefix}/${category}`); } - return prefix; + return addBasePath(prefix); }; return ( diff --git a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/no_data_card.stories.tsx b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/no_data_card.stories.tsx index 4cea040af5f3..9c1b2d032207 100644 --- a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/no_data_card.stories.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_card/no_data_card.stories.tsx @@ -11,7 +11,7 @@ import { NoDataCard } from './no_data_card'; import type { NoDataCardProps } from './types'; export default { - title: 'Page Template/No Data Page/No Data Card', + title: 'Page Template/No Data/No Data Card', description: 'A wrapper around EuiCard, to be used on NoData page', }; diff --git a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_config_page/index.tsx b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_config_page/index.ts similarity index 99% rename from packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_config_page/index.tsx rename to packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_config_page/index.ts index 0bdde4002139..f8c272c8f987 100644 --- a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_config_page/index.tsx +++ b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_config_page/index.ts @@ -5,5 +5,4 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - export { NoDataConfigPage, NoDataConfigPageWithSolutionNavBar } from './no_data_config_page'; diff --git a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_page.stories.tsx b/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_page.stories.tsx deleted file mode 100644 index cbb8ef6b0446..000000000000 --- a/packages/kbn-shared-ux-components/src/page_template/no_data_page/no_data_page.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { servicesFactory } from '@kbn/shared-ux-storybook'; -import { NoDataPageProps } from './types'; -import { NoDataPage } from './no_data_page'; - -const services = servicesFactory({}); - -export default { - title: 'Page Template/No Data Page/No Data Page', - description: 'No Data Page of PageTemplate', -}; -const action = { - elasticAgent: {}, -}; -type Params = Pick; - -export const PureComponent = (params: Params) => { - return ; -}; - -PureComponent.argTypes = { - solution: { - control: 'text', - defaultValue: 'Observability', - }, - logo: { - control: { type: 'radio' }, - options: ['logoElastic', 'logoKibana', 'logoCloud', undefined], - defaultValue: undefined, - }, -}; diff --git a/packages/kbn-shared-ux-components/src/toolbar/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap b/packages/kbn-shared-ux-components/src/toolbar/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap index 60a49e521ab0..c3b7dc63bce9 100644 --- a/packages/kbn-shared-ux-components/src/toolbar/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap +++ b/packages/kbn-shared-ux-components/src/toolbar/buttons/icon_button_group/__snapshots__/icon_button_group.test.tsx.snap @@ -8,6 +8,13 @@ exports[` is rendered 1`] = ` "navigateToUrl": [Function], } } + data={ + Object { + "hasDataView": [Function], + "hasESData": [Function], + "hasUserDataView": [Function], + } + } docLinks={ Object { "dataViewsDocLink": "dummy link", diff --git a/packages/kbn-shared-ux-components/src/toolbar/buttons/primary/__snapshots__/primary.test.tsx.snap b/packages/kbn-shared-ux-components/src/toolbar/buttons/primary/__snapshots__/primary.test.tsx.snap index 8b7b51f22874..9dc8913562d2 100644 --- a/packages/kbn-shared-ux-components/src/toolbar/buttons/primary/__snapshots__/primary.test.tsx.snap +++ b/packages/kbn-shared-ux-components/src/toolbar/buttons/primary/__snapshots__/primary.test.tsx.snap @@ -8,6 +8,13 @@ exports[` is rendered 1`] = ` "navigateToUrl": [Function], } } + data={ + Object { + "hasDataView": [Function], + "hasESData": [Function], + "hasUserDataView": [Function], + } + } docLinks={ Object { "dataViewsDocLink": "dummy link", diff --git a/packages/kbn-shared-ux-services/jest.config.js b/packages/kbn-shared-ux-services/jest.config.js deleted file mode 100755 index f1ef008d0f62..000000000000 --- a/packages/kbn-shared-ux-services/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../..', - roots: ['/packages/kbn-shared-ux-services'], -}; diff --git a/packages/kbn-shared-ux-services/src/context.tsx b/packages/kbn-shared-ux-services/src/context.tsx index 55d05c57a08f..6131c23e2737 100644 --- a/packages/kbn-shared-ux-services/src/context.tsx +++ b/packages/kbn-shared-ux-services/src/context.tsx @@ -63,3 +63,8 @@ export const useDocLinks = () => useSharedUxServices().docLinks; export const useHttp = () => useSharedUxServices().http; export const useApplication = () => useSharedUxServices().application; + +/** + * React hook for accessing the pre-wired `SharedUxDataService`. + */ +export const useData = () => useSharedUxServices().data; diff --git a/packages/kbn-shared-ux-services/src/index.ts b/packages/kbn-shared-ux-services/src/index.ts index d9ea2c670c9c..744718a63b42 100755 --- a/packages/kbn-shared-ux-services/src/index.ts +++ b/packages/kbn-shared-ux-services/src/index.ts @@ -14,6 +14,7 @@ export type { SharedUxHttpService, SharedUxPlatformService, SharedUxUserPermissionsService, + SharedUxDataService, } from './services'; export { @@ -24,6 +25,7 @@ export { useHttp, usePermissions, usePlatformService, + useData, useSharedUxServices, } from './context'; diff --git a/packages/kbn-shared-ux-services/src/services/data.ts b/packages/kbn-shared-ux-services/src/services/data.ts new file mode 100644 index 000000000000..2750c9bd3208 --- /dev/null +++ b/packages/kbn-shared-ux-services/src/services/data.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * A service providing data information. Typically used for handling of empty state.. + */ +export interface SharedUxDataService { + /** True if the cluster contains data, false otherwise. */ + hasESData: () => Promise; + /** True if Kibana instance contains user-created data view, false otherwise. */ + hasUserDataView: () => Promise; + /** True if Kibana instance contains any data view, including system-created ones. */ + hasDataView: () => Promise; +} diff --git a/packages/kbn-shared-ux-services/src/services/index.ts b/packages/kbn-shared-ux-services/src/services/index.ts index 485264353feb..cf0e211ef492 100644 --- a/packages/kbn-shared-ux-services/src/services/index.ts +++ b/packages/kbn-shared-ux-services/src/services/index.ts @@ -12,6 +12,7 @@ export type { SharedUxEditorsService } from './editors'; export type { SharedUxHttpService } from './http'; export type { SharedUxUserPermissionsService } from './permissions'; export type { SharedUxPlatformService } from './platform'; +export type { SharedUxDataService } from './data'; export { mockServicesFactory, mockServiceFactories } from './mock'; export { stubServicesFactory, stubServiceFactories } from './stub'; diff --git a/packages/kbn-shared-ux-services/src/services/mock/data.mock.ts b/packages/kbn-shared-ux-services/src/services/mock/data.mock.ts new file mode 100644 index 000000000000..bb9d59643348 --- /dev/null +++ b/packages/kbn-shared-ux-services/src/services/mock/data.mock.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ServiceFactory } from '../../types'; +import { SharedUxDataService } from '../data'; + +/** + * A factory function for creating a Jest-based implementation of `SharedUxDataService`. + */ +export type MockDataServiceFactory = ServiceFactory; + +export interface MockDataServiceFactoryConfig { + hasESData: boolean; + hasDataView: boolean; + hasUserDataView: boolean; +} + +/** + * A factory function for creating a Jest-based implementation of `SharedUxDataService`. + */ +export const dataServiceFactory: (config?: MockDataServiceFactoryConfig) => SharedUxDataService = ( + config?: MockDataServiceFactoryConfig +) => ({ + hasESData: () => Promise.resolve(config?.hasESData || false), + hasDataView: () => Promise.resolve(config?.hasDataView || false), + hasUserDataView: () => Promise.resolve(config?.hasUserDataView || false), +}); diff --git a/packages/kbn-shared-ux-services/src/services/mock/index.ts b/packages/kbn-shared-ux-services/src/services/mock/index.ts index 63256345d1bb..604a8ae677b9 100644 --- a/packages/kbn-shared-ux-services/src/services/mock/index.ts +++ b/packages/kbn-shared-ux-services/src/services/mock/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SharedUxServices, ServiceFactory } from '../../types'; +import type { SharedUxServices } from '../../types'; import { applicationServiceFactory } from './application.mock'; import { docLinksServiceFactory } from './doc_links.mock'; @@ -14,6 +14,7 @@ import { editorsServiceFactory } from './editors.mock'; import { httpServiceFactory } from './http.mock'; import { userPermissionsServiceFactory } from './permissions.mock'; import { platformServiceFactory } from './platform.mock'; +import { dataServiceFactory, MockDataServiceFactoryConfig } from './data.mock'; export type { MockApplicationServiceFactory } from './application.mock'; export type { MockDocLinksServiceFactory } from './doc_links.mock'; @@ -28,17 +29,25 @@ export { editorsServiceFactory } from './editors.mock'; export { httpServiceFactory } from './http.mock'; export { userPermissionsServiceFactory } from './permissions.mock'; export { platformServiceFactory } from './platform.mock'; +export { dataServiceFactory } from './data.mock'; + +export interface MockServicesFactoryParams { + config: MockDataServiceFactoryConfig; +} /** * A factory function for creating a Jest-based implementation of `SharedUxServices`. */ -export const mockServicesFactory: ServiceFactory = () => ({ +export const mockServicesFactory: (params?: MockServicesFactoryParams) => SharedUxServices = ( + params?: MockServicesFactoryParams +) => ({ application: applicationServiceFactory(), docLinks: docLinksServiceFactory(), editors: editorsServiceFactory(), http: httpServiceFactory(), permissions: userPermissionsServiceFactory(), platform: platformServiceFactory(), + data: dataServiceFactory(params?.config), }); /** @@ -51,4 +60,5 @@ export const mockServiceFactories = { httpServiceFactory, platformServiceFactory, userPermissionsServiceFactory, + dataServiceFactory, }; diff --git a/packages/kbn-shared-ux-services/src/services/stub/data.ts b/packages/kbn-shared-ux-services/src/services/stub/data.ts new file mode 100644 index 000000000000..833c64e1f9d8 --- /dev/null +++ b/packages/kbn-shared-ux-services/src/services/stub/data.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ServiceFactory } from '../../types'; +import { SharedUxDataService } from '../data'; + +/** + * A factory function for creating a simple stubbed implementation of `SharedUxDataSevice`. + */ +export type DataServiceFactory = ServiceFactory; + +/** + * A factory function for creating a simple stubbed implementation of `SharedUxDataSevice`. + */ +export const dataServiceFactory: DataServiceFactory = () => ({ + hasESData: () => Promise.resolve(true), + hasDataView: () => Promise.resolve(false), + hasUserDataView: () => Promise.resolve(false), +}); diff --git a/packages/kbn-shared-ux-services/src/services/stub/index.ts b/packages/kbn-shared-ux-services/src/services/stub/index.ts index 10323b7db876..ab8b0ca3f4d9 100644 --- a/packages/kbn-shared-ux-services/src/services/stub/index.ts +++ b/packages/kbn-shared-ux-services/src/services/stub/index.ts @@ -14,6 +14,7 @@ import { editorsServiceFactory } from './editors'; import { httpServiceFactory } from './http'; import { platformServiceFactory } from './platform'; import { userPermissionsServiceFactory } from './permissions'; +import { dataServiceFactory } from './data'; /** * A factory function for creating simple stubbed implementations of all `SharedUxServices`. @@ -25,6 +26,7 @@ export const stubServicesFactory: ServiceFactory = () => ({ http: httpServiceFactory(), permissions: userPermissionsServiceFactory(), platform: platformServiceFactory(), + data: dataServiceFactory(), }); /** @@ -37,4 +39,5 @@ export const stubServiceFactories = { httpServiceFactory, platformServiceFactory, userPermissionsServiceFactory, + dataServiceFactory, }; diff --git a/packages/kbn-shared-ux-services/src/types.ts b/packages/kbn-shared-ux-services/src/types.ts index 2bfbafb880e8..a0a4ec32c0e7 100755 --- a/packages/kbn-shared-ux-services/src/types.ts +++ b/packages/kbn-shared-ux-services/src/types.ts @@ -10,6 +10,7 @@ import { FC } from 'react'; import { SharedUxApplicationService, + SharedUxDataService, SharedUxDocLinksService, SharedUxEditorsService, SharedUxHttpService, @@ -32,6 +33,7 @@ export interface SharedUxServices { http: SharedUxHttpService; permissions: SharedUxUserPermissionsService; platform: SharedUxPlatformService; + data: SharedUxDataService; } /** diff --git a/packages/kbn-shared-ux-storybook/jest.config.js b/packages/kbn-shared-ux-storybook/jest.config.js deleted file mode 100644 index 91285e025f06..000000000000 --- a/packages/kbn-shared-ux-storybook/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../..', - roots: ['/packages/kbn-shared-ux-storybook'], -}; diff --git a/packages/kbn-shared-ux-storybook/src/index.ts b/packages/kbn-shared-ux-storybook/src/index.ts index 51f1c6116502..6b310673eb00 100755 --- a/packages/kbn-shared-ux-storybook/src/index.ts +++ b/packages/kbn-shared-ux-storybook/src/index.ts @@ -16,4 +16,7 @@ export { platformServiceFactory, servicesFactory, userPermissionsServiceFactory, + dataServiceFactory, } from './services'; + +export type { DataServiceFactoryConfig } from './services'; diff --git a/packages/kbn-shared-ux-storybook/src/services/data.ts b/packages/kbn-shared-ux-storybook/src/services/data.ts new file mode 100644 index 000000000000..dbfd2fceb421 --- /dev/null +++ b/packages/kbn-shared-ux-storybook/src/services/data.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ServiceFactory, SharedUxDataService } from '@kbn/shared-ux-services'; + +export interface DataServiceFactoryConfig { + hasESData: boolean; + hasDataView: boolean; + hasUserDataView: boolean; +} + +/** + * A factory function for creating a Storybook implementation of `SharedUxDataService`. + */ +export type SharedUxDataServiceFactory = ServiceFactory< + SharedUxDataService, + DataServiceFactoryConfig +>; + +/** + * A factory function for creating a Storybook implementation of `SharedUxDataService`. + */ +export const dataServiceFactory: SharedUxDataServiceFactory = (params) => { + return { + hasESData: () => Promise.resolve(params.hasESData || false), + hasDataView: () => Promise.resolve(params.hasDataView || false), + hasUserDataView: () => Promise.resolve(params.hasUserDataView || false), + }; +}; diff --git a/packages/kbn-shared-ux-storybook/src/services/doc_links.ts b/packages/kbn-shared-ux-storybook/src/services/doc_links.ts index 5ae1dcccc366..eff942989956 100644 --- a/packages/kbn-shared-ux-storybook/src/services/doc_links.ts +++ b/packages/kbn-shared-ux-storybook/src/services/doc_links.ts @@ -18,4 +18,5 @@ export type SharedUxDocLinksServiceFactory = ServiceFactory ({ dataViewsDocLink: 'https://www.elastic.co/guide/en/kibana/master/data-views.html', + kibanaGuideDocLink: 'https://www.elastic.co/guide/en/kibana/master/index.html', }); diff --git a/packages/kbn-shared-ux-storybook/src/services/index.ts b/packages/kbn-shared-ux-storybook/src/services/index.ts index 2de34f339231..ff6ad1f1f291 100644 --- a/packages/kbn-shared-ux-storybook/src/services/index.ts +++ b/packages/kbn-shared-ux-storybook/src/services/index.ts @@ -14,6 +14,7 @@ import { editorsServiceFactory } from './editors'; import { httpServiceFactory } from './http'; import { platformServiceFactory } from './platform'; import { userPermissionsServiceFactory } from './permissions'; +import { dataServiceFactory, DataServiceFactoryConfig } from './data'; export { applicationServiceFactory } from './application'; export { docLinksServiceFactory } from './doc_links'; @@ -21,6 +22,7 @@ export { editorsServiceFactory } from './editors'; export { httpServiceFactory } from './http'; export { platformServiceFactory } from './platform'; export { userPermissionsServiceFactory } from './permissions'; +export { dataServiceFactory } from './data'; /** * A factory function for creating a Storybook implementation of `SharedUxServices`. @@ -32,4 +34,7 @@ export const servicesFactory: ServiceFactory = (params) => http: httpServiceFactory(params), permissions: userPermissionsServiceFactory(), platform: platformServiceFactory(params), + data: dataServiceFactory(params as DataServiceFactoryConfig), }); + +export type { DataServiceFactoryConfig } from './data'; diff --git a/packages/kbn-sort-package-json/jest.config.js b/packages/kbn-sort-package-json/jest.config.js deleted file mode 100644 index ae0651be19e6..000000000000 --- a/packages/kbn-sort-package-json/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-sort-package-json'], -}; diff --git a/packages/kbn-stdio-dev-helpers/jest.config.js b/packages/kbn-stdio-dev-helpers/jest.config.js deleted file mode 100644 index 31a8aab16c7e..000000000000 --- a/packages/kbn-stdio-dev-helpers/jest.config.js +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-stdio-dev-helpers'], -}; diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index 6245532b00e4..fb535c2766cc 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -63,12 +63,16 @@ module.exports = { rootDirectory: '.', }, ], - [ - '@kbn/test/target_node/jest/ci_stats_jest_reporter', - { - testGroupType: 'Jest Unit Tests', - }, - ], + ...(process.env.TEST_GROUP_TYPE_UNIT + ? [ + [ + '@kbn/test/target_node/jest/ci_stats_jest_reporter', + { + testGroupType: process.env.TEST_GROUP_TYPE_UNIT, + }, + ], + ] + : []), ], // The paths to modules that run some code to configure or set up the testing environment before each test diff --git a/packages/kbn-test/jest_integration/jest-preset.js b/packages/kbn-test/jest_integration/jest-preset.js index f5593e3f57fb..1f2626aef532 100644 --- a/packages/kbn-test/jest_integration/jest-preset.js +++ b/packages/kbn-test/jest_integration/jest-preset.js @@ -28,12 +28,16 @@ module.exports = { reportName: 'Jest Integration Tests', }, ], - [ - '@kbn/test/target_node/jest/ci_stats_jest_reporter', - { - testGroupType: 'Jest Integration Tests', - }, - ], + ...(process.env.TEST_GROUP_TYPE_INTEGRATION + ? [ + [ + '@kbn/test/target_node/jest/ci_stats_jest_reporter', + { + testGroupType: process.env.TEST_GROUP_TYPE_INTEGRATION, + }, + ], + ] + : []), ], coverageReporters: !!process.env.CI ? [['json', { file: 'jest-integration.json' }]] diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/ci_stats_ftr_reporter.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/ci_stats_ftr_reporter.ts index 838b8ee9b9aa..ee993122d7d9 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/ci_stats_ftr_reporter.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/ci_stats_ftr_reporter.ts @@ -80,6 +80,7 @@ export function setupCiStatsFtrTestGroupReporter({ durationMs: 0, type: config.path.startsWith('x-pack') ? 'X-Pack Functional Tests' : 'Functional Tests', name: Path.relative(REPO_ROOT, config.path), + result: 'skip', meta: { ciGroup: config.get('suiteTags.include').find((t: string) => t.startsWith('ciGroup')), tags: [ @@ -117,6 +118,11 @@ export function setupCiStatsFtrTestGroupReporter({ errors.set(test, error); }); + let passCount = 0; + let failCount = 0; + runner.on('pass', () => (passCount += 1)); + runner.on('fail', () => (failCount += 1)); + runner.on('hook end', (hook: Runnable) => { if (hook.isFailed()) { const error = errors.get(hook); @@ -150,6 +156,7 @@ export function setupCiStatsFtrTestGroupReporter({ // update the durationMs group.durationMs = Date.now() - startMs; + group.result = failCount ? 'fail' : passCount ? 'pass' : 'skip'; }); lifecycle.cleanup.add(async () => { diff --git a/packages/kbn-test/src/jest/ci_stats_jest_reporter.ts b/packages/kbn-test/src/jest/ci_stats_jest_reporter.ts index 6294594ff6cf..3ac4a64c1f3f 100644 --- a/packages/kbn-test/src/jest/ci_stats_jest_reporter.ts +++ b/packages/kbn-test/src/jest/ci_stats_jest_reporter.ts @@ -39,6 +39,8 @@ export default class CiStatsJestReporter extends BaseReporter { private readonly reportName: string; private readonly rootDir: string; private startTime: number | undefined; + private passCount = 0; + private failCount = 0; private group: CiStatsReportTestsOptions['group'] | undefined; private readonly testRuns: CiStatsReportTestsOptions['testRuns'] = []; @@ -79,6 +81,7 @@ export default class CiStatsJestReporter extends BaseReporter { startTime: new Date(this.startTime).toJSON(), meta: {}, durationMs: 0, + result: 'skip', }; } @@ -89,6 +92,14 @@ export default class CiStatsJestReporter extends BaseReporter { let elapsedTime = 0; for (const t of testResult.testResults) { + const result = t.status === 'failed' ? 'fail' : t.status === 'passed' ? 'pass' : 'skip'; + + if (result === 'fail') { + this.failCount += 1; + } else if (result === 'pass') { + this.passCount += 1; + } + const startTime = new Date(testResult.perfStats.start + elapsedTime).toJSON(); elapsedTime += t.duration ?? 0; this.testRuns.push({ @@ -97,7 +108,7 @@ export default class CiStatsJestReporter extends BaseReporter { seq: this.testRuns.length + 1, file: Path.relative(this.rootDir, testResult.testFilePath), name: t.title, - result: t.status === 'failed' ? 'fail' : t.status === 'passed' ? 'pass' : 'skip', + result, suites: t.ancestorTitles, type: 'test', error: t.failureMessages.join('\n\n'), @@ -107,11 +118,12 @@ export default class CiStatsJestReporter extends BaseReporter { } async onRunComplete() { - if (!this.reporter || !this.group || !this.testRuns.length || !this.startTime) { + if (!this.reporter || !this.group || !this.startTime) { return; } this.group.durationMs = Date.now() - this.startTime; + this.group.result = this.failCount ? 'fail' : this.passCount ? 'pass' : 'skip'; await this.reporter.reportTests({ group: this.group, diff --git a/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts b/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts index 42766ad3f5a3..7a50e88b3396 100644 --- a/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts +++ b/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts @@ -17,14 +17,14 @@ import { getAllRepoRelativeBazelPackageDirs } from '@kbn/bazel-packages'; import { JestConfigs, CONFIG_NAMES } from './configs'; const unitTestingTemplate: string = `module.exports = { - preset: '@kbn/test', + preset: '@kbn/test/jest_node', rootDir: '{{{relToRoot}}}', roots: ['/{{{modulePath}}}'], }; `; const integrationTestingTemplate: string = `module.exports = { - preset: '@kbn/test/jest_integration', + preset: '@kbn/test/jest_integration_node', rootDir: '{{{relToRoot}}}', roots: ['/{{{modulePath}}}'], }; diff --git a/packages/kbn-test/src/jest/setup/polyfills.jsdom.js b/packages/kbn-test/src/jest/setup/polyfills.jsdom.js index ebe6178dbdd9..5857ca9b99b8 100644 --- a/packages/kbn-test/src/jest/setup/polyfills.jsdom.js +++ b/packages/kbn-test/src/jest/setup/polyfills.jsdom.js @@ -14,7 +14,3 @@ require('whatwg-fetch'); if (!global.URL.hasOwnProperty('createObjectURL')) { Object.defineProperty(global.URL, 'createObjectURL', { value: () => '' }); } - -// Will be replaced with a better solution in EUI -// https://github.com/elastic/eui/issues/3713 -global._isJest = true; diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 0a709181b04d..ddcb71fdb2c8 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -37,6 +37,10 @@ module.exports = (_, argv) => { 'regenerator-runtime/runtime', 'whatwg-fetch', 'symbol-observable', + // Parts of node-libs-browser that are used in many places across Kibana + 'buffer', + 'punycode', + 'util', /** * babel runtime helpers referenced from entry chunks diff --git a/packages/kbn-ci-stats-client/jest.config.js b/scripts/find_node_libs_browser_polyfills_in_use.js similarity index 73% rename from packages/kbn-ci-stats-client/jest.config.js rename to scripts/find_node_libs_browser_polyfills_in_use.js index d855d7886d0d..4e53e5e55107 100644 --- a/packages/kbn-ci-stats-client/jest.config.js +++ b/scripts/find_node_libs_browser_polyfills_in_use.js @@ -6,8 +6,5 @@ * Side Public License, v 1. */ -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-ci-stats-client'], -}; +require('../src/setup_node_env/no_transpilation'); +require('@kbn/optimizer').runFindNodeLibsBrowserPolyfillsInEntryBundlesCli(); diff --git a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap index 69439d192774..a3e3ca7a7c20 100644 --- a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap +++ b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap @@ -141,6 +141,8 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiImage.openImage": [Function], "euiLink.external.ariaLabel": "External link", "euiLink.newTarget.screenReaderOnlyText": "(opens in a new tab or window)", + "euiMark.highlightEnd": "highlight end", + "euiMark.highlightStart": "highlight start", "euiMarkdownEditorFooter.closeButton": "Close", "euiMarkdownEditorFooter.errorsTitle": "Errors", "euiMarkdownEditorFooter.mdSyntaxLink": "GitHub flavored markdown", diff --git a/src/core/public/i18n/i18n_eui_mapping.tsx b/src/core/public/i18n/i18n_eui_mapping.tsx index 9969f4ee23f5..5344fddc4fe2 100644 --- a/src/core/public/i18n/i18n_eui_mapping.tsx +++ b/src/core/public/i18n/i18n_eui_mapping.tsx @@ -618,6 +618,12 @@ export const getEuiContextMapping = (): EuiTokensObject => { defaultMessage: '(opens in a new tab or window)', } ), + 'euiMark.highlightStart': i18n.translate('core.euiMark.highlightStart', { + defaultMessage: 'highlight start', + }), + 'euiMark.highlightEnd': i18n.translate('core.euiMark.highlightEnd', { + defaultMessage: 'highlight end', + }), 'euiMarkdownEditorFooter.closeButton': i18n.translate( 'core.euiMarkdownEditorFooter.closeButton', { diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 1cfcb7b5cf28..bd5fd75e3099 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -77,6 +77,6 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.3.0': ['Elastic License 2.0'], - '@elastic/eui@54.0.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@55.0.1': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry }; diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx b/src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx index a7d0eb8cad8c..50853ea6c656 100644 --- a/src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx +++ b/src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx @@ -81,7 +81,7 @@ class MetricVisComponent extends Component { title = `${bucketValue} - ${title}`; } - const shouldBrush = stops.length > 1 && shouldApplyColor(color ?? ''); + const shouldBrush = shouldApplyColor(color ?? ''); return { label: title, value: formattedValue, diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/send_request.test.ts b/src/plugins/console/public/application/hooks/use_send_current_request/send_request.test.ts index 60ced085c689..841633cded51 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request/send_request.test.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request/send_request.test.ts @@ -49,34 +49,43 @@ describe('sendRequest', () => { expect(mockedSendRequest).toHaveBeenCalledTimes(1); }); - it('should send multiple requests', async () => { - mockedSendRequest.mockResolvedValue([ - { - response: { - statusCode: 200, + describe('with multiple requests', () => { + it('should return results with exceptions', async () => { + mockedSendRequest.mockResolvedValue([ + { + response: { + statusCode: 200, + }, }, - }, - { - response: { - statusCode: 200, + { + response: { + statusCode: 200, + }, }, - }, - ]); - - const args = { - http: mockContextValue.services.http, - requests: [ - { method: 'GET', url: 'test-1', data: [] }, - { method: 'GET', url: 'test-2', data: [] }, - ], - }; - const results = await sendRequest(args); + { + response: { + statusCode: 400, + }, + }, + ]); - const [firstRequest, secondRequest] = results; - expect(firstRequest.response.statusCode).toEqual(200); - expect(secondRequest.response.statusCode).toEqual(200); - expect(mockedSendRequest).toHaveBeenCalledWith(args); - expect(mockedSendRequest).toHaveBeenCalledTimes(1); + const args = { + http: mockContextValue.services.http, + requests: [ + { method: 'GET', url: 'success', data: [] }, + { method: 'GET', url: 'success', data: [] }, + { method: 'GET', url: 'fail', data: [] }, + ], + }; + const results = await sendRequest(args); + + const [firstCall, secondCall, thirdCall] = results; + expect(firstCall.response.statusCode).toEqual(200); + expect(secondCall.response.statusCode).toEqual(200); + expect(thirdCall.response.statusCode).toEqual(400); + expect(mockedSendRequest).toHaveBeenCalledWith(args); + expect(mockedSendRequest).toHaveBeenCalledTimes(1); + }); }); it('should handle errors', async () => { diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/send_request.ts b/src/plugins/console/public/application/hooks/use_send_current_request/send_request.ts index 1247f3f78aa6..1ac47df30fca 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request/send_request.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request/send_request.ts @@ -33,6 +33,9 @@ export interface RequestResult { response: ResponseObject; } +const getContentType = (response: Response | undefined) => + (response?.headers.get('Content-Type') as BaseResponseType) ?? ''; + let CURRENT_REQ_ID = 0; export function sendRequest(args: RequestArgs): Promise { const requests = args.requests.slice(); @@ -111,7 +114,7 @@ export function sendRequest(args: RequestArgs): Promise { timeMs: Date.now() - startTime, statusCode: response.status, statusText: response.statusText, - contentType: response.headers.get('Content-Type') as BaseResponseType, + contentType: getContentType(response), value, }, request: { @@ -128,9 +131,8 @@ export function sendRequest(args: RequestArgs): Promise { } catch (error) { let value; const { response, body } = error as IHttpFetchError; - const contentType = response?.headers.get('Content-Type') ?? ''; const statusCode = response?.status ?? 500; - const statusText = error?.response?.statusText ?? 'error'; + const statusText = response?.statusText ?? 'error'; if (body) { value = JSON.stringify(body, null, 2); @@ -142,10 +144,10 @@ export function sendRequest(args: RequestArgs): Promise { value = '# ' + req.method + ' ' + req.url + '\n' + value; } - reject({ + const result = { response: { value, - contentType, + contentType: getContentType(response), timeMs: Date.now() - startTime, statusCode, statusText, @@ -155,7 +157,16 @@ export function sendRequest(args: RequestArgs): Promise { method, path, }, - }); + }; + + // Reject on unknown errors + if (!response) { + reject(result); + } + + // Add error to the list of results + results.push(result); + await sendNextRequest(); } }; diff --git a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap index 74d803a6ff17..b495cf52657d 100644 --- a/src/plugins/data_view_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap +++ b/src/plugins/data_view_editor/public/components/preview_panel/indices_list/__snapshots__/indices_list.test.tsx.snap @@ -65,7 +65,6 @@ exports[`IndicesList should change pages 1`] = ` panelPaddingSize="none" >

({ + interval: 'auto', + }), + }, + } as unknown as GetStateReturn, viewMode: VIEW_MODE.DOCUMENT_LEVEL, setDiscoverViewMode: jest.fn(), }; diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx index 8251abba41fa..539220e17eb5 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.tsx @@ -215,6 +215,7 @@ export function DiscoverChart({ diff --git a/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx b/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx index a62bd9c2317d..80cd8562d3fa 100644 --- a/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx +++ b/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx @@ -15,6 +15,7 @@ import { DiscoverHistogram } from './histogram'; import React from 'react'; import * as hooks from '../../utils/use_data_state'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { GetStateReturn } from '../../services/discover_state'; const chartData = { xAxisOrderedValues: [ @@ -76,6 +77,13 @@ function mountComponent(fetchStatus: FetchStatus) { const props = { savedSearchData$: charts$, timefilterUpdateHandler, + stateContainer: { + appStateContainer: { + getState: () => ({ + interval: 'auto', + }), + }, + } as unknown as GetStateReturn, }; return mountWithIntl( diff --git a/src/plugins/discover/public/application/main/components/chart/histogram.tsx b/src/plugins/discover/public/application/main/components/chart/histogram.tsx index c9533f70fc8e..4502060fb245 100644 --- a/src/plugins/discover/public/application/main/components/chart/histogram.tsx +++ b/src/plugins/discover/public/application/main/components/chart/histogram.tsx @@ -45,10 +45,12 @@ import { useDiscoverServices } from '../../../../utils/use_discover_services'; import { DataCharts$, DataChartsMessage } from '../../utils/use_saved_search'; import { FetchStatus } from '../../../types'; import { useDataState } from '../../utils/use_data_state'; +import { GetStateReturn } from '../../services/discover_state'; export interface DiscoverHistogramProps { savedSearchData$: DataCharts$; timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; + stateContainer: GetStateReturn; } function getTimezone(uiSettings: IUiSettingsClient) { @@ -64,6 +66,7 @@ function getTimezone(uiSettings: IUiSettingsClient) { export function DiscoverHistogram({ savedSearchData$, timefilterUpdateHandler, + stateContainer, }: DiscoverHistogramProps) { const { data, theme, uiSettings, fieldFormats } = useDiscoverServices(); const chartTheme = theme.useChartsTheme(); @@ -123,8 +126,20 @@ export function DiscoverHistogram({ from: dateMath.parse(from), to: dateMath.parse(to, { roundUp: true }), }; - return `${toMoment(timeRange.from)} - ${toMoment(timeRange.to)}`; - }, [from, to, toMoment]); + const intervalText = i18n.translate('discover.histogramTimeRangeIntervalDescription', { + defaultMessage: '(interval: {value})', + values: { + value: `${ + stateContainer.appStateContainer.getState().interval === 'auto' + ? `${i18n.translate('discover.histogramTimeRangeIntervalAuto', { + defaultMessage: 'Auto', + })} - ` + : '' + }${bucketInterval?.description}`, + }, + }); + return `${toMoment(timeRange.from)} - ${toMoment(timeRange.to)} ${intervalText}`; + }, [from, to, toMoment, bucketInterval, stateContainer]); if (!chartData && fetchStatus === FetchStatus.LOADING) { return ( diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx index 43d58f63f5fa..26978cc9e6f5 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx @@ -145,7 +145,14 @@ function mountComponent(indexPattern: DataView, prevSidebarClosed?: boolean) { savedSearchRefetch$: new Subject(), searchSource: searchSourceMock, state: { columns: [] }, - stateContainer: { setAppState: () => {} } as unknown as GetStateReturn, + stateContainer: { + setAppState: () => {}, + appStateContainer: { + getState: () => ({ + interval: 'auto', + }), + }, + } as unknown as GetStateReturn, setExpandedDoc: jest.fn(), }; diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/__snapshots__/cron_editor.test.tsx.snap b/src/plugins/es_ui_shared/public/components/cron_editor/__snapshots__/cron_editor.test.tsx.snap index 17cf07121e7c..692c006fe099 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/__snapshots__/cron_editor.test.tsx.snap +++ b/src/plugins/es_ui_shared/public/components/cron_editor/__snapshots__/cron_editor.test.tsx.snap @@ -23,7 +23,7 @@ exports[`CronEditor is rendered with a DAY frequency 1`] = ` labelType="label" >
@@ -1663,7 +1663,7 @@ exports[`CronEditor is rendered with a HOUR frequency 1`] = ` labelType="label" >
@@ -2860,7 +2860,7 @@ exports[`CronEditor is rendered with a MINUTE frequency 1`] = ` labelType="label" >
@@ -4195,7 +4195,7 @@ exports[`CronEditor is rendered with a MONTH frequency 1`] = ` labelType="label" >
@@ -5287,7 +5287,7 @@ exports[`CronEditor is rendered with a WEEK frequency 1`] = ` labelType="label" >
@@ -6078,7 +6078,7 @@ exports[`CronEditor is rendered with a WEEK frequency 1`] = ` labelType="label" >
@@ -7170,7 +7170,7 @@ exports[`CronEditor is rendered with a YEAR frequency 1`] = ` labelType="label" >
@@ -8160,7 +8160,7 @@ exports[`CronEditor is rendered with a YEAR frequency 1`] = ` labelType="label" >
@@ -8610,7 +8610,7 @@ exports[`CronEditor is rendered with a YEAR frequency 1`] = ` labelType="label" >
diff --git a/src/plugins/event_annotation/jest.config.js b/src/plugins/event_annotation/jest.config.js deleted file mode 100644 index a6ea4a6b430d..000000000000 --- a/src/plugins/event_annotation/jest.config.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/src/plugins/event_annotation'], - coverageDirectory: '/target/kibana-coverage/jest/src/plugins/event_annotation', - coverageReporters: ['text', 'html'], - collectCoverageFrom: [ - '/src/plugins/event_annotation/{common,public,server}/**/*.{ts,tsx}', - ], -}; diff --git a/src/plugins/shared_ux/jest.config.js b/src/plugins/shared_ux/jest.config.js deleted file mode 100644 index bc8d67e5ac35..000000000000 --- a/src/plugins/shared_ux/jest.config.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/src/plugins/shared_ux'], - transform: { - '^.+\\.stories\\.tsx?$': '@storybook/addon-storyshots/injectFileName', - }, - coverageDirectory: '/target/kibana-coverage/jest/src/plugins/shared_ux', - coverageReporters: ['text', 'html'], - collectCoverageFrom: ['/src/plugins/shared_ux/{common,public,server}/**/*.{js,ts,tsx}'], -}; diff --git a/src/plugins/shared_ux/kibana.json b/src/plugins/shared_ux/kibana.json index 540514b4ab7e..308a252f70b5 100755 --- a/src/plugins/shared_ux/kibana.json +++ b/src/plugins/shared_ux/kibana.json @@ -9,6 +9,6 @@ "description": "A plugin providing components and services for shared user experiences in Kibana.", "server": true, "ui": true, - "requiredPlugins": ["dataViewEditor"], + "requiredPlugins": ["dataViewEditor", "dataViews"], "optionalPlugins": [] } diff --git a/src/plugins/shared_ux/public/services/data.ts b/src/plugins/shared_ux/public/services/data.ts new file mode 100644 index 000000000000..d68d35848e81 --- /dev/null +++ b/src/plugins/shared_ux/public/services/data.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SharedUxDataService } from '@kbn/shared-ux-services'; +import { KibanaPluginServiceFactory } from './types'; +import { SharedUXPluginStartDeps } from '../types'; + +export type DataServiceFactory = KibanaPluginServiceFactory< + SharedUxDataService, + SharedUXPluginStartDeps +>; + +/** + * A factory function for creating a Kibana-based implementation of `SharedUXDataService`. + */ +export const dataServiceFactory: DataServiceFactory = ({ coreStart, startPlugins }) => ({ + hasDataView: startPlugins.dataViews.hasData.hasDataView, + hasESData: startPlugins.dataViews.hasData.hasESData, + hasUserDataView: startPlugins.dataViews.hasData.hasUserDataView, +}); diff --git a/src/plugins/shared_ux/public/services/index.ts b/src/plugins/shared_ux/public/services/index.ts index 358a2b8035ec..c8a572c26251 100644 --- a/src/plugins/shared_ux/public/services/index.ts +++ b/src/plugins/shared_ux/public/services/index.ts @@ -17,6 +17,7 @@ import { editorsServiceFactory } from './editors'; import { docLinksServiceFactory } from './doc_links'; import { httpServiceFactory } from './http'; import { applicationServiceFactory } from './application'; +import { dataServiceFactory } from './data'; /** * A factory function for creating a Kibana-based implementation of `SharedUXServices`. @@ -31,4 +32,5 @@ export const servicesFactory: KibanaPluginServiceFactory< docLinks: docLinksServiceFactory(params), http: httpServiceFactory(params), application: applicationServiceFactory(params), + data: dataServiceFactory(params), }); diff --git a/src/plugins/shared_ux/public/types/index.ts b/src/plugins/shared_ux/public/types/index.ts index 5a6aace584be..e3e7cb760268 100644 --- a/src/plugins/shared_ux/public/types/index.ts +++ b/src/plugins/shared_ux/public/types/index.ts @@ -10,6 +10,7 @@ import { SharedUxServices } from '@kbn/shared-ux-services'; import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; /** @internal */ export interface SharedUXPluginSetup {} @@ -62,4 +63,5 @@ export interface SharedUXPluginSetupDeps {} /** @internal */ export interface SharedUXPluginStartDeps { dataViewEditor: DataViewEditorStart; + dataViews: DataViewsPublicPluginStart; } diff --git a/src/plugins/shared_ux/tsconfig.json b/src/plugins/shared_ux/tsconfig.json index 9e9844d98598..3a287353a534 100644 --- a/src/plugins/shared_ux/tsconfig.json +++ b/src/plugins/shared_ux/tsconfig.json @@ -18,6 +18,9 @@ }, { "path": "../data_view_editor/tsconfig.json" + }, + { + "path": "../data_views/tsconfig.json" } ] } diff --git a/src/plugins/unified_search/public/typeahead/_suggestion.scss b/src/plugins/unified_search/public/typeahead/_suggestion.scss index 9e0c163f9c94..e466a52e7fc1 100644 --- a/src/plugins/unified_search/public/typeahead/_suggestion.scss +++ b/src/plugins/unified_search/public/typeahead/_suggestion.scss @@ -18,7 +18,7 @@ $kbnTypeaheadTypes: ( } .kbnTypeahead__popover--bottom { - @include euiBottomShadow($adjustBorders: true); + @include euiBottomShadow; border-bottom-left-radius: $euiBorderRadius; border-bottom-right-radius: $euiBorderRadius; } diff --git a/src/plugins/vis_types/metric/public/to_ast.ts b/src/plugins/vis_types/metric/public/to_ast.ts index 22176341f45a..322ea561abeb 100644 --- a/src/plugins/vis_types/metric/public/to_ast.ts +++ b/src/plugins/vis_types/metric/public/to_ast.ts @@ -83,7 +83,7 @@ export const toExpressionAst: VisToExpressionAst = (vis, params) => { ) ); - if (colorsRange && colorsRange.length) { + if (colorsRange && colorsRange.length > 1) { const stopsWithColors = getStopsWithColorsFromRanges(colorsRange, colorSchema, invertColors); const palette = buildExpressionFunction('palette', { ...stopsWithColors, diff --git a/src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts b/src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts index 31e4cdaf176c..e74afb755668 100644 --- a/src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts +++ b/src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts @@ -24,7 +24,7 @@ export function getArgValueSuggestions() { } const indexPatternTitle = get(indexPatternArg, 'value.text'); - return (await indexPatterns.find(indexPatternTitle)).find( + return (await indexPatterns.find(indexPatternTitle, 1)).find( (index) => index.title === indexPatternTitle ); } diff --git a/src/plugins/vis_types/timelion/public/timelion_vis_type.tsx b/src/plugins/vis_types/timelion/public/timelion_vis_type.tsx index cac1f127e11c..f32a485ac256 100644 --- a/src/plugins/vis_types/timelion/public/timelion_vis_type.tsx +++ b/src/plugins/vis_types/timelion/public/timelion_vis_type.tsx @@ -57,7 +57,7 @@ export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) ); if (indexArg?.value.text) { - return getIndexPatterns().find(indexArg.value.text); + return getIndexPatterns().find(indexArg.value.text, 1); } } catch { // timelion expression is invalid diff --git a/src/plugins/vis_types/timelion/server/series_functions/es/index.js b/src/plugins/vis_types/timelion/server/series_functions/es/index.js index 2001a39bf790..5fb0480e074c 100644 --- a/src/plugins/vis_types/timelion/server/series_functions/es/index.js +++ b/src/plugins/vis_types/timelion/server/series_functions/es/index.js @@ -104,7 +104,7 @@ export default new Datasource('es', { fit: 'nearest', }); const indexPatternsService = tlConfig.getIndexPatternsService(); - const indexPatternSpec = (await indexPatternsService.find(config.index)).find( + const indexPatternSpec = (await indexPatternsService.find(config.index, 1)).find( (index) => index.title === config.index ); diff --git a/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap b/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap index c70c4406a34f..f49b0bb75951 100644 --- a/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap +++ b/src/plugins/vis_types/vega/public/__snapshots__/vega_visualization.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VegaVisualizations VegaVisualization - basics should show vega graph (may fail in dev env) 1`] = `"
"`; +exports[`VegaVisualizations VegaVisualization - basics should show vega graph (may fail in dev env) 1`] = `"
"`; -exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 1`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; +exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 1`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; -exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 2`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; +exports[`VegaVisualizations VegaVisualization - basics should show vegalite graph and update on resize (may fail in dev env) 2`] = `"
  • \\"width\\" and \\"height\\" params are ignored because \\"autosize\\" is enabled. Set \\"autosize\\": \\"none\\" to disable
"`; diff --git a/src/plugins/vis_types/vega/public/components/vega_vis.scss b/src/plugins/vis_types/vega/public/components/vega_vis.scss index ce66dc1d7f92..cee2cd16ad0a 100644 --- a/src/plugins/vis_types/vega/public/components/vega_vis.scss +++ b/src/plugins/vis_types/vega/public/components/vega_vis.scss @@ -14,15 +14,21 @@ // flex-direction determined by js } +.vgaVis--autoresize { + @include euiScrollBar; + max-width: 100%; + max-height: 100%; + overflow: auto; + .vgaVis__view { + overflow: hidden; + } +} + .vgaVis__view { z-index: 0; flex: 1 1 100%; display: block; - max-width: 100%; - max-height: 100%; - width: 100%; - height: 100%; canvas { display: block; diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js index e518894c5511..c99387446679 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_types/vega/public/vega_view/vega_base_view.js @@ -98,6 +98,11 @@ export class VegaBaseView { this._initialized = true; try { + if (this._parser.useResize) { + this._$parentEl.addClass('vgaVis--autoresize'); + } else { + this._$parentEl.removeClass('vgaVis--autoresize'); + } this._$parentEl.empty().addClass(`vgaVis`).css('flex-direction', this._parser.containerDir); // bypass the onWarn warning checks - in some cases warnings may still need to be shown despite being disabled @@ -110,10 +115,7 @@ export class VegaBaseView { return; } - this._$container = $('
') - // Force a height here because css is not loaded in mocha test - .css('height', '100%') - .appendTo(this._$parentEl); + this._$container = $('
').appendTo(this._$parentEl); this._$controls = $( `
` ).appendTo(this._$parentEl); @@ -262,9 +264,9 @@ export class VegaBaseView { } } - async resize(dimensions) { + async resize() { if (this._parser.useResize && this._view) { - this.updateVegaSize(this._view, dimensions); + this.updateVegaSize(this._view); await this._view.runAsync(); // The derived class should create this method diff --git a/src/plugins/vis_types/vega/public/vega_view/vega_view.js b/src/plugins/vis_types/vega/public/vega_view/vega_view.js index 11b7ca125a4b..a5241c9f0f0d 100644 --- a/src/plugins/vis_types/vega/public/vega_view/vega_view.js +++ b/src/plugins/vis_types/vega/public/vega_view/vega_view.js @@ -18,6 +18,8 @@ export class VegaView extends VegaBaseView { if (this._parser.useResize) this.updateVegaSize(view); view.initialize(this._$container.get(0), this._$controls.get(0)); + // resize again to take controls into account + if (this._parser.useResize) this.updateVegaSize(view); if (this._parser.useHover) view.hover(); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 5ccb59e17880..d68fb4b25312 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -97,8 +97,13 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('visualize ciGroup11', function () { this.tags('ciGroup11'); - loadTestFile(require.resolve('./_tag_cloud')); loadTestFile(require.resolve('./_tsvb_time_series')); + }); + + describe('visualize ciGroup12', function () { + this.tags('ciGroup12'); + + loadTestFile(require.resolve('./_tag_cloud')); loadTestFile(require.resolve('./_tsvb_markdown')); loadTestFile(require.resolve('./_tsvb_table')); loadTestFile(require.resolve('./_vega_chart')); diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts index 235afa27f0c8..1e3e6a9634f4 100644 --- a/test/functional/page_objects/home_page.ts +++ b/test/functional/page_objects/home_page.ts @@ -93,6 +93,21 @@ export class HomePageObject extends FtrService { await this.find.clickByLinkText('Map'); } + async launchSampleLogs(id: string) { + await this.launchSampleDataSet(id); + await this.find.clickByLinkText('Logs'); + } + + async launchSampleGraph(id: string) { + await this.launchSampleDataSet(id); + await this.find.clickByLinkText('Graph'); + } + + async launchSampleML(id: string) { + await this.launchSampleDataSet(id); + await this.find.clickByLinkText('ML jobs'); + } + async launchSampleDataSet(id: string) { await this.addSampleDataSet(id); await this.common.closeToastIfExists(); diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index f0ea135bd9ba..fbf6b96b3136 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -891,6 +891,7 @@ export class VisualBuilderPageObject extends FtrService { } public async getChartDebugState(chartData?: DebugState) { + await this.header.waitUntilLoadingHasFinished(); return chartData ?? (await this.elasticChart.getChartDebugData())!; } @@ -908,7 +909,6 @@ export class VisualBuilderPageObject extends FtrService { chartData?: DebugState, itemType: 'areas' | 'bars' | 'annotations' = 'areas' ) { - await this.header.waitUntilLoadingHasFinished(); return (await this.getChartDebugState(chartData))?.[itemType]; } diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index c77e600df5f0..eed17e563860 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -104,7 +104,7 @@ The following table describes the properties of the `options` object. ### Executor -This is the primary function for a rule type. Whenever the rule needs to execute, this function will perform the execution. It receives a variety of parameters. The following table describes the properties the executor receives. +This is the primary function for a rule type. Whenever the rule needs to execute, this function will perform the execution. This function is for running a custom query that returns documents meeting a condition and report them to the framework using the `services.alertFactory`. The function receives a variety of parameters. The following table describes the properties the executor receives. **executor(options)** diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerting/kibana.json index fc45f22d9c9a..f4e6a917b19d 100644 --- a/x-pack/plugins/alerting/kibana.json +++ b/x-pack/plugins/alerting/kibana.json @@ -17,8 +17,9 @@ "features", "kibanaUtils", "licensing", + "spaces", "taskManager" ], - "optionalPlugins": ["usageCollection", "spaces", "security", "monitoringCollection"], + "optionalPlugins": ["usageCollection", "security", "monitoringCollection"], "extraPublicDirs": ["common", "common/parse_duration"] } diff --git a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts index 3a8a0a072cc1..0ee4cce387d1 100644 --- a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts +++ b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts @@ -16,8 +16,8 @@ export interface AlertingAuthorizationClientFactoryOpts { ruleTypeRegistry: RuleTypeRegistry; securityPluginSetup?: SecurityPluginSetup; securityPluginStart?: SecurityPluginStart; - getSpace: (request: KibanaRequest) => Promise; - getSpaceId: (request: KibanaRequest) => string | undefined; + getSpace: (request: KibanaRequest) => Promise; + getSpaceId: (request: KibanaRequest) => string; features: FeaturesPluginStart; } @@ -26,8 +26,8 @@ export class AlertingAuthorizationClientFactory { private ruleTypeRegistry!: RuleTypeRegistry; private securityPluginStart?: SecurityPluginStart; private features!: FeaturesPluginStart; - private getSpace!: (request: KibanaRequest) => Promise; - private getSpaceId!: (request: KibanaRequest) => string | undefined; + private getSpace!: (request: KibanaRequest) => Promise; + private getSpaceId!: (request: KibanaRequest) => string; public initialize(options: AlertingAuthorizationClientFactoryOpts) { if (this.isInitialized) { diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index 29739d2ca53d..b342eddaa0c1 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -21,6 +21,7 @@ import { eventLogMock } from '@kbn/event-log-plugin/server/mocks'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; import { monitoringCollectionMock } from '@kbn/monitoring-collection-plugin/server/mocks'; +import { spacesMock } from '@kbn/spaces-plugin/server/mocks'; const generateAlertingConfig = (): AlertingConfig => ({ healthCheck: { @@ -212,6 +213,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createStart(), encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), features: mockFeatures(), + spaces: spacesMock.createStart(), licensing: licensingMock.createStart(), eventLog: eventLogMock.createStart(), taskManager: taskManagerMock.createStart(), @@ -250,6 +252,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createStart(), encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), features: mockFeatures(), + spaces: spacesMock.createStart(), licensing: licensingMock.createStart(), eventLog: eventLogMock.createStart(), taskManager: taskManagerMock.createStart(), @@ -299,6 +302,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createStart(), encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), features: mockFeatures(), + spaces: spacesMock.createStart(), licensing: licensingMock.createStart(), eventLog: eventLogMock.createStart(), taskManager: taskManagerMock.createStart(), diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index cfd2701e1f88..6589b1537f76 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -149,7 +149,7 @@ export interface AlertingPluginsStart { features: FeaturesPluginStart; eventLog: IEventLogClientService; licensing: LicensingPluginStart; - spaces?: SpacesPluginStart; + spaces: SpacesPluginStart; security?: SecurityPluginStart; data: DataPluginStart; } @@ -374,10 +374,10 @@ export class AlertingPlugin { securityPluginSetup: security, securityPluginStart: plugins.security, async getSpace(request: KibanaRequest) { - return plugins.spaces?.spacesService.getActiveSpace(request); + return plugins.spaces.spacesService.getActiveSpace(request); }, getSpaceId(request: KibanaRequest) { - return plugins.spaces?.spacesService.getSpaceId(request); + return plugins.spaces.spacesService.getSpaceId(request); }, features: plugins.features, }); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_attributes.ts b/x-pack/plugins/alerting/server/rules_client/lib/validate_attributes.ts index ad17ede1b99a..208709a4b8b3 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/validate_attributes.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/validate_attributes.ts @@ -7,7 +7,7 @@ import { KueryNode } from '@kbn/es-query'; import { get, isEmpty } from 'lodash'; -import mappings from '../../saved_objects/mappings.json'; +import { alertMappings } from '../../saved_objects/mappings'; const astFunctionType = ['is', 'range', 'nested']; @@ -92,7 +92,7 @@ export const iterateFilterKureyNode = ({ } else if (ast.type === 'literal' && ast.value && typeof ast.value === 'string') { const key = ast.value.replace('.attributes', ''); const mappingKey = 'properties.' + key.split('.').join('.properties.'); - const field = get(mappings, mappingKey); + const field = get(alertMappings, mappingKey); if (field != null && field.type === 'nested') { localNestedKeys = ast.value; } diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d925315928f2..00f67437ae4f 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -144,7 +144,7 @@ export interface ConstructorOptions { ruleTypeRegistry: RuleTypeRegistry; minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; encryptedSavedObjectsClient: EncryptedSavedObjectsClient; - spaceId?: string; + spaceId: string; namespace?: string; getUserName: () => Promise; createAPIKey: (name: string) => Promise; @@ -281,7 +281,7 @@ const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = { export class RulesClient { private readonly logger: Logger; private readonly getUserName: () => Promise; - private readonly spaceId?: string; + private readonly spaceId: string; private readonly namespace?: string; private readonly taskManager: TaskManagerStartContract; private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index 19184d3baa32..d7dc9612b603 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -26,7 +26,7 @@ export interface RulesClientFactoryOpts { ruleTypeRegistry: RuleTypeRegistry; securityPluginSetup?: SecurityPluginSetup; securityPluginStart?: SecurityPluginStart; - getSpaceId: (request: KibanaRequest) => string | undefined; + getSpaceId: (request: KibanaRequest) => string; spaceIdToNamespace: SpaceIdToNamespaceFunction; encryptedSavedObjectsClient: EncryptedSavedObjectsClient; actions: ActionsPluginStartContract; @@ -44,7 +44,7 @@ export class RulesClientFactory { private ruleTypeRegistry!: RuleTypeRegistry; private securityPluginSetup?: SecurityPluginSetup; private securityPluginStart?: SecurityPluginStart; - private getSpaceId!: (request: KibanaRequest) => string | undefined; + private getSpaceId!: (request: KibanaRequest) => string; private spaceIdToNamespace!: SpaceIdToNamespaceFunction; private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient; private actions!: ActionsPluginStartContract; diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index 47e172005ccb..85e4dc5a8e05 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -10,10 +10,9 @@ import type { SavedObject, SavedObjectsExportTransformContext, SavedObjectsServiceSetup, - SavedObjectsTypeMappingDefinition, } from '@kbn/core/server'; import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; -import mappings from './mappings.json'; +import { alertMappings } from './mappings'; import { getMigrations } from './migrations'; import { transformRulesForExport } from './transform_rule_for_export'; import { RawRule } from '../types'; @@ -60,7 +59,7 @@ export function setupSavedObjects( namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', migrations: getMigrations(encryptedSavedObjects, isPreconfigured), - mappings: mappings.alert as SavedObjectsTypeMappingDefinition, + mappings: alertMappings, management: { displayName: 'rule', importableAndExportable: true, diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.json b/x-pack/plugins/alerting/server/saved_objects/mappings.json deleted file mode 100644 index a027dd389575..000000000000 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "alert": { - "properties": { - "enabled": { - "type": "boolean" - }, - "name": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword" - } - } - }, - "tags": { - "type": "keyword" - }, - "alertTypeId": { - "type": "keyword" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "consumer": { - "type": "keyword" - }, - "legacyId": { - "type": "keyword" - }, - "actions": { - "type": "nested", - "properties": { - "group": { - "type": "keyword" - }, - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "params": { - "type": "flattened", - "ignore_above": 4096 - }, - "mapped_params": { - "properties": { - "risk_score": { - "type": "float" - }, - "severity": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "createdBy": { - "type": "keyword" - }, - "updatedBy": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "updatedAt": { - "type": "date" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "notifyWhen": { - "type": "keyword" - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "meta": { - "properties": { - "versionApiKeyLastmodified": { - "type": "keyword" - } - } - }, - "monitoring": { - "properties": { - "execution": { - "properties": { - "history": { - "properties": { - "duration": { - "type": "long" - }, - "success": { - "type": "boolean" - }, - "timestamp": { - "type": "date" - } - } - }, - "calculated_metrics": { - "properties": { - "p50": { - "type": "long" - }, - "p95": { - "type": "long" - }, - "p99": { - "type": "long" - }, - "success_ratio": { - "type": "float" - } - } - } - } - } - } - }, - "executionStatus": { - "properties": { - "numberOfTriggeredActions": { - "type": "long" - }, - "status": { - "type": "keyword" - }, - "lastExecutionDate": { - "type": "date" - }, - "lastDuration": { - "type": "long" - }, - "error": { - "properties": { - "reason": { - "type": "keyword" - }, - "message": { - "type": "keyword" - } - } - }, - "warning": { - "properties": { - "reason": { - "type": "keyword" - }, - "message": { - "type": "keyword" - } - } - } - } - }, - "snoozeEndTime": { - "type": "date", - "format": "strict_date_time" - } - } - } -} diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.ts b/x-pack/plugins/alerting/server/saved_objects/mappings.ts new file mode 100644 index 000000000000..5e2803222ecb --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsTypeMappingDefinition } from '@kbn/core/server'; + +export const alertMappings: SavedObjectsTypeMappingDefinition = { + properties: { + enabled: { + type: 'boolean', + }, + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + normalizer: 'lowercase', + }, + }, + }, + tags: { + type: 'keyword', + }, + alertTypeId: { + type: 'keyword', + }, + schedule: { + properties: { + interval: { + type: 'keyword', + }, + }, + }, + consumer: { + type: 'keyword', + }, + legacyId: { + type: 'keyword', + }, + actions: { + type: 'nested', + properties: { + group: { + type: 'keyword', + }, + actionRef: { + type: 'keyword', + }, + actionTypeId: { + type: 'keyword', + }, + params: { + enabled: false, + type: 'object', + }, + }, + }, + params: { + type: 'flattened', + ignore_above: 4096, + }, + mapped_params: { + properties: { + risk_score: { + type: 'float', + }, + severity: { + type: 'keyword', + }, + }, + }, + scheduledTaskId: { + type: 'keyword', + }, + createdBy: { + type: 'keyword', + }, + updatedBy: { + type: 'keyword', + }, + createdAt: { + type: 'date', + }, + updatedAt: { + type: 'date', + }, + apiKey: { + type: 'binary', + }, + apiKeyOwner: { + type: 'keyword', + }, + throttle: { + type: 'keyword', + }, + notifyWhen: { + type: 'keyword', + }, + muteAll: { + type: 'boolean', + }, + mutedInstanceIds: { + type: 'keyword', + }, + meta: { + properties: { + versionApiKeyLastmodified: { + type: 'keyword', + }, + }, + }, + monitoring: { + properties: { + execution: { + properties: { + history: { + properties: { + duration: { + type: 'long', + }, + success: { + type: 'boolean', + }, + timestamp: { + type: 'date', + }, + }, + }, + calculated_metrics: { + properties: { + p50: { + type: 'long', + }, + p95: { + type: 'long', + }, + p99: { + type: 'long', + }, + success_ratio: { + type: 'float', + }, + }, + }, + }, + }, + }, + }, + executionStatus: { + properties: { + numberOfTriggeredActions: { + type: 'long', + }, + status: { + type: 'keyword', + }, + lastExecutionDate: { + type: 'date', + }, + lastDuration: { + type: 'long', + }, + error: { + properties: { + reason: { + type: 'keyword', + }, + message: { + type: 'keyword', + }, + }, + }, + warning: { + properties: { + reason: { + type: 'keyword', + }, + message: { + type: 'keyword', + }, + }, + }, + }, + }, + snoozeEndTime: { + type: 'date', + format: 'strict_date_time', + }, + }, +}; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index 6b736006fc07..921412d4e79e 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -2253,6 +2253,41 @@ describe('successful migrations', () => { }); }); + describe('8.3.0', () => { + test('removes internal tags', () => { + const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; + const alert = getMockData( + { + tags: [ + '__internal_immutable:false', + '__internal_rule_id:064e3fed-6328-416b-bb85-c08265088f41', + 'test-tag', + ], + alertTypeId: 'siem.queryRule', + }, + true + ); + + const migratedAlert830 = migration830(alert, migrationContext); + + expect(migratedAlert830.attributes.tags).toEqual(['test-tag']); + }); + + test('do not remove internal tags if rule is not Security solution rule', () => { + const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; + const alert = getMockData( + { + tags: ['__internal_immutable:false', 'tag-1'], + }, + true + ); + + const migratedAlert830 = migration830(alert, migrationContext); + + expect(migratedAlert830.attributes.tags).toEqual(['__internal_immutable:false', 'tag-1']); + }); + }); + describe('Metrics Inventory Threshold rule', () => { test('Migrates incorrect action group spelling', () => { const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index 18de27eb6919..69d88e196dcf 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -152,6 +152,12 @@ export function getMigrations( pipeMigrations(addMappedParams) ); + const migrationRules830 = createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(removeInternalTags) + ); + return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyWhen, '7.11.0'), @@ -163,6 +169,7 @@ export function getMigrations( '8.0.0': executeMigrationWithErrorHandling(migrationRules800, '8.0.0'), '8.0.1': executeMigrationWithErrorHandling(migrationRules801, '8.0.1'), '8.2.0': executeMigrationWithErrorHandling(migrationRules820, '8.2.0'), + '8.3.0': executeMigrationWithErrorHandling(migrationRules830, '8.3.0'), }; } @@ -864,6 +871,32 @@ function getCorrespondingAction( ) as RawRuleAction; } } +/** + * removes internal tags(starts with '__internal') from Security Solution rules + * @param doc rule to be migrated + * @returns migrated rule if it's Security Solution rule or unchanged if not + */ +function removeInternalTags( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + if (!isDetectionEngineAADRuleType(doc)) { + return doc; + } + + const { + attributes: { tags }, + } = doc; + + const filteredTags = (tags ?? []).filter((tag) => !tag.startsWith('__internal_')); + + return { + ...doc, + attributes: { + ...doc.attributes, + tags: filteredTags, + }, + }; +} function pipeMigrations(...migrations: AlertMigration[]): AlertMigration { return (doc: SavedObjectUnsanitizedDoc) => diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__stories__/__snapshots__/extended_template.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__stories__/__snapshots__/extended_template.stories.storyshot index 24d6e94de0ec..beab512ea62e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__stories__/__snapshots__/extended_template.stories.storyshot +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__stories__/__snapshots__/extended_template.stories.storyshot @@ -11,7 +11,7 @@ exports[`Storyshots arguments/AxisConfig extended 1`] = ` } >
can navigate Autoplay Settings 1`] = `
-
- - + -
+ + +
@@ -143,54 +141,52 @@ exports[` can navigate Autoplay Settings 2`] = `
-
- - + -
+ + +
can navigate Autoplay Settings 2`] = `
-
+
-
- - - Cycle Slides -
-
-
+ + + Cycle Slides + +
+
+ +
-
- -
+ Set a custom interval + +
+
-
- -
-
-
- Use shorthand notation, like 30s, 10m, or 1h +
+
+ Use shorthand notation, like 30s, 10m, or 1h +
+
+
-
- -
+ +
- -
+
+
@@ -398,54 +392,52 @@ exports[` can navigate Toolbar Settings, closes when activated 1`] =
-
- - + -
+ + +
@@ -498,54 +490,52 @@ exports[` can navigate Toolbar Settings, closes when activated 2`] =
-
- - + -
+ + +
can navigate Toolbar Settings, closes when activated 2`] =
-
+
-
- - - Hide Toolbar -
-
+ - Hide the toolbar when the mouse is not within the Canvas? -
+ Hide Toolbar + +
+
+ Hide the toolbar when the mouse is not within the Canvas?
@@ -643,4 +631,4 @@ exports[` can navigate Toolbar Settings, closes when activated 2`] =
`; -exports[` can navigate Toolbar Settings, closes when activated 3`] = `"

You are in a dialog. To close this dialog, hit escape.

Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"

You are in a dialog. To close this dialog, hit escape.

Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/autoplay_settings.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/autoplay_settings.stories.storyshot index e803600fe8d5..df3d090de313 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/autoplay_settings.stories.storyshot +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__stories__/__snapshots__/autoplay_settings.stories.storyshot @@ -82,7 +82,7 @@ exports[`Storyshots shareables/Footer/Settings/AutoplaySettings component: off, className="euiFlexItem" >
; export type AlertComment = SnakeToCamelCase; export type CaseUserActions = SnakeToCamelCase; export type CaseExternalService = SnakeToCamelCase; - -interface BasicCase { - id: string; - owner: string; - closedAt: string | null; - closedBy: ElasticUser | null; - comments: Comment[]; - createdAt: string; - createdBy: ElasticUser; - status: CaseStatuses; - title: string; - totalAlerts: number; - totalComment: number; - updatedAt: string | null; - updatedBy: ElasticUser | null; - version: string; -} - -export interface Case extends BasicCase { - connector: CaseConnector; - description: string; - externalService: CaseExternalService | null; - settings: CaseAttributes['settings']; - tags: string[]; -} +export type Case = Omit, 'comments'> & { comments: Comment[] }; export interface ResolvedCase { case: Case; 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 ef6d789e97a3..87d53aae14e2 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 @@ -560,6 +560,7 @@ describe('AllCasesListGeneric', () => { username: 'lknope', }, description: 'Security banana Issue', + duration: null, externalService: { connectorId: '123', connectorName: 'connector name', diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx index 66ff8c751c46..cfc16f1fb6e8 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx @@ -146,6 +146,25 @@ describe('ServiceNowITSM Fields', () => { expect(screen.queryByTestId('deprecated-connector-warning-callout')).not.toBeInTheDocument(); }); + it('does not show the deprecated callout when the connector is preconfigured', async () => { + render( + + ); + expect(screen.queryByTestId('deprecated-connector-warning-callout')).not.toBeInTheDocument(); + }); + + it('does not show the deprecated callout when the config of the connector is undefined', async () => { + render( + // @ts-expect-error + + ); + expect(screen.queryByTestId('deprecated-connector-warning-callout')).not.toBeInTheDocument(); + }); + it('should hide subcategory if selecting a category without subcategories', async () => { // Failed Login doesn't have defined subcategories const customFields = { diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx index 279cab7e6f87..a2c61ac78be0 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx @@ -180,6 +180,25 @@ describe('ServiceNowSIR Fields', () => { expect(screen.queryByTestId('deprecated-connector-warning-callout')).not.toBeInTheDocument(); }); + it('does not show the deprecated callout when the connector is preconfigured', async () => { + render( + + ); + expect(screen.queryByTestId('deprecated-connector-warning-callout')).not.toBeInTheDocument(); + }); + + it('does not show the deprecated callout when the config of the connector is undefined', async () => { + render( + // @ts-expect-error + + ); + expect(screen.queryByTestId('deprecated-connector-warning-callout')).not.toBeInTheDocument(); + }); + test('it should hide subcategory if selecting a category without subcategories', async () => { // Failed Login doesn't have defined subcategories const customFields = { diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/validator.test.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/validator.test.ts index aa643191ac62..ab21a6b5c779 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/validator.test.ts +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/validator.test.ts @@ -22,7 +22,7 @@ describe('ServiceNow validator', () => { expect(connectorValidator(invalidConnector)).toEqual({ message: 'Deprecated connector' }); }); - test('it does not returns an error message if the connector does not uses the table API', () => { + test('it does not return an error message if the connector does not uses the table API', () => { const invalidConnector = { ...connector, config: { @@ -33,5 +33,16 @@ describe('ServiceNow validator', () => { expect(connectorValidator(invalidConnector)).toBeFalsy(); }); + + test('it does not return an error message if the config of the connector is undefined', () => { + const { config, ...invalidConnector } = connector; + + // @ts-expect-error + expect(connectorValidator(invalidConnector)).toBeFalsy(); + }); + + test('it does not return an error message if the config of the connector is preconfigured', () => { + expect(connectorValidator({ ...connector, isPreconfigured: true })).toBeFalsy(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/validator.ts b/x-pack/plugins/cases/public/components/connectors/servicenow/validator.ts index 7d56163c4835..fed290071552 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/validator.ts +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/validator.ts @@ -15,10 +15,18 @@ import { CaseActionConnector } from '../../types'; export const connectorValidator = ( connector: CaseActionConnector ): ReturnType => { - const { - config: { usesTableApi }, - } = connector; - if (usesTableApi) { + /** + * It is not possible to know if a preconfigured connector + * is deprecated or not as the config property of a + * preconfigured connector is not returned by the + * actions framework + */ + + if (connector.isPreconfigured || connector.config == null) { + return; + } + + if (connector.config?.usesTableApi) { return { message: 'Deprecated connector', }; diff --git a/x-pack/plugins/cases/public/components/utils.test.ts b/x-pack/plugins/cases/public/components/utils.test.ts index 71218492dec9..278bb28b8662 100644 --- a/x-pack/plugins/cases/public/components/utils.test.ts +++ b/x-pack/plugins/cases/public/components/utils.test.ts @@ -83,5 +83,16 @@ describe('Utils', () => { }) ).toBe(true); }); + + it('returns false if the connector preconfigured', () => { + expect(isDeprecatedConnector({ ...connector, isPreconfigured: true })).toBe(false); + }); + + it('returns false if the config is undefined', () => { + expect( + // @ts-expect-error + isDeprecatedConnector({ ...connector, config: undefined }) + ).toBe(false); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/utils.ts b/x-pack/plugins/cases/public/components/utils.ts index 5ff675a31ce6..34ebffb4eacb 100644 --- a/x-pack/plugins/cases/public/components/utils.ts +++ b/x-pack/plugins/cases/public/components/utils.ts @@ -74,7 +74,13 @@ export const getConnectorIcon = ( // TODO: Remove when the applications are certified export const isDeprecatedConnector = (connector?: CaseActionConnector): boolean => { - if (connector == null) { + /** + * It is not possible to know if a preconfigured connector + * is deprecated or not as the config property of a + * preconfigured connector is not returned by the + * actions framework + */ + if (connector == null || connector.config == null || connector.isPreconfigured) { return false; } diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index 69931629a77c..97572b535fc0 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -154,6 +154,7 @@ export const basicCase: Case = { fields: null, }, description: 'Security banana Issue', + duration: null, externalService: null, status: CaseStatuses.open, tags, @@ -245,6 +246,7 @@ export const mockCase: Case = { type: ConnectorTypes.none, fields: null, }, + duration: null, description: 'Security banana Issue', externalService: null, status: CaseStatuses.open, @@ -383,6 +385,7 @@ export const basicCaseSnake: CaseResponse = { connector: { id: 'none', name: 'My Connector', type: ConnectorTypes.none, fields: null }, created_at: basicCreatedAt, created_by: elasticUserSnake, + duration: null, external_service: null, updated_at: basicUpdatedAt, updated_by: elasticUserSnake, diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index ae53cb03c28a..6569dcd3f52b 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -50,6 +50,7 @@ import { import { UpdateAlertRequest } from '../alerts/types'; import { CasesClientArgs } from '..'; import { Operations, OwnerEntity } from '../../authorization'; +import { getClosedInfoForUpdate, getDurationForUpdate } from './utils'; /** * Throws an error if any of the requests attempt to update the owner of a case. @@ -311,37 +312,29 @@ export const update = async ( throwIfUpdateOwner(updateCases); throwIfTitleIsInvalid(updateCases); - // eslint-disable-next-line @typescript-eslint/naming-convention - const { username, full_name, email } = user; const updatedDt = new Date().toISOString(); const updatedCases = await caseService.patchCases({ cases: updateCases.map(({ updateReq, originalCase }) => { // intentionally removing owner from the case so that we don't accidentally allow it to be updated const { id: caseId, version, owner, ...updateCaseAttributes } = updateReq; - let closedInfo = {}; - if (updateCaseAttributes.status && updateCaseAttributes.status === CaseStatuses.closed) { - closedInfo = { - closed_at: updatedDt, - closed_by: { email, full_name, username }, - }; - } else if ( - updateCaseAttributes.status && - (updateCaseAttributes.status === CaseStatuses.open || - updateCaseAttributes.status === CaseStatuses['in-progress']) - ) { - closedInfo = { - closed_at: null, - closed_by: null, - }; - } + return { caseId, originalCase, updatedAttributes: { ...updateCaseAttributes, - ...closedInfo, + ...getClosedInfoForUpdate({ + user, + closedDate: updatedDt, + status: updateCaseAttributes.status, + }), + ...getDurationForUpdate({ + status: updateCaseAttributes.status, + closedAt: updatedDt, + createdAt: originalCase.attributes.created_at, + }), updated_at: updatedDt, - updated_by: { email, full_name, username }, + updated_by: user, }, version, }; diff --git a/x-pack/plugins/cases/server/client/cases/utils.test.ts b/x-pack/plugins/cases/server/client/cases/utils.test.ts index 3dca8014957d..4832ffe5b2ea 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.test.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.test.ts @@ -24,13 +24,15 @@ import { import { createIncident, + getClosedInfoForUpdate, + getDurationForUpdate, getLatestPushInfo, prepareFieldsForTransformation, transformComments, transformers, transformFields, } from './utils'; -import { Actions } from '../../../common/api'; +import { Actions, CaseStatuses } from '../../../common/api'; import { flattenCaseSavedObject } from '../../common/utils'; import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { casesConnectors } from '../../connectors'; @@ -836,5 +838,119 @@ describe('utils', () => { }); }); }); + + describe('getClosedInfoForUpdate', () => { + const date = '2021-02-03T17:41:26.108Z'; + const user = { full_name: 'Elastic', username: 'elastic', email: 'elastic@elastic.co' }; + + it('returns the correct closed info when the case closes', async () => { + expect( + getClosedInfoForUpdate({ status: CaseStatuses.closed, closedDate: date, user }) + ).toEqual({ + closed_at: date, + closed_by: user, + }); + }); + + it.each([[CaseStatuses.open], [CaseStatuses['in-progress']]])( + 'returns the correct closed info when the case %s', + async (status) => { + expect(getClosedInfoForUpdate({ status, closedDate: date, user })).toEqual({ + closed_at: null, + closed_by: null, + }); + } + ); + + it('returns undefined if the status is not provided', async () => { + expect(getClosedInfoForUpdate({ closedDate: date, user })).toBe(undefined); + }); + }); + + describe('getDurationForUpdate', () => { + const createdAt = '2021-11-23T19:00:00Z'; + const closedAt = '2021-11-23T19:02:00Z'; + + it('returns the correct duration when the case closes', () => { + expect(getDurationForUpdate({ status: CaseStatuses.closed, closedAt, createdAt })).toEqual({ + duration: 120, + }); + }); + + it.each([[CaseStatuses.open], [CaseStatuses['in-progress']]])( + 'returns the correct duration when the case %s', + (status) => { + expect(getDurationForUpdate({ status, closedAt, createdAt })).toEqual({ + duration: null, + }); + } + ); + + it('returns undefined if the status is not provided', async () => { + expect(getDurationForUpdate({ closedAt, createdAt })).toBe(undefined); + }); + + it.each([['invalid'], [null]])( + 'returns undefined if the createdAt date is %s', + (createdAtInvalid) => { + expect( + getDurationForUpdate({ + status: CaseStatuses.closed, + closedAt, + // @ts-expect-error + createdAt: createdAtInvalid, + }) + ).toBe(undefined); + } + ); + + it.each([['invalid'], [null]])( + 'returns undefined if the closedAt date is %s', + (closedAtInvalid) => { + expect( + getDurationForUpdate({ + status: CaseStatuses.closed, + // @ts-expect-error + closedAt: closedAtInvalid, + createdAt, + }) + ).toBe(undefined); + } + ); + + it('returns undefined if created_at > closed_at', async () => { + expect( + getDurationForUpdate({ + status: CaseStatuses.closed, + closedAt: '2021-11-23T19:00:00Z', + createdAt: '2021-11-23T19:05:00Z', + }) + ).toBe(undefined); + }); + + it('rounds the seconds correctly', () => { + expect( + getDurationForUpdate({ + status: CaseStatuses.closed, + createdAt: '2022-04-11T15:56:00.087Z', + closedAt: '2022-04-11T15:58:56.187Z', + }) + ).toEqual({ + duration: 176, + }); + }); + + it('rounds the zero correctly', () => { + expect( + getDurationForUpdate({ + status: CaseStatuses.closed, + createdAt: '2022-04-11T15:56:00.087Z', + closedAt: '2022-04-11T15:56:00.187Z', + }) + ).toEqual({ + duration: 0, + }); + }); + }); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/utils.ts b/x-pack/plugins/cases/server/client/cases/utils.ts index 42a9a5446690..01c1a9ab897b 100644 --- a/x-pack/plugins/cases/server/client/cases/utils.ts +++ b/x-pack/plugins/cases/server/client/cases/utils.ts @@ -23,6 +23,9 @@ import { CommentRequestAlertType, CommentRequestActionsType, ActionTypes, + CaseStatuses, + User, + CaseAttributes, } from '../../../common/api'; import { CasesClientGetAlertsResponse } from '../alerts/types'; import { @@ -405,3 +408,62 @@ export const getCommentContextFromAttributes = ( }; } }; + +export const getClosedInfoForUpdate = ({ + user, + status, + closedDate, +}: { + closedDate: string; + user: User; + status?: CaseStatuses; +}): Pick | undefined => { + if (status && status === CaseStatuses.closed) { + return { + closed_at: closedDate, + closed_by: user, + }; + } + + if (status && (status === CaseStatuses.open || status === CaseStatuses['in-progress'])) { + return { + closed_at: null, + closed_by: null, + }; + } +}; + +export const getDurationForUpdate = ({ + status, + closedAt, + createdAt, +}: { + closedAt: string; + createdAt: CaseAttributes['created_at']; + status?: CaseStatuses; +}): Pick | undefined => { + if (status && status === CaseStatuses.closed) { + try { + if (createdAt != null && closedAt != null) { + const createdAtMillis = new Date(createdAt).getTime(); + const closedAtMillis = new Date(closedAt).getTime(); + + if ( + !isNaN(createdAtMillis) && + !isNaN(closedAtMillis) && + closedAtMillis >= createdAtMillis + ) { + return { duration: Math.floor((closedAtMillis - createdAtMillis) / 1000) }; + } + } + } catch (err) { + // Silence date errors + } + } + + if (status && (status === CaseStatuses.open || status === CaseStatuses['in-progress'])) { + return { + duration: null, + }; + } +}; diff --git a/x-pack/plugins/cases/server/client/configure/client.test.ts b/x-pack/plugins/cases/server/client/configure/client.test.ts index f96f55a823aa..2889f00b6d28 100644 --- a/x-pack/plugins/cases/server/client/configure/client.test.ts +++ b/x-pack/plugins/cases/server/client/configure/client.test.ts @@ -5,11 +5,10 @@ * 2.0. */ -import { CasesClientArgs } from '../types'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { getConnectors } from './client'; import { actionsClientMock } from '@kbn/actions-plugin/server/mocks'; -import { ActionType } from '@kbn/actions-plugin/common/types'; +import { CasesClientArgs } from '../types'; +import { getConnectors } from './client'; describe('client', () => { describe('getConnectors', () => { @@ -18,68 +17,163 @@ describe('client', () => { const args = { actionsClient, logger } as unknown as CasesClientArgs; - const jiraType: ActionType = { - id: '.jira', - name: '1', - enabled: true, - enabledInConfig: true, - enabledInLicense: true, - minimumLicenseRequired: 'basic', - }; + const actionTypes = [ + { + id: '.jira', + name: '1', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'basic' as const, + }, + { + id: '.servicenow', + name: '2', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'basic' as const, + }, + { + id: '.unsupported', + name: '3', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'basic' as const, + }, + { + id: '.swimlane', + name: 'swimlane', + enabled: true, + enabledInConfig: true, + enabledInLicense: false, + minimumLicenseRequired: 'basic' as const, + }, + ]; + + const connectors = [ + { + id: '1', + actionTypeId: '.jira', + name: '1', + config: {}, + isPreconfigured: false, + isDeprecated: false, + referencedByCount: 1, + }, + { + id: '2', + actionTypeId: '.servicenow', + name: '2', + config: {}, + isPreconfigured: false, + isDeprecated: false, + referencedByCount: 1, + }, + { + id: '3', + actionTypeId: '.unsupported', + name: '3', + config: {}, + isPreconfigured: false, + isDeprecated: false, + referencedByCount: 1, + }, + ]; beforeEach(() => { jest.clearAllMocks(); }); - it('removes connectors without a config field defined', async () => { - actionsClient.listTypes.mockImplementation(async () => [jiraType]); + it('remove unsupported connectors', async () => { + actionsClient.listTypes.mockImplementation(async () => actionTypes); + actionsClient.getAll.mockImplementation(async () => connectors); - actionsClient.getAll.mockImplementation(async () => [ + expect(await getConnectors(args)).toEqual([ { id: '1', actionTypeId: '.jira', name: '1', + config: {}, + isPreconfigured: false, + isDeprecated: false, + referencedByCount: 1, + }, + { + id: '2', + actionTypeId: '.servicenow', + name: '2', + config: {}, isPreconfigured: false, isDeprecated: false, referencedByCount: 1, }, ]); - - expect(await getConnectors(args)).toEqual([]); }); - it('removes connectors that are pre configured', async () => { - actionsClient.listTypes.mockImplementation(async () => [jiraType]); - + it('returns preconfigured connectors', async () => { + actionsClient.listTypes.mockImplementation(async () => actionTypes); actionsClient.getAll.mockImplementation(async () => [ + ...connectors, + { + id: '4', + actionTypeId: '.servicenow', + name: 'sn-preconfigured', + config: {}, + isPreconfigured: true, + isDeprecated: false, + referencedByCount: 1, + }, + ]); + + expect(await getConnectors(args)).toEqual([ { id: '1', actionTypeId: '.jira', name: '1', config: {}, + isPreconfigured: false, + isDeprecated: false, + referencedByCount: 1, + }, + { + id: '2', + actionTypeId: '.servicenow', + name: '2', + config: {}, + isPreconfigured: false, + isDeprecated: false, + referencedByCount: 1, + }, + { + id: '4', + actionTypeId: '.servicenow', + name: 'sn-preconfigured', + config: {}, isPreconfigured: true, isDeprecated: false, referencedByCount: 1, }, ]); - - expect(await getConnectors(args)).toEqual([]); }); - it('includes connectors that have a config and are not pre configured', async () => { - actionsClient.listTypes.mockImplementation(async () => [ - jiraType, + it('filter out connectors that are unsupported by the current license', async () => { + actionsClient.listTypes.mockImplementation(async () => actionTypes); + actionsClient.getAll.mockImplementation(async () => [ + ...connectors, { - id: '.servicenow', - name: '2', - enabled: true, - enabledInConfig: true, - enabledInLicense: true, - minimumLicenseRequired: 'basic', + id: '4', + actionTypeId: '.swimlane', + name: 'swimlane', + config: {}, + isPreconfigured: false, + isDeprecated: false, + referencedByCount: 1, }, ]); - const connectors = [ + expect(await getConnectors(args)).toEqual([ { id: '1', actionTypeId: '.jira', @@ -98,11 +192,7 @@ describe('client', () => { isDeprecated: false, referencedByCount: 1, }, - ]; - - actionsClient.getAll.mockImplementation(async () => connectors); - - expect(await getConnectors(args)).toEqual(connectors); + ]); }); }); }); diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts index c4b07019627e..9bb6b8331626 100644 --- a/x-pack/plugins/cases/server/client/configure/client.ts +++ b/x-pack/plugins/cases/server/client/configure/client.ts @@ -223,9 +223,7 @@ function isConnectorSupported( ): boolean { return ( SUPPORTED_CONNECTORS.includes(action.actionTypeId) && - actionTypes[action.actionTypeId]?.enabledInLicense && - action.config != null && - !action.isPreconfigured + actionTypes[action.actionTypeId]?.enabledInLicense ); } diff --git a/x-pack/plugins/cases/server/client/utils.test.ts b/x-pack/plugins/cases/server/client/utils.test.ts index 0210ce9eaf3d..24e1135020a8 100644 --- a/x-pack/plugins/cases/server/client/utils.test.ts +++ b/x-pack/plugins/cases/server/client/utils.test.ts @@ -87,6 +87,7 @@ describe('utils', () => { "username": "elastic", }, "description": "A description", + "duration": null, "external_service": null, "owner": "securitySolution", "settings": Object { @@ -117,18 +118,6 @@ describe('utils', () => { expect(node).toBeFalsy(); }); - it('returns undefined if the from is malformed', () => { - expect(() => buildRangeFilter({ from: '<' })).toThrowError( - 'Invalid "from" and/or "to" query parameters' - ); - }); - - it('returns undefined if the to is malformed', () => { - expect(() => buildRangeFilter({ to: '<' })).toThrowError( - 'Invalid "from" and/or "to" query parameters' - ); - }); - it('creates a range filter with only the from correctly', () => { const node = buildRangeFilter({ from: 'now-1M' }); expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(` @@ -216,6 +205,7 @@ describe('utils', () => { field: 'test', savedObjectType: 'test-type', }); + expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(` Object { "bool": Object { @@ -253,5 +243,51 @@ describe('utils', () => { } `); }); + + it('escapes the query correctly', () => { + const node = buildRangeFilter({ + from: '2022-04-27T12:55:47.576Z', + to: '2022-04-27T12:56:47.576Z', + field: '= ${from}` : undefined; - const toKQL = to != null ? `${savedObjectType}.attributes.${field} <= ${to}` : undefined; + const fromKQL = + from != null + ? `${escapeKuery(savedObjectType)}.attributes.${escapeKuery(field)} >= ${escapeKuery(from)}` + : undefined; + const toKQL = + to != null + ? `${escapeKuery(savedObjectType)}.attributes.${escapeKuery(field)} <= ${escapeKuery(to)}` + : undefined; const rangeKQLQuery = `${fromKQL != null ? fromKQL : ''} ${ fromKQL != null && toKQL != null ? 'and' : '' diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index 47af4b11a0a9..974c36bd0d8a 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -103,6 +103,7 @@ describe('common utils', () => { "username": "elastic", }, "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, "external_service": null, "id": "mock-id-1", "owner": "securitySolution", @@ -141,6 +142,7 @@ describe('common utils', () => { "username": "elastic", }, "description": "Oh no, a bad meanie destroying data!", + "duration": null, "external_service": null, "id": "mock-id-2", "owner": "securitySolution", @@ -183,6 +185,7 @@ describe('common utils', () => { "username": "elastic", }, "description": "Oh no, a bad meanie going LOLBins all over the place!", + "duration": null, "external_service": null, "id": "mock-id-3", "owner": "securitySolution", @@ -229,6 +232,7 @@ describe('common utils', () => { "username": "elastic", }, "description": "Oh no, a bad meanie going LOLBins all over the place!", + "duration": null, "external_service": null, "id": "mock-id-4", "owner": "securitySolution", @@ -292,6 +296,7 @@ describe('common utils', () => { "username": "elastic", }, "description": "Oh no, a bad meanie going LOLBins all over the place!", + "duration": null, "external_service": null, "id": "mock-id-3", "owner": "securitySolution", @@ -346,6 +351,7 @@ describe('common utils', () => { "username": "elastic", }, "description": "Oh no, a bad meanie going LOLBins all over the place!", + "duration": null, "external_service": null, "id": "mock-id-3", "owner": "securitySolution", @@ -423,6 +429,7 @@ describe('common utils', () => { "username": "elastic", }, "description": "Oh no, a bad meanie going LOLBins all over the place!", + "duration": null, "external_service": null, "id": "mock-id-3", "owner": "securitySolution", @@ -475,6 +482,7 @@ describe('common utils', () => { "username": "elastic", }, "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, "external_service": null, "id": "mock-id-1", "owner": "securitySolution", diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 9385e83c948c..11e77c5eb457 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -55,6 +55,7 @@ export const transformNewCase = ({ newCase: CasePostRequest; }): CaseAttributes => ({ ...newCase, + duration: null, closed_at: null, closed_by: null, created_at: new Date().toISOString(), diff --git a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts index cf5de90f4f61..cc45ef0e2d06 100644 --- a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -34,6 +34,7 @@ export const mockCases: Array> = [ email: 'testemail@elastic.co', username: 'elastic', }, + duration: null, description: 'This is a brand new case of a bad meanie defacing data', external_service: null, title: 'Super Bad Security Issue', @@ -72,6 +73,7 @@ export const mockCases: Array> = [ email: 'testemail@elastic.co', username: 'elastic', }, + duration: null, description: 'Oh no, a bad meanie destroying data!', external_service: null, title: 'Damaging Data Destruction Detected', @@ -110,6 +112,7 @@ export const mockCases: Array> = [ email: 'testemail@elastic.co', username: 'elastic', }, + duration: null, description: 'Oh no, a bad meanie going LOLBins all over the place!', external_service: null, title: 'Another bad one', @@ -152,6 +155,7 @@ export const mockCases: Array> = [ email: 'testemail@elastic.co', username: 'elastic', }, + duration: null, description: 'Oh no, a bad meanie going LOLBins all over the place!', external_service: null, status: CaseStatuses.closed, diff --git a/x-pack/plugins/cases/server/saved_object_types/cases.ts b/x-pack/plugins/cases/server/saved_object_types/cases.ts index 441bd818189a..e1df2366247e 100644 --- a/x-pack/plugins/cases/server/saved_object_types/cases.ts +++ b/x-pack/plugins/cases/server/saved_object_types/cases.ts @@ -59,6 +59,9 @@ export const createCaseSavedObjectType = ( }, }, }, + duration: { + type: 'unsigned_long', + }, description: { type: 'text', }, diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts index bdc6b6ca18e6..70e0e91caa57 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts @@ -15,7 +15,7 @@ import { import { CASE_SAVED_OBJECT } from '../../../common/constants'; import { getNoneCaseConnector } from '../../common/utils'; import { createExternalService, ESCaseConnectorWithId } from '../../services/test_utils'; -import { caseConnectorIdMigration, removeCaseType } from './cases'; +import { addDuration, caseConnectorIdMigration, removeCaseType } from './cases'; // eslint-disable-next-line @typescript-eslint/naming-convention const create_7_14_0_case = ({ @@ -371,4 +371,129 @@ describe('case migrations', () => { }); }); }); + + describe('addDuration', () => { + it('adds the duration correctly', () => { + const doc = { + id: '123', + attributes: { + created_at: '2021-11-23T19:00:00Z', + closed_at: '2021-11-23T19:02:00Z', + }, + type: 'abc', + references: [], + } as unknown as SavedObjectSanitizedDoc; + + expect(addDuration(doc)).toEqual({ + ...doc, + attributes: { + ...doc.attributes, + duration: 120, + }, + }); + }); + + it.each([['invalid'], [null]])( + 'returns null if the createdAt date is %s', + (createdAtInvalid) => { + const doc = { + id: '123', + attributes: { + created_at: createdAtInvalid, + closed_at: '2021-11-23T19:02:00Z', + }, + type: 'abc', + references: [], + } as unknown as SavedObjectSanitizedDoc; + + expect(addDuration(doc)).toEqual({ + ...doc, + attributes: { + ...doc.attributes, + duration: null, + }, + }); + } + ); + + it.each([['invalid'], [null]])('returns null if the closedAt date is %s', (closedAtInvalid) => { + const doc = { + id: '123', + attributes: { + created_at: '2021-11-23T19:02:00Z', + closed_at: closedAtInvalid, + }, + type: 'abc', + references: [], + } as unknown as SavedObjectSanitizedDoc; + + expect(addDuration(doc)).toEqual({ + ...doc, + attributes: { + ...doc.attributes, + duration: null, + }, + }); + }); + + it('returns null if created_at > closed_at', () => { + const doc = { + id: '123', + attributes: { + created_at: '2021-11-23T19:05:00Z', + closed_at: '2021-11-23T19:00:00Z', + }, + type: 'abc', + references: [], + } as unknown as SavedObjectSanitizedDoc; + + expect(addDuration(doc)).toEqual({ + ...doc, + attributes: { + ...doc.attributes, + duration: null, + }, + }); + }); + + it('rounds the seconds correctly', () => { + const doc = { + id: '123', + attributes: { + created_at: '2022-04-11T15:56:00.087Z', + closed_at: '2022-04-11T15:58:56.187Z', + }, + type: 'abc', + references: [], + } as unknown as SavedObjectSanitizedDoc; + + expect(addDuration(doc)).toEqual({ + ...doc, + attributes: { + ...doc.attributes, + duration: 176, + }, + }); + }); + + it('rounds to zero correctly', () => { + const doc = { + id: '123', + attributes: { + created_at: '2022-04-11T15:56:00.087Z', + closed_at: '2022-04-11T15:56:00.187Z', + }, + type: 'abc', + references: [], + } as unknown as SavedObjectSanitizedDoc; + + expect(addDuration(doc)).toEqual({ + ...doc, + attributes: { + ...doc.attributes, + duration: 0, + }, + }); + }); + }); }); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts index eaa93a585015..91a462c5c805 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts @@ -11,7 +11,7 @@ import { cloneDeep, unset } from 'lodash'; import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from '@kbn/core/server'; import { addOwnerToSO, SanitizedCaseOwner } from '.'; import { ESConnectorFields } from '../../services'; -import { ConnectorTypes } from '../../../common/api'; +import { CaseAttributes, ConnectorTypes } from '../../../common/api'; import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME, @@ -86,6 +86,34 @@ export const removeCaseType = ( return { ...docCopy, references: doc.references ?? [] }; }; +export const addDuration = ( + doc: SavedObjectUnsanitizedDoc> +): SavedObjectSanitizedDoc => { + let duration = null; + + try { + const createdAt = doc.attributes.created_at; + const closedAt = doc.attributes.closed_at; + + if (createdAt != null && closedAt != null) { + const createdAtMillis = new Date(createdAt).getTime(); + const closedAtMillis = new Date(closedAt).getTime(); + + if (!isNaN(createdAtMillis) && !isNaN(closedAtMillis) && closedAtMillis >= createdAtMillis) { + duration = Math.floor((closedAtMillis - createdAtMillis) / 1000); + } + } + } catch (err) { + // Silence date errors + } + + /** + * Duration is the time from the creation of the case to the close of the case in seconds + * If an error occurs or the case has not been closed then the duration is set to null + */ + return { ...doc, attributes: { ...doc.attributes, duration }, references: doc.references ?? [] }; +}; + export const caseMigrations = { '7.10.0': ( doc: SavedObjectUnsanitizedDoc @@ -147,4 +175,5 @@ export const caseMigrations = { }, '7.15.0': caseConnectorIdMigration, '8.1.0': removeCaseType, + '8.3.0': addDuration, }; diff --git a/x-pack/plugins/cases/server/services/cases/index.test.ts b/x-pack/plugins/cases/server/services/cases/index.test.ts index 7e576b83404d..5666b102dda5 100644 --- a/x-pack/plugins/cases/server/services/cases/index.test.ts +++ b/x-pack/plugins/cases/server/services/cases/index.test.ts @@ -160,6 +160,7 @@ describe('CasesService', () => { "username": "elastic", }, "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, "owner": "securitySolution", "settings": Object { "syncAlerts": true, @@ -500,6 +501,7 @@ describe('CasesService', () => { "username": "elastic", }, "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, "external_service": Object { "connector_name": ".jira", "external_id": "100", diff --git a/x-pack/plugins/cases/server/services/test_utils.ts b/x-pack/plugins/cases/server/services/test_utils.ts index 16c6445af774..617dedd368ab 100644 --- a/x-pack/plugins/cases/server/services/test_utils.ts +++ b/x-pack/plugins/cases/server/services/test_utils.ts @@ -105,6 +105,7 @@ export const basicCaseFields = { email: 'testemail@elastic.co', username: 'elastic', }, + duration: null, description: 'This is a brand new case of a bad meanie defacing data', title: 'Super Bad Security Issue', status: CaseStatuses.open, diff --git a/x-pack/plugins/cloud/server/env.ts b/x-pack/plugins/cloud/server/env.ts new file mode 100644 index 000000000000..435e62bf4769 --- /dev/null +++ b/x-pack/plugins/cloud/server/env.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. + */ + +// Best effort to get instance size from process.env +export function readInstanceSizeMb(): undefined | number { + const capacityString = process.env.CLOUD_KIBANA_CAPACITY; + if (capacityString) { + const instanceSizeMb = parseInt(capacityString, 10); + return isNaN(instanceSizeMb) ? undefined : instanceSizeMb; + } +} diff --git a/x-pack/plugins/cloud/server/plugin.ts b/x-pack/plugins/cloud/server/plugin.ts index fbf3c925cff8..284d37804be2 100644 --- a/x-pack/plugins/cloud/server/plugin.ts +++ b/x-pack/plugins/cloud/server/plugin.ts @@ -14,6 +14,7 @@ import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { parseDeploymentIdFromDeploymentUrl } from './utils'; import { registerFullstoryRoute } from './routes/fullstory'; import { registerChatRoute } from './routes/chat'; +import { readInstanceSizeMb } from './env'; interface PluginsSetup { usageCollection?: UsageCollectionSetup; @@ -24,6 +25,7 @@ export interface CloudSetup { cloudId?: string; deploymentId?: string; isCloudEnabled: boolean; + instanceSizeMb?: number; apm: { url?: string; secretToken?: string; @@ -41,7 +43,7 @@ export class CloudPlugin implements Plugin { this.isDev = this.context.env.mode.dev; } - public setup(core: CoreSetup, { usageCollection, security }: PluginsSetup) { + public setup(core: CoreSetup, { usageCollection, security }: PluginsSetup): CloudSetup { this.logger.debug('Setting up Cloud plugin'); const isCloudEnabled = getIsCloudEnabled(this.config.id); registerCloudUsageCollector(usageCollection, { isCloudEnabled }); @@ -64,6 +66,7 @@ export class CloudPlugin implements Plugin { return { cloudId: this.config.id, + instanceSizeMb: readInstanceSizeMb(), deploymentId: parseDeploymentIdFromDeploymentUrl(this.config.deployment_url), isCloudEnabled, apm: { diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 363adc568ba9..cbb283d73137 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -20,7 +20,7 @@ export const LATEST_FINDINGS_INDEX_NAME = 'cloud_security_posture.findings_lates export const BENCHMARK_SCORE_INDEX_NAME = 'cloud_security_posture.scores'; export const AGENT_LOGS_INDEX_PATTERN = '.logs-cis_kubernetes_benchmark.metadata*'; -export const CSP_KUBEBEAT_INDEX_PATTERN = 'logs-cis_kubernetes_benchmark.findings-*'; +export const CSP_LATEST_FINDINGS_DATA_VIEW = 'logs-cloud_security_posture.findings_latest-*'; export const FINDINGS_INDEX_PATTERN = 'logs-' + FINDINGS_DATA_STREAM_NAME + '-default*'; export const LATEST_FINDINGS_INDEX_PATTERN = 'logs-' + LATEST_FINDINGS_INDEX_NAME + '-default'; export const BENCHMARK_SCORE_INDEX_PATTERN = 'logs-' + BENCHMARK_SCORE_INDEX_NAME + '-default'; diff --git a/x-pack/plugins/cloud_security_posture/common/schemas/csp_rule.ts b/x-pack/plugins/cloud_security_posture/common/schemas/csp_rule.ts index a2c7f06e0a67..a6aaa26e7a1a 100644 --- a/x-pack/plugins/cloud_security_posture/common/schemas/csp_rule.ts +++ b/x-pack/plugins/cloud_security_posture/common/schemas/csp_rule.ts @@ -8,7 +8,7 @@ import { schema as rt, TypeOf } from '@kbn/config-schema'; export const cspRuleAssetSavedObjectType = 'csp_rule'; -// TODO: needs to be shared with kubebeat +// TODO: needs to be shared with cloudbeat export const cspRuleSchema = rt.object({ id: rt.string(), name: rt.string(), diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts index 0aee5ce2f350..4bc388d39ebe 100644 --- a/x-pack/plugins/cloud_security_posture/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/common/types.ts @@ -21,7 +21,7 @@ export interface Stats extends FindingsEvaluation { postureScore: Score; } -export interface ResourceType extends FindingsEvaluation { +export interface GroupedFindingsEvaluation extends FindingsEvaluation { name: string; } @@ -36,13 +36,13 @@ export interface Cluster { lastUpdate: number; // unix epoch time }; stats: Stats; - resourcesTypes: ResourceType[]; + groupedFindingsEvaluation: GroupedFindingsEvaluation[]; trend: PostureTrend[]; } export interface ComplianceDashboardData { stats: Stats; - resourcesTypes: ResourceType[]; + groupedFindingsEvaluation: GroupedFindingsEvaluation[]; clusters: Cluster[]; trend: PostureTrend[]; } diff --git a/x-pack/plugins/cloud_security_posture/public/common/api/use_kubebeat_data_view.ts b/x-pack/plugins/cloud_security_posture/public/common/api/use_latest_findings_data_view.ts similarity index 60% rename from x-pack/plugins/cloud_security_posture/public/common/api/use_kubebeat_data_view.ts rename to x-pack/plugins/cloud_security_posture/public/common/api/use_latest_findings_data_view.ts index 20d22b93cb9d..21708c3be1f5 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/api/use_kubebeat_data_view.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/api/use_latest_findings_data_view.ts @@ -7,23 +7,19 @@ import { useQuery } from 'react-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { CSP_KUBEBEAT_INDEX_PATTERN } from '../../../common/constants'; +import { CSP_LATEST_FINDINGS_DATA_VIEW } from '../../../common/constants'; import { CspClientPluginStartDeps } from '../../types'; /** * TODO: use perfected kibana data views */ -export const useKubebeatDataView = () => { +export const useLatestFindingsDataView = () => { const { data: { dataViews }, } = useKibana().services; - // TODO: check if index exists - // if not, no point in creating a data view - // const check = () => http?.get(`/kubebeat`); - // TODO: use `dataViews.get(ID)` - const findDataView = async () => (await dataViews.find(CSP_KUBEBEAT_INDEX_PATTERN))?.[0]; + const findDataView = async () => (await dataViews.find(CSP_LATEST_FINDINGS_DATA_VIEW))?.[0]; - return useQuery(['kubebeat_dataview'], findDataView); + return useQuery(['latest_findings_dataview'], findDataView); }; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.ts index b5ce12095f99..8b1e49762c87 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.ts @@ -18,7 +18,7 @@ const getFindingsQuery = (queryValue: Query['query']): Pick((a, [key, value]) => { - a.push(`${key} : "${value}"`); + a.push(`${key}: "${value}"`); return a; }, []) .join(' and '); diff --git a/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx b/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx index af1cf6c5c43c..ab193ab6dc8b 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx @@ -22,9 +22,9 @@ const getColor = (type: Props['type']): EuiBadgeProps['color'] => { export const CspEvaluationBadge = ({ type }: Props) => ( {type === 'failed' ? ( - + ) : ( - + )} ); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx index 990140f3cf94..48e9d3f67e8e 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx @@ -8,9 +8,6 @@ import React from 'react'; import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { UseQueryResult } from 'react-query/types/react/types'; -import { createStubDataView } from '@kbn/data-views-plugin/public/data_views/data_view.stub'; -import { CSP_KUBEBEAT_INDEX_PATTERN } from '../../../common/constants'; -import { useKubebeatDataView } from '../../common/api/use_kubebeat_data_view'; import { createCspBenchmarkIntegrationFixture } from '../../test/fixtures/csp_benchmark_integration'; import { createReactQueryResponse } from '../../test/fixtures/react_query'; import { TestProvider } from '../../test/test_provider'; @@ -24,7 +21,6 @@ import { useCspBenchmarkIntegrations } from './use_csp_benchmark_integrations'; import { useCisKubernetesIntegration } from '../../common/api/use_cis_kubernetes_integration'; jest.mock('./use_csp_benchmark_integrations'); -jest.mock('../../common/api/use_kubebeat_data_view'); jest.mock('../../common/api/use_cis_kubernetes_integration'); describe('', () => { @@ -35,17 +31,6 @@ describe('', () => { (useCisKubernetesIntegration as jest.Mock).mockImplementation(() => ({ data: { item: { status: 'installed' } }, })); - // Required for the page template to render the benchmarks page - (useKubebeatDataView as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: createStubDataView({ - spec: { - id: CSP_KUBEBEAT_INDEX_PATTERN, - }, - }), - }) - ); }); const renderBenchmarks = ( diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx index 086fea34a2c5..b6b309cadbb0 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx @@ -14,7 +14,7 @@ import { EuiLink, EuiText, } from '@elastic/eui'; -import { ComplianceDashboardData, ResourceType } from '../../../../common/types'; +import { ComplianceDashboardData, GroupedFindingsEvaluation } from '../../../../common/types'; import { CompactFormattedNumber } from '../../../components/compact_formatted_number'; import * as TEXT from '../translations'; import { INTERNAL_FEATURE_FLAGS } from '../../../../common/constants'; @@ -59,17 +59,17 @@ const mockData = [ ]; export interface RisksTableProps { - data: ComplianceDashboardData['resourcesTypes']; + data: ComplianceDashboardData['groupedFindingsEvaluation']; maxItems: number; - onCellClick: (resourceTypeName: string) => void; + onCellClick: (name: string) => void; onViewAllClick: () => void; } export const getTopRisks = ( - resourcesTypes: ComplianceDashboardData['resourcesTypes'], + groupedFindingsEvaluation: ComplianceDashboardData['groupedFindingsEvaluation'], maxItems: number ) => { - const filtered = resourcesTypes.filter((x) => x.totalFailed > 0); + const filtered = groupedFindingsEvaluation.filter((x) => x.totalFailed > 0); const sorted = filtered.slice().sort((first, second) => second.totalFailed - first.totalFailed); return sorted.slice(0, maxItems); @@ -85,15 +85,18 @@ export const RisksTable = ({ () => [ { field: 'name', - name: TEXT.RESOURCE_TYPE, - render: (resourceTypeName: ResourceType['name']) => ( - onCellClick(resourceTypeName)}>{resourceTypeName} + name: TEXT.CIS_SECTION, + render: (name: GroupedFindingsEvaluation['name']) => ( + onCellClick(name)}>{name} ), }, { field: 'totalFailed', name: TEXT.FINDINGS, - render: (totalFailed: ResourceType['totalFailed'], resource: ResourceType) => ( + render: ( + totalFailed: GroupedFindingsEvaluation['totalFailed'], + resource: GroupedFindingsEvaluation + ) => ( <> @@ -114,7 +117,7 @@ export const RisksTable = ({ return ( - + rowHeader="name" items={INTERNAL_FEATURE_FLAGS.showRisksMock ? getTopRisks(mockData, maxItems) : items} columns={columns} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx index a3b32ef3fe2c..daa154f62aad 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmarks_section.tsx @@ -42,19 +42,19 @@ export const BenchmarksSection = ({ const [layerValue] = element; const evaluation = layerValue[0].groupByRollup as Evaluation; - navToFindings({ cluster_id: clusterId, 'result.evaluation': evaluation }); + navToFindings({ 'cluster_id.keyword': clusterId, 'result.evaluation.keyword': evaluation }); }; - const handleCellClick = (clusterId: string, resourceTypeName: string) => { + const handleCellClick = (clusterId: string, ruleSection: string) => { navToFindings({ - cluster_id: clusterId, - 'resource.type': resourceTypeName, - 'result.evaluation': RULE_FAILED, + 'cluster_id.keyword': clusterId, + 'rule.section.keyword': ruleSection, + 'result.evaluation.keyword': RULE_FAILED, }); }; const handleViewAllClick = (clusterId: string) => { - navToFindings({ cluster_id: clusterId, 'result.evaluation': RULE_FAILED }); + navToFindings({ 'cluster_id.keyword': clusterId, 'result.evaluation.keyword': RULE_FAILED }); }; return ( @@ -110,7 +110,7 @@ export const BenchmarksSection = ({ handleCellClick(cluster.meta.clusterId, resourceTypeName) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx index c123459d9e96..b7782e5778bd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx @@ -32,15 +32,18 @@ export const SummarySection = ({ complianceData }: { complianceData: ComplianceD const [layerValue] = element; const evaluation = layerValue[0].groupByRollup as Evaluation; - navToFindings({ 'result.evaluation': evaluation }); + navToFindings({ 'result.evaluation.keyword': evaluation }); }; - const handleCellClick = (resourceTypeName: string) => { - navToFindings({ 'resource.type': resourceTypeName, 'result.evaluation': RULE_FAILED }); + const handleCellClick = (ruleSection: string) => { + navToFindings({ + 'rule.section.keyword': ruleSection, + 'result.evaluation.keyword': RULE_FAILED, + }); }; const handleViewAllClick = () => { - navToFindings({ 'result.evaluation': RULE_FAILED }); + navToFindings({ 'result.evaluation.keyword': RULE_FAILED }); }; return ( @@ -58,7 +61,7 @@ export const SummarySection = ({ complianceData }: { complianceData: ComplianceD { @@ -41,7 +41,7 @@ const Wrapper = ({ ); describe('', () => { - it("renders the success state component when 'kubebeat' DataView exists and request status is 'success'", async () => { + it("renders the success state component when 'latest findings' DataView exists and request status is 'success'", async () => { const data = dataPluginMock.createStartContract(); const unifiedSearch = unifiedSearchPluginMock.createStartContract(); const source = await data.search.searchSource.create(); @@ -51,11 +51,11 @@ describe('', () => { })); (source.fetch$ as jest.Mock).mockReturnValue(of({ rawResponse: { hits: { hits: [] } } })); - (useKubebeatDataView as jest.Mock).mockReturnValue({ + (useLatestFindingsDataView as jest.Mock).mockReturnValue({ status: 'success', data: createStubDataView({ spec: { - id: CSP_KUBEBEAT_INDEX_PATTERN, + id: CSP_LATEST_FINDINGS_DATA_VIEW, }, }), } as UseQueryResult); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx index 4eae5610da15..da3c1b32fd21 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx @@ -5,14 +5,14 @@ * 2.0. */ import React from 'react'; -import { useKubebeatDataView } from '../../common/api/use_kubebeat_data_view'; +import { useLatestFindingsDataView } from '../../common/api/use_latest_findings_data_view'; import { allNavigationItems } from '../../common/navigation/constants'; import { useCspBreadcrumbs } from '../../common/navigation/use_csp_breadcrumbs'; import { FindingsContainer } from './findings_container'; import { CspPageTemplate } from '../../components/csp_page_template'; export const Findings = () => { - const dataViewQuery = useKubebeatDataView(); + const dataViewQuery = useLatestFindingsDataView(); useCspBreadcrumbs([allNavigationItems.findings]); return ( diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_container.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_container.test.tsx index ea2cd9ce7c0c..132a64976a04 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_container.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_container.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { FindingsContainer, getDefaultQuery } from './findings_container'; import { createStubDataView } from '@kbn/data-views-plugin/common/mocks'; -import { CSP_KUBEBEAT_INDEX_PATTERN } from '../../../common/constants'; +import { CSP_LATEST_FINDINGS_DATA_VIEW } from '../../../common/constants'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { TestProvider } from '../../test/test_provider'; @@ -19,7 +19,7 @@ import { RisonObject } from 'rison-node'; import { buildEsQuery } from '@kbn/es-query'; import { getFindingsCountAggQuery } from './use_findings_count'; -jest.mock('../../common/api/use_kubebeat_data_view'); +jest.mock('../../common/api/use_latest_findings_data_view'); jest.mock('../../common/api/use_cis_kubernetes_integration'); jest.mock('react-router-dom', () => ({ @@ -38,7 +38,7 @@ describe('', () => { const dataMock = dataPluginMock.createStartContract(); const dataView = createStubDataView({ spec: { - id: CSP_KUBEBEAT_INDEX_PATTERN, + id: CSP_LATEST_FINDINGS_DATA_VIEW, }, }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/__snapshots__/resource_tab.test.ts.snap b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/__snapshots__/resource_tab.test.ts.snap new file mode 100644 index 000000000000..ffa5d91e2d67 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/__snapshots__/resource_tab.test.ts.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`prepareDescriptionList create description lists accordingly 1`] = ` +Array [ + Object { + "description": + string + , + "title": + + a + + , + }, + Object { + "description": + 123 + , + "title": + + b + + , + }, + Object { + "description": "undefined", + "title": + + c + + , + }, + Object { + "description": + null + , + "title": + + d + + , + }, + Object { + "description": + true + , + "title": + + e + + , + }, + Object { + "description": + false + , + "title": + + f + + , + }, + Object { + "description": + [ + { + "a": "another string", + "b": 123 + } +] + , + "title": + + g + + , + }, +] +`; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx similarity index 75% rename from x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout.tsx rename to x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx index 65493bd49334..f475c9fa5fcb 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx @@ -25,13 +25,15 @@ import { type PropsOf, } from '@elastic/eui'; import { assertNever } from '@kbn/std'; -import type { CspFinding } from './types'; -import { CspEvaluationBadge } from '../../components/csp_evaluation_badge'; -import * as TEXT from './translations'; -import cisLogoIcon from '../../assets/icons/cis_logo.svg'; -import k8sLogoIcon from '../../assets/icons/k8s_logo.svg'; +import type { CspFinding } from '../types'; +import { CspEvaluationBadge } from '../../../components/csp_evaluation_badge'; +import * as TEXT from '../translations'; +import cisLogoIcon from '../../../assets/icons/cis_logo.svg'; +import k8sLogoIcon from '../../../assets/icons/k8s_logo.svg'; +import { ResourceTab } from './resource_tab'; +import { JsonTab } from './json_tab'; -const tabs = ['remediation', 'resource', 'general'] as const; +const tabs = ['remediation', 'resource', 'general', 'json'] as const; const CodeBlock: React.FC> = (props) => ( @@ -51,8 +53,44 @@ interface FindingFlyoutProps { findings: CspFinding; } +const Cards = ({ data }: { data: Card[] }) => ( + + {data.map((card) => ( + + + ({ title: v[0], description: v[1] }))} + style={{ flexFlow: 'column' }} + descriptionProps={{ + style: { width: '100%' }, + }} + /> + + + ))} + +); + +const FindingsTab = ({ tab, findings }: { findings: CspFinding; tab: FindingsTab }) => { + switch (tab) { + case 'remediation': + return ; + case 'resource': + return ; + case 'general': + return ; + case 'json': + return ; + default: + assertNever(tab); + } +}; + export const FindingsRuleFlyout = ({ onClose, findings }: FindingFlyoutProps) => { const [tab, setTab] = useState('remediation'); + return ( @@ -89,76 +127,6 @@ export const FindingsRuleFlyout = ({ onClose, findings }: FindingFlyoutProps) => ); }; -const Cards = ({ data }: { data: Card[] }) => ( - - {data.map((card) => ( - - - ({ title: v[0], description: v[1] }))} - style={{ flexFlow: 'column' }} - descriptionProps={{ - style: { width: '100%' }, - }} - /> - - - ))} - -); - -const FindingsTab = ({ tab, findings }: { findings: CspFinding; tab: FindingsTab }) => { - switch (tab) { - case 'remediation': - return ; - case 'resource': - return ; - case 'general': - return ; - default: - assertNever(tab); - } -}; - -const getResourceCards = ({ resource, host }: CspFinding): Card[] => [ - { - title: TEXT.RESOURCE, - listItems: [ - [TEXT.FILENAME, {resource.filename}], - [TEXT.MODE, resource.mode], - [TEXT.PATH, {resource.path}], - [TEXT.TYPE, resource.type], - [TEXT.UID, resource.uid], - ], - }, - { - title: TEXT.HOST, - listItems: [ - [TEXT.ARCHITECTURE, host.architecture], - [TEXT.CONTAINERIZED, host.containerized ? 'true' : 'false'], - [TEXT.HOSTNAME, host.hostname], - [TEXT.ID, {host.id}], - [TEXT.IP, {host.ip.join(', ')}], - [TEXT.MAC, {host.mac.join(', ')}], - [TEXT.NAME, host.name], - ], - }, - { - title: TEXT.OS, - listItems: [ - [TEXT.CODENAME, host.os.codename], - [TEXT.FAMILY, host.os.family], - [TEXT.KERNEL, host.os.kernel], - [TEXT.NAME, host.os.name], - [TEXT.PLATFORM, host.os.platform], - [TEXT.TYPE, host.os.type], - [TEXT.VERSION, host.os.version], - ], - }, -]; - const getGeneralCards = ({ rule }: CspFinding): Card[] => [ { title: TEXT.RULE, @@ -197,7 +165,7 @@ const getGeneralCards = ({ rule }: CspFinding): Card[] => [ const getRemediationCards = ({ result, ...rest }: CspFinding): Card[] => [ { - title: TEXT.RESULT, + title: TEXT.RESULT_DETAILS, listItems: [ [TEXT.EXPECTED, ''], [TEXT.EVIDENCE, {JSON.stringify(result.evidence, null, 2)}], diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/json_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/json_tab.tsx new file mode 100644 index 000000000000..ed5845abf19b --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/json_tab.tsx @@ -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 React from 'react'; +import { CodeEditor } from '@kbn/kibana-react-plugin/public'; +import { XJsonLang } from '@kbn/monaco'; +import { CspFinding } from '../types'; + +const offsetHeight = 120; + +export const JsonTab = ({ data }: { data: CspFinding }) => ( +
+ +
+); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.test.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.test.ts new file mode 100644 index 000000000000..94886e8fb254 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { prepareDescriptionList } from './resource_tab'; + +const mockData = { + a: 'string', + b: 123, + c: undefined, + d: null, + e: true, + f: false, + g: [{ a: 'another string', b: 123 }], +}; + +describe('prepareDescriptionList', () => { + it('create description lists accordingly', () => { + const descriptionList = prepareDescriptionList(mockData); + expect(descriptionList).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.tsx new file mode 100644 index 000000000000..7919b836a3a7 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/resource_tab.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 { + EuiAccordion, + EuiCode, + EuiCodeBlock, + EuiDescriptionList, + EuiPanel, + EuiSpacer, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { getFlattenedObject } from '@kbn/std'; +import { CspFinding } from '../types'; +import * as TEXT from '../translations'; + +const getDescriptionDisplay = (value: unknown) => { + if (value === undefined) return 'undefined'; + if (typeof value === 'boolean' || value === null) { + return {JSON.stringify(value)}; + } + + if (typeof value === 'object') { + return ( + + {JSON.stringify(value, null, 2)} + + ); + } + + return {value as string}; +}; + +export const prepareDescriptionList = (data: Record) => + Object.entries(getFlattenedObject(data)) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([key, value]) => ({ + title: ( + + {key} + + ), + description: getDescriptionDisplay(value), + })); + +export const ResourceTab = ({ data }: { data: CspFinding }) => { + const { euiTheme } = useEuiTheme(); + + const accordions = useMemo( + () => [ + { + title: TEXT.RESOURCE, + id: 'resourceAccordion', + listItems: prepareDescriptionList(data.resource), + }, + { + title: TEXT.HOST, + id: 'hostAccordion', + listItems: prepareDescriptionList(data.host), + }, + ], + [data.host, data.resource] + ); + + return ( + <> + {accordions.map((accordion) => ( + + + + {accordion.title} +
+ } + arrowDisplay="right" + initialIsOpen + > + + + + + + ))} + + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_table.tsx index 189cf4299438..60e521be7997 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_table.tsx @@ -8,7 +8,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import { type Criteria, EuiToolTip, - EuiLink, EuiTableFieldDataColumnType, EuiEmptyPrompt, EuiBasicTable, @@ -17,13 +16,14 @@ import { } from '@elastic/eui'; import moment from 'moment'; import { SortDirection } from '@kbn/data-plugin/common'; +import { EuiTableActionsColumnType } from '@elastic/eui/src/components/basic_table/table_types'; import { extractErrorMessage } from '../../../common/utils/helpers'; import * as TEST_SUBJECTS from './test_subjects'; import * as TEXT from './translations'; import type { CspFinding } from './types'; import { CspEvaluationBadge } from '../../components/csp_evaluation_badge'; import type { FindingsGroupByNoneQuery, CspFindingsResult } from './use_findings'; -import { FindingsRuleFlyout } from './findings_flyout'; +import { FindingsRuleFlyout } from './findings_flyout/findings_flyout'; interface BaseFindingsTableProps extends FindingsGroupByNoneQuery { setQuery(query: Partial): void; @@ -42,6 +42,65 @@ const FindingsTableComponent = ({ }: FindingsTableProps) => { const [selectedFinding, setSelectedFinding] = useState(); + const columns: Array< + EuiTableFieldDataColumnType | EuiTableActionsColumnType + > = useMemo( + () => [ + { + width: '40px', + actions: [ + { + name: 'Expand', + description: 'Expand', + type: 'icon', + icon: 'expand', + onClick: (item) => setSelectedFinding(item), + }, + ], + }, + { + field: 'resource_id', + name: TEXT.RESOURCE_ID, + truncateText: true, + width: '15%', + sortable: true, + render: resourceFilenameRenderer, + }, + { + field: 'result.evaluation', + name: TEXT.RESULT, + width: '100px', + sortable: true, + render: resultEvaluationRenderer, + }, + { + field: 'rule.name', + name: TEXT.RULE, + sortable: true, + }, + { + field: 'cluster_id', + name: TEXT.SYSTEM_ID, + truncateText: true, + sortable: true, + }, + { + field: 'rule.section', + name: TEXT.CIS_SECTION, + sortable: true, + truncateText: true, + }, + { + field: '@timestamp', + name: TEXT.LAST_CHECKED, + truncateText: true, + sortable: true, + render: timestampRenderer, + }, + ], + [] + ); + const pagination = useMemo( () => getEuiPaginationFromEsSearchSource({ @@ -54,13 +113,6 @@ const FindingsTableComponent = ({ const sorting = useMemo(() => getEuiSortFromEsSearchSource(sort), [sort]); - const getCellProps = useCallback( - (item: CspFinding, column: EuiTableFieldDataColumnType) => ({ - onClick: column.field === 'rule.name' ? () => setSelectedFinding(item) : undefined, - }), - [] - ); - const onTableChange = useCallback( (params: Criteria) => { setQuery(getEsSearchQueryFromEuiTableParams(params)); @@ -90,7 +142,7 @@ const FindingsTableComponent = ({ pagination={pagination} sorting={sorting} onChange={onTableChange} - cellProps={getCellProps} + hasActions /> {selectedFinding && ( ( - {moment.duration(moment().diff(timestamp)).humanize()} + {moment(timestamp).fromNow()} ); @@ -148,47 +200,8 @@ const resourceFilenameRenderer = (filename: string) => ( ); -const ruleNameRenderer = (name: string) => ( - - {name} - -); - const resultEvaluationRenderer = (type: PropsOf['type']) => ( ); -const columns: Array> = [ - { - field: 'resource.filename', - name: TEXT.RESOURCE, - truncateText: true, - width: '15%', - sortable: true, - render: resourceFilenameRenderer, - }, - { - field: 'rule.name', - name: TEXT.RULE_NAME, - truncateText: true, - render: ruleNameRenderer, - sortable: true, - }, - { - field: 'result.evaluation', - name: TEXT.EVALUATION, - width: '100px', - render: resultEvaluationRenderer, - sortable: true, - }, - { - field: '@timestamp', - width: '100px', - name: TEXT.TIMESTAMP, - truncateText: true, - render: timestampRenderer, - sortable: true, - }, -]; - export const FindingsTable = React.memo(FindingsTableComponent); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/translations.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/translations.ts index 5ffa474e7a63..df950e59a90d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/translations.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/translations.ts @@ -42,6 +42,41 @@ export const RESOURCE = i18n.translate('xpack.csp.findings.resourceLabel', { defaultMessage: 'Resource', }); +export const RESOURCE_ID = i18n.translate( + 'xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel', + { + defaultMessage: 'Resource ID', + } +); + +export const RESULT = i18n.translate( + 'xpack.csp.findings.findingsTable.findingsTableColumn.resultColumnLabel', + { + defaultMessage: 'Result', + } +); + +export const SYSTEM_ID = i18n.translate( + 'xpack.csp.findings.findingsTable.findingsTableColumn.systemIdColumnLabel', + { + defaultMessage: 'System ID', + } +); + +export const CIS_SECTION = i18n.translate( + 'xpack.csp.findings.findingsTable.findingsTableColumn.ruleSectionColumnLabel', + { + defaultMessage: 'CIS Section', + } +); + +export const LAST_CHECKED = i18n.translate( + 'xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel', + { + defaultMessage: 'Last Checked', + } +); + export const FILENAME = i18n.translate('xpack.csp.findings.filenameLabel', { defaultMessage: 'Filename', }); @@ -106,7 +141,7 @@ export const AUDIT = i18n.translate('xpack.csp.findings.auditLabel', { defaultMessage: 'Audit', }); -export const RESULT = i18n.translate('xpack.csp.findings.resultLabel', { +export const RESULT_DETAILS = i18n.translate('xpack.csp.findings.resultLabel', { defaultMessage: 'Result Details', }); @@ -144,7 +179,7 @@ export const ID = i18n.translate('xpack.csp.findings.idLabel', { }); export const HOST = i18n.translate('xpack.csp.findings.hostLabel', { - defaultMessage: 'HOST', + defaultMessage: 'Host', }); export const ARCHITECTURE = i18n.translate('xpack.csp.findings.architectureLabel', { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/types.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/types.ts index 79d8f3507c89..405ea9f23137 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/types.ts @@ -69,6 +69,7 @@ interface CspFindingResource { mode: string; path: string; type: string; + [other_keys: string]: unknown; } interface CspFindingHost { @@ -88,6 +89,7 @@ interface CspFindingHost { family: string; name: string; }; + [other_keys: string]: unknown; } interface CspFindingAgent { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/use_findings_by_resource.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/use_findings_by_resource.ts index b5596d55a666..36d7ef7b32c9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/use_findings_by_resource.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/use_findings_by_resource.ts @@ -55,7 +55,7 @@ export const getFindingsByResourceAggQuery = ({ sources: [ { resource_id: { terms: { field: 'resource_id.keyword' } } }, { cluster_id: { terms: { field: 'cluster_id.keyword' } } }, - { cis_section: { terms: { field: 'rule.section' } } }, + { cis_section: { terms: { field: 'rule.section.keyword' } } }, ], }, aggs: { diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/latest_findings_mapping.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/latest_findings_mapping.ts index 4392cc5a47d3..80c35083e09a 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_indices/latest_findings_mapping.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/latest_findings_mapping.ts @@ -103,6 +103,24 @@ export const latestFindingsMapping: MappingTypeMapping = { }, }, }, + section: { + type: 'text', + fields: { + keyword: { + ignore_above: 1024, + type: 'keyword', + }, + }, + }, + }, + }, + cluster_id: { + type: 'text', + fields: { + keyword: { + ignore_above: 1024, + type: 'keyword', + }, }, }, }, diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index d1c0d7c0eb01..9fdea8e97938 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -10,7 +10,7 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/type import type { ComplianceDashboardData } from '../../../common/types'; import { LATEST_FINDINGS_INDEX_PATTERN, STATS_ROUTE_PATH } from '../../../common/constants'; import { CspAppContext } from '../../plugin'; -import { getResourcesTypes } from './get_resources_types'; +import { getGroupedFindingsEvaluation } from './get_grouped_findings_evaluation'; import { ClusterWithoutTrend, getClusters } from './get_clusters'; import { getStats } from './get_stats'; import { CspRouter } from '../../types'; @@ -55,12 +55,14 @@ export const defineGetComplianceDashboardRoute = ( match_all: {}, }; - const [stats, resourcesTypes, clustersWithoutTrends, trends] = await Promise.all([ - getStats(esClient, query, pitId), - getResourcesTypes(esClient, query, pitId), - getClusters(esClient, query, pitId), - getTrends(esClient), - ]); + const [stats, groupedFindingsEvaluation, clustersWithoutTrends, trends] = await Promise.all( + [ + getStats(esClient, query, pitId), + getGroupedFindingsEvaluation(esClient, query, pitId), + getClusters(esClient, query, pitId), + getTrends(esClient), + ] + ); // Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive // ends. Not waiting on the promise returned from the `closePointInTime` call to avoid delaying the request @@ -73,7 +75,7 @@ export const defineGetComplianceDashboardRoute = ( const body: ComplianceDashboardData = { stats, - resourcesTypes, + groupedFindingsEvaluation, clusters, trend, }; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts index 1630d4fe3537..a24e60dd7be8 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.test.ts @@ -66,7 +66,7 @@ describe('getClustersFromAggs', () => { totalPassed: 6, postureScore: 50.0, }, - resourcesTypes: [ + groupedFindingsEvaluation: [ { name: 'foo_type', totalFindings: 6, diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts index 4a4afda9a12b..239511f71fea 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_clusters.ts @@ -12,14 +12,17 @@ import type { SearchRequest, } from '@elastic/elasticsearch/lib/api/types'; import { Cluster } from '../../../common/types'; -import { getResourceTypeFromAggs, resourceTypeAggQuery } from './get_resources_types'; -import type { ResourceTypeQueryResult } from './get_resources_types'; +import { + getFailedFindingsFromAggs, + failedFindingsAggQuery, +} from './get_grouped_findings_evaluation'; +import type { FailedFindingsQueryResult } from './get_grouped_findings_evaluation'; import { findingsEvaluationAggsQuery, getStatsFromFindingsEvaluationsAggs } from './get_stats'; import { KeyDocCount } from './compliance_dashboard'; type UnixEpochTime = number; -export interface ClusterBucket extends ResourceTypeQueryResult, KeyDocCount { +export interface ClusterBucket extends FailedFindingsQueryResult, KeyDocCount { failed_findings: { doc_count: number; }; @@ -59,7 +62,7 @@ export const getClustersQuery = (query: QueryDslQueryContainer, pitId: string): }, }, }, - ...resourceTypeAggQuery, + ...failedFindingsAggQuery, ...findingsEvaluationAggsQuery, }, }, @@ -92,12 +95,12 @@ export const getClustersFromAggs = (clusters: ClusterBucket[]): ClusterWithoutTr const resourcesTypesAggs = cluster.aggs_by_resource_type.buckets; if (!Array.isArray(resourcesTypesAggs)) throw new Error('missing aggs by resource type per cluster'); - const resourcesTypes = getResourceTypeFromAggs(resourcesTypesAggs); + const groupedFindingsEvaluation = getFailedFindingsFromAggs(resourcesTypesAggs); return { meta, stats, - resourcesTypes, + groupedFindingsEvaluation, }; }); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_resources_types.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.test.ts similarity index 77% rename from x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_resources_types.test.ts rename to x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.test.ts index 411290738f33..46c73c425015 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_resources_types.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { getResourceTypeFromAggs, ResourceTypeBucket } from './get_resources_types'; +import { getFailedFindingsFromAggs, FailedFindingsBucket } from './get_grouped_findings_evaluation'; -const resourceTypeBuckets: ResourceTypeBucket[] = [ +const resourceTypeBuckets: FailedFindingsBucket[] = [ { key: 'foo_type', doc_count: 41, @@ -30,9 +30,9 @@ const resourceTypeBuckets: ResourceTypeBucket[] = [ }, ]; -describe('getResourceTypeFromAggs', () => { +describe('getFailedFindingsFromAggs', () => { it('should return value matching ComplianceDashboardData["resourcesTypes"]', async () => { - const resourceTypes = getResourceTypeFromAggs(resourceTypeBuckets); + const resourceTypes = getFailedFindingsFromAggs(resourceTypeBuckets); expect(resourceTypes).toEqual([ { name: 'foo_type', diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_resources_types.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.ts similarity index 66% rename from x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_resources_types.ts rename to x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.ts index ecb5ee755fb6..66e5e8e271a2 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_resources_types.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/get_grouped_findings_evaluation.ts @@ -14,11 +14,11 @@ import type { import type { ComplianceDashboardData } from '../../../common/types'; import { KeyDocCount } from './compliance_dashboard'; -export interface ResourceTypeQueryResult { - aggs_by_resource_type: Aggregation; +export interface FailedFindingsQueryResult { + aggs_by_resource_type: Aggregation; } -export interface ResourceTypeBucket extends KeyDocCount { +export interface FailedFindingsBucket extends KeyDocCount { failed_findings: { doc_count: number; }; @@ -27,10 +27,10 @@ export interface ResourceTypeBucket extends KeyDocCount { }; } -export const resourceTypeAggQuery = { +export const failedFindingsAggQuery = { aggs_by_resource_type: { terms: { - field: 'type.keyword', + field: 'rule.section.keyword', }, aggs: { failed_findings: { @@ -46,15 +46,15 @@ export const resourceTypeAggQuery = { export const getRisksEsQuery = (query: QueryDslQueryContainer, pitId: string): SearchRequest => ({ size: 0, query, - aggs: resourceTypeAggQuery, + aggs: failedFindingsAggQuery, pit: { id: pitId, }, }); -export const getResourceTypeFromAggs = ( - queryResult: ResourceTypeBucket[] -): ComplianceDashboardData['resourcesTypes'] => +export const getFailedFindingsFromAggs = ( + queryResult: FailedFindingsBucket[] +): ComplianceDashboardData['groupedFindingsEvaluation'] => queryResult.map((bucket) => ({ name: bucket.key, totalFindings: bucket.doc_count, @@ -62,17 +62,19 @@ export const getResourceTypeFromAggs = ( totalPassed: bucket.passed_findings.doc_count || 0, })); -export const getResourcesTypes = async ( +export const getGroupedFindingsEvaluation = async ( esClient: ElasticsearchClient, query: QueryDslQueryContainer, pitId: string -): Promise => { - const resourceTypesQueryResult = await esClient.search( +): Promise => { + const resourceTypesQueryResult = await esClient.search( getRisksEsQuery(query, pitId) ); - const resourceTypes = resourceTypesQueryResult.aggregations?.aggs_by_resource_type.buckets; - if (!Array.isArray(resourceTypes)) throw new Error('missing resources types buckets'); + const ruleSections = resourceTypesQueryResult.aggregations?.aggs_by_resource_type.buckets; + if (!Array.isArray(ruleSections)) { + return []; + } - return getResourceTypeFromAggs(resourceTypes); + return getFailedFindingsFromAggs(ruleSections); }; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/findings/findings.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/findings/findings.test.ts deleted file mode 100644 index 35d359929af9..000000000000 --- a/x-pack/plugins/cloud_security_posture/server/routes/findings/findings.test.ts +++ /dev/null @@ -1,505 +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 { - elasticsearchClientMock, - ElasticsearchClientMock, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '@kbn/core/server/elasticsearch/client/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { KibanaRequest } from '@kbn/core/server/http/router/request'; -import { httpServerMock, httpServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { CspAppService } from '../../lib/csp_app_services'; -import { CspAppContext } from '../../plugin'; -import { - defineFindingsIndexRoute, - findingsInputSchema, - DEFAULT_FINDINGS_PER_PAGE, -} from './findings'; - -export const getMockCspContext = (mockEsClient: ElasticsearchClientMock): KibanaRequest => { - return { - core: { - elasticsearch: { - client: { asCurrentUser: mockEsClient }, - }, - }, - fleet: { authz: { fleet: { all: true } } }, - } as unknown as KibanaRequest; -}; - -describe('findings API', () => { - let logger: ReturnType; - - beforeEach(() => { - logger = loggingSystemMock.createLogger(); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('validate the API route path', async () => { - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - - const [config, _] = router.get.mock.calls[0]; - - expect(config.path).toEqual('/internal/cloud_security_posture/findings'); - }); - - it('should accept to a user with fleet.all privilege', async () => { - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - const [_, handler] = router.get.mock.calls[0]; - - const mockContext = { - fleet: { authz: { fleet: { all: true } } }, - } as unknown as KibanaRequest; - - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest(); - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - - expect(res.forbidden).toHaveBeenCalledTimes(0); - }); - - it('should reject to a user without fleet.all privilege', async () => { - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - const [_, handler] = router.get.mock.calls[0]; - - const mockContext = { - fleet: { authz: { fleet: { all: false } } }, - } as unknown as KibanaRequest; - - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest(); - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - - expect(res.forbidden).toHaveBeenCalledTimes(1); - }); - - describe('test input schema', () => { - it('expect to find default values', async () => { - const validatedQuery = findingsInputSchema.validate({}); - - expect(validatedQuery).toMatchObject({ - page: 1, - per_page: DEFAULT_FINDINGS_PER_PAGE, - sort_order: expect.stringMatching('desc'), - }); - }); - - it('should throw when page field is not a positive integer', async () => { - expect(() => { - findingsInputSchema.validate({ page: -2 }); - }).toThrow(); - }); - - it('should throw when per_page field is not a positive integer', async () => { - expect(() => { - findingsInputSchema.validate({ per_page: -2 }); - }).toThrow(); - }); - - it('should throw when latest_run is not a boolean', async () => { - expect(() => { - findingsInputSchema.validate({ latest_cycle: 'some string' }); // expects to get boolean - }).toThrow(); - }); - - it('should not throw when latest_run is a boolean', async () => { - expect(() => { - findingsInputSchema.validate({ latest_cycle: true }); - }).not.toThrow(); - }); - - it('should throw when sort_field is not string', async () => { - expect(() => { - findingsInputSchema.validate({ sort_field: true }); - }).toThrow(); - }); - - it('should not throw when sort_field is a string', async () => { - expect(() => { - findingsInputSchema.validate({ sort_field: 'field1' }); - }).not.toThrow(); - }); - - it('should throw when sort_order is not `asc` or `desc`', async () => { - expect(() => { - findingsInputSchema.validate({ sort_order: 'Other Direction' }); - }).toThrow(); - }); - - it('should not throw when `asc` is input for sort_order field', async () => { - expect(() => { - findingsInputSchema.validate({ sort_order: 'asc' }); - }).not.toThrow(); - }); - - it('should not throw when `desc` is input for sort_order field', async () => { - expect(() => { - findingsInputSchema.validate({ sort_order: 'desc' }); - }).not.toThrow(); - }); - - it('should throw when fields is not string', async () => { - expect(() => { - findingsInputSchema.validate({ fields: ['field1', 'field2'] }); - }).toThrow(); - }); - - it('should not throw when fields is a string', async () => { - expect(() => { - findingsInputSchema.validate({ sort_field: 'field1, field2' }); - }).not.toThrow(); - }); - }); - - describe('test query building', () => { - it('takes cycle_id and validate the filter was built right', async () => { - const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - - const [_, handler] = router.get.mock.calls[0]; - const mockContext = getMockCspContext(mockEsClient); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { latest_cycle: true }, - }); - - mockEsClient.search.mockResolvedValueOnce( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values - { - aggregations: { - group: { - buckets: [ - { - group_docs: { - hits: { - hits: [{ fields: { 'cycle_id.keyword': ['randomId1'] } }], - }, - }, - }, - ], - }, - }, - } - ); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - - expect(mockEsClient.search).toHaveBeenCalledTimes(2); - - const handlerArgs = mockEsClient.search.mock.calls[1][0]; - - expect(handlerArgs).toMatchObject({ - query: { - bool: { - filter: [{ terms: { 'cycle_id.keyword': ['randomId1'] } }], - }, - }, - }); - }); - - it('validate that default sort is timestamp desc', async () => { - const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - - const [_, handler] = router.get.mock.calls[0]; - const mockContext = getMockCspContext(mockEsClient); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { - sort_order: 'desc', - }, - }); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - - const handlerArgs = mockEsClient.search.mock.calls[0][0]; - - expect(handlerArgs).toMatchObject({ - sort: [{ '@timestamp': { order: 'desc' } }], - }); - }); - - it('should build sort request by `sort_field` and `sort_order` - asc', async () => { - const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - - const [_, handler] = router.get.mock.calls[0]; - const mockContext = getMockCspContext(mockEsClient); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { - sort_field: 'agent.id', - sort_order: 'asc', - }, - }); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - - const handlerArgs = mockEsClient.search.mock.calls[0][0]; - - expect(handlerArgs).toMatchObject({ - sort: [{ 'agent.id': 'asc' }], - }); - }); - - it('should build sort request by `sort_field` and `sort_order` - desc', async () => { - const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - - const [_, handler] = router.get.mock.calls[0]; - const mockContext = getMockCspContext(mockEsClient); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { - sort_field: 'agent.id', - sort_order: 'desc', - }, - }); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - - const handlerArgs = mockEsClient.search.mock.calls[0][0]; - - expect(handlerArgs).toMatchObject({ - sort: [{ 'agent.id': 'desc' }], - }); - }); - - it('takes `page` number and `per_page` validate that the requested selected page was called', async () => { - const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - - const [_, handler] = router.get.mock.calls[0]; - const mockContext = getMockCspContext(mockEsClient); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { - per_page: 10, - page: 3, - }, - }); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - await handler(context, req, res); - - expect(mockEsClient.search).toHaveBeenCalledTimes(1); - const handlerArgs = mockEsClient.search.mock.calls[0][0]; - - expect(handlerArgs).toMatchObject({ - from: 20, - size: 10, - }); - }); - - it('should format request by fields filter', async () => { - const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - defineFindingsIndexRoute(router, cspContext); - - const [_, handler] = router.get.mock.calls[0]; - - const mockContext = getMockCspContext(mockEsClient); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { - fields: 'field1,field2,field3', - }, - }); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - - const handlerArgs = mockEsClient.search.mock.calls[0][0]; - - expect(handlerArgs).toMatchObject({ - _source: ['field1', 'field2', 'field3'], - }); - }); - - it('takes dslQuery and validate the conversion to esQuery filter', async () => { - const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - - defineFindingsIndexRoute(router, cspContext); - - const [_, handler] = router.get.mock.calls[0]; - const mockContext = getMockCspContext(mockEsClient); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { - kquery: 'result.evaluation.keyword:failed', - }, - }); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - const handlerArgs = mockEsClient.search.mock.calls[0][0]; - - expect(handlerArgs).toMatchObject({ - query: { - bool: { - filter: [ - { - bool: { - minimum_should_match: 1, - should: [{ match: { 'result.evaluation.keyword': 'failed' } }], - }, - }, - ], - }, - }, - }); - }); - - it('takes dslQuery and latest_cycle filter validate the conversion to esQuery filter', async () => { - const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - const router = httpServiceMock.createRouter(); - const cspAppContextService = new CspAppService(); - const cspContext: CspAppContext = { - logger, - service: cspAppContextService, - }; - - defineFindingsIndexRoute(router, cspContext); - const [_, handler] = router.get.mock.calls[0]; - - const mockContext = getMockCspContext(mockEsClient); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { - kquery: 'result.evaluation.keyword:failed', - latest_cycle: true, - }, - }); - - mockEsClient.search.mockResolvedValueOnce( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values - { - aggregations: { - group: { - buckets: [ - { - group_docs: { - hits: { - hits: [{ fields: { 'cycle_id.keyword': ['randomId1'] } }], - }, - }, - }, - ], - }, - }, - } - ); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - await handler(context, req, res); - - const handlerArgs = mockEsClient.search.mock.calls[1][0]; - // console.log(handlerArgs.query.bool); - expect(handlerArgs).toMatchObject({ - query: { - bool: { - filter: [ - { - bool: { - should: [{ match: { 'result.evaluation.keyword': 'failed' } }], - minimum_should_match: 1, - }, - }, - { terms: { 'cycle_id.keyword': ['randomId1'] } }, - ], - }, - }, - }); - }); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/findings/findings.ts b/x-pack/plugins/cloud_security_posture/server/routes/findings/findings.ts deleted file mode 100644 index 4c9851be902b..000000000000 --- a/x-pack/plugins/cloud_security_posture/server/routes/findings/findings.ts +++ /dev/null @@ -1,178 +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 { Logger } from '@kbn/core/server'; -import { SearchRequest, QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; -import { QueryDslBoolQuery } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { schema as rt, TypeOf } from '@kbn/config-schema'; -import type { SortOrder } from '@elastic/elasticsearch/lib/api/types'; -import { transformError } from '@kbn/securitysolution-es-utils'; -import { getLatestCycleIds } from './get_latest_cycle_ids'; - -import { CSP_KUBEBEAT_INDEX_PATTERN, FINDINGS_ROUTE_PATH } from '../../../common/constants'; -import { CspAppContext } from '../../plugin'; -import { CspRouter } from '../../types'; - -type FindingsQuerySchema = TypeOf; - -export const DEFAULT_FINDINGS_PER_PAGE = 20; - -export interface FindingsOptions { - size: number; - from?: number; - page?: number; - sortField?: string; - sortOrder?: SortOrder; - fields?: string[]; -} - -const getPointerForFirstDoc = (page: number, perPage: number): number => - page <= 1 ? 0 : page * perPage - perPage; - -const getSort = (sortField: string | undefined, sortOrder: string) => - sortField - ? { sort: [{ [sortField]: sortOrder }] } - : { sort: [{ '@timestamp': { order: sortOrder } }] }; - -const getSearchFields = (fields: string | undefined) => - fields ? { _source: fields.split(',') } : {}; - -const getFindingsEsQuery = ( - query: QueryDslQueryContainer, - options: FindingsOptions -): SearchRequest => { - return { - index: CSP_KUBEBEAT_INDEX_PATTERN, - query, - ...options, - }; -}; - -const buildLatestCycleFilter = ( - filter: QueryDslQueryContainer[], - latestCycleIds?: string[] -): QueryDslQueryContainer[] => { - if (!!latestCycleIds) { - filter.push({ - terms: { 'cycle_id.keyword': latestCycleIds }, - }); - } - return filter; -}; - -const convertKqueryToElasticsearchQuery = ( - kquery: string | undefined, - logger: Logger -): QueryDslQueryContainer[] => { - let dslFilterQuery: QueryDslBoolQuery['filter']; - try { - dslFilterQuery = kquery ? toElasticsearchQuery(fromKueryExpression(kquery)) : []; - if (!Array.isArray(dslFilterQuery)) { - dslFilterQuery = [dslFilterQuery]; - } - } catch (err) { - logger.warn(`Invalid kuery syntax for the filter (${kquery}) error: ${err.message}`); - throw err; - } - return dslFilterQuery; -}; - -const buildQueryRequest = ( - kquery: string | undefined, - latestCycleIds: string[] | undefined, - logger: Logger -): QueryDslQueryContainer => { - const kqueryFilter = convertKqueryToElasticsearchQuery(kquery, logger); - const filter = buildLatestCycleFilter(kqueryFilter, latestCycleIds); - const query = { - bool: { - filter, - }, - }; - return query; -}; - -const buildOptionsRequest = (queryParams: FindingsQuerySchema): FindingsOptions => ({ - size: queryParams.per_page, - from: getPointerForFirstDoc(queryParams.page, queryParams.per_page), - ...getSort(queryParams.sort_field, queryParams.sort_order), - ...getSearchFields(queryParams.fields), -}); - -export const defineFindingsIndexRoute = (router: CspRouter, cspContext: CspAppContext): void => - router.get( - { - path: FINDINGS_ROUTE_PATH, - validate: { query: findingsInputSchema }, - }, - async (context, request, response) => { - if (!(await context.fleet).authz.fleet.all) { - return response.forbidden(); - } - - try { - const esClient = (await context.core).elasticsearch.client.asCurrentUser; - const options = buildOptionsRequest(request.query); - - const latestCycleIds = - request.query.latest_cycle === true - ? await getLatestCycleIds(esClient, cspContext.logger) - : undefined; - - if (request.query.latest_cycle === true && latestCycleIds === undefined) { - return response.ok({ body: [] }); - } - - const query = buildQueryRequest(request.query.kquery, latestCycleIds, cspContext.logger); - const esQuery = getFindingsEsQuery(query, options); - - const findings = await esClient.search(esQuery); - const hits = findings.hits.hits; - - return response.ok({ body: hits }); - } catch (err) { - const error = transformError(err); - cspContext.logger.error(`Failed to fetch Findings ${error.message}`); - return response.customError({ - body: { message: error.message }, - statusCode: error.statusCode, - }); - } - } - ); - -export const findingsInputSchema = rt.object({ - /** - * The page of objects to return - */ - page: rt.number({ defaultValue: 1, min: 1 }), - /** - * The number of objects to include in each page - */ - per_page: rt.number({ defaultValue: DEFAULT_FINDINGS_PER_PAGE, min: 0 }), - /** - * Boolean flag to indicate for receiving only the latest findings - */ - latest_cycle: rt.maybe(rt.boolean()), - /** - * The field to use for sorting the found objects. - */ - sort_field: rt.maybe(rt.string()), - /** - * The order to sort by - */ - sort_order: rt.oneOf([rt.literal('asc'), rt.literal('desc')], { defaultValue: 'desc' }), - /** - * The fields in the entity to return in the response - */ - fields: rt.maybe(rt.string()), - /** - * kql query - */ - kquery: rt.maybe(rt.string()), -}); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/findings/get_latest_cycle.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/findings/get_latest_cycle.test.ts deleted file mode 100644 index 38a67e95df4a..000000000000 --- a/x-pack/plugins/cloud_security_posture/server/routes/findings/get_latest_cycle.test.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 { - elasticsearchClientMock, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '@kbn/core/server/elasticsearch/client/mocks'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { getLatestCycleIds } from './get_latest_cycle_ids'; - -const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - -describe('get latest cycle ids', () => { - let logger: ReturnType; - - beforeEach(() => { - logger = loggingSystemMock.createLogger(); - jest.resetAllMocks(); - }); - - it('expect to throw when find empty bucket', async () => { - mockEsClient.search.mockResolvedValueOnce( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values - { - aggregations: { - group: { - buckets: [{}], - }, - }, - } - ); - expect(getLatestCycleIds(mockEsClient, logger)).rejects.toThrow(); - }); - - it('expect to find 1 cycle id', async () => { - mockEsClient.search.mockResolvedValueOnce( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values - { - aggregations: { - group: { - buckets: [ - { - group_docs: { - hits: { - hits: [{ fields: { 'cycle_id.keyword': ['randomId1'] } }], - }, - }, - }, - ], - }, - }, - } - ); - const response = await getLatestCycleIds(mockEsClient, logger); - expect(response).toEqual(expect.arrayContaining(['randomId1'])); - }); - - it('expect to find multiple cycle ids', async () => { - mockEsClient.search.mockResolvedValueOnce( - // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values - { - aggregations: { - group: { - buckets: [ - { - group_docs: { - hits: { - hits: [{ fields: { 'cycle_id.keyword': ['randomId1'] } }], - }, - }, - }, - { - group_docs: { - hits: { - hits: [{ fields: { 'cycle_id.keyword': ['randomId2'] } }], - }, - }, - }, - { - group_docs: { - hits: { - hits: [{ fields: { 'cycle_id.keyword': ['randomId3'] } }], - }, - }, - }, - ], - }, - }, - } - ); - const response = await getLatestCycleIds(mockEsClient, logger); - expect(response).toEqual(expect.arrayContaining(['randomId1', 'randomId2', 'randomId3'])); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/findings/get_latest_cycle_ids.ts b/x-pack/plugins/cloud_security_posture/server/routes/findings/get_latest_cycle_ids.ts deleted file mode 100644 index 781a60727f59..000000000000 --- a/x-pack/plugins/cloud_security_posture/server/routes/findings/get_latest_cycle_ids.ts +++ /dev/null @@ -1,59 +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 { Logger } from '@kbn/core/server'; -import { AggregationsFiltersAggregate, SearchRequest } from '@elastic/elasticsearch/lib/api/types'; -import type { ElasticsearchClient } from '@kbn/core/server'; -import { AGENT_LOGS_INDEX_PATTERN } from '../../../common/constants'; - -const getAgentLogsEsQuery = (): SearchRequest => ({ - index: AGENT_LOGS_INDEX_PATTERN, - size: 0, - query: { - bool: { - filter: [{ term: { 'status.keyword': 'end' } }], - }, - }, - aggs: { - group: { - terms: { field: 'agent.id.keyword' }, - aggs: { - group_docs: { - top_hits: { - size: 1, - sort: [{ '@timestamp': { order: 'desc' } }], - }, - }, - }, - }, - }, - fields: ['cycle_id.keyword', 'agent.id.keyword'], - _source: false, -}); - -const getCycleId = (v: any): string => v.group_docs.hits.hits?.[0]?.fields['cycle_id.keyword'][0]; - -export const getLatestCycleIds = async ( - esClient: ElasticsearchClient, - logger: Logger -): Promise => { - try { - const agentLogs = await esClient.search(getAgentLogsEsQuery()); - const aggregations = agentLogs.aggregations; - if (!aggregations) { - return; - } - const buckets = (aggregations.group as Record).buckets; - if (!Array.isArray(buckets)) { - return; - } - - return buckets.map(getCycleId); - } catch (err) { - logger.error('Failed to fetch cycle_ids'); - throw new Error('Failed to fetch cycle_ids'); - } -}; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/index.ts b/x-pack/plugins/cloud_security_posture/server/routes/index.ts index a0981e2a956c..f5c500f24a0d 100755 --- a/x-pack/plugins/cloud_security_posture/server/routes/index.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/index.ts @@ -7,14 +7,12 @@ import { defineGetComplianceDashboardRoute } from './compliance_dashboard/compliance_dashboard'; import { defineGetBenchmarksRoute } from './benchmarks/benchmarks'; -import { defineFindingsIndexRoute as defineGetFindingsIndexRoute } from './findings/findings'; import { defineUpdateRulesConfigRoute } from './configuration/update_rules_configuration'; import { CspAppContext } from '../plugin'; import { CspRouter } from '../types'; export function defineRoutes(router: CspRouter, cspContext: CspAppContext) { defineGetComplianceDashboardRoute(router, cspContext); - defineGetFindingsIndexRoute(router, cspContext); defineGetBenchmarksRoute(router, cspContext); defineUpdateRulesConfigRoute(router, cspContext); } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx index 3807234fd5c1..ea5abb74d5ac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx @@ -92,7 +92,7 @@ export const AuditLogsModal: React.FC = () => { }, { type: 'field', - field: 'outcome', + field: 'event.outcome', header: i18n.translate( 'xpack.enterpriseSearch.appSearch.engines.auditLogsModal.headers.outcome', { @@ -101,7 +101,8 @@ export const AuditLogsModal: React.FC = () => { ), }, { - type: 'message', + type: 'field', + field: 'message', width: '50%', }, ]} diff --git a/x-pack/plugins/fleet/.storybook/context/cloud.ts b/x-pack/plugins/fleet/.storybook/context/cloud.ts index 1291f94cea78..eccb41d6aa8c 100644 --- a/x-pack/plugins/fleet/.storybook/context/cloud.ts +++ b/x-pack/plugins/fleet/.storybook/context/cloud.ts @@ -11,9 +11,9 @@ export const getCloud = ({ isCloudEnabled }: { isCloudEnabled: boolean }) => { const cloud: CloudSetup = { isCloudEnabled, baseUrl: 'https://base.url', - cloudId: 'cloud-id', + cloudId: isCloudEnabled ? 'cloud-id' : undefined, cname: 'found.io', - deploymentUrl: 'https://deployment.url', + deploymentUrl: isCloudEnabled ? 'https://deployment.url' : undefined, organizationUrl: 'https://organization.url', profileUrl: 'https://profile.url', snapshotsUrl: 'https://snapshots.url', diff --git a/x-pack/plugins/fleet/.storybook/context/http.ts b/x-pack/plugins/fleet/.storybook/context/http.ts index 5167ad550036..ff5b36a82aa6 100644 --- a/x-pack/plugins/fleet/.storybook/context/http.ts +++ b/x-pack/plugins/fleet/.storybook/context/http.ts @@ -27,7 +27,7 @@ export const getHttp = (basepath = BASE_PATH) => { serverBasePath: basepath, }, get: (async (path: string, options: HttpFetchOptions) => { - action('get')(path, options); + action('get')(path, JSON.stringify(options)); // TODO: all of this needs revision, as it's far too clunky... but it works for now, // with the few paths we're supporting. if (path === '/api/fleet/agents/setup') { @@ -35,7 +35,7 @@ export const getHttp = (basepath = BASE_PATH) => { isReady = true; return { isReady: false, missing_requirements: ['api_keys', 'fleet_server'] }; } - return { isInitialized: true, nonFatalErrors: [] }; + return { isReady: true, isInitialized: true, nonFatalErrors: [], missing_requirements: [] }; } if (path === '/api/fleet/epm/categories') { @@ -79,9 +79,59 @@ export const getHttp = (basepath = BASE_PATH) => { return { success: true }; } - action(path)('KP: UNSUPPORTED ROUTE'); + if (path.match('/api/fleet/agent_policies')) { + return { items: [] }; + } + + if (path.match('/api/fleet/settings')) { + return { item: { fleet_server_hosts: [] } }; + } + + if (path.match('/api/fleet/outputs')) { + return { + items: [{ name: 'Default Output', is_default: true, hosts: ['https://test.es:9200'] }], + }; + } + + action(path)(`UNSUPPORTED ROUTE: GET ${path}`); return {}; }) as HttpHandler, + post: (async (path: string, options: HttpFetchOptions) => { + action('post')(path, JSON.stringify(options)); + + if (path.match('/api/fleet/settings')) { + return { items: [] }; + } + + if (path.match('/api/fleet/service_tokens')) { + return { + name: 'test-token', + value: 'test-token-value', + }; + } + + if (path.match('/api/fleet/agent_policies')) { + return { + item: { + id: 'test-policy', + name: 'Test Policy', + namespace: 'default', + description: 'Test Policy', + monitoring_enabled: ['metrics'], + data_output_id: 'test-output', + monitoring_output_id: 'test-output', + status: 'active', + packagePolicies: ['test-package-policy'], + updated_on: new Date(), + updated_by: 'elastic', + revision: 0, + agents: 0, + }, + }; + } + + action(path)(`UNSUPPORTED ROUTE: POST ${path}`); + }) as HttpHandler, } as unknown as HttpStart; return http; diff --git a/x-pack/plugins/fleet/.storybook/context/index.tsx b/x-pack/plugins/fleet/.storybook/context/index.tsx index a60ecaf5a157..15ee77506cc0 100644 --- a/x-pack/plugins/fleet/.storybook/context/index.tsx +++ b/x-pack/plugins/fleet/.storybook/context/index.tsx @@ -66,7 +66,7 @@ export const StorybookContext: React.FC<{ storyContext?: StoryContext }> = ({ chrome: getChrome(), cloud: { ...getCloud({ isCloudEnabled }), - CloudContextProvider: () => <>, + CloudContextProvider: ({ children }) => <>{children}, }, customIntegrations: { ContextProvider: getStorybookContextProvider(), diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts new file mode 100644 index 000000000000..3398de60c078 --- /dev/null +++ b/x-pack/plugins/fleet/common/experimental_features.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. + */ + +export type ExperimentalFeatures = typeof allowedExperimentalValues; + +/** + * A list of allowed values that can be used in `xpack.fleet.enableExperimental`. + * This object is then used to validate and parse the value entered. + */ +export const allowedExperimentalValues = Object.freeze({ + addIntegrationStepsPage: false, +}); + +type ExperimentalConfigKeys = Array; +type Mutable = { -readonly [P in keyof T]: T[P] }; + +const FleetInvalidExperimentalValue = class extends Error {}; +const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly; + +/** + * Parses the string value used in `xpack.fleet.enableExperimental` kibana configuration, + * which should be a string of values delimited by a comma (`,`) + * + * @param configValue + * @throws FleetInvalidExperimentalValue + */ +export const parseExperimentalConfigValue = (configValue: string[]): ExperimentalFeatures => { + const enabledFeatures: Mutable> = {}; + + for (const value of configValue) { + if (!isValidExperimentalValue(value)) { + throw new FleetInvalidExperimentalValue(`[${value}] is not a supported experimental feature`); + } + + enabledFeatures[value as keyof ExperimentalFeatures] = true; + } + + return { + ...allowedExperimentalValues, + ...enabledFeatures, + }; +}; + +export const isValidExperimentalValue = (value: string) => { + return allowedKeys.includes(value as keyof ExperimentalFeatures); +}; + +export const getExperimentalAllowedValues = (): string[] => [...allowedKeys]; diff --git a/x-pack/plugins/fleet/common/index.ts b/x-pack/plugins/fleet/common/index.ts index 46a8e2d01fc9..60bbcafc4884 100644 --- a/x-pack/plugins/fleet/common/index.ts +++ b/x-pack/plugins/fleet/common/index.ts @@ -11,6 +11,7 @@ export * from './constants'; export * from './services'; export * from './types'; +export * from './experimental_features'; export type { FleetAuthz } from './authz'; export { calculateAuthz } from './authz'; export { createFleetAuthzMock } from './mocks'; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 77dd66a5c20d..b3c37f5e567c 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -115,7 +115,10 @@ "type": "string" } }, - "required": ["name", "version"] + "required": [ + "name", + "version" + ] } } } @@ -292,7 +295,17 @@ }, "operationId": "list-all-packages" }, - "parameters": [] + "parameters": [ + { + "in": "query", + "name": "excludeInstallStatus", + "schema": { + "type": "boolean", + "default": false + }, + "description": "Whether to exclude the install status of each package. Enabling this option will opt in to caching for the response via `cache-control` headers. If you don't need up-to-date installation info for a package, and are querying for a list of available packages, providing this flag can improve performance substantially." + } + ] }, "/epm/packages/_bulk": { "post": { @@ -337,13 +350,21 @@ "properties": { "status": { "type": "string", - "enum": ["installed", "installing", "install_failed", "not_installed"] + "enum": [ + "installed", + "installing", + "install_failed", + "not_installed" + ] }, "savedObject": { "type": "string" } }, - "required": ["status", "savedObject"] + "required": [ + "status", + "savedObject" + ] } ] } @@ -399,11 +420,16 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -462,11 +488,16 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -518,13 +549,21 @@ "properties": { "status": { "type": "string", - "enum": ["installed", "installing", "install_failed", "not_installed"] + "enum": [ + "installed", + "installing", + "install_failed", + "not_installed" + ] }, "savedObject": { "type": "string" } }, - "required": ["status", "savedObject"] + "required": [ + "status", + "savedObject" + ] } ] } @@ -587,7 +626,10 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } }, "_meta": { @@ -595,12 +637,18 @@ "properties": { "install_source": { "type": "string", - "enum": ["registry", "upload", "bundled"] + "enum": [ + "registry", + "upload", + "bundled" + ] } } } }, - "required": ["items"] + "required": [ + "items" + ] } } } @@ -661,11 +709,16 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } } }, - "required": ["items"] + "required": [ + "items" + ] } } } @@ -736,11 +789,16 @@ ] } }, - "required": ["id", "type"] + "required": [ + "id", + "type" + ] } } }, - "required": ["items"] + "required": [ + "items" + ] } } } @@ -840,7 +898,9 @@ "$ref": "#/components/schemas/package_usage_stats" } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -915,7 +975,10 @@ "type": "string" } }, - "required": ["admin_username", "admin_password"] + "required": [ + "admin_username", + "admin_password" + ] } } } @@ -1156,7 +1219,9 @@ "type": "string" } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -1217,7 +1282,9 @@ "$ref": "#/components/schemas/agent" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1240,7 +1307,9 @@ "$ref": "#/components/schemas/agent" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1266,10 +1335,14 @@ "properties": { "action": { "type": "string", - "enum": ["deleted"] + "enum": [ + "deleted" + ] } }, - "required": ["action"] + "required": [ + "action" + ] } } } @@ -1389,7 +1462,9 @@ "type": "string" } }, - "required": ["policy_id"] + "required": [ + "policy_id" + ] } } } @@ -1436,7 +1511,9 @@ }, "statusCode": { "type": "number", - "enum": [400] + "enum": [ + 400 + ] } } } @@ -1544,7 +1621,9 @@ "type": "string" } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -1580,7 +1659,10 @@ ] } }, - "required": ["policy_id", "agents"] + "required": [ + "policy_id", + "agents" + ] } } } @@ -1608,7 +1690,9 @@ "type": "string" } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -1647,7 +1731,9 @@ ] } }, - "required": ["agents"] + "required": [ + "agents" + ] } } } @@ -1682,7 +1768,12 @@ "type": "number" } }, - "required": ["items", "total", "page", "perPage"] + "required": [ + "items", + "total", + "page", + "perPage" + ] } } } @@ -1766,7 +1857,9 @@ "$ref": "#/components/schemas/agent_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1791,7 +1884,9 @@ "$ref": "#/components/schemas/agent_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1845,7 +1940,9 @@ "$ref": "#/components/schemas/agent_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -1864,7 +1961,9 @@ "type": "string" } }, - "required": ["name"] + "required": [ + "name" + ] } } }, @@ -1891,7 +1990,10 @@ "type": "boolean" } }, - "required": ["id", "success"] + "required": [ + "id", + "success" + ] } } } @@ -1907,7 +2009,9 @@ "type": "string" } }, - "required": ["agentPolicyId"] + "required": [ + "agentPolicyId" + ] } } } @@ -1983,7 +2087,12 @@ "type": "number" } }, - "required": ["items", "page", "perPage", "total"] + "required": [ + "items", + "page", + "perPage", + "total" + ] } } } @@ -2009,7 +2118,9 @@ }, "action": { "type": "string", - "enum": ["created"] + "enum": [ + "created" + ] } } } @@ -2052,7 +2163,9 @@ "$ref": "#/components/schemas/enrollment_api_key" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2074,10 +2187,14 @@ "properties": { "action": { "type": "string", - "enum": ["deleted"] + "enum": [ + "deleted" + ] } }, - "required": ["action"] + "required": [ + "action" + ] } } } @@ -2127,7 +2244,12 @@ "type": "number" } }, - "required": ["items", "page", "perPage", "total"] + "required": [ + "items", + "page", + "perPage", + "total" + ] } } } @@ -2152,7 +2274,9 @@ }, "action": { "type": "string", - "enum": ["created"] + "enum": [ + "created" + ] } } } @@ -2194,7 +2318,9 @@ "$ref": "#/components/schemas/enrollment_api_key" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2215,10 +2341,14 @@ "properties": { "action": { "type": "string", - "enum": ["deleted"] + "enum": [ + "deleted" + ] } }, - "required": ["action"] + "required": [ + "action" + ] } } } @@ -2260,7 +2390,9 @@ "type": "number" } }, - "required": ["items"] + "required": [ + "items" + ] } } } @@ -2286,7 +2418,9 @@ "$ref": "#/components/schemas/package_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2348,7 +2482,9 @@ "type": "boolean" } }, - "required": ["packagePolicyIds"] + "required": [ + "packagePolicyIds" + ] } } } @@ -2373,7 +2509,10 @@ "type": "boolean" } }, - "required": ["id", "success"] + "required": [ + "id", + "success" + ] } } } @@ -2404,7 +2543,9 @@ } } }, - "required": ["packagePolicyIds"] + "required": [ + "packagePolicyIds" + ] } } } @@ -2429,7 +2570,10 @@ "type": "boolean" } }, - "required": ["id", "success"] + "required": [ + "id", + "success" + ] } } } @@ -2458,7 +2602,9 @@ "type": "string" } }, - "required": ["packagePolicyIds"] + "required": [ + "packagePolicyIds" + ] } } } @@ -2483,7 +2629,9 @@ "$ref": "#/components/schemas/upgrade_agent_diff" } }, - "required": ["hasErrors"] + "required": [ + "hasErrors" + ] } } } @@ -2508,7 +2656,9 @@ "$ref": "#/components/schemas/package_policy" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2553,7 +2703,10 @@ "type": "boolean" } }, - "required": ["item", "sucess"] + "required": [ + "item", + "sucess" + ] } } } @@ -2636,7 +2789,9 @@ }, "type": { "type": "string", - "enum": ["elasticsearch"] + "enum": [ + "elasticsearch" + ] }, "is_default": { "type": "boolean" @@ -2657,7 +2812,10 @@ "type": "string" } }, - "required": ["name", "type"] + "required": [ + "name", + "type" + ] } } } @@ -2681,7 +2839,9 @@ "$ref": "#/components/schemas/output" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2714,7 +2874,9 @@ "type": "string" } }, - "required": ["id"] + "required": [ + "id" + ] } } } @@ -2740,7 +2902,9 @@ }, "type": { "type": "string", - "enum": ["elasticsearch"] + "enum": [ + "elasticsearch" + ] }, "is_default": { "type": "boolean" @@ -2764,7 +2928,10 @@ "type": "string" } }, - "required": ["name", "type"] + "required": [ + "name", + "type" + ] } } } @@ -2781,7 +2948,9 @@ "$ref": "#/components/schemas/output" } }, - "required": ["item"] + "required": [ + "item" + ] } } } @@ -2900,11 +3069,17 @@ "type": "string" } }, - "required": ["name", "message"] + "required": [ + "name", + "message" + ] } } }, - "required": ["isInitialized", "nonFatalErrors"] + "required": [ + "isInitialized", + "nonFatalErrors" + ] }, "preconfigured_agent_policies": { "title": "Preconfigured agent policies", @@ -2926,7 +3101,10 @@ "type": "array", "items": { "type": "string", - "enum": ["logs", "metrics"] + "enum": [ + "logs", + "metrics" + ] } }, "namespace": { @@ -3023,14 +3201,20 @@ } } }, - "required": ["type"] + "required": [ + "type" + ] } } } } } }, - "required": ["name", "namespace", "package_policies"] + "required": [ + "name", + "namespace", + "package_policies" + ] }, "settings": { "title": "Settings", @@ -3052,7 +3236,10 @@ } } }, - "required": ["fleet_server_hosts", "id"] + "required": [ + "fleet_server_hosts", + "id" + ] }, "fleet_settings_response": { "title": "Fleet settings response", @@ -3062,7 +3249,9 @@ "$ref": "#/components/schemas/settings" } }, - "required": ["item"] + "required": [ + "item" + ] }, "get_categories_response": { "title": "Get categories response", @@ -3084,7 +3273,11 @@ "type": "number" } }, - "required": ["id", "title", "count"] + "required": [ + "id", + "title", + "count" + ] } }, "items": { @@ -3102,11 +3295,17 @@ "type": "number" } }, - "required": ["id", "title", "count"] + "required": [ + "id", + "title", + "count" + ] } } }, - "required": ["items"] + "required": [ + "items" + ] }, "search_result": { "title": "Search result", @@ -3173,7 +3372,9 @@ } } }, - "required": ["items"] + "required": [ + "items" + ] }, "bulk_install_packages_response": { "title": "Bulk install packages response", @@ -3209,7 +3410,9 @@ } } }, - "required": ["items"] + "required": [ + "items" + ] }, "package_info": { "title": "Package information", @@ -3289,7 +3492,10 @@ "type": "string" } }, - "required": ["src", "path"] + "required": [ + "src", + "path" + ] } }, "icons": { @@ -3339,7 +3545,10 @@ "type": "string" } }, - "required": ["name", "default"] + "required": [ + "name", + "default" + ] } }, "type": { @@ -3349,7 +3558,14 @@ "type": "string" } }, - "required": ["title", "name", "release", "ingeset_pipeline", "type", "package"] + "required": [ + "title", + "name", + "release", + "ingeset_pipeline", + "type", + "package" + ] } }, "download": { @@ -3411,7 +3627,9 @@ "type": "integer" } }, - "required": ["agent_policy_count"] + "required": [ + "agent_policy_count" + ] }, "fleet_status_response": { "title": "Fleet status response", @@ -3424,23 +3642,38 @@ "type": "array", "items": { "type": "string", - "enum": ["tls_required", "api_keys", "fleet_admin_user", "fleet_server"] + "enum": [ + "tls_required", + "api_keys", + "fleet_admin_user", + "fleet_server" + ] } }, "missing_optional_features": { "type": "array", "items": { "type": "string", - "enum": ["encrypted_saved_object_encryption_key_required"] + "enum": [ + "encrypted_saved_object_encryption_key_required" + ] } } }, - "required": ["isReady", "missing_requirements", "missing_optional_features"] + "required": [ + "isReady", + "missing_requirements", + "missing_optional_features" + ] }, "agent_type": { "type": "string", "title": "Agent type", - "enum": ["PERMANENT", "EPHEMERAL", "TEMPORARY"] + "enum": [ + "PERMANENT", + "EPHEMERAL", + "TEMPORARY" + ] }, "agent_metadata": { "title": "Agent metadata", @@ -3449,7 +3682,13 @@ "agent_status": { "type": "string", "title": "Agent status", - "enum": ["offline", "error", "online", "inactive", "warning"] + "enum": [ + "offline", + "error", + "online", + "inactive", + "warning" + ] }, "agent": { "title": "Agent", @@ -3504,7 +3743,13 @@ "type": "string" } }, - "required": ["type", "active", "enrolled_at", "id", "status"] + "required": [ + "type", + "active", + "enrolled_at", + "id", + "status" + ] }, "get_agents_response": { "title": "Get Agent response", @@ -3533,7 +3778,12 @@ "type": "number" } }, - "required": ["items", "total", "page", "perPage"] + "required": [ + "items", + "total", + "page", + "perPage" + ] }, "bulk_upgrade_agents": { "title": "Bulk upgrade agents", @@ -3559,7 +3809,10 @@ ] } }, - "required": ["agents", "version"] + "required": [ + "agents", + "version" + ] }, "upgrade_agent": { "title": "Upgrade agent", @@ -3571,7 +3824,9 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] }, { "type": "object", @@ -3583,7 +3838,9 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } ] }, @@ -3600,7 +3857,12 @@ }, "type": { "type": "string", - "enum": ["POLICY_CHANGE", "UNENROLL", "UPGRADE", "POLICY_REASSIGN"] + "enum": [ + "POLICY_CHANGE", + "UNENROLL", + "UPGRADE", + "POLICY_REASSIGN" + ] } } }, @@ -3614,7 +3876,12 @@ "properties": { "log_level": { "type": "string", - "enum": ["debug", "info", "warning", "error"] + "enum": [ + "debug", + "info", + "warning", + "error" + ] } } } @@ -3642,7 +3909,10 @@ "type": "array", "items": { "type": "string", - "enum": ["metrics", "logs"] + "enum": [ + "metrics", + "logs" + ] } }, "data_output_id": { @@ -3654,7 +3924,10 @@ "nullable": true } }, - "required": ["name", "namespace"] + "required": [ + "name", + "namespace" + ] }, "new_package_policy": { "title": "New package policy", @@ -3677,7 +3950,10 @@ "type": "string" } }, - "required": ["name", "version"] + "required": [ + "name", + "version" + ] }, "namespace": { "type": "string" @@ -3713,7 +3989,10 @@ "type": "object" } }, - "required": ["type", "enabled"] + "required": [ + "type", + "enabled" + ] } }, "policy_id": { @@ -3726,7 +4005,10 @@ "type": "string" } }, - "required": ["inputs", "name"] + "required": [ + "inputs", + "name" + ] }, "package_policy": { "title": "Package policy", @@ -3745,7 +4027,10 @@ "items": {} } }, - "required": ["id", "revision"] + "required": [ + "id", + "revision" + ] }, { "$ref": "#/components/schemas/new_package_policy" @@ -3765,7 +4050,10 @@ }, "status": { "type": "string", - "enum": ["active", "inactive"] + "enum": [ + "active", + "inactive" + ] }, "packagePolicies": { "oneOf": [ @@ -3803,7 +4091,10 @@ "type": "number" } }, - "required": ["id", "status"] + "required": [ + "id", + "status" + ] } ] }, @@ -3880,7 +4171,13 @@ "type": "string" } }, - "required": ["id", "api_key_id", "api_key", "active", "created_at"] + "required": [ + "id", + "api_key_id", + "api_key", + "active", + "created_at" + ] }, "upgrade_diff": { "title": "Package policy Upgrade dryrun", @@ -3946,10 +4243,16 @@ "type": "string" } }, - "required": ["dataset", "type"] + "required": [ + "dataset", + "type" + ] } }, - "required": ["id", "data_stream"] + "required": [ + "id", + "data_stream" + ] } ] }, @@ -3979,7 +4282,9 @@ "type": "string" } }, - "required": ["namespace"] + "required": [ + "namespace" + ] }, "use_output": { "type": "string" @@ -3998,7 +4303,10 @@ "type": "string" } }, - "required": ["name", "version"] + "required": [ + "name", + "version" + ] } } }, @@ -4006,7 +4314,14 @@ "$ref": "#/components/schemas/full_agent_policy_input_stream" } }, - "required": ["id", "name", "revision", "type", "data_stream", "use_output"] + "required": [ + "id", + "name", + "revision", + "type", + "data_stream", + "use_output" + ] } ] }, @@ -4044,7 +4359,11 @@ "type": "string" } }, - "required": ["name", "title", "version"] + "required": [ + "name", + "title", + "version" + ] }, "namespace": { "type": "string" @@ -4080,7 +4399,11 @@ "type": "object" } }, - "required": ["type", "enabled", "streams"] + "required": [ + "type", + "enabled", + "streams" + ] } }, "policy_id": { @@ -4096,7 +4419,12 @@ "type": "boolean" } }, - "required": ["name", "namespace", "policy_id", "enabled"] + "required": [ + "name", + "namespace", + "policy_id", + "enabled" + ] }, "output": { "title": "Output", @@ -4116,7 +4444,9 @@ }, "type": { "type": "string", - "enum": ["elasticsearch"] + "enum": [ + "elasticsearch" + ] }, "hosts": { "type": "array", @@ -4154,7 +4484,12 @@ } } }, - "required": ["id", "is_default", "name", "type"] + "required": [ + "id", + "is_default", + "name", + "type" + ] } } }, @@ -4163,4 +4498,4 @@ "basicAuth": [] } ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 011a242df7fd..36175ffc59a8 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -186,13 +186,16 @@ paths: operationId: list-all-packages parameters: - in: query - name: includeInstallStatus + name: excludeInstallStatus schema: type: boolean default: false description: >- - Whether to include the install status of each package. Defaults to - false to allow for caching of package requests. + Whether to exclude the install status of each package. Enabling this + option will opt in to caching for the response via `cache-control` + headers. If you don't need up-to-date installation info for a package, + and are querying for a list of available packages, providing this flag + can improve performance substantially. /epm/packages/_bulk: post: summary: Packages - Bulk install diff --git a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml index 295ab298745f..9c29b9d18357 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/epm@packages.yaml @@ -11,8 +11,12 @@ get: operationId: list-all-packages parameters: - in: query - name: includeInstallStatus + name: excludeInstallStatus schema: type: boolean default: false - description: Whether to include the install status of each package. Defaults to false to allow for caching of package requests. + description: >- + Whether to exclude the install status of each package. Enabling this option will opt in to + caching for the response via `cache-control` headers. If you don't need up-to-date installation + info for a package, and are querying for a list of available packages, providing this flag can + improve performance substantially. diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts index d04855480dd6..ce1cb5a294f8 100644 --- a/x-pack/plugins/fleet/common/types/index.ts +++ b/x-pack/plugins/fleet/common/types/index.ts @@ -32,6 +32,7 @@ export interface FleetConfigType { packages?: PreconfiguredPackage[]; outputs?: PreconfiguredOutput[]; agentIdVerificationEnabled?: boolean; + enableExperimental?: string[]; developer?: { disableRegistryVersionCheck?: boolean; allowAgentUpgradeSourceUri?: boolean; diff --git a/x-pack/plugins/fleet/cypress/integration/fleet_settings.spec.ts b/x-pack/plugins/fleet/cypress/integration/fleet_settings.spec.ts index 76c8f129584b..338030e91f3d 100644 --- a/x-pack/plugins/fleet/cypress/integration/fleet_settings.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/fleet_settings.spec.ts @@ -30,20 +30,20 @@ describe('Edit settings', () => { it('should update Fleet server hosts', () => { cy.getBySel('editHostsBtn').click(); - cy.get('[placeholder="Specify host URL"').type('http://localhost:8220'); + cy.get('[placeholder="Specify host URL"').type('https://localhost:8220'); cy.intercept('/api/fleet/settings', { - item: { id: 'fleet-default-settings', fleet_server_hosts: ['http://localhost:8220'] }, + item: { id: 'fleet-default-settings', fleet_server_hosts: ['https://localhost:8220'] }, }); cy.intercept('PUT', '/api/fleet/settings', { - fleet_server_hosts: ['http://localhost:8220'], + fleet_server_hosts: ['https://localhost:8220'], }).as('updateSettings'); cy.getBySel('saveApplySettingsBtn').click(); cy.getBySel(CONFIRM_MODAL_BTN).click(); cy.wait('@updateSettings').then((interception) => { - expect(interception.request.body.fleet_server_hosts[0]).to.equal('http://localhost:8220'); + expect(interception.request.body.fleet_server_hosts[0]).to.equal('https://localhost:8220'); }); }); diff --git a/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts b/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts index 4f7f4378c0fc..c269d1fe2ba2 100644 --- a/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/fleet_startup.spec.ts @@ -77,12 +77,15 @@ describe('Fleet startup', () => { }); it('should create Fleet Server policy', () => { + cy.getBySel('fleetServerFlyoutTab-advanced').click(); cy.getBySel('createFleetServerPolicyBtn').click(); // verify policy is created and has fleet server and system package verifyPolicy('Fleet Server policy 1', ['Fleet Server', 'System']); navigateToTab(AGENTS_TAB); + cy.getBySel('fleetServerFlyoutTab-advanced').click(); + // verify create button changed to dropdown cy.getBySel('agentPolicyDropdown'); diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.tsx new file mode 100644 index 000000000000..87b4a1bda7ff --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/advanced_tab.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 { EuiSteps } from '@elastic/eui'; +import React from 'react'; + +import { useAdvancedForm } from './hooks'; + +import { + getAddFleetServerHostStep, + getSelectAgentPolicyStep, + getGenerateServiceTokenStep, + getSetDeploymentModeStep, + getInstallFleetServerStep, + getConfirmFleetServerConnectionStep, +} from './steps'; + +export const AdvancedTab: React.FunctionComponent = () => { + const { + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, + fleetServerPolicyId, + setFleetServerPolicyId, + isFleetServerReady, + serviceToken, + isLoadingServiceToken, + generateServiceToken, + fleetServerHostForm, + deploymentMode, + setDeploymentMode, + } = useAdvancedForm(); + + const steps = [ + getSelectAgentPolicyStep({ + policyId: fleetServerPolicyId, + setPolicyId: setFleetServerPolicyId, + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, + }), + getSetDeploymentModeStep({ + deploymentMode, + setDeploymentMode, + disabled: !Boolean(fleetServerPolicyId), + }), + getAddFleetServerHostStep({ fleetServerHostForm, disabled: !Boolean(fleetServerPolicyId) }), + getGenerateServiceTokenStep({ + serviceToken, + generateServiceToken, + isLoadingServiceToken, + disabled: !Boolean(fleetServerHostForm.isFleetServerHostSubmitted), + }), + getInstallFleetServerStep({ + isFleetServerReady, + serviceToken, + fleetServerHost: fleetServerHostForm.fleetServerHost, + fleetServerPolicyId, + disabled: !Boolean(serviceToken), + }), + getConfirmFleetServerConnectionStep({ isFleetServerReady, disabled: !Boolean(serviceToken) }), + ]; + + return ; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx new file mode 100644 index 000000000000..0508bd9108d3 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/fleet_server_host_combobox.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { useState } from 'react'; +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { EuiComboBox, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; + +interface Props { + fleetServerHost: string | undefined; + fleetServerHostSettings: string[]; + isDisabled: boolean; + isInvalid: boolean; + onFleetServerHostChange: (host: string) => void; +} + +export const FleetServerHostComboBox: React.FunctionComponent = ({ + fleetServerHost, + fleetServerHostSettings, + isDisabled = false, + isInvalid = false, + onFleetServerHostChange, +}) => { + // Track options that are created inline + const [createdOptions, setCreatedOptions] = useState([]); + + const options = [...createdOptions, ...fleetServerHostSettings].map((option) => ({ + label: option, + value: option, + })); + + const handleChange = (selectedOptions: Array>) => { + const host = selectedOptions[0].value ?? ''; + onFleetServerHostChange(host); + }; + + const handleCreateOption = (option: string) => { + setCreatedOptions([...createdOptions, option]); + onFleetServerHostChange(option); + }; + + return ( + + fullWidth + isClearable={false} + singleSelection={{ asPlainText: true }} + placeholder="https://fleet-server-host.com:8220" + options={options} + customOptionText={i18n.translate( + 'xpack.fleet.fleetServerSetup.addFleetServerHostCustomOptionText', + { + defaultMessage: 'Add {searchValuePlaceholder} as a new Fleet Server host', + values: { searchValuePlaceholder: '{searchValue}' }, + } + )} + selectedOptions={fleetServerHost ? [{ label: fleetServerHost, value: fleetServerHost }] : []} + prepend={ + + + + } + noSuggestions={fleetServerHostSettings.length === 0} + data-test-subj="fleetServerHostInput" + isDisabled={isDisabled} + isInvalid={isInvalid} + onChange={handleChange} + onCreateOption={handleCreateOption} + /> + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/index.ts new file mode 100644 index 000000000000..904271187dc8 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/components/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 * from './fleet_server_host_combobox'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/flyout.stories.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/flyout.stories.tsx new file mode 100644 index 000000000000..b08c965d084c --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/flyout.stories.tsx @@ -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 React from 'react'; +import { EuiButton } from '@elastic/eui'; + +import { FleetServerFlyout as FleetServerFlyoutComponent } from '.'; + +export const FleetServerFlyout = () => { + const [isOpen, setIsOpen] = React.useState(false); + + return ( +
+ setIsOpen(true)}> + Show flyout + + {isOpen && setIsOpen(false)} />} +
+ ); +}; + +FleetServerFlyout.args = { + isCloudEnabled: false, +}; + +export default { + component: FleetServerFlyout, + title: 'Sections/Fleet/Agents/Fleet Server Instructions/In Flyout', +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/index.ts new file mode 100644 index 000000000000..6c702d5433ce --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/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. + */ + +// These hooks are tightly coupled to each tab of the Fleet server instructions component, and provide +// all necessary data to drive those UI's +export * from './use_advanced_form'; +export * from './use_quick_start_form'; + +// These are individual hooks for one-off consumption. These are typically composed in the hooks above, +// but exported here to support individual usage. +export * from './use_wait_for_fleet_server'; +export * from './use_select_fleet_server_policy'; +export * from './use_service_token'; +export * from './use_fleet_server_host'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form.ts new file mode 100644 index 000000000000..5ed9f9faa037 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState } from 'react'; + +import type { DeploymentMode } from '../steps'; + +import { useFleetServerHost } from './use_fleet_server_host'; +import { useSelectFleetServerPolicy } from './use_select_fleet_server_policy'; +import { useServiceToken } from './use_service_token'; +import { useWaitForFleetServer } from './use_wait_for_fleet_server'; + +/** + * Provides all data/state required for the "advanced" tab in the Fleet Server instructions/flyout + */ +export const useAdvancedForm = (defaultAgentPolicyId?: string) => { + const { + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, + fleetServerPolicyId, + setFleetServerPolicyId, + } = useSelectFleetServerPolicy(defaultAgentPolicyId); + const { isFleetServerReady } = useWaitForFleetServer(); + const { serviceToken, isLoadingServiceToken, generateServiceToken } = useServiceToken(); + const fleetServerHostForm = useFleetServerHost(); + + const [deploymentMode, setDeploymentMode] = useState('quickstart'); + + return { + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, + fleetServerPolicyId, + setFleetServerPolicyId, + isFleetServerReady, + serviceToken, + isLoadingServiceToken, + generateServiceToken, + fleetServerHostForm, + deploymentMode, + setDeploymentMode, + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts new file mode 100644 index 000000000000..05eeccf4a931 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_fleet_server_host.ts @@ -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 { i18n } from '@kbn/i18n'; +import { useCallback, useEffect, useState } from 'react'; + +import { sendPutSettings, useGetSettings } from '../../../hooks'; + +const URL_REGEX = /^(https):\/\/[^\s$.?#].[^\s]*$/gm; + +export interface FleetServerHostForm { + saveFleetServerHost: () => Promise; + fleetServerHost?: string; + fleetServerHostSettings: string[]; + isFleetServerHostSubmitted: boolean; + setFleetServerHost: React.Dispatch>; + error?: string; + validateFleetServerHost: () => boolean; +} + +export const useFleetServerHost = (): FleetServerHostForm => { + const [fleetServerHost, setFleetServerHost] = useState(); + const [isFleetServerHostSubmitted, setIsFleetServerHostSubmitted] = useState(false); + const [error, setError] = useState(); + + const { data: settings } = useGetSettings(); + + useEffect(() => { + const settingsFleetServerHosts = settings?.item.fleet_server_hosts ?? []; + + if (settingsFleetServerHosts.length) { + setFleetServerHost(settingsFleetServerHosts[0]); + } + }, [settings?.item.fleet_server_hosts]); + + const validateFleetServerHost = useCallback(() => { + if (!fleetServerHost) { + setError( + i18n.translate('xpack.fleet.fleetServerHost.requiredError', { + defaultMessage: 'Fleet server host is required.', + }) + ); + + return false; + } else if (!fleetServerHost.startsWith('https')) { + setError( + i18n.translate('xpack.fleet.fleetServerHost.requiresHttpsError', { + defaultMessage: 'Fleet server host must begin with "https"', + }) + ); + + return false; + } else if (!fleetServerHost.match(URL_REGEX)) { + setError( + i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostInvalidUrlError', { + defaultMessage: 'Invalid URL', + }) + ); + + return false; + } + + return true; + }, [fleetServerHost]); + + const saveFleetServerHost = useCallback(async () => { + setIsFleetServerHostSubmitted(false); + + if (!validateFleetServerHost()) { + return; + } + + // If the Fleet Server host provided already exists in settings, don't submit it + if (settings?.item.fleet_server_hosts.includes(fleetServerHost!)) { + setIsFleetServerHostSubmitted(true); + return; + } + + const res = await sendPutSettings({ + fleet_server_hosts: [fleetServerHost!, ...(settings?.item.fleet_server_hosts || [])], + }); + + if (res.error) { + throw res.error; + } + + setIsFleetServerHostSubmitted(true); + }, [fleetServerHost, settings?.item.fleet_server_hosts, validateFleetServerHost]); + + return { + saveFleetServerHost, + fleetServerHost, + fleetServerHostSettings: settings?.item.fleet_server_hosts ?? [], + isFleetServerHostSubmitted, + setFleetServerHost, + error, + validateFleetServerHost, + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts new file mode 100644 index 000000000000..84fd39aeec37 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_quick_start_form.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState, useCallback, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { sendCreateAgentPolicy, sendGetOneAgentPolicy, useStartServices } from '../../../hooks'; + +import type { NewAgentPolicy } from '../../../types'; + +import { useSelectFleetServerPolicy } from './use_select_fleet_server_policy'; +import { useServiceToken } from './use_service_token'; +import { useFleetServerHost } from './use_fleet_server_host'; + +const QUICK_START_FLEET_SERVER_POLICY_FIELDS: NewAgentPolicy = { + id: 'fleet-server-policy', + name: 'Fleet Server Policy', + description: 'Fleet Server policy generated by Kibana', + namespace: 'default', + has_fleet_server: true, + monitoring_enabled: ['logs', 'metrics'], + is_default_fleet_server: true, +}; + +export type QuickStartCreateFormStatus = 'initial' | 'loading' | 'error' | 'success'; + +export interface QuickStartCreateForm { + status: QuickStartCreateFormStatus; + error?: string; + submit: () => void; + fleetServerHost?: string; + fleetServerHostSettings: string[]; + isFleetServerHostSubmitted: boolean; + onFleetServerHostChange: (value: string) => void; + fleetServerPolicyId?: string; + serviceToken?: string; +} + +/** + * Provides a unified interface that combines the following operations: + * 1. Setting a Fleet Server host in Fleet's settings + * 2. Creating an agent policy that contains the `fleet_server` integration + * 3. Generating a service token used by Fleet Server + */ +export const useQuickStartCreateForm = (): QuickStartCreateForm => { + const [status, setStatus] = useState<'initial' | 'loading' | 'error' | 'success'>('initial'); + const [error, setError] = useState(); + + const { + fleetServerHost, + fleetServerHostSettings, + isFleetServerHostSubmitted, + setFleetServerHost, + validateFleetServerHost, + saveFleetServerHost, + error: fleetServerError, + } = useFleetServerHost(); + + // When a validation error is surfaced from the Fleet Server host form, we want to treat it + // the same way we do errors from the service token or policy creation steps + useEffect(() => { + setStatus('error'); + setError(fleetServerError); + }, [fleetServerError]); + + const { notifications } = useStartServices(); + const { fleetServerPolicyId, setFleetServerPolicyId } = useSelectFleetServerPolicy(); + const { serviceToken, generateServiceToken } = useServiceToken(); + + const onFleetServerHostChange = useCallback( + (value: string) => { + setFleetServerHost(value); + }, + [setFleetServerHost] + ); + + const submit = useCallback(async () => { + try { + if (validateFleetServerHost()) { + setStatus('loading'); + await saveFleetServerHost(); + await generateServiceToken(); + + const existingPolicy = await sendGetOneAgentPolicy( + QUICK_START_FLEET_SERVER_POLICY_FIELDS.id! + ); + + // Don't attempt to create the policy if it's already been created in a previous quick start flow + if (existingPolicy.data?.item) { + setFleetServerPolicyId(existingPolicy.data?.item.id); + } else { + const createPolicyResponse = await sendCreateAgentPolicy( + QUICK_START_FLEET_SERVER_POLICY_FIELDS, + { + withSysMonitoring: true, + } + ); + + setFleetServerPolicyId(createPolicyResponse.data?.item.id); + } + + setFleetServerHost(fleetServerHost); + setStatus('success'); + } + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle', { + defaultMessage: 'Error adding Fleet Server host', + }), + }); + + setStatus('error'); + setError(err.message); + } + }, [ + validateFleetServerHost, + saveFleetServerHost, + generateServiceToken, + setFleetServerHost, + fleetServerHost, + setFleetServerPolicyId, + notifications.toasts, + ]); + + return { + status, + error, + submit, + fleetServerPolicyId, + fleetServerHost, + fleetServerHostSettings, + isFleetServerHostSubmitted, + onFleetServerHostChange, + serviceToken, + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_select_fleet_server_policy.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_select_fleet_server_policy.ts new file mode 100644 index 000000000000..add318e85a7a --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_select_fleet_server_policy.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 { useEffect, useMemo, useState } from 'react'; + +import { useGetAgentPolicies } from '../../../hooks'; +import { policyHasFleetServer } from '../../../services'; + +export const useSelectFleetServerPolicy = (defaultAgentPolicyId?: string) => { + const [fleetServerPolicyId, setFleetServerPolicyId] = useState( + defaultAgentPolicyId + ); + const { data: agentPoliciesData, resendRequest } = useGetAgentPolicies({ + full: true, + }); + + const eligibleFleetServerPolicies = useMemo( + () => + agentPoliciesData + ? agentPoliciesData.items?.filter((item) => policyHasFleetServer(item)) + : [], + [agentPoliciesData] + ); + + useEffect(() => { + // Default to the first policy found with a fleet server integration installed + if (eligibleFleetServerPolicies.length && !fleetServerPolicyId) { + setFleetServerPolicyId(eligibleFleetServerPolicies[0].id); + } + }, [eligibleFleetServerPolicies, fleetServerPolicyId]); + + return { + fleetServerPolicyId, + setFleetServerPolicyId, + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies: resendRequest, + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_service_token.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_service_token.ts new file mode 100644 index 000000000000..3b729a6776b5 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_service_token.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 { useState, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { useStartServices, sendGenerateServiceToken } from '../../../hooks'; + +export const useServiceToken = () => { + const { notifications } = useStartServices(); + const [serviceToken, setServiceToken] = useState(); + const [isLoadingServiceToken, setIsLoadingServiceToken] = useState(false); + + const generateServiceToken = useCallback(async () => { + setIsLoadingServiceToken(true); + try { + const { data } = await sendGenerateServiceToken(); + if (data?.value) { + setServiceToken(data?.value); + } + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.fleetServerSetup.errorGeneratingTokenTitleText', { + defaultMessage: 'Error generating token', + }), + }); + } finally { + setIsLoadingServiceToken(false); + } + }, [notifications.toasts]); + + return { serviceToken, isLoadingServiceToken, generateServiceToken }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts new file mode 100644 index 000000000000..4da59560e408 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.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 { i18n } from '@kbn/i18n'; +import { useEffect, useState } from 'react'; + +import { sendGetFleetStatus, useStartServices } from '../../../hooks'; + +const REFRESH_INTERVAL = 10000; + +/** + * Polls the Fleet status endpoint until the `fleet_server` requirement does not appear + * in the `missing_requirements` list. + */ +export const useWaitForFleetServer = () => { + const [isFleetServerReady, setIsFleetServerReady] = useState(false); + const { notifications } = useStartServices(); + + useEffect(() => { + let interval: ReturnType | null = null; + + if (!isFleetServerReady) { + interval = setInterval(async () => { + try { + const res = await sendGetFleetStatus(); + + if (res.error) { + throw res.error; + } + if (res.data?.isReady && !res.data?.missing_requirements?.includes('fleet_server')) { + setIsFleetServerReady(true); + + if (interval) { + clearInterval(interval); + } + } + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.fleetServerSetup.errorRefreshingFleetServerStatus', { + defaultMessage: 'Error refreshing Fleet Server status', + }), + }); + } + }, REFRESH_INTERVAL); + } + + const cleanup = () => { + if (interval) { + clearInterval(interval); + } + }; + + return cleanup; + }, [notifications.toasts, isFleetServerReady]); + + return { isFleetServerReady }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx new file mode 100644 index 000000000000..6c48b499b955 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + EuiFlexGroup, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiLink, + EuiSpacer, + EuiTab, + EuiTabs, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import styled from 'styled-components'; + +import { useStartServices } from '../../hooks'; + +import { QuickStartTab } from './quick_start_tab'; +import { AdvancedTab } from './advanced_tab'; + +const ContentWrapper = styled(EuiFlexGroup)` + height: 100%; + margin: 0 auto; +`; + +interface Props { + onClose: () => void; +} + +const useFleetServerTabs = () => { + const [currentTab, setCurrentTab] = useState('quickStart'); + + const quickStartTab = { + id: 'quickStart', + name: 'Quick Start', + content: , + }; + + const advancedTab = { + id: 'advanced', + name: 'Advanced', + content: , + }; + + const currentTabContent = + currentTab === 'quickStart' ? quickStartTab.content : advancedTab.content; + + return { tabs: [quickStartTab, advancedTab], currentTab, setCurrentTab, currentTabContent }; +}; + +const Header: React.FunctionComponent<{ + isFlyout?: boolean; + currentTab: string; + tabs: Array<{ id: string; name: string; content: React.ReactNode }>; + onTabClick: (id: string) => void; +}> = ({ isFlyout = false, currentTab: currentTabId, tabs, onTabClick }) => { + const { docLinks } = useStartServices(); + + return ( + <> + +

+ +

+
+ + + + + + + + ), + }} + /> + + + + + + {tabs.map((tab) => ( + onTabClick(tab.id)} + > + {tab.name} + + ))} + + + ); +}; + +// Renders instructions inside of a flyout +export const FleetServerFlyout: React.FunctionComponent = ({ onClose }) => { + const { tabs, currentTab, setCurrentTab, currentTabContent } = useFleetServerTabs(); + + return ( + + +
setCurrentTab(id)} + isFlyout + /> + + + {currentTabContent} + + ); +}; + +// Renders instructions directly +export const FleetServerInstructions: React.FunctionComponent = () => { + const { tabs, currentTab, setCurrentTab, currentTabContent } = useFleetServerTabs(); + + return ( + +
setCurrentTab(id)} /> + + + + {currentTabContent} + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx new file mode 100644 index 000000000000..9993fab723a4 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/instructions.stories.tsx @@ -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 React from 'react'; + +import { FleetServerInstructions as FleetServerInstructionsComponent } from '.'; + +export const FleetServerInstructions = () => { + return ; +}; + +FleetServerInstructions.args = { + isCloudEnabled: false, +}; + +export default { + component: FleetServerInstructions, + title: 'Sections/Fleet/Agents/Fleet Server Instructions/Without Flyout', +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.tsx new file mode 100644 index 000000000000..cf8abc2fe9e1 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/quick_start_tab.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 from 'react'; +import { EuiSteps } from '@elastic/eui'; + +import { useQuickStartCreateForm, useWaitForFleetServer } from './hooks'; + +import { + getGettingStartedStep, + getConfirmFleetServerConnectionStep, + getInstallFleetServerStep, +} from './steps'; + +export const QuickStartTab: React.FunctionComponent = () => { + const quickStartCreateForm = useQuickStartCreateForm(); + const { isFleetServerReady } = useWaitForFleetServer(); + + const steps = [ + getGettingStartedStep({ + quickStartCreateForm, + }), + getInstallFleetServerStep({ + isFleetServerReady, + fleetServerHost: quickStartCreateForm.fleetServerHost, + fleetServerPolicyId: quickStartCreateForm.fleetServerPolicyId, + serviceToken: quickStartCreateForm.serviceToken, + disabled: quickStartCreateForm.status !== 'success', + }), + getConfirmFleetServerConnectionStep({ + isFleetServerReady, + disabled: quickStartCreateForm.status !== 'success', + }), + ]; + + return ; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx new file mode 100644 index 000000000000..e64e23f039f8 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/add_fleet_server_host.tsx @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useCallback } from 'react'; +import type { EuiStepProps } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiCode, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormErrorText, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useStartServices, useLink } from '../../../hooks'; +import type { FleetServerHostForm } from '../hooks'; +import { FleetServerHostComboBox } from '../components'; + +export const getAddFleetServerHostStep = ({ + fleetServerHostForm, + disabled, +}: { + fleetServerHostForm: FleetServerHostForm; + disabled: boolean; +}): EuiStepProps => { + return { + title: i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostStepTitle', { + defaultMessage: 'Add your Fleet Server host', + }), + status: disabled ? 'disabled' : undefined, + children: disabled ? null : ( + + ), + }; +}; + +export const AddFleetServerHostStepContent = ({ + fleetServerHostForm, +}: { + fleetServerHostForm: FleetServerHostForm; +}) => { + const { + fleetServerHost, + fleetServerHostSettings, + setFleetServerHost, + validateFleetServerHost, + saveFleetServerHost, + error, + } = fleetServerHostForm; + + const [isLoading, setIsLoading] = useState(false); + const [submittedFleetServerHost, setSubmittedFleetServerHost] = useState(); + const { notifications } = useStartServices(); + const { getHref } = useLink(); + + const onSubmit = useCallback(async () => { + try { + setSubmittedFleetServerHost(''); + setIsLoading(true); + + if (validateFleetServerHost()) { + await saveFleetServerHost(); + setSubmittedFleetServerHost(fleetServerHost); + } + } catch (err) { + notifications.toasts.addError(err, { + title: i18n.translate('xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle', { + defaultMessage: 'Error adding Fleet Server host', + }), + }); + } finally { + setIsLoading(false); + } + }, [validateFleetServerHost, saveFleetServerHost, fleetServerHost, notifications.toasts]); + + const onChange = useCallback( + (host: string) => { + setFleetServerHost(host); + + if (error) { + validateFleetServerHost(); + } + }, + [error, setFleetServerHost, validateFleetServerHost] + ); + + return ( + + + 8220 }} + /> + + + + + + {error && {error}} + + + + + + + + {submittedFleetServerHost && ( + <> + + + } + > + + + + ), + }} + /> + + + )} + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx new file mode 100644 index 000000000000..5aa5c01a108a --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.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 React, { useContext } from 'react'; + +import type { EuiStepProps } from '@elastic/eui'; +import { EuiButton, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { agentFlyoutContext } from '../../../sections/agents'; + +export function getConfirmFleetServerConnectionStep({ + disabled, + isFleetServerReady, +}: { + disabled: boolean; + isFleetServerReady: boolean; +}): EuiStepProps { + return { + title: isFleetServerReady + ? i18n.translate('xpack.fleet.fleetServerFlyout.confirmConnectionSuccessTitle', { + defaultMessage: 'Fleet Server connected', + }) + : i18n.translate('xpack.fleet.fleetServerFlyout.confirmConnectionTitle', { + defaultMessage: 'Confirm connection', + }), + status: isFleetServerReady ? 'complete' : 'disabled', + children: !disabled && ( + + ), + }; +} + +const ConfirmFleetServerConnectionStepContent: React.FunctionComponent<{ + isFleetServerReady: boolean; +}> = ({ isFleetServerReady }) => { + const addAgentFlyout = useContext(agentFlyoutContext); + + return isFleetServerReady ? ( + <> + + + + + + + + + + + ) : ( + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/create_service_token.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/create_service_token.tsx new file mode 100644 index 000000000000..dcfa150c2d32 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/create_service_token.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import styled from 'styled-components'; + +import type { EuiStepProps } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +const FlexItemWithMinWidth = styled(EuiFlexItem)` + min-width: 0px; + max-width: 100%; +`; + +export const ContentWrapper = styled(EuiFlexGroup)` + height: 100%; + margin: 0 auto; + max-width: 800px; +`; + +// Otherwise the copy button is over the text +const CommandCode = styled.div.attrs(() => { + return { + className: 'eui-textBreakAll', + }; +})` + margin-right: ${(props) => props.theme.eui.paddingSizes.m}; +`; + +export const getGenerateServiceTokenStep = ({ + disabled = false, + serviceToken, + generateServiceToken, + isLoadingServiceToken, +}: { + disabled?: boolean; + serviceToken?: string; + generateServiceToken: () => void; + isLoadingServiceToken: boolean; +}): EuiStepProps => { + return { + title: i18n.translate('xpack.fleet.fleetServerSetup.stepGenerateServiceTokenTitle', { + defaultMessage: 'Generate a service token', + }), + status: disabled ? 'disabled' : undefined, + children: !disabled && ( + + ), + }; +}; + +const ServiceTokenStepContent: React.FunctionComponent<{ + serviceToken?: string; + generateServiceToken: () => void; + isLoadingServiceToken: boolean; +}> = ({ serviceToken, generateServiceToken, isLoadingServiceToken }) => { + return ( + <> + + + + + {!serviceToken ? ( + + + { + generateServiceToken(); + }} + data-test-subj="fleetServerGenerateServiceTokenBtn" + > + + + + + ) : ( + <> + + } + /> + + + + + + + + + + {serviceToken} + + + + + )} + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx new file mode 100644 index 000000000000..50e9b6b72002 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/get_started.tsx @@ -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 React from 'react'; + +import type { EuiStepProps } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiCode, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormErrorText, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useLink } from '../../../hooks'; + +import type { QuickStartCreateForm } from '../hooks'; +import { FleetServerHostComboBox } from '../components'; + +export function getGettingStartedStep({ + quickStartCreateForm, +}: { + quickStartCreateForm: QuickStartCreateForm; +}): EuiStepProps { + return { + title: i18n.translate('xpack.fleet.fleetServerFlyout.getStartedTitle', { + defaultMessage: 'Get started with Fleet Server', + }), + status: quickStartCreateForm.status === 'success' ? 'complete' : 'current', + children: , + }; +} + +const GettingStartedStepContent: React.FunctionComponent<{ + quickStartCreateForm: QuickStartCreateForm; +}> = ({ quickStartCreateForm }) => { + const { getHref } = useLink(); + + const { fleetServerHost, fleetServerHostSettings, onFleetServerHostChange } = + quickStartCreateForm; + + if (quickStartCreateForm.status === 'success') { + return ( + + + {fleetServerHost}, + fleetSettingsLink: ( + + + + ), + }} + /> + + + ); + } + + return ( + <> + + 8220 }} + /> + + + + + + + + + + {quickStartCreateForm.status === 'error' && ( + {quickStartCreateForm.error} + )} + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/index.ts new file mode 100644 index 000000000000..11adf2693927 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './add_fleet_server_host'; +export * from './confirm_fleet_server_connection'; +export * from './create_service_token'; +export * from './get_started'; +export * from './install_fleet_server'; +export * from './select_agent_policy'; +export * from './set_deployment_mode'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx new file mode 100644 index 000000000000..dac9555ba314 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.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 from 'react'; + +import type { EuiStepProps } from '@elastic/eui'; +import { EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { PLATFORM_TYPE } from '../../../hooks'; +import { useDefaultOutput, useKibanaVersion } from '../../../hooks'; + +import { PlatformSelector } from '../..'; + +import { getInstallCommandForPlatform } from '../utils'; + +export function getInstallFleetServerStep({ + isFleetServerReady, + disabled, + serviceToken, + fleetServerHost, + fleetServerPolicyId, +}: { + isFleetServerReady: boolean; + disabled: boolean; + serviceToken?: string; + fleetServerHost?: string; + fleetServerPolicyId?: string; +}): EuiStepProps { + return { + title: i18n.translate('xpack.fleet.fleetServerFlyout.installFleetServerTitle', { + defaultMessage: 'Install Fleet Server to a centralized host', + }), + status: disabled ? 'disabled' : isFleetServerReady ? 'complete' : 'incomplete', + children: !disabled && ( + + ), + }; +} + +const InstallFleetServerStepContent: React.FunctionComponent<{ + serviceToken?: string; + fleetServerHost?: string; + fleetServerPolicyId?: string; +}> = ({ serviceToken, fleetServerHost, fleetServerPolicyId }) => { + const kibanaVersion = useKibanaVersion(); + const { output } = useDefaultOutput(); + + const installCommands = (['linux', 'mac', 'windows', 'deb', 'rpm'] as PLATFORM_TYPE[]).reduce( + (acc, platform) => { + acc[platform] = getInstallCommandForPlatform( + platform, + output?.hosts?.[0] ?? '', + serviceToken ?? '', + fleetServerPolicyId, + fleetServerHost, + false, + output?.ca_trusted_fingerprint, + kibanaVersion + ); + + return acc; + }, + {} as Record + ); + + return ( + <> + + + + + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/select_agent_policy.tsx new file mode 100644 index 000000000000..778716bacb02 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/select_agent_policy.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiStepProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; + +import { SelectCreateAgentPolicy } from '../..'; + +import type { GetAgentPoliciesResponseItem } from '../../../types'; + +export const getSelectAgentPolicyStep = ({ + policyId, + setPolicyId, + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, +}: { + policyId?: string; + setPolicyId: (v?: string) => void; + eligibleFleetServerPolicies: GetAgentPoliciesResponseItem[]; + refreshEligibleFleetServerPolicies: () => void; +}): EuiStepProps => { + return { + title: + eligibleFleetServerPolicies.length === 0 + ? i18n.translate('xpack.fleet.fleetServerSetup.stepCreateAgentPolicyTitle', { + defaultMessage: 'Create a policy for Fleet Server', + }) + : i18n.translate('xpack.fleet.fleetServerSetup.stepSelectAgentPolicyTitle', { + defaultMessage: 'Select a policy for Fleet Server', + }), + status: policyId ? 'complete' : undefined, + children: ( + + ), + }; +}; + +const SelectAgentPolicyStepContent: React.FunctionComponent<{ + policyId?: string; + setPolicyId: (v?: string) => void; + eligibleFleetServerPolicies: GetAgentPoliciesResponseItem[]; + refreshEligibleFleetServerPolicies: () => void; +}> = ({ + policyId, + setPolicyId, + eligibleFleetServerPolicies, + refreshEligibleFleetServerPolicies, +}) => { + useEffect(() => { + // Select default value + if (eligibleFleetServerPolicies.length && !policyId) { + setPolicyId(eligibleFleetServerPolicies[0].id); + } + }, [eligibleFleetServerPolicies, policyId, setPolicyId]); + + const setSelectedPolicyId = (agentPolicyId?: string) => { + setPolicyId(agentPolicyId); + }; + + return ( + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/set_deployment_mode.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/set_deployment_mode.tsx new file mode 100644 index 000000000000..9833fc1d4527 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/set_deployment_mode.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useMemo } from 'react'; + +import type { EuiStepProps } from '@elastic/eui'; +import { EuiRadioGroup, EuiSpacer } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export type DeploymentMode = 'production' | 'quickstart'; + +export const getSetDeploymentModeStep = ({ + deploymentMode, + setDeploymentMode, + disabled, +}: { + deploymentMode: DeploymentMode; + setDeploymentMode: (v: DeploymentMode) => void; + disabled: boolean; +}): EuiStepProps => { + return { + title: i18n.translate('xpack.fleet.fleetServerSetup.stepDeploymentModeTitle', { + defaultMessage: 'Choose a deployment mode for security', + }), + status: disabled ? 'disabled' : undefined, + children: disabled ? null : ( + + ), + }; +}; + +const DeploymentModeStepContent = ({ + deploymentMode, + setDeploymentMode, +}: { + deploymentMode: DeploymentMode; + setDeploymentMode: (v: DeploymentMode) => void; +}) => { + const onChangeCallback = useCallback( + (v: string) => { + const value = v.split('_')[0]; + if (value === 'production' || value === 'quickstart') { + setDeploymentMode(value); + } + }, + [setDeploymentMode] + ); + + // radio id has to be unique so that the component works even if appears twice in DOM (Agents tab, Add agent flyout) + const radioSuffix = useMemo(() => Date.now(), []); + + return ( + <> + + + + + + + + ), + }} + /> + ), + }, + { + id: `production_${radioSuffix}`, + label: ( + + + + ), + }} + /> + ), + }, + ]} + idSelected={`${deploymentMode}_${radioSuffix}`} + onChange={onChangeCallback} + name={`radio group ${radioSuffix}`} + /> + + ); +}; diff --git a/x-pack/test/upgrade/page_objects.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/index.ts similarity index 75% rename from x-pack/test/upgrade/page_objects.ts rename to x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/index.ts index c8b0c9050dbb..fcbab4728cbe 100644 --- a/x-pack/test/upgrade/page_objects.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/index.ts @@ -5,6 +5,4 @@ * 2.0. */ -import { pageObjects } from '../functional/page_objects'; - -export { pageObjects }; +export * from './install_command_utils'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.test.ts similarity index 56% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts rename to x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.test.ts index 774b7871f035..89a246c5c626 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.test.ts @@ -10,50 +10,85 @@ import { getInstallCommandForPlatform } from './install_command_utils'; describe('getInstallCommandForPlatform', () => { describe('without policy id', () => { it('should return the correct command if the the policyId is not set for linux', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'linux', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.linux).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--linux-x86_64.zip \\\\ + tar xzvf elastic-agent--linux-x86_64.zip \\\\ + cd elastic-agent--linux-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); }); it('should return the correct command if the the policyId is not set for mac', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'mac', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.mac).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--darwin-x86_64.tar.gz \\\\ + tar xzvf elastic-agent--darwin-x86_64.tar.gz \\\\ + cd elastic-agent--darwin-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); }); it('should return the correct command if the the policyId is not set for windows', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'windows', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.windows).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install \` + expect(res).toMatchInlineSnapshot(` + "wget https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--windows-x86_64.tar.gz -OutFile elastic-agent--windows-x86_64.tar.gz \` + Expand-Archive .\\\\elastic-agent--windows-x86_64.tar.gz \` + cd elastic-agent--windows-x86_64\` + .\\\\elastic-agent.exe install \` --fleet-server-es=http://elasticsearch:9200 \` --fleet-server-service-token=service-token-1" `); }); it('should return the correct command if the the policyId is not set for rpm', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'rpm', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.rpm).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--x86_64.rpm \\\\ + tar xzvf elastic-agent--x86_64.rpm \\\\ + cd elastic-agent--x86_64 \\\\ + sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); }); it('should return the correct command if the the policyId is not set for deb', () => { - const res = getInstallCommandForPlatform('http://elasticsearch:9200', 'service-token-1'); + const res = getInstallCommandForPlatform( + 'deb', + 'http://elasticsearch:9200', + 'service-token-1' + ); - expect(res.deb).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--amd64.deb \\\\ + tar xzvf elastic-agent--amd64.deb \\\\ + cd elastic-agent--amd64 \\\\ + sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1" `); @@ -61,6 +96,7 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command sslCATrustedFingerprint option is passed', () => { const res = getInstallCommandForPlatform( + 'linux', 'http://elasticsearch:9200', 'service-token-1', undefined, @@ -69,8 +105,11 @@ describe('getInstallCommandForPlatform', () => { 'fingerprint123456' ); - expect(res.linux).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--linux-x86_64.zip \\\\ + tar xzvf elastic-agent--linux-x86_64.zip \\\\ + cd elastic-agent--linux-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-es-ca-trusted-fingerprint=fingerprint123456" @@ -81,13 +120,17 @@ describe('getInstallCommandForPlatform', () => { describe('with policy id', () => { it('should return the correct command if the the policyId is set for linux', () => { const res = getInstallCommandForPlatform( + 'linux', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' ); - expect(res.linux).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--linux-x86_64.zip \\\\ + tar xzvf elastic-agent--linux-x86_64.zip \\\\ + cd elastic-agent--linux-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -96,13 +139,17 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for mac', () => { const res = getInstallCommandForPlatform( + 'mac', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' ); - expect(res.mac).toMatchInlineSnapshot(` - "sudo ./elastic-agent install \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--darwin-x86_64.tar.gz \\\\ + tar xzvf elastic-agent--darwin-x86_64.tar.gz \\\\ + cd elastic-agent--darwin-x86_64 \\\\ + sudo ./elastic-agent install \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -111,13 +158,17 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for windows', () => { const res = getInstallCommandForPlatform( + 'windows', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' ); - expect(res.windows).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install \` + expect(res).toMatchInlineSnapshot(` + "wget https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--windows-x86_64.tar.gz -OutFile elastic-agent--windows-x86_64.tar.gz \` + Expand-Archive .\\\\elastic-agent--windows-x86_64.tar.gz \` + cd elastic-agent--windows-x86_64\` + .\\\\elastic-agent.exe install \` --fleet-server-es=http://elasticsearch:9200 \` --fleet-server-service-token=service-token-1 \` --fleet-server-policy=policy-1" @@ -126,13 +177,17 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for rpm', () => { const res = getInstallCommandForPlatform( + 'rpm', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' ); - expect(res.rpm).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--x86_64.rpm \\\\ + tar xzvf elastic-agent--x86_64.rpm \\\\ + cd elastic-agent--x86_64 \\\\ + sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -141,13 +196,17 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for deb', () => { const res = getInstallCommandForPlatform( + 'deb', 'http://elasticsearch:9200', 'service-token-1', 'policy-1' ); - expect(res.deb).toMatchInlineSnapshot(` - "sudo elastic-agent enroll \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--amd64.deb \\\\ + tar xzvf elastic-agent--amd64.deb \\\\ + cd elastic-agent--amd64 \\\\ + sudo elastic-agent enroll \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1" @@ -158,6 +217,7 @@ describe('getInstallCommandForPlatform', () => { describe('with policy id and fleet server host and production deployment', () => { it('should return the correct command if the the policyId is set for linux', () => { const res = getInstallCommandForPlatform( + 'linux', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -165,8 +225,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.linux).toMatchInlineSnapshot(` - "sudo ./elastic-agent install --url=http://fleetserver:8220 \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--linux-x86_64.zip \\\\ + tar xzvf elastic-agent--linux-x86_64.zip \\\\ + cd elastic-agent--linux-x86_64 \\\\ + sudo ./elastic-agent install--url=http://fleetserver:8220 \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ @@ -179,6 +242,7 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for mac', () => { const res = getInstallCommandForPlatform( + 'mac', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -186,8 +250,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.mac).toMatchInlineSnapshot(` - "sudo ./elastic-agent install --url=http://fleetserver:8220 \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--darwin-x86_64.tar.gz \\\\ + tar xzvf elastic-agent--darwin-x86_64.tar.gz \\\\ + cd elastic-agent--darwin-x86_64 \\\\ + sudo ./elastic-agent install --url=http://fleetserver:8220 \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ @@ -200,6 +267,7 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for windows', () => { const res = getInstallCommandForPlatform( + 'windows', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -207,8 +275,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.windows).toMatchInlineSnapshot(` - ".\\\\elastic-agent.exe install --url=http://fleetserver:8220 \` + expect(res).toMatchInlineSnapshot(` + "wget https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--windows-x86_64.tar.gz -OutFile elastic-agent--windows-x86_64.tar.gz \` + Expand-Archive .\\\\elastic-agent--windows-x86_64.tar.gz \` + cd elastic-agent--windows-x86_64\` + .\\\\elastic-agent.exe install --url=http://fleetserver:8220 \` --fleet-server-es=http://elasticsearch:9200 \` --fleet-server-service-token=service-token-1 \` --fleet-server-policy=policy-1 \` @@ -221,6 +292,7 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for rpm', () => { const res = getInstallCommandForPlatform( + 'rpm', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -228,8 +300,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.rpm).toMatchInlineSnapshot(` - "sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--x86_64.rpm \\\\ + tar xzvf elastic-agent--x86_64.rpm \\\\ + cd elastic-agent--x86_64 \\\\ + sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ @@ -242,6 +317,7 @@ describe('getInstallCommandForPlatform', () => { it('should return the correct command if the the policyId is set for deb', () => { const res = getInstallCommandForPlatform( + 'deb', 'http://elasticsearch:9200', 'service-token-1', 'policy-1', @@ -249,8 +325,11 @@ describe('getInstallCommandForPlatform', () => { true ); - expect(res.deb).toMatchInlineSnapshot(` - "sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ + expect(res).toMatchInlineSnapshot(` + "curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent--amd64.deb \\\\ + tar xzvf elastic-agent--amd64.deb \\\\ + cd elastic-agent--amd64 \\\\ + sudo elastic-agent enroll --url=http://fleetserver:8220 \\\\ --fleet-server-es=http://elasticsearch:9200 \\\\ --fleet-server-service-token=service-token-1 \\\\ --fleet-server-policy=policy-1 \\\\ diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.ts new file mode 100644 index 000000000000..525af7cf9510 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/utils/install_command_utils.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { PLATFORM_TYPE } from '../../../hooks'; + +export type CommandsByPlatform = { + [key in PLATFORM_TYPE]: string; +}; + +function getArtifact(platform: PLATFORM_TYPE, kibanaVersion: string) { + const ARTIFACT_BASE_URL = 'https://artifacts.elastic.co/downloads/beats/elastic-agent'; + + const artifactMap: Record< + PLATFORM_TYPE, + { fullUrl: string; filename: string; unpackedDir: string } + > = { + linux: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-linux-x86_64.zip`, + filename: `elastic-agent-${kibanaVersion}-linux-x86_64.zip`, + unpackedDir: `elastic-agent-${kibanaVersion}-linux-x86_64`, + }, + mac: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz`, + filename: `elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz`, + unpackedDir: `elastic-agent-${kibanaVersion}-darwin-x86_64`, + }, + windows: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-windows-x86_64.tar.gz`, + filename: `elastic-agent-${kibanaVersion}-windows-x86_64.tar.gz`, + unpackedDir: `elastic-agent-${kibanaVersion}-windows-x86_64`, + }, + deb: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-amd64.deb`, + filename: `elastic-agent-${kibanaVersion}-amd64.deb`, + unpackedDir: `elastic-agent-${kibanaVersion}-amd64`, + }, + rpm: { + fullUrl: `${ARTIFACT_BASE_URL}/elastic-agent-${kibanaVersion}-x86_64.rpm`, + filename: `elastic-agent-${kibanaVersion}-x86_64.rpm`, + unpackedDir: `elastic-agent-${kibanaVersion}-x86_64`, + }, + }; + + return artifactMap[platform]; +} + +export function getInstallCommandForPlatform( + platform: PLATFORM_TYPE, + esHost: string, + serviceToken: string, + policyId?: string, + fleetServerHost?: string, + isProductionDeployment?: boolean, + sslCATrustedFingerprint?: string, + kibanaVersion?: string +): string { + const newLineSeparator = platform === 'windows' ? '`\n' : '\\\n'; + + const artifact = getArtifact(platform, kibanaVersion ?? ''); + const downloadCommand = + platform === 'windows' + ? [ + `wget ${artifact.fullUrl} -OutFile ${artifact.filename}`, + `Expand-Archive .\\${artifact.filename}`, + `cd ${artifact.unpackedDir}`, + ].join(` ${newLineSeparator}`) + : [ + `curl -L -O ${artifact.fullUrl}`, + `tar xzvf ${artifact.filename}`, + `cd ${artifact.unpackedDir}`, + ].join(` ${newLineSeparator}`); + + const commandArguments = []; + + if (isProductionDeployment && fleetServerHost) { + commandArguments.push(['url', fleetServerHost]); + } + + commandArguments.push(['fleet-server-es', esHost]); + commandArguments.push(['fleet-server-service-token', serviceToken]); + if (policyId) { + commandArguments.push(['fleet-server-policy', policyId]); + } + + if (sslCATrustedFingerprint) { + commandArguments.push(['fleet-server-es-ca-trusted-fingerprint', sslCATrustedFingerprint]); + } + + if (isProductionDeployment) { + commandArguments.push(['certificate-authorities', '']); + if (!sslCATrustedFingerprint) { + commandArguments.push(['fleet-server-es-ca', '']); + } + commandArguments.push(['fleet-server-cert', '']); + commandArguments.push(['fleet-server-cert-key', '']); + } + + const commandArgumentsStr = commandArguments.reduce((acc, [key, val]) => { + if (acc === '' && key === 'url') { + return `--${key}=${val}`; + } + const valOrEmpty = val ? `=${val}` : ''; + return (acc += ` ${newLineSeparator} --${key}${valOrEmpty}`); + }, ''); + + const commands = { + linux: `${downloadCommand} ${newLineSeparator}sudo ./elastic-agent install${commandArgumentsStr}`, + mac: `${downloadCommand} ${newLineSeparator}sudo ./elastic-agent install ${commandArgumentsStr}`, + windows: `${downloadCommand}${newLineSeparator}.\\elastic-agent.exe install ${commandArgumentsStr}`, + deb: `${downloadCommand} ${newLineSeparator}sudo elastic-agent enroll ${commandArgumentsStr}`, + rpm: `${downloadCommand} ${newLineSeparator}sudo elastic-agent enroll ${commandArgumentsStr}`, + }; + + return commands[platform]; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/components/index.ts index 5e927c5b0e3d..a299013ab547 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/index.ts @@ -8,3 +8,4 @@ export * from '../../../components'; export * from './search_bar'; +export * from './fleet_server_instructions'; 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 9fdcc0f73297..e1c6bbafa21a 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 @@ -28,11 +28,11 @@ import { useStartServices } from '../../../../hooks'; import { AgentPolicyPackageBadge } from '../../../../components'; -import { policyHasFleetServer } from '../../../agents/services/has_fleet_server'; - import { AgentPolicyDeleteProvider } from '../agent_policy_delete_provider'; import type { ValidationResults } from '../agent_policy_validation'; +import { policyHasFleetServer } from '../../../../services'; + import { useOutputOptions, DEFAULT_OUTPUT_VALUE } from './hooks'; interface Props { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index e9b04aacecb6..44e87d7fb4e6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -18,8 +18,7 @@ import { AgentUpgradeAgentModal, } from '../../components'; import { useAgentRefresh } from '../hooks'; -import { isAgentUpgradeable } from '../../../../services'; -import { policyHasFleetServer } from '../../services/has_fleet_server'; +import { isAgentUpgradeable, policyHasFleetServer } from '../../../../services'; export const AgentDetailsActionMenu: React.FunctionComponent<{ agent: Agent; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index 7b3f1fedb160..d5757419e8ea 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react'; +import React, { useState, useMemo, useCallback, useRef, useEffect, useContext } from 'react'; import { EuiBasicTable, EuiButton, @@ -52,6 +52,8 @@ import { } from '../components'; import { useFleetServerUnhealthy } from '../hooks/use_fleet_server_unhealthy'; +import { agentFlyoutContext } from '..'; + import { AgentTableHeader } from './components/table_header'; import type { SelectionMode } from './components/bulk_actions'; import { SearchAndFilterBar } from './components/search_and_filter_bar'; @@ -201,6 +203,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { isOpen: false, }); + const flyoutContext = useContext(agentFlyoutContext); + // Agent actions states const [agentToReassign, setAgentToReassign] = useState(undefined); const [agentToUnenroll, setAgentToUnenroll] = useState(undefined); @@ -378,11 +382,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { // Fleet server unhealthy status const { isUnhealthy: isFleetServerUnhealthy } = useFleetServerUnhealthy(); const onClickAddFleetServer = useCallback(() => { - setEnrollmentFlyoutState({ - isOpen: true, - selectedPolicyId: agentPolicies.length > 0 ? agentPolicies[0].id : undefined, - }); - }, [agentPolicies]); + flyoutContext?.openFleetServerFlyout(); + }, [flyoutContext]); const columns = [ { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/enrollment_recommendation.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/enrollment_recommendation.tsx new file mode 100644 index 000000000000..86990d84d513 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/enrollment_recommendation.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useContext } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useStartServices } from '../../../../hooks'; + +import { agentFlyoutContext } from '../..'; + +export const EnrollmentRecommendation: React.FunctionComponent<{ + showStandaloneTab: () => void; +}> = ({ showStandaloneTab }) => { + const flyoutContext = useContext(agentFlyoutContext); + + const { docLinks } = useStartServices(); + + return ( + <> + + + + + + + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + + + + ), + }} + /> +
  • +
+
+ + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx deleted file mode 100644 index 93547bba36de..000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx +++ /dev/null @@ -1,748 +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, { useState, useMemo, useCallback, useEffect } from 'react'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, - EuiLink, - EuiSteps, - EuiCode, - EuiCodeBlock, - EuiCallOut, - EuiRadioGroup, - EuiFieldText, - EuiForm, - EuiFormErrorText, -} from '@elastic/eui'; -import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; -import styled from 'styled-components'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { DownloadStep, SelectCreateAgentPolicy } from '../../../../components'; -import { - useStartServices, - useDefaultOutput, - sendGenerateServiceToken, - usePlatform, - useGetAgentPolicies, - useGetSettings, - sendPutSettings, - sendGetFleetStatus, - useFleetStatus, - useLink, -} from '../../../../hooks'; -import type { PLATFORM_TYPE } from '../../../../hooks'; -import type { AgentPolicy } from '../../../../types'; -import { FleetServerOnPremRequiredCallout } from '../../components'; - -import { policyHasFleetServer } from '../../services/has_fleet_server'; - -import { PlatformSelector } from '../../../../../../components/enrollment_instructions/manual/platform_selector'; - -import type { CommandsByPlatform } from './install_command_utils'; -import { getInstallCommandForPlatform } from './install_command_utils'; - -const URL_REGEX = /^(https):\/\/[^\s$.?#].[^\s]*$/gm; -const REFRESH_INTERVAL = 10000; - -type DeploymentMode = 'production' | 'quickstart'; - -const FlexItemWithMinWidth = styled(EuiFlexItem)` - min-width: 0px; - max-width: 100%; -`; - -export const ContentWrapper = styled(EuiFlexGroup)` - height: 100%; - margin: 0 auto; - max-width: 800px; -`; - -// Otherwise the copy button is over the text -const CommandCode = styled.div.attrs(() => { - return { - className: 'eui-textBreakAll', - }; -})` - margin-right: ${(props) => props.theme.eui.paddingSizes.m}; -`; - -export const ServiceTokenStep = ({ - disabled = false, - serviceToken, - getServiceToken, - isLoadingServiceToken, -}: { - disabled?: boolean; - serviceToken?: string; - getServiceToken: () => void; - isLoadingServiceToken: boolean; -}): EuiStepProps => { - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepGenerateServiceTokenTitle', { - defaultMessage: 'Generate a service token', - }), - status: disabled ? 'disabled' : undefined, - children: !disabled && ( - <> - - - - - {!serviceToken ? ( - - - { - getServiceToken(); - }} - data-test-subj="fleetServerGenerateServiceTokenBtn" - > - - - - - ) : ( - <> - - } - /> - - - - - - - - - - {serviceToken} - - - - - )} - - ), - }; -}; - -export const FleetServerCommandStep = ({ - serviceToken, - installCommand, - platform, - setPlatform, -}: { - serviceToken?: string; - installCommand: CommandsByPlatform; - platform: string; - setPlatform: (platform: PLATFORM_TYPE) => void; -}): EuiStepProps => { - const { docLinks } = useStartServices(); - - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepInstallAgentTitle', { - defaultMessage: 'Start Fleet Server', - }), - status: !serviceToken ? 'disabled' : undefined, - children: serviceToken ? ( - <> - - - - - ), - }} - /> - - - - - ) : null, - }; -}; - -export const useFleetServerInstructions = (policyId?: string) => { - const { output } = useDefaultOutput(); - const { notifications } = useStartServices(); - const [serviceToken, setServiceToken] = useState(); - const [isLoadingServiceToken, setIsLoadingServiceToken] = useState(false); - const { platform, setPlatform } = usePlatform(); - const [deploymentMode, setDeploymentMode] = useState('production'); - const { data: settings, resendRequest: refreshSettings } = useGetSettings(); - const fleetServerHost = settings?.item.fleet_server_hosts?.[0]; - const esHost = output?.hosts?.[0]; - const sslCATrustedFingerprint: string | undefined = output?.ca_trusted_fingerprint; - - const installCommand = useMemo((): CommandsByPlatform => { - if (!serviceToken || !esHost) { - return { - linux: '', - mac: '', - windows: '', - deb: '', - rpm: '', - }; - } - - return getInstallCommandForPlatform( - esHost, - serviceToken, - policyId, - fleetServerHost, - deploymentMode === 'production', - sslCATrustedFingerprint - ); - }, [serviceToken, esHost, policyId, fleetServerHost, deploymentMode, sslCATrustedFingerprint]); - - const getServiceToken = useCallback(async () => { - setIsLoadingServiceToken(true); - try { - const { data } = await sendGenerateServiceToken(); - if (data?.value) { - setServiceToken(data?.value); - } - } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.fleet.fleetServerSetup.errorGeneratingTokenTitleText', { - defaultMessage: 'Error generating token', - }), - }); - } - - setIsLoadingServiceToken(false); - }, [notifications.toasts]); - - const addFleetServerHost = useCallback( - async (host: string) => { - const res = await sendPutSettings({ - fleet_server_hosts: [host, ...(settings?.item.fleet_server_hosts || [])], - }); - if (res.error) { - throw res.error; - } - await refreshSettings(); - }, - [refreshSettings, settings?.item.fleet_server_hosts] - ); - - return { - addFleetServerHost, - fleetServerHost, - deploymentMode, - setDeploymentMode, - serviceToken, - getServiceToken, - isLoadingServiceToken, - installCommand, - platform, - setPlatform, - }; -}; - -const AgentPolicySelectionStep = ({ - selectedPolicy, - setPolicyId, - agentPolicies, - refreshAgentPolicies, -}: { - selectedPolicy?: AgentPolicy; - setPolicyId: (v?: string) => void; - agentPolicies: AgentPolicy[]; - refreshAgentPolicies: () => void; -}): EuiStepProps => { - return { - title: - agentPolicies.length === 0 - ? i18n.translate('xpack.fleet.fleetServerSetup.stepCreateAgentPolicyTitle', { - defaultMessage: 'Create an agent policy to host Fleet Server', - }) - : i18n.translate('xpack.fleet.fleetServerSetup.stepSelectAgentPolicyTitle', { - defaultMessage: 'Select an agent policy to host Fleet Server', - }), - status: undefined, - children: ( - <> - - - ), - }; -}; - -export const addFleetServerHostStep = ({ - addFleetServerHost, -}: { - addFleetServerHost: (v: string) => Promise; -}): EuiStepProps => { - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostStepTitle', { - defaultMessage: 'Add your Fleet Server host', - }), - status: undefined, - children: , - }; -}; - -export const AddFleetServerHostStepContent = ({ - addFleetServerHost, -}: { - addFleetServerHost: (v: string) => Promise; -}) => { - const [calloutHost, setCalloutHost] = useState(); - const [isLoading, setIsLoading] = useState(false); - const [fleetServerHost, setFleetServerHost] = useState(''); - const [error, setError] = useState(); - const { notifications } = useStartServices(); - - const { getHref } = useLink(); - - const validate = useCallback( - (host: string) => { - if (host.match(URL_REGEX)) { - setError(undefined); - return true; - } else { - setError( - i18n.translate('xpack.fleet.fleetServerSetup.addFleetServerHostInvalidUrlError', { - defaultMessage: 'Valid https URL required.', - }) - ); - return false; - } - }, - [setError] - ); - - const onSubmit = useCallback(async () => { - try { - setIsLoading(true); - if (validate(fleetServerHost)) { - await addFleetServerHost(fleetServerHost); - setCalloutHost(fleetServerHost); - setFleetServerHost(''); - } else { - setCalloutHost(''); - } - } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.fleet.fleetServerSetup.errorAddingFleetServerHostTitle', { - defaultMessage: 'Error adding Fleet Server host', - }), - }); - } finally { - setIsLoading(false); - } - }, [fleetServerHost, addFleetServerHost, validate, notifications.toasts]); - - const onChange = useCallback( - (e: React.ChangeEvent) => { - setFleetServerHost(e.target.value); - if (error) { - validate(e.target.value); - } - }, - [error, validate, setFleetServerHost] - ); - - return ( - - - 8220 }} - /> - - - - - - - - } - data-test-subj="fleetServerHostInput" - /> - {error && {error}} - - - - - - - - {calloutHost && ( - <> - - - } - > - - - - ), - }} - /> - - - )} - - ); -}; - -export const deploymentModeStep = ({ - deploymentMode, - setDeploymentMode, -}: { - deploymentMode: DeploymentMode; - setDeploymentMode: (v: DeploymentMode) => void; -}): EuiStepProps => { - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepDeploymentModeTitle', { - defaultMessage: 'Choose a deployment mode for security', - }), - status: undefined, - children: ( - - ), - }; -}; - -const DeploymentModeStepContent = ({ - deploymentMode, - setDeploymentMode, -}: { - deploymentMode: DeploymentMode; - setDeploymentMode: (v: DeploymentMode) => void; -}) => { - const onChangeCallback = useCallback( - (v: string) => { - const value = v.split('_')[0]; - if (value === 'production' || value === 'quickstart') { - setDeploymentMode(value); - } - }, - [setDeploymentMode] - ); - - // radio id has to be unique so that the component works even if appears twice in DOM (Agents tab, Add agent flyout) - const radioSuffix = useMemo(() => Date.now(), []); - - return ( - <> - - - - - - - - ), - }} - /> - ), - }, - { - id: `production_${radioSuffix}`, - label: ( - - - - ), - }} - /> - ), - }, - ]} - idSelected={`${deploymentMode}_${radioSuffix}`} - onChange={onChangeCallback} - name={`radio group ${radioSuffix}`} - /> - - ); -}; - -const WaitingForFleetServerStep = ({ - status, -}: { - status: 'loading' | 'disabled' | 'complete'; -}): EuiStepProps => { - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepWaitingForFleetServerTitle', { - defaultMessage: 'Waiting for Fleet Server to connect...', - }), - status, - children: undefined, - }; -}; - -const CompleteStep = (): EuiStepProps => { - const fleetStatus = useFleetStatus(); - - const onContinueClick = () => { - fleetStatus.refresh(); - }; - - return { - title: i18n.translate('xpack.fleet.fleetServerSetup.stepFleetServerCompleteTitle', { - defaultMessage: 'Fleet Server connected', - }), - status: 'complete', - children: ( - <> - - - - - - - ), - }; -}; - -const findPolicyById = (policies: AgentPolicy[], id: string | undefined) => { - if (!id) return undefined; - return policies.find((p) => p.id === id); -}; - -export const OnPremInstructions: React.FC = () => { - const { notifications } = useStartServices(); - - const { data, resendRequest: refreshAgentPolicies } = useGetAgentPolicies({ full: true }); - - const agentPolicies = useMemo( - () => (data ? data.items.filter((item) => policyHasFleetServer(item)) : []), - [data] - ); - - // Select default value - let defaultValue = ''; - if (agentPolicies.length) { - defaultValue = agentPolicies[0].id; - } - const [policyId, setPolicyId] = useState(defaultValue); - const selectedPolicy = findPolicyById(agentPolicies, policyId); - - const { - serviceToken, - getServiceToken, - isLoadingServiceToken, - installCommand, - platform, - setPlatform, - deploymentMode, - setDeploymentMode, - fleetServerHost, - addFleetServerHost, - } = useFleetServerInstructions(policyId); - - const { docLinks } = useStartServices(); - - const [isWaitingForFleetServer, setIsWaitingForFleetServer] = useState(true); - - useEffect(() => { - const interval = setInterval(async () => { - try { - const res = await sendGetFleetStatus(); - if (res.error) { - throw res.error; - } - if (res.data?.isReady && !res.data?.missing_requirements?.includes('fleet_server')) { - setIsWaitingForFleetServer(false); - } - } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.fleet.fleetServerSetup.errorRefreshingFleetServerStatus', { - defaultMessage: 'Error refreshing Fleet Server status', - }), - }); - } - }, REFRESH_INTERVAL); - - return () => clearInterval(interval); - }, [notifications.toasts]); - - return ( - <> - - - -

- -

- - - - - ), - }} - /> -
- - - - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/index.tsx index 3118fda75400..293be6d8b98e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/index.tsx @@ -6,4 +6,4 @@ */ export { CloudInstructions } from './fleet_server_cloud_instructions'; -export * from './fleet_server_on_prem_instructions'; +export { EnrollmentRecommendation } from './enrollment_recommendation'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts deleted file mode 100644 index b73eb547b6dd..000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils.ts +++ /dev/null @@ -1,65 +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 { PLATFORM_TYPE } from '../../../../hooks'; - -export type CommandsByPlatform = { - [key in PLATFORM_TYPE]: string; -}; - -export function getInstallCommandForPlatform( - esHost: string, - serviceToken: string, - policyId?: string, - fleetServerHost?: string, - isProductionDeployment?: boolean, - sslCATrustedFingerprint?: string -): CommandsByPlatform { - const commandArguments: Array<[string, string] | [string]> = []; - - if (isProductionDeployment && fleetServerHost) { - commandArguments.push(['url', fleetServerHost]); - } - - commandArguments.push(['fleet-server-es', esHost]); - commandArguments.push(['fleet-server-service-token', serviceToken]); - if (policyId) { - commandArguments.push(['fleet-server-policy', policyId]); - } - - if (sslCATrustedFingerprint) { - commandArguments.push(['fleet-server-es-ca-trusted-fingerprint', sslCATrustedFingerprint]); - } - - if (isProductionDeployment) { - commandArguments.push(['certificate-authorities', '']); - if (!sslCATrustedFingerprint) { - commandArguments.push(['fleet-server-es-ca', '']); - } - commandArguments.push(['fleet-server-cert', '']); - commandArguments.push(['fleet-server-cert-key', '']); - } - - const commandArgumentsStr = (platform?: string) => { - const newLineSeparator = platform === 'windows' ? '`\n' : '\\\n'; - return commandArguments.reduce((acc, [key, val]) => { - if (acc === '' && key === 'url') { - return `--${key}=${val}`; - } - const valOrEmpty = val ? `=${val}` : ''; - return (acc += ` ${newLineSeparator} --${key}${valOrEmpty}`); - }, ''); - }; - - return { - linux: `sudo ./elastic-agent install ${commandArgumentsStr()}`, - mac: `sudo ./elastic-agent install ${commandArgumentsStr()}`, - windows: `.\\elastic-agent.exe install ${commandArgumentsStr('windows')}`, - deb: `sudo elastic-agent enroll ${commandArgumentsStr()}`, - rpm: `sudo elastic-agent enroll ${commandArgumentsStr()}`, - }; -} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx index bee7fbfd8537..b4818a6908ad 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/fleet_server_requirement_page.tsx @@ -13,9 +13,11 @@ import { useStartServices, sendGetPermissionsCheck } from '../../../hooks'; import { FleetServerMissingPrivileges } from '../components/fleet_server_callouts'; -import { Loading } from '../../../components'; +import { Loading } from '../components'; -import { CloudInstructions, OnPremInstructions } from './components'; +import { FleetServerInstructions } from '../../../components'; + +import { CloudInstructions, EnrollmentRecommendation } from './components'; const FlexItemWithMinWidth = styled(EuiFlexItem)` min-width: 0px; @@ -27,7 +29,16 @@ const ContentWrapper = styled(EuiFlexGroup)` margin: 0 auto; `; -export const FleetServerRequirementPage = () => { +export const FleetServerRequirementPage: React.FunctionComponent< + | { + showEnrollmentRecommendation?: false; + showStandaloneTab?: never; + } + | { + showEnrollmentRecommendation?: true; + showStandaloneTab: () => void; + } +> = ({ showStandaloneTab = () => {}, showEnrollmentRecommendation = true }) => { const startService = useStartServices(); const deploymentUrl = startService.cloud?.deploymentUrl; @@ -56,12 +67,7 @@ export const FleetServerRequirementPage = () => { return ( <> - + {deploymentUrl ? ( @@ -69,8 +75,10 @@ export const FleetServerRequirementPage = () => { ) : permissionsError ? ( + ) : showEnrollmentRecommendation ? ( + ) : ( - + )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx index d2434e109ca3..57da2fcf36d7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { createContext, useCallback, useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { Router, Route, Switch, useHistory } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPortal } from '@elastic/eui'; import { FLEET_ROUTING_PATHS } from '../../constants'; -import { Loading, Error, AgentEnrollmentFlyout } from '../../components'; +import { Loading, Error, AgentEnrollmentFlyout, FleetServerFlyout } from '../../components'; import { useConfig, useFleetStatus, useBreadcrumbs, useAuthz, useGetSettings } from '../../hooks'; import { DefaultLayout, WithoutHeaderLayout } from '../../layouts'; @@ -21,6 +21,18 @@ import { AgentDetailsPage } from './agent_details_page'; import { NoAccessPage } from './error_pages/no_access'; import { FleetServerUpgradeModal } from './components/fleet_server_upgrade_modal'; +// TODO: Move all instances of toggling these flyouts to a global context object to avoid cases in which +// we can render duplicate "stacked" flyouts +export const agentFlyoutContext = createContext< + | { + openEnrollmentFlyout: () => void; + closeEnrollmentFlyout: () => void; + openFleetServerFlyout: () => void; + closeFleetServerFlyout: () => void; + } + | undefined +>(undefined); + export const AgentsApp: React.FunctionComponent = () => { useBreadcrumbs('agent_list'); const history = useHistory(); @@ -31,6 +43,8 @@ export const AgentsApp: React.FunctionComponent = () => { const settings = useGetSettings(); const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); + const [isFleetServerFlyoutOpen, setIsFleetServerFlyoutOpen] = useState(false); + const [fleetServerModalVisible, setFleetServerModalVisible] = useState(false); const onCloseFleetServerModal = useCallback(() => { setFleetServerModalVisible(false); @@ -81,15 +95,6 @@ export const AgentsApp: React.FunctionComponent = () => { const rightColumn = hasOnlyFleetServerMissingRequirement ? ( <> - {isEnrollmentFlyoutOpen && ( - - setIsEnrollmentFlyoutOpen(false)} - /> - - )} { ) : undefined; return ( - - - - - - - - {fleetServerModalVisible && ( - - )} - {hasOnlyFleetServerMissingRequirement ? ( - - ) : ( - - )} - - - - + setIsEnrollmentFlyoutOpen(true), + closeEnrollmentFlyout: () => setIsEnrollmentFlyoutOpen(false), + openFleetServerFlyout: () => setIsFleetServerFlyoutOpen(true), + closeFleetServerFlyout: () => setIsFleetServerFlyoutOpen(false), + }} + > + + + + + + + + {fleetServerModalVisible && ( + + )} + {hasOnlyFleetServerMissingRequirement ? ( + + ) : ( + + )} + + + + + {isEnrollmentFlyoutOpen && ( + + setIsEnrollmentFlyoutOpen(false)} + /> + + )} + + {isFleetServerFlyoutOpen && ( + + setIsFleetServerFlyoutOpen(false)} /> + + )} + + ); }; 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 151a3d5354c1..aeb49928f3d1 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 @@ -27,7 +27,7 @@ describe('useFleetServerHostsForm', () => { const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); act(() => - result.current.fleetServerHostsInput.props.onChange(['http://test.fr', 'http://test.fr']) + result.current.fleetServerHostsInput.props.onChange(['https://test.fr', 'https://test.fr']) ); await act(() => result.current.submit()); @@ -54,7 +54,7 @@ describe('useFleetServerHostsForm', () => { testRenderer.startServices.http.post.mockResolvedValue({}); const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); - act(() => result.current.fleetServerHostsInput.props.onChange(['http://test.fr'])); + act(() => result.current.fleetServerHostsInput.props.onChange(['https://test.fr'])); await act(() => result.current.submit()); expect(onSucess).toBeCalled(); @@ -67,14 +67,14 @@ describe('useFleetServerHostsForm', () => { const { result } = testRenderer.renderHook(() => useFleetServerHostsForm([], onSucess)); act(() => - result.current.fleetServerHostsInput.props.onChange(['http://test.fr', 'http://test.fr']) + result.current.fleetServerHostsInput.props.onChange(['https://test.fr', 'https://test.fr']) ); await act(() => result.current.submit()); expect(onSucess).not.toBeCalled(); expect(result.current.isDisabled).toBeTruthy(); - act(() => result.current.fleetServerHostsInput.props.onChange(['http://test.fr'])); + act(() => result.current.fleetServerHostsInput.props.onChange(['https://test.fr'])); expect(result.current.isDisabled).toBeFalsy(); await act(() => result.current.submit()); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx index ac196576ba88..b19e8ddda042 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/fleet_server_hosts_flyout/use_fleet_server_host_form.tsx @@ -15,7 +15,7 @@ import { isDiffPathProtocol } from '../../../../../../../common'; import { useConfirmModal } from '../../hooks/use_confirm_modal'; import { getAgentAndPolicyCount } from '../../services/agent_and_policies_count'; -const URL_REGEX = /^(https?):\/\/[^\s$.?#].[^\s]*$/gm; +const URL_REGEX = /^(https):\/\/[^\s$.?#].[^\s]*$/gm; const ConfirmTitle = () => ( + ) : ( + + ), + } + : undefined; + + return ( + onClick(e)} + data-test-subj="addIntegrationPolicyButton" + tooltip={tooltip} + > + + + ); +} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/index.tsx index 8716d78dfb7b..c0438bf6dfe8 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/index.tsx @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +export { AddIntegrationButton } from './add_integration_button'; export { UpdateIcon } from './update_icon'; export { IntegrationAgentPolicyCount } from './integration_agent_policy_count'; export { IconPanel, LoadingIconPanel } from './icon_panel'; 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 5ac283687635..d17b24a4dcdb 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 @@ -35,26 +35,25 @@ import { useAuthz, usePermissionCheck, } from '../../../../hooks'; -import { - PLUGIN_ID, - INTEGRATIONS_PLUGIN_ID, - INTEGRATIONS_ROUTING_PATHS, - pagePathGetters, -} from '../../../../constants'; +import { INTEGRATIONS_ROUTING_PATHS } from '../../../../constants'; import { useGetPackageInfoByKey, useLink, useAgentPolicyContext } from '../../../../hooks'; import { pkgKeyFromPackageInfo } from '../../../../services'; -import type { - CreatePackagePolicyRouteState, - DetailViewPanelName, - PackageInfo, -} from '../../../../types'; +import type { DetailViewPanelName, PackageInfo } from '../../../../types'; import { InstallStatus } from '../../../../types'; -import { Error, EuiButtonWithTooltip, Loading } from '../../../../components'; +import { Error, Loading } from '../../../../components'; import type { WithHeaderLayoutProps } from '../../../../layouts'; import { WithHeaderLayout } from '../../../../layouts'; import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from '../../components/release_badge'; -import { IntegrationAgentPolicyCount, UpdateIcon, IconPanel, LoadingIconPanel } from './components'; +import { getInstallPkgRouteOptions } from './utils'; + +import { + IntegrationAgentPolicyCount, + UpdateIcon, + IconPanel, + LoadingIconPanel, + AddIntegrationButton, +} from './components'; import { AssetsPage } from './assets'; import { OverviewPage } from './overview'; import { PackagePoliciesPage } from './policies'; @@ -257,7 +256,6 @@ export function Detail() { const handleAddIntegrationPolicyClick = useCallback( (ev) => { ev.preventDefault(); - // The object below, given to `createHref` is explicitly accessing keys of `location` in order // to ensure that dependencies to this `useCallback` is set correctly (because `location` is mutable) const currentPath = history.createHref({ @@ -266,65 +264,14 @@ export function Detail() { hash, }); - const path = pagePathGetters.add_integration_to_policy({ + const navigateOptions = getInstallPkgRouteOptions({ + currentPath, + integration, + agentPolicyId: agentPolicyIdFromContext, pkgkey, - ...(integration ? { integration } : {}), - ...(agentPolicyIdFromContext ? { agentPolicyId: agentPolicyIdFromContext } : {}), - })[1]; - - let redirectToPath: CreatePackagePolicyRouteState['onSaveNavigateTo'] & - CreatePackagePolicyRouteState['onCancelNavigateTo']; - let onSaveQueryParams: CreatePackagePolicyRouteState['onSaveQueryParams']; - if (agentPolicyIdFromContext) { - redirectToPath = [ - PLUGIN_ID, - { - path: pagePathGetters.policy_details({ - policyId: agentPolicyIdFromContext, - })[1], - }, - ]; - - onSaveQueryParams = { - showAddAgentHelp: true, - openEnrollmentFlyout: true, - }; - } else { - redirectToPath = [ - INTEGRATIONS_PLUGIN_ID, - { - path: pagePathGetters.integration_details_policies({ - pkgkey, - ...(integration ? { integration } : {}), - })[1], - }, - ]; - - onSaveQueryParams = { - showAddAgentHelp: { renameKey: 'showAddAgentHelpForPolicyId', policyIdAsValue: true }, - openEnrollmentFlyout: { renameKey: 'addAgentToPolicyId', policyIdAsValue: true }, - }; - } - - const redirectBackRouteState: CreatePackagePolicyRouteState = { - onSaveNavigateTo: redirectToPath, - onSaveQueryParams, - onCancelNavigateTo: [ - INTEGRATIONS_PLUGIN_ID, - { - path: pagePathGetters.integration_details_overview({ - pkgkey, - ...(integration ? { integration } : {}), - })[1], - }, - ], - onCancelUrl: currentPath, - }; - - services.application.navigateToApp(PLUGIN_ID, { - path, - state: redirectBackRouteState, }); + + services.application.navigateToApp(...navigateOptions); }, [ history, @@ -375,10 +322,8 @@ export function Detail() { { isDivider: true }, { content: ( - - ) : ( - - ), - } - : undefined - } - > - - + /> ), }, ].map((item, index) => ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts new file mode 100644 index 000000000000..4ee67874e61c --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getInstallPkgRouteOptions } from '.'; + +// this is always the same +const expectedOnCancelNavigateTo = [ + 'integrations', + { + path: '/detail/myintegration-1.0.0/overview?integration=myintegration', + }, +]; + +describe('getInstallPkgRouteOptions', () => { + it('should redirect to integrations app on save if no agentPolicyId present', () => { + const opts = { + currentPath: 'currentPath', + integration: 'myintegration', + pkgkey: 'myintegration-1.0.0', + }; + + const expectedRedirectURl = '/detail/myintegration-1.0.0/policies?integration=myintegration'; + + const expectedOptions = { + path: '/integrations/myintegration-1.0.0/add-integration/myintegration', + state: { + onCancelUrl: 'currentPath', + onCancelNavigateTo: expectedOnCancelNavigateTo, + onSaveNavigateTo: ['integrations', { path: expectedRedirectURl }], + onSaveQueryParams: { + showAddAgentHelp: { renameKey: 'showAddAgentHelpForPolicyId', policyIdAsValue: true }, + openEnrollmentFlyout: { renameKey: 'addAgentToPolicyId', policyIdAsValue: true }, + }, + }, + }; + + expect(getInstallPkgRouteOptions(opts)).toEqual(['fleet', expectedOptions]); + }); + + it('should redirect to fleet app on save if agentPolicyId present', () => { + const opts = { + currentPath: 'currentPath', + integration: 'myintegration', + pkgkey: 'myintegration-1.0.0', + agentPolicyId: '12345', + }; + + const expectedRedirectURl = '/policies/12345'; + + const expectedOptions = { + path: '/integrations/myintegration-1.0.0/add-integration/myintegration?policyId=12345', + state: { + onCancelUrl: 'currentPath', + onCancelNavigateTo: expectedOnCancelNavigateTo, + onSaveNavigateTo: ['fleet', { path: expectedRedirectURl }], + onSaveQueryParams: { + showAddAgentHelp: true, + openEnrollmentFlyout: true, + }, + }, + }; + + expect(getInstallPkgRouteOptions(opts)).toEqual(['fleet', expectedOptions]); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts new file mode 100644 index 000000000000..dc34ac91447f --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { CreatePackagePolicyRouteState } from '../../../../../types'; +import { PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, pagePathGetters } from '../../../../../constants'; + +/* + * When the install package button is pressed, this fn decides which page to navigate to + * by generating the options to be passed to `services.application.navigateToApp`. + */ +export const getInstallPkgRouteOptions = ({ + currentPath, + integration, + agentPolicyId, + pkgkey, +}: { + currentPath: string; + integration: string | null; + agentPolicyId?: string; + pkgkey: string; +}): [string, { path: string; state: unknown }] => { + const integrationOpts: { integration?: string } = integration ? { integration } : {}; + const path = pagePathGetters.add_integration_to_policy({ + pkgkey, + ...integrationOpts, + ...(agentPolicyId ? { agentPolicyId } : {}), + })[1]; + + let redirectToPath: CreatePackagePolicyRouteState['onSaveNavigateTo'] & + CreatePackagePolicyRouteState['onCancelNavigateTo']; + let onSaveQueryParams: CreatePackagePolicyRouteState['onSaveQueryParams']; + if (agentPolicyId) { + redirectToPath = [ + PLUGIN_ID, + { + path: pagePathGetters.policy_details({ + policyId: agentPolicyId, + })[1], + }, + ]; + + onSaveQueryParams = { + showAddAgentHelp: true, + openEnrollmentFlyout: true, + }; + } else { + redirectToPath = [ + INTEGRATIONS_PLUGIN_ID, + { + path: pagePathGetters.integration_details_policies({ + pkgkey, + ...integrationOpts, + })[1], + }, + ]; + + onSaveQueryParams = { + showAddAgentHelp: { renameKey: 'showAddAgentHelpForPolicyId', policyIdAsValue: true }, + openEnrollmentFlyout: { renameKey: 'addAgentToPolicyId', policyIdAsValue: true }, + }; + } + + const state: CreatePackagePolicyRouteState = { + onSaveNavigateTo: redirectToPath, + onSaveQueryParams, + onCancelNavigateTo: [ + INTEGRATIONS_PLUGIN_ID, + { + path: pagePathGetters.integration_details_overview({ + pkgkey, + ...integrationOpts, + })[1], + }, + ], + onCancelUrl: currentPath, + }; + + return [PLUGIN_ID, { path, state }]; +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/index.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/index.ts new file mode 100644 index 000000000000..020e115f2309 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/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 { getInstallPkgRouteOptions } from './get_install_route_options'; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts index 408f884116e2..5b70d8a56ebd 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.mocks.ts @@ -18,7 +18,6 @@ jest.mock('../../hooks/use_request', () => { return { ...module, useGetSettings: jest.fn(), - sendGetFleetStatus: jest.fn(), sendGetOneAgentPolicy: jest.fn(), useGetAgents: jest.fn(), useGetAgentPolicies: jest.fn(), @@ -36,14 +35,14 @@ jest.mock('../../applications/fleet/sections/agents/hooks/use_fleet_server_unhea }); jest.mock( - '../../applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions', + '../../applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form', () => { const module = jest.requireActual( - '../../applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions' + '../../applications/fleet/components/fleet_server_instructions/hooks/use_advanced_form' ); return { ...module, - useFleetServerInstructions: jest.fn(), + useAdvancedForm: jest.fn(), }; } ); @@ -82,6 +81,9 @@ jest.mock('./steps', () => { }; }); -jest.mock('../../applications/fleet/sections/agents/services/has_fleet_server', () => { - return { policyHasFleetServer: jest.fn().mockReturnValue(true) }; +jest.mock('../../services', () => { + return { + ...jest.requireActual('../../services'), + policyHasFleetServer: jest.fn().mockReturnValue(true), + }; }); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index b2680129dd7d..6e46ec90d5fa 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -25,8 +25,7 @@ import { useFleetStatus, } from '../../hooks'; -import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components'; - +import { useAdvancedForm } from '../../applications/fleet/components/fleet_server_instructions/hooks'; import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy'; import type { FlyOutProps } from './types'; @@ -44,8 +43,8 @@ const TestComponent = (props: FlyOutProps) => ( ); -const setup = async (props?: FlyOutProps) => { - const testBed = await registerTestBed(TestComponent)(props); +const setup = (props?: FlyOutProps) => { + const testBed = registerTestBed(TestComponent)(props); const { find, component } = testBed; return { @@ -78,7 +77,7 @@ const testAgentPolicy: AgentPolicy = { describe('', () => { let testBed: TestBed; - beforeEach(async () => { + beforeEach(() => { (useGetSettings as jest.Mock).mockReturnValue({ data: { item: { fleet_server_hosts: ['test'] } }, }); @@ -93,13 +92,24 @@ describe('', () => { data: { item: { package_policies: [] } }, }); - (useFleetServerInstructions as jest.Mock).mockReturnValue({ + (useAdvancedForm as jest.Mock).mockReturnValue({ + eligibleFleetServerPolicies: [{ name: 'test', id: 'test' }], + refreshEligibleFleetServerPolicies: jest.fn(), + fleetServerPolicyId: 'test', + setFleetServerPolicyId: jest.fn(), + isFleetServerReady: true, serviceToken: 'test', - getServiceToken: jest.fn(), isLoadingServiceToken: false, - installCommand: jest.fn(), - platform: 'test', - setPlatform: jest.fn(), + generateServiceToken: jest.fn(), + fleetServerHostForm: { + saveFleetServerHost: jest.fn(), + fleetServerHost: 'https://test.server:8220', + setFleetServerHost: jest.fn(), + error: '', + validateFleetServerHost: jest.fn(), + }, + deploymentMode: 'quickstart', + setDeploymentMode: jest.fn(), }); (useGetAgents as jest.Mock).mockReturnValue({ @@ -111,8 +121,8 @@ describe('', () => { refreshAgentPolicies: jest.fn(), }); - await act(async () => { - testBed = await setup({ + act(() => { + testBed = setup({ onClose: jest.fn(), }); testBed.component.update(); @@ -123,15 +133,15 @@ describe('', () => { jest.clearAllMocks(); }); - it('should show loading when agent policies are loading', async () => { + it('should show loading when agent policies are loading', () => { (useAgentEnrollmentFlyoutData as jest.Mock).mockReturnValue?.({ agentPolicies: [], refreshAgentPolicies: jest.fn(), isLoadingInitialAgentPolicies: true, }); - await act(async () => { - testBed = await setup({ + act(() => { + testBed = setup({ onClose: jest.fn(), }); testBed.component.update(); @@ -143,7 +153,7 @@ describe('', () => { }); describe('managed instructions', () => { - it('uses the agent policy selection step', async () => { + it('uses the agent policy selection step', () => { const { exists } = testBed; expect(exists('agentEnrollmentFlyout')).toBe(true); @@ -152,10 +162,10 @@ describe('', () => { }); describe('with a specific policy', () => { - beforeEach(async () => { + beforeEach(() => { jest.clearAllMocks(); - await act(async () => { - testBed = await setup({ + act(() => { + testBed = setup({ agentPolicy: testAgentPolicy, onClose: jest.fn(), }); @@ -172,10 +182,10 @@ describe('', () => { }); describe('with a specific policy when no agentPolicies set', () => { - beforeEach(async () => { + beforeEach(() => { jest.clearAllMocks(); - await act(async () => { - testBed = await setup({ + act(() => { + testBed = setup({ agentPolicy: testAgentPolicy, onClose: jest.fn(), }); @@ -189,41 +199,41 @@ describe('', () => { expect(exists('agent-enrollment-key-selection-step')).toBe(true); }); }); - }); - // Skipped due to UI changing in https://github.com/elastic/kibana/issues/125534. These tests should be rethought overall - // to provide value around the new flyout structure - describe.skip('standalone instructions', () => { - it('uses the agent policy selection step', async () => { - const { exists, actions } = testBed; - actions.goToStandaloneTab(); + // Skipped due to UI changing in https://github.com/elastic/kibana/issues/125534. These tests should be rethought overall + // to provide value around the new flyout structure + describe.skip('standalone instructions', () => { + it('uses the agent policy selection step', async () => { + const { exists, actions } = testBed; + actions.goToStandaloneTab(); - expect(exists('agentEnrollmentFlyout')).toBe(true); - expect(exists('agent-policy-selection-step')).toBe(true); - expect(exists('agent-enrollment-key-selection-step')).toBe(false); - }); + expect(exists('agentEnrollmentFlyout')).toBe(true); + expect(exists('agent-policy-selection-step')).toBe(true); + expect(exists('agent-enrollment-key-selection-step')).toBe(false); + }); - describe('with a specific policy', () => { - beforeEach(async () => { - jest.clearAllMocks(); - await act(async () => { - testBed = await setup({ - agentPolicy: testAgentPolicy, - onClose: jest.fn(), + describe('with a specific policy', () => { + beforeEach(() => { + jest.clearAllMocks(); + act(() => { + testBed = setup({ + agentPolicy: testAgentPolicy, + onClose: jest.fn(), + }); + testBed.component.update(); }); - testBed.component.update(); }); - }); - it('does not use either of the agent policy selection or enrollment key steps', () => { - const { exists, actions } = testBed; - jest.clearAllMocks(); + it('does not use either of the agent policy selection or enrollment key steps', () => { + const { exists, actions } = testBed; + jest.clearAllMocks(); - actions.goToStandaloneTab(); + actions.goToStandaloneTab(); - expect(exists('agentEnrollmentFlyout')).toBe(true); - expect(exists('agent-policy-selection-step')).toBe(false); - expect(exists('agent-enrollment-key-selection-step')).toBe(false); + expect(exists('agentEnrollmentFlyout')).toBe(true); + expect(exists('agent-policy-selection-step')).toBe(false); + expect(exists('agent-enrollment-key-selection-step')).toBe(false); + }); }); }); }); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx index 81ec1236920b..304239ac77da 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_policy_select_create.tsx @@ -20,7 +20,7 @@ import { AgentPolicySelection } from '.'; interface Props { agentPolicies: AgentPolicy[]; - selectedPolicy?: AgentPolicy; + selectedPolicyId?: string; setSelectedPolicyId: (agentPolicyId?: string) => void; excludeFleetServer?: boolean; withKeySelection: boolean; @@ -34,7 +34,7 @@ export const SelectCreateAgentPolicy: React.FC = ({ agentPolicies, excludeFleetServer, setSelectedPolicyId, - selectedPolicy, + selectedPolicyId, withKeySelection, selectedApiKeyId, onKeyChange, @@ -111,7 +111,7 @@ export const SelectCreateAgentPolicy: React.FC = ({ onKeyChange={onKeyChange} excludeFleetServer={excludeFleetServer} onClickCreatePolicy={onClickCreatePolicy} - selectedPolicy={selectedPolicy} + selectedPolicyId={selectedPolicyId} setSelectedPolicyId={setSelectedPolicyId} isFleetServerPolicy={isFleetServerPolicy} /> 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 c5a607630852..4c02ddeeaaf2 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 @@ -23,7 +23,7 @@ describe('step select agent policy', () => { (renderResult = testRenderer.render( void; excludeFleetServer?: boolean; onClickCreatePolicy: () => void; @@ -50,23 +50,10 @@ type Props = { } ); -const resolveAgentId = ( - agentPolicies: AgentPolicy[], - selectedAgentPolicyId?: string -): undefined | string => { - if (agentPolicies.length && !selectedAgentPolicyId) { - if (agentPolicies.length === 1) { - return agentPolicies[0].id; - } - } - - return selectedAgentPolicyId; -}; - export const AgentPolicySelection: React.FC = (props) => { const { agentPolicies, - selectedPolicy, + selectedPolicyId, setSelectedPolicyId, excludeFleetServer, onClickCreatePolicy, @@ -75,17 +62,6 @@ export const AgentPolicySelection: React.FC = (props) => { const hasFleetAllPrivileges = useAuthz().fleet.all; - useEffect( - function useDefaultAgentPolicyEffect() { - const resolvedId = resolveAgentId(agentPolicies, selectedPolicy?.id); - // find AgentPolicy - if (resolvedId !== selectedPolicy?.id) { - setSelectedPolicyId(resolvedId); - } - }, - [agentPolicies, setSelectedPolicyId, selectedPolicy] - ); - const onChangeCallback = (event: React.ChangeEvent) => { const { value } = event.target; setSelectedPolicyId(value); @@ -144,7 +120,7 @@ export const AgentPolicySelection: React.FC = (props) => { value: agentPolicy.id, text: agentPolicy.name, }))} - value={selectedPolicy?.id} + value={selectedPolicyId} onChange={onChangeCallback} aria-label={i18n.translate( 'xpack.fleet.enrollmentStepAgentPolicy.policySelectAriaLabel', @@ -152,28 +128,28 @@ export const AgentPolicySelection: React.FC = (props) => { defaultMessage: 'Agent policy', } )} - hasNoInitialSelection={!selectedPolicy?.id} + hasNoInitialSelection={!selectedPolicyId} data-test-subj="agentPolicyDropdown" - isInvalid={!selectedPolicy?.id} + isInvalid={!selectedPolicyId} /> - {selectedPolicy?.id && !isFleetServerPolicy && ( + {selectedPolicyId && !isFleetServerPolicy && ( <> )} - {props.withKeySelection && props.onKeyChange && selectedPolicy?.id && ( + {props.withKeySelection && props.onKeyChange && selectedPolicyId && ( <> )} diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx index 5e6ea53261cd..7dba93e5ddd3 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx @@ -13,17 +13,19 @@ import { useFleetStatus, useGetAgents } from '../../hooks'; import { FleetServerRequirementPage } from '../../applications/fleet/sections/agents/agent_requirements_page'; -import { policyHasFleetServer } from '../../applications/fleet/sections/agents/services/has_fleet_server'; - import { FLEET_SERVER_PACKAGE } from '../../constants'; import { useFleetServerUnhealthy } from '../../applications/fleet/sections/agents/hooks/use_fleet_server_unhealthy'; import { Loading } from '..'; +import { policyHasFleetServer } from '../../services'; + +import { AdvancedTab } from '../../applications/fleet/components/fleet_server_instructions/advanced_tab'; + import type { InstructionProps } from './types'; -import { ManagedSteps, StandaloneSteps, FleetServerSteps } from './steps'; +import { ManagedSteps, StandaloneSteps } from './steps'; import { DefaultMissingRequirements } from './default_missing_requirements'; export const Instructions = (props: InstructionProps) => { @@ -35,6 +37,7 @@ export const Instructions = (props: InstructionProps) => { selectionType, setSelectionType, mode, + setMode, isIntegrationFlow, } = props; const fleetStatus = useFleetStatus(); @@ -86,7 +89,7 @@ export const Instructions = (props: InstructionProps) => { if (mode === 'managed') { if (showFleetServerEnrollment) { - return ; + return setMode('standalone')} />; } else if (showAgentEnrollment) { return ( <> @@ -101,11 +104,7 @@ export const Instructions = (props: InstructionProps) => { )} - {isFleetServerPolicySelected ? ( - - ) : ( - - )} + {isFleetServerPolicySelected ? : } ); } diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/agent_policy_selection_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/agent_policy_selection_step.tsx index c26bfd3f0e2b..a0828bb72f48 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/agent_policy_selection_step.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/agent_policy_selection_step.tsx @@ -40,7 +40,7 @@ export const AgentPolicySelectionStep = ({ <> = ({ return ; }; - -export const FleetServerSteps: React.FunctionComponent = ({ - agentPolicy, - agentPolicies, - selectedPolicy, - setSelectedPolicyId, - refreshAgentPolicies, -}) => { - const [selectedApiKeyId, setSelectedAPIKeyId] = useState(); - - const apiKey = useGetOneEnrollmentAPIKey(selectedApiKeyId); - const apiKeyData = apiKey?.data; - const fleetServerInstructions = useFleetServerInstructions(apiKeyData?.item?.policy_id); - - const fleetServerSteps = useMemo(() => { - const { - serviceToken, - getServiceToken, - isLoadingServiceToken, - installCommand: managedInstallCommands, - platform, - setPlatform, - deploymentMode, - setDeploymentMode, - addFleetServerHost, - } = fleetServerInstructions; - - return [ - deploymentModeStep({ deploymentMode, setDeploymentMode }), - addFleetServerHostStep({ addFleetServerHost }), - ServiceTokenStep({ serviceToken, getServiceToken, isLoadingServiceToken }), - FleetServerCommandStep({ - serviceToken, - installCommand: managedInstallCommands, - platform, - setPlatform, - }), - ]; - }, [fleetServerInstructions]); - - const instructionsSteps = useMemo(() => { - const steps: EuiContainedStepProps[] = !agentPolicy - ? [ - AgentPolicySelectionStep({ - selectedPolicy, - agentPolicies, - selectedApiKeyId, - setSelectedAPIKeyId, - setSelectedPolicyId, - refreshAgentPolicies, - }), - ] - : [ - AgentEnrollmentKeySelectionStep({ - selectedPolicy, - selectedApiKeyId, - setSelectedAPIKeyId, - }), - ]; - - steps.push(...fleetServerSteps); - - return steps; - }, [ - agentPolicy, - selectedPolicy, - agentPolicies, - selectedApiKeyId, - setSelectedPolicyId, - refreshAgentPolicies, - fleetServerSteps, - ]); - - return ; -}; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_managed_agent_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_managed_agent_step.tsx index 59f6fdeafe72..bbb3d8d4794c 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_managed_agent_step.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_managed_agent_step.tsx @@ -14,7 +14,7 @@ import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/st import type { GetOneEnrollmentAPIKeyResponse } from '../../../../common/types/rest_spec/enrollment_api_key'; import { InstallSection } from '../../enrollment_instructions/install_section'; -import type { CommandsByPlatform } from '../../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils'; +import type { CommandsByPlatform } from '../../../applications/fleet/components/fleet_server_instructions/utils/install_command_utils'; import type { K8sMode } from '../types'; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_standalone_agent_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_standalone_agent_step.tsx index fb6ddfd393dc..74ce555f7c2e 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_standalone_agent_step.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_standalone_agent_step.tsx @@ -10,7 +10,8 @@ import { i18n } from '@kbn/i18n'; import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; -import type { CommandsByPlatform } from '../../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils'; +import type { CommandsByPlatform } from '../../../applications/fleet/components/fleet_server_instructions/utils/install_command_utils'; + import { InstallSection } from '../../enrollment_instructions/install_section'; import type { K8sMode } from '../types'; diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx index 0cf43902db7e..1ebe68b8c528 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx @@ -7,13 +7,12 @@ import React from 'react'; +import type { CommandsByPlatform } from '../../applications/fleet/components/fleet_server_instructions/utils'; + import { InstallationMessage } from '../agent_enrollment_flyout/installation_message'; import type { K8sMode } from '../agent_enrollment_flyout/types'; - -import type { CommandsByPlatform } from '../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils'; - -import { PlatformSelector } from './manual/platform_selector'; +import { PlatformSelector } from '../platform_selector'; interface Props { installCommand: CommandsByPlatform; diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/standalone/index.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/standalone/index.tsx index 89c1dfe3cac3..75378cdc8637 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/standalone/index.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/standalone/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { CommandsByPlatform } from '../../../applications/fleet/sections/agents/agent_requirements_page/components/install_command_utils'; +import type { CommandsByPlatform } from '../../../applications/fleet/components/fleet_server_instructions/utils/install_command_utils'; import type { K8sMode } from '../../agent_enrollment_flyout/types'; export const StandaloneInstructions = ( diff --git a/x-pack/plugins/fleet/public/components/index.ts b/x-pack/plugins/fleet/public/components/index.ts index 9df4182bc8a4..723b376699e0 100644 --- a/x-pack/plugins/fleet/public/components/index.ts +++ b/x-pack/plugins/fleet/public/components/index.ts @@ -23,3 +23,4 @@ export { AddAgentHelpPopover } from './add_agent_help_popover'; export { EuiButtonWithTooltip } from './eui_button_with_tooltip'; export * from './link_and_revision'; export * from './agent_enrollment_flyout'; +export * from './platform_selector'; diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/platform_selector.tsx b/x-pack/plugins/fleet/public/components/platform_selector.tsx similarity index 96% rename from x-pack/plugins/fleet/public/components/enrollment_instructions/manual/platform_selector.tsx rename to x-pack/plugins/fleet/public/components/platform_selector.tsx index e03e43907f82..ae18f56b4b3a 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/manual/platform_selector.tsx +++ b/x-pack/plugins/fleet/public/components/platform_selector.tsx @@ -10,8 +10,8 @@ import styled from 'styled-components'; import { EuiSpacer, EuiCodeBlock, EuiButtonGroup, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { PLATFORM_TYPE } from '../../../hooks'; -import { PLATFORM_OPTIONS, usePlatform } from '../../../hooks'; +import type { PLATFORM_TYPE } from '../hooks'; +import { PLATFORM_OPTIONS, usePlatform } from '../hooks'; interface Props { linuxCommand: string; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index c94f2ce139ab..34fa9d2cbcfc 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -49,8 +49,14 @@ import { setupRouteService, appRoutesService, calculateAuthz, + parseExperimentalConfigValue, +} from '../common'; +import type { + CheckPermissionsResponse, + PostFleetSetupResponse, + FleetAuthz, + ExperimentalFeatures, } from '../common'; -import type { CheckPermissionsResponse, PostFleetSetupResponse, FleetAuthz } from '../common'; import type { FleetConfigType } from '../common/types'; @@ -60,6 +66,7 @@ import { setHttpClient } from './hooks/use_request'; import { createPackageSearchProvider } from './search_provider'; import { TutorialDirectoryHeaderLink, TutorialModuleNotice } from './components/home_integration'; import { createExtensionRegistrationCallback } from './services/ui_extensions'; +import { ExperimentalFeaturesService } from './services/experimental_features'; import type { UIExtensionRegistrationCallback, UIExtensionsStorage } from './types'; import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extension'; @@ -113,10 +120,12 @@ export class FleetPlugin implements Plugin(); + this.experimentalFeatures = parseExperimentalConfigValue(this.config.enableExperimental || []); this.kibanaVersion = initializerContext.env.packageInfo.version; } @@ -251,6 +260,7 @@ export class FleetPlugin implements Plugin core.http.get(appRoutesService.getCheckPermissionsPath()) diff --git a/x-pack/plugins/fleet/public/services/experimental_features.ts b/x-pack/plugins/fleet/public/services/experimental_features.ts new file mode 100644 index 000000000000..afe5d0d65e3f --- /dev/null +++ b/x-pack/plugins/fleet/public/services/experimental_features.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ExperimentalFeatures } from '../../common/experimental_features'; + +export class ExperimentalFeaturesService { + private static experimentalFeatures?: ExperimentalFeatures; + + public static init(experimentalFeatures: ExperimentalFeatures) { + this.experimentalFeatures = experimentalFeatures; + } + + public static get(): ExperimentalFeatures { + if (!this.experimentalFeatures) { + this.throwUninitializedError(); + } + + return this.experimentalFeatures; + } + + private static throwUninitializedError(): never { + throw new Error('Experimental features services not initialized'); + } +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/has_fleet_server.ts b/x-pack/plugins/fleet/public/services/has_fleet_server.ts similarity index 79% rename from x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/has_fleet_server.ts rename to x-pack/plugins/fleet/public/services/has_fleet_server.ts index c10049303234..e1100d6447aa 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/services/has_fleet_server.ts +++ b/x-pack/plugins/fleet/public/services/has_fleet_server.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { AgentPolicy, PackagePolicy } from '../../../types'; -import { FLEET_SERVER_PACKAGE } from '../../../constants'; +import { FLEET_SERVER_PACKAGE } from '../constants'; +import type { AgentPolicy, PackagePolicy } from '../types'; export function policyHasFleetServer(agentPolicy: AgentPolicy) { return agentPolicy.package_policies?.some( diff --git a/x-pack/plugins/fleet/public/services/index.ts b/x-pack/plugins/fleet/public/services/index.ts index 306b081dce6c..2c1bedeaef82 100644 --- a/x-pack/plugins/fleet/public/services/index.ts +++ b/x-pack/plugins/fleet/public/services/index.ts @@ -12,6 +12,7 @@ export type { PackagePolicyConfigValidationResults, PackagePolicyInputValidationResults, } from '../../common'; +export { ExperimentalFeaturesService } from './experimental_features'; export { AgentStatusKueryHelper, agentPolicyRouteService, @@ -44,3 +45,4 @@ export { export * from './pkg_key_from_package_info'; export * from './ui_extensions'; export * from './increment_policy_name'; +export * from './has_fleet_server'; diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 3228c0395932..97ff8ade0585 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -11,6 +11,9 @@ import { schema } from '@kbn/config-schema'; import type { TypeOf } from '@kbn/config-schema'; import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; +import { getExperimentalAllowedValues, isValidExperimentalValue } from '../common'; +const allowedExperimentalValues = getExperimentalAllowedValues(); + import { PreconfiguredPackagesSchema, PreconfiguredAgentPoliciesSchema, @@ -139,6 +142,27 @@ export const config: PluginConfigDescriptor = { allowAgentUpgradeSourceUri: schema.boolean({ defaultValue: false }), bundledPackageLocation: schema.string({ defaultValue: DEFAULT_BUNDLED_PACKAGE_LOCATION }), }), + /** + * For internal use. A list of string values (comma delimited) that will enable experimental + * type of functionality that is not yet released. + * + * @example + * xpack.fleet.enableExperimental: + * - feature1 + * - feature2 + */ + enableExperimental: schema.arrayOf(schema.string(), { + defaultValue: () => [], + validate(list) { + for (const key of list) { + if (!isValidExperimentalValue(key)) { + return `[${key}] is not allowed. Allowed values are: ${allowedExperimentalValues.join( + ', ' + )}`; + } + } + }, + }), }), }; diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 01fe5b48f9e3..f3423fb3f1c0 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -22,7 +22,7 @@ import type { PackagePolicyServiceInterface } from '../services/package_policy'; import type { AgentPolicyServiceInterface } from '../services'; import type { FleetAppContext } from '../plugin'; import { createMockTelemetryEventsSender } from '../telemetry/__mocks__'; -import type { FleetConfigType } from '../../common'; +import type { FleetConfigType, ExperimentalFeatures } from '../../common'; import { createFleetAuthzMock } from '../../common'; import { agentServiceMock } from '../services/agents/agent_service.mock'; import type { FleetRequestHandlerContext } from '../types'; @@ -61,6 +61,7 @@ export const createAppContextStartContractMock = ( securitySetup: securityMock.createSetup(), securityStart: securityMock.createStart(), logger: loggingSystemMock.create().get(), + experimentalFeatures: {} as ExperimentalFeatures, isProductionMode: true, configInitialValue: { agents: { enabled: true, elasticsearch: {} }, diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index c9e3b9388087..ea38068a7613 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -42,8 +42,8 @@ import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/server'; -import type { FleetConfigType, FleetAuthz } from '../common'; -import { INTEGRATIONS_PLUGIN_ID } from '../common'; +import type { FleetConfigType, FleetAuthz, ExperimentalFeatures } from '../common'; +import { INTEGRATIONS_PLUGIN_ID, parseExperimentalConfigValue } from '../common'; import { PLUGIN_ID, @@ -119,6 +119,7 @@ export interface FleetAppContext { securityStart: SecurityPluginStart; config$?: Observable; configInitialValue: FleetConfigType; + experimentalFeatures: ExperimentalFeatures; savedObjects: SavedObjectsServiceStart; isProductionMode: PluginInitializerContext['env']['mode']['prod']; kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; @@ -392,6 +393,9 @@ export class FleetPlugin securityStart: plugins.security, configInitialValue: this.configInitialValue, config$: this.config$, + experimentalFeatures: parseExperimentalConfigValue( + this.configInitialValue.enableExperimental || [] + ), savedObjects: core.savedObjects, isProductionMode: this.isProductionMode, kibanaVersion: this.kibanaVersion, diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 71409f8d115c..088e4d7c8b3f 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -26,7 +26,7 @@ import type { SecurityPluginStart, SecurityPluginSetup } from '@kbn/security-plu import type { CloudSetup } from '@kbn/cloud-plugin/server'; -import type { FleetConfigType } from '../../common'; +import type { FleetConfigType, ExperimentalFeatures } from '../../common'; import type { ExternalCallback, ExternalCallbacksStorage, @@ -43,6 +43,7 @@ class AppContextService { private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; private data: DataPluginStart | undefined; private esClient: ElasticsearchClient | undefined; + private experimentalFeatures?: ExperimentalFeatures; private securitySetup: SecurityPluginSetup | undefined; private securityStart: SecurityPluginStart | undefined; private config$?: Observable; @@ -65,6 +66,7 @@ class AppContextService { this.securitySetup = appContext.securitySetup; this.securityStart = appContext.securityStart; this.savedObjects = appContext.savedObjects; + this.experimentalFeatures = appContext.experimentalFeatures; this.isProductionMode = appContext.isProductionMode; this.cloud = appContext.cloud; this.logger = appContext.logger; @@ -126,6 +128,13 @@ class AppContextService { return this.config$; } + public getExperimentalFeatures() { + if (!this.experimentalFeatures) { + throw new Error('experimentalFeatures not set.'); + } + return this.experimentalFeatures; + } + public getSavedObjects() { if (!this.savedObjects) { throw new Error('Saved objects start service not set.'); diff --git a/x-pack/plugins/ml/common/openapi/README.md b/x-pack/plugins/ml/common/openapi/README.md new file mode 100644 index 000000000000..28246e8b43d3 --- /dev/null +++ b/x-pack/plugins/ml/common/openapi/README.md @@ -0,0 +1,20 @@ +# OpenAPI (Experimental) + +The current self-contained spec file can be used for online tools like those found at https://openapi.tools/. This spec is experimental and may be incomplete or change later. + +A guide about the openApi specification can be found at [https://swagger.io/docs/specification/about/](https://swagger.io/docs/specification/about/). + + ## Tools + +It is possible to validate the docs before bundling them by running the following command in the `x-pack/plugins/ml/common/openapi/` folder: + ``` + npx swagger-cli validate ml_apis.yaml + ``` + +Then generate the `bundled` files with the following commands: + + ``` + npx @redocly/openapi-cli bundle --ext yaml --output bundled.yaml ml_apis.yaml + npx @redocly/openapi-cli bundle --ext json --output bundled.json ml_apis.yaml + ``` + diff --git a/x-pack/plugins/ml/common/openapi/bundled.json b/x-pack/plugins/ml/common/openapi/bundled.json new file mode 100644 index 000000000000..455bf02bbd23 --- /dev/null +++ b/x-pack/plugins/ml/common/openapi/bundled.json @@ -0,0 +1,225 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Machine learning APIs", + "description": "Kibana APIs for the machine learning feature", + "version": "1.0.0", + "license": { + "name": "Elastic License 2.0", + "url": "https://www.elastic.co/licensing/elastic-license" + } + }, + "tags": [ + { + "name": "ml", + "description": "Machine learning" + } + ], + "servers": [ + { + "url": "https://localhost:5601/" + } + ], + "paths": { + "/s/{spaceId}/api/ml/saved_objects/sync": { + "get": { + "description": "Synchronizes Kibana saved objects for machine learning jobs and trained models. You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges. This API runs automatically when you start Kibana and periodically thereafter.\n", + "parameters": [ + { + "$ref": "#/components/parameters/spaceParam" + }, + { + "$ref": "#/components/parameters/simulateParam" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/syncResponse" + } + } + } + } + } + } + } + }, + "/api/ml/saved_objects/sync": { + "get": { + "description": "Synchronizes Kibana saved objects for machine learning jobs and trained models. You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges. This API runs automatically when you start Kibana and periodically thereafter.\n", + "parameters": [ + { + "$ref": "#/components/parameters/simulateParam" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/syncResponse" + } + } + } + } + } + } + } + } + }, + "components": { + "parameters": { + "spaceParam": { + "in": "path", + "name": "spaceId", + "description": "An identifier for the space.", + "required": true, + "schema": { + "type": "string" + } + }, + "simulateParam": { + "in": "query", + "name": "simulate", + "description": "When true, simulates the synchronization by returning only the list actions that would be performed.", + "required": false, + "schema": { + "type": "boolean" + }, + "example": "true" + } + }, + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic" + } + }, + "schemas": { + "syncResponse": { + "type": "object", + "properties": { + "datafeedsAdded": { + "type": "object", + "description": "If a saved object for an anomaly detection job is missing a datafeed identifier, it is added. This list contains the datafeed identifiers and indicates whether the synchronization was successful.", + "additionalProperties": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + }, + "datafeedsRemoved": { + "type": "object", + "description": "If a saved object for an anomaly detection job references a datafeed that no longer exists, it is deleted. This list contains the datafeed identifiers and indicates whether the synchronization was successful.", + "additionalProperties": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + }, + "savedObjectsCreated": { + "type": "object", + "description": "If saved objects are missing for machine learning jobs or trained models, they are created. This list contains the job and model identifiers and indicates whether the synchronization was successful.", + "properties": { + "anomaly-detector": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + } + } + } + }, + "data-frame-analytics": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + } + } + } + }, + "trained-model": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + } + } + } + } + } + }, + "savedObjectsDeleted": { + "type": "object", + "description": "If saved objects exist for machine learning jobs or trained models that no longer exist, they are deleted. This list contains the job and model identifiers and indicates whether the synchronization was successful.", + "properties": { + "anomaly-detector": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + } + } + } + }, + "data-frame-analytics": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + } + } + } + }, + "trained-model": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "basicAuth": [] + } + ] +} \ No newline at end of file diff --git a/x-pack/plugins/ml/common/openapi/bundled.yaml b/x-pack/plugins/ml/common/openapi/bundled.yaml new file mode 100644 index 000000000000..235979ff1651 --- /dev/null +++ b/x-pack/plugins/ml/common/openapi/bundled.yaml @@ -0,0 +1,167 @@ +openapi: 3.0.1 +info: + title: Machine learning APIs + description: Kibana APIs for the machine learning feature + version: 1.0.0 + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +tags: + - name: ml + description: Machine learning +servers: + - url: https://localhost:5601/ +paths: + /s/{spaceId}/api/ml/saved_objects/sync: + get: + description: > + Synchronizes Kibana saved objects for machine learning jobs and trained + models. You must have `all` privileges for the **Machine Learning** + feature in the **Analytics** section of the Kibana feature privileges. + This API runs automatically when you start Kibana and periodically + thereafter. + parameters: + - $ref: '#/components/parameters/spaceParam' + - $ref: '#/components/parameters/simulateParam' + responses: + '200': + description: Indicates a successful call + content: + application/json: + schema: + items: + $ref: '#/components/schemas/syncResponse' + /api/ml/saved_objects/sync: + get: + description: > + Synchronizes Kibana saved objects for machine learning jobs and trained + models. You must have `all` privileges for the **Machine Learning** + feature in the **Analytics** section of the Kibana feature privileges. + This API runs automatically when you start Kibana and periodically + thereafter. + parameters: + - $ref: '#/components/parameters/simulateParam' + responses: + '200': + description: Indicates a successful call + content: + application/json: + schema: + items: + $ref: '#/components/schemas/syncResponse' +components: + parameters: + spaceParam: + in: path + name: spaceId + description: An identifier for the space. + required: true + schema: + type: string + simulateParam: + in: query + name: simulate + description: >- + When true, simulates the synchronization by returning only the list + actions that would be performed. + required: false + schema: + type: boolean + example: 'true' + securitySchemes: + basicAuth: + type: http + scheme: basic + schemas: + syncResponse: + type: object + properties: + datafeedsAdded: + type: object + description: >- + If a saved object for an anomaly detection job is missing a datafeed + identifier, it is added. This list contains the datafeed identifiers + and indicates whether the synchronization was successful. + additionalProperties: + type: object + properties: + success: + type: boolean + datafeedsRemoved: + type: object + description: >- + If a saved object for an anomaly detection job references a datafeed + that no longer exists, it is deleted. This list contains the + datafeed identifiers and indicates whether the synchronization was + successful. + additionalProperties: + type: object + properties: + success: + type: boolean + savedObjectsCreated: + type: object + description: >- + If saved objects are missing for machine learning jobs or trained + models, they are created. This list contains the job and model + identifiers and indicates whether the synchronization was + successful. + properties: + anomaly-detector: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + data-frame-analytics: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + trained-model: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + savedObjectsDeleted: + type: object + description: >- + If saved objects exist for machine learning jobs or trained models + that no longer exist, they are deleted. This list contains the job + and model identifiers and indicates whether the synchronization was + successful. + properties: + anomaly-detector: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + data-frame-analytics: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + trained-model: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true +security: + - basicAuth: [] diff --git a/x-pack/plugins/ml/common/openapi/ml_apis.yaml b/x-pack/plugins/ml/common/openapi/ml_apis.yaml new file mode 100644 index 000000000000..827aa1075960 --- /dev/null +++ b/x-pack/plugins/ml/common/openapi/ml_apis.yaml @@ -0,0 +1,146 @@ +openapi: 3.0.1 +info: + title: Machine learning APIs + description: Kibana APIs for the machine learning feature + version: "1.0.0" + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +tags: + - name: ml + description: Machine learning +servers: + - url: https://localhost:5601/ +paths: + /s/{spaceId}/api/ml/saved_objects/sync: + get: + description: > + Synchronizes Kibana saved objects for machine learning jobs and trained models. + You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges. + This API runs automatically when you start Kibana and periodically thereafter. + parameters: + - $ref: '#/components/parameters/spaceParam' + - $ref: '#/components/parameters/simulateParam' + responses: + '200': + description: Indicates a successful call + content: + application/json: + schema: + items: + $ref: '#/components/schemas/syncResponse' + /api/ml/saved_objects/sync: + get: + description: > + Synchronizes Kibana saved objects for machine learning jobs and trained models. + You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges. + This API runs automatically when you start Kibana and periodically thereafter. + parameters: + - $ref: '#/components/parameters/simulateParam' + responses: + '200': + description: Indicates a successful call + content: + application/json: + schema: + items: + $ref: '#/components/schemas/syncResponse' +components: + parameters: + spaceParam: + in: path + name: spaceId + description: An identifier for the space. + required: true + schema: + type: string + simulateParam: + in: query + name: simulate + description: When true, simulates the synchronization by returning only the list actions that would be performed. + required: false + schema: + type: boolean + example: 'true' + securitySchemes: + basicAuth: + type: http + scheme: basic + schemas: + syncResponse: + type: object + properties: + datafeedsAdded: + type: object + description: If a saved object for an anomaly detection job is missing a datafeed identifier, it is added. This list contains the datafeed identifiers and indicates whether the synchronization was successful. + additionalProperties: + type: object + properties: + success: + type: boolean + datafeedsRemoved: + type: object + description: If a saved object for an anomaly detection job references a datafeed that no longer exists, it is deleted. This list contains the datafeed identifiers and indicates whether the synchronization was successful. + additionalProperties: + type: object + properties: + success: + type: boolean + savedObjectsCreated: + type: object + description: If saved objects are missing for machine learning jobs or trained models, they are created. This list contains the job and model identifiers and indicates whether the synchronization was successful. + properties: + anomaly-detector: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + data-frame-analytics: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + trained-model: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + savedObjectsDeleted: + type: object + description: If saved objects exist for machine learning jobs or trained models that no longer exist, they are deleted. This list contains the job and model identifiers and indicates whether the synchronization was successful. + properties: + anomaly-detector: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + data-frame-analytics: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true + trained-model: + type: object + additionalProperties: + type: object + properties: + success: + type: boolean + example: true +security: + - basicAuth: [] \ No newline at end of file diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx index 1cc63a2688bf..cb299ee9dba3 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx @@ -639,8 +639,8 @@ export const LinksMenuUI = (props: LinksMenuProps) => { return ( ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts index c4611a174091..0a75c6467f9d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts @@ -7,12 +7,12 @@ import { useState, useEffect } from 'react'; import { + buildEsQuery, + buildQueryFromFilters, decorateQuery, fromKueryExpression, - luceneStringToDsl, toElasticsearchQuery, } from '@kbn/es-query'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { useMlContext } from '../../../../../contexts/ml'; import { SEARCH_QUERY_LANGUAGE } from '../../../../../../../common/constants/search'; import { getQueryFromSavedSearchObject } from '../../../../../util/index_utils'; @@ -36,19 +36,42 @@ export function useSavedSearch() { const { currentSavedSearch, currentDataView, kibanaConfig } = mlContext; const getQueryData = () => { - let qry: estypes.QueryDslQueryContainer = {}; + let qry: any = {}; let qryString; if (currentSavedSearch !== null) { - const { query } = getQueryFromSavedSearchObject(currentSavedSearch); + const { query, filter } = getQueryFromSavedSearchObject(currentSavedSearch); const queryLanguage = query.language; qryString = query.query; if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) { const ast = fromKueryExpression(qryString); qry = toElasticsearchQuery(ast, currentDataView); + const filterQuery = buildQueryFromFilters(filter, currentDataView); + if (qry.bool === undefined) { + qry.bool = {}; + // toElasticsearchQuery may add a single match_all item to the + // root of its returned query, rather than putting it inside + // a bool.should + // in this case, move it to a bool.should + if (qry.match_all !== undefined) { + qry.bool.should = { + match_all: qry.match_all, + }; + delete qry.match_all; + } + } + + if (Array.isArray(qry.bool.filter) === false) { + qry.bool.filter = qry.bool.filter === undefined ? [] : [qry.bool.filter]; + } + if (Array.isArray(qry.bool.must_not) === false) { + qry.bool.must_not = qry.bool.must_not === undefined ? [] : [qry.bool.must_not]; + } + qry.bool.filter = [...qry.bool.filter, ...filterQuery.filter]; + qry.bool.must_not = [...qry.bool.must_not, ...filterQuery.must_not]; } else { - qry = luceneStringToDsl(qryString); + qry = buildEsQuery(currentDataView, [query], filter); decorateQuery(qry, kibanaConfig.get('query:queryString:options')); } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx index c35ad5bacf37..524556e12a9a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx @@ -108,7 +108,7 @@ export const Page: FC<{ /> ) : null} {jobIdToUse !== undefined && ( - + { <> {isIdSelectorFlyoutVisible ? ( { ) : null} {jobId !== undefined ? ( - + { - {mapJobId || mapModelId || analyticsId ? ( + {jobId ?? modelId ? ( ) : ( diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap index 969406724537..45d9296f1752 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap @@ -25,6 +25,7 @@ exports[`AddItemPopover calls addItems with multiple items on clicking Add butto display="inlineBlock" hasArrow={true} id="add_item_popover" + initialFocus="#filter_list_add_item_input_row" isOpen={false} ownFocus={true} panelClassName="ml-add-filter-item-popover" @@ -37,6 +38,7 @@ exports[`AddItemPopover calls addItems with multiple items on clicking Add butto fullWidth={false} hasChildLabel={true} hasEmptyLabelSpace={false} + id="filter_list_add_item_input_row" label={
; + +export const postElasticsearchCcrResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_ccr_shard.ts b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_ccr_shard.ts new file mode 100644 index 000000000000..64f75658420f --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_ccr_shard.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 * as rt from 'io-ts'; +import { clusterUuidRT, ccsRT, timeRangeRT } from '../shared'; + +export const postElasticsearchCcrShardRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, + index: rt.string, + shardId: rt.string, +}); + +export const postElasticsearchCcrShardRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + timeRange: timeRangeRT, + }), +]); + +export type PostElasticsearchCcrShardRequestPayload = rt.TypeOf< + typeof postElasticsearchCcrShardRequestPayloadRT +>; + +export const postElasticsearchCcrShardResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_index_detail.ts b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_index_detail.ts new file mode 100644 index 000000000000..b9bb491c0c38 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_index_detail.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 * as rt from 'io-ts'; +import { ccsRT, clusterUuidRT, createLiteralValueFromUndefinedRT, timeRangeRT } from '../shared'; + +export const postElasticsearchIndexDetailRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, + id: rt.string, +}); + +export const postElasticsearchIndexDetailRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + is_advanced: rt.union([rt.boolean, createLiteralValueFromUndefinedRT(false)]), + timeRange: timeRangeRT, + }), +]); + +export type PostElasticsearchIndexDetailRequestPayload = rt.TypeOf< + typeof postElasticsearchIndexDetailRequestPayloadRT +>; + +export const postElasticsearchIndexDetailResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_indices.ts b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_indices.ts new file mode 100644 index 000000000000..57a4b953f15d --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_indices.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 * as rt from 'io-ts'; +import { + booleanFromStringRT, + ccsRT, + clusterUuidRT, + createLiteralValueFromUndefinedRT, + timeRangeRT, +} from '../shared'; + +export const postElasticsearchIndicesRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, +}); + +export const postElasticsearchIndicesRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + is_advanced: rt.union([rt.boolean, createLiteralValueFromUndefinedRT(false)]), + timeRange: timeRangeRT, + }), +]); + +export type PostElasticsearchIndicesRequestPayload = rt.TypeOf< + typeof postElasticsearchIndicesRequestPayloadRT +>; + +export const postElasticsearchIndicesRequestQueryRT = rt.type({ + show_system_indices: booleanFromStringRT, +}); + +export const postElasticsearchIndicesResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_ml_jobs.ts b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_ml_jobs.ts new file mode 100644 index 000000000000..3cbe83fa5d27 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_ml_jobs.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { clusterUuidRT, ccsRT, timeRangeRT } from '../shared'; + +export const postElasticsearchMlJobsRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, +}); + +export const postElasticsearchMlJobsRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + timeRange: timeRangeRT, + }), +]); + +export type PostElasticsearchMlJobsRequestPayload = rt.TypeOf< + typeof postElasticsearchMlJobsRequestPayloadRT +>; + +export const postElasticsearchMlJobsResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_node_detail.ts b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_node_detail.ts new file mode 100644 index 000000000000..b29d9fb7b8da --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_node_detail.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 * as rt from 'io-ts'; +import { ccsRT, clusterUuidRT, createLiteralValueFromUndefinedRT, timeRangeRT } from '../shared'; + +export const postElasticsearchNodeDetailRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, + nodeUuid: rt.string, +}); + +export const postElasticsearchNodeDetailRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + showSystemIndices: rt.boolean, // show/hide system indices in shard allocation table + }), + rt.type({ + is_advanced: rt.union([rt.boolean, createLiteralValueFromUndefinedRT(false)]), + timeRange: timeRangeRT, + }), +]); + +export type PostElasticsearchNodeDetailRequestPayload = rt.TypeOf< + typeof postElasticsearchNodeDetailRequestPayloadRT +>; + +export const postElasticsearchNodeDetailResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_nodes.ts b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_nodes.ts new file mode 100644 index 000000000000..bba6525d9a2c --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_nodes.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 * as rt from 'io-ts'; +import { clusterUuidRT, ccsRT, timeRangeRT, paginationRT, sortingRT } from '../shared'; + +export const postElasticsearchNodesRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, +}); + +export const postElasticsearchNodesRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + queryText: rt.string, + sort: sortingRT, + }), + rt.type({ + timeRange: timeRangeRT, + pagination: paginationRT, + }), +]); + +export type PostElasticsearchNodesRequestPayload = rt.TypeOf< + typeof postElasticsearchNodesRequestPayloadRT +>; + +export const postElasticsearchNodesResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_overview.ts b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_overview.ts new file mode 100644 index 000000000000..da82f7cfedbf --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/elasticsearch/post_elasticsearch_overview.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { clusterUuidRT, ccsRT, timeRangeRT } from '../shared'; + +export const postElasticsearchOverviewRequestParamsRT = rt.type({ + clusterUuid: clusterUuidRT, +}); + +export const postElasticsearchOverviewRequestPayloadRT = rt.intersection([ + rt.partial({ + ccs: ccsRT, + }), + rt.type({ + timeRange: timeRangeRT, + }), +]); + +export type PostElasticsearchOverviewRequestPayload = rt.TypeOf< + typeof postElasticsearchOverviewRequestPayloadRT +>; + +export const postElasticsearchOverviewResponsePayloadRT = rt.type({ + // TODO: add payload entries +}); diff --git a/x-pack/plugins/monitoring/common/http_api/shared/index.ts b/x-pack/plugins/monitoring/common/http_api/shared/index.ts index db7d3dce6c46..4e47c7239e39 100644 --- a/x-pack/plugins/monitoring/common/http_api/shared/index.ts +++ b/x-pack/plugins/monitoring/common/http_api/shared/index.ts @@ -5,6 +5,10 @@ * 2.0. */ -export * from './cluster'; export * from './ccs'; +export * from './cluster'; +export * from './literal_value'; +export * from './pagination'; +export * from './query_string_boolean'; +export * from './sorting'; export * from './time_range'; diff --git a/x-pack/plugins/monitoring/common/http_api/shared/literal_value.ts b/x-pack/plugins/monitoring/common/http_api/shared/literal_value.ts new file mode 100644 index 000000000000..55719d6b31b1 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/shared/literal_value.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 * as rt from 'io-ts'; + +export const createLiteralValueFromUndefinedRT = ( + literalValue: LiteralValue +) => + rt.undefined.pipe( + new rt.Type( + 'BooleanFromString', + rt.literal(literalValue).is, + (_value, _context) => rt.success(literalValue), + () => undefined + ) + ); diff --git a/x-pack/plugins/monitoring/common/http_api/shared/pagination.ts b/x-pack/plugins/monitoring/common/http_api/shared/pagination.ts new file mode 100644 index 000000000000..50f1f77b0361 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/shared/pagination.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 * as rt from 'io-ts'; + +export const paginationRT = rt.type({ + index: rt.number, + size: rt.number, +}); diff --git a/x-pack/plugins/monitoring/common/http_api/shared/query_string_boolean.ts b/x-pack/plugins/monitoring/common/http_api/shared/query_string_boolean.ts new file mode 100644 index 000000000000..b9b6fcae3780 --- /dev/null +++ b/x-pack/plugins/monitoring/common/http_api/shared/query_string_boolean.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 { chain } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; + +export const booleanFromStringRT = new rt.Type( + 'BooleanFromString', + rt.boolean.is, + (value, context) => + pipe( + rt.string.validate(value, context), + chain((stringValue) => + stringValue === 'true' + ? rt.success(true) + : stringValue === 'false' + ? rt.success(false) + : rt.failure(value, context) + ) + ), + String +); diff --git a/x-pack/plugins/security_solution/server/usage/queries/utils/is_elastic_rule.ts b/x-pack/plugins/monitoring/common/http_api/shared/sorting.ts similarity index 56% rename from x-pack/plugins/security_solution/server/usage/queries/utils/is_elastic_rule.ts rename to x-pack/plugins/monitoring/common/http_api/shared/sorting.ts index f08959702b29..c52c46a41376 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/utils/is_elastic_rule.ts +++ b/x-pack/plugins/monitoring/common/http_api/shared/sorting.ts @@ -5,7 +5,14 @@ * 2.0. */ -import { INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; +import * as rt from 'io-ts'; -export const isElasticRule = (tags: string[] = []) => - tags.includes(`${INTERNAL_IMMUTABLE_KEY}:true`); +const sortingDirectionRT = rt.keyof({ + asc: null, + desc: null, +}); + +export const sortingRT = rt.partial({ + field: rt.string, + direction: sortingDirectionRT, +}); diff --git a/x-pack/plugins/monitoring/common/http_api/shared/time_range.ts b/x-pack/plugins/monitoring/common/http_api/shared/time_range.ts index edeb56d1e2ea..4021e8d52f4c 100644 --- a/x-pack/plugins/monitoring/common/http_api/shared/time_range.ts +++ b/x-pack/plugins/monitoring/common/http_api/shared/time_range.ts @@ -6,8 +6,29 @@ */ import * as rt from 'io-ts'; +import moment from 'moment'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { chain } from 'fp-ts/lib/Either'; + +export const timestampFromStringRT = new rt.Type( + 'timestampFromStringRT', + (input): input is number => typeof input === 'number', + (input, context) => + pipe( + rt.string.validate(input, context), + chain((stringInput) => { + const momentValue = moment.utc(stringInput); + return momentValue.isValid() + ? rt.success(momentValue.valueOf()) + : rt.failure(stringInput, context); + }) + ), + (output) => new Date(output).toISOString() +); export const timeRangeRT = rt.type({ - min: rt.string, - max: rt.string, + min: timestampFromStringRT, + max: timestampFromStringRT, }); + +export type TimeRange = rt.TypeOf; diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/index_advanced_page.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/index_advanced_page.tsx index 734d59f31067..410a909a9c4a 100644 --- a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/index_advanced_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/index_advanced_page.tsx @@ -29,6 +29,7 @@ export const ElasticsearchIndexAdvancedPage: React.FC = ({ clust const { index }: { index: string } = useParams(); const { zoomInfo, onBrush } = useCharts(); const clusterUuid = globalState.cluster_uuid; + const ccs = globalState.ccs; const [data, setData] = useState({} as any); const [alerts, setAlerts] = useState({}); @@ -60,6 +61,7 @@ export const ElasticsearchIndexAdvancedPage: React.FC = ({ clust const response = await services.http?.fetch(url, { method: 'POST', body: JSON.stringify({ + ccs, timeRange: { min: bounds.min.toISOString(), max: bounds.max.toISOString(), @@ -84,7 +86,7 @@ export const ElasticsearchIndexAdvancedPage: React.FC = ({ clust }); setAlerts(alertsResponse); } - }, [clusterUuid, services.data?.query.timefilter.timefilter, services.http, index]); + }, [services.data?.query.timefilter.timefilter, services.http, clusterUuid, index, ccs]); return ( = ({ clusters }) = const { index }: { index: string } = useParams(); const { zoomInfo, onBrush } = useCharts(); const clusterUuid = globalState.cluster_uuid; + const ccs = globalState.ccs; const [data, setData] = useState({} as any); const [indexLabel, setIndexLabel] = useState(labels.index as any); const [nodesByIndicesData, setNodesByIndicesData] = useState([]); @@ -72,6 +73,7 @@ export const ElasticsearchIndexPage: React.FC = ({ clusters }) = const response = await services.http?.fetch<{ shards: unknown[]; nodes: unknown[] }>(url, { method: 'POST', body: JSON.stringify({ + ccs, timeRange: { min: bounds.min.toISOString(), max: bounds.max.toISOString(), @@ -103,7 +105,7 @@ export const ElasticsearchIndexPage: React.FC = ({ clusters }) = }); setAlerts(alertsResponse); } - }, [clusterUuid, services.data?.query.timefilter.timefilter, services.http, index]); + }, [services.data?.query.timefilter.timefilter, services.http, clusterUuid, index, ccs]); return ( Have you set up monitoring yet? If so, make sure that the selected time period in the upper right includes monitoring data.

+

+ If you have configured monitoring data to be sent to a dedicated monitoring cluster you should access that data with the Kibana instance attached to the monitoring cluster. +

Have you set up monitoring yet? If so, make sure that the selected time period in the upper right includes monitoring data.

+

+ If you have configured monitoring data to be sent to a dedicated monitoring cluster you should access that data with the Kibana instance attached to the monitoring cluster. +

+

+ +

diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.ts index 541320e8499f..bf999d67a4d8 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.ts @@ -36,6 +36,9 @@ import { ElasticsearchModifiedSource } from '../../../../../common/types/es'; interface Node { name: string; uuid: string; +} + +interface NodeWithStatus extends Node { isOnline: boolean; shardCount: number; } @@ -52,7 +55,7 @@ export async function getPaginatedNodes( nodesShardCount, }: { clusterStats: { - cluster_state?: { nodes: Record }; + cluster_state?: { nodes?: Record }; elasticsearch?: ElasticsearchModifiedSource['elasticsearch']; }; nodesShardCount: { nodes: Record }; @@ -67,10 +70,11 @@ export async function getPaginatedNodes( clusterStats?.cluster_state?.nodes ?? clusterStats?.elasticsearch?.cluster?.stats?.state?.nodes ?? {}; - for (const node of nodes) { - node.isOnline = !isUndefined(clusterStateNodes && clusterStateNodes[node.uuid]); - node.shardCount = nodesShardCount?.nodes[node.uuid]?.shardCount ?? 0; - } + const nodesWithStatus: NodeWithStatus[] = nodes.map((node) => ({ + ...node, + isOnline: !isUndefined(clusterStateNodes && clusterStateNodes[node.uuid]), + shardCount: nodesShardCount?.nodes[node.uuid]?.shardCount ?? 0, + })); // `metricSet` defines a list of metrics that are sortable in the UI // but we don't need to fetch all the data for these metrics to perform @@ -80,13 +84,13 @@ export async function getPaginatedNodes( const filters = [ { terms: { - 'source_node.name': nodes.map((node) => node.name), + 'source_node.name': nodesWithStatus.map((node) => node.name), }, }, ]; const groupBy = { field: `source_node.uuid`, - include: nodes.map((node) => node.uuid), + include: nodesWithStatus.map((node) => node.uuid), size, }; const metricSeriesData = await getMetrics( @@ -94,7 +98,7 @@ export async function getPaginatedNodes( 'elasticsearch', metricSet, filters, - { nodes }, + { nodes: nodesWithStatus }, 4, groupBy ); @@ -106,7 +110,7 @@ export async function getPaginatedNodes( const metricList = metricSeriesData[metricName]; for (const metricItem of metricList[0]) { - const node = nodes.find((n) => n.uuid === metricItem.groupedBy); + const node = nodesWithStatus.find((n) => n.uuid === metricItem.groupedBy); if (!node) { continue; } @@ -124,7 +128,7 @@ export async function getPaginatedNodes( // Manually apply pagination/sorting/filtering concerns // Filtering - const filteredNodes = filter(nodes, queryText, ['name']); // We only support filtering by name right now + const filteredNodes = filter(nodesWithStatus, queryText, ['name']); // We only support filtering by name right now // Sorting const sortedNodes = sortNodes(filteredNodes, sort); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/sort_nodes.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/sort_nodes.ts index 33d29f2a0599..1fbd0f0130e4 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/sort_nodes.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/sort_nodes.ts @@ -9,7 +9,10 @@ import { orderBy } from 'lodash'; type Node = Record; -export function sortNodes(nodes: Node[], sort?: { field: string; direction: 'asc' | 'desc' }) { +export function sortNodes( + nodes: T[], + sort?: { field: string; direction: 'asc' | 'desc' } +) { if (!sort || !sort.field) { return nodes; } diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts index 9c2c5cf45235..df60ce3ffece 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts @@ -6,22 +6,15 @@ */ import { get } from 'lodash'; -// @ts-ignore -import { checkParam } from '../../error_missing_required'; -// @ts-ignore +import { ElasticsearchModifiedSource, ElasticsearchResponse } from '../../../../common/types/es'; +import { Globals } from '../../../static_globals'; +import { LegacyRequest } from '../../../types'; +import { getNewIndexPatterns } from '../../cluster/get_index_patterns'; import { createQuery } from '../../create_query'; -// @ts-ignore import { ElasticsearchMetric } from '../../metrics'; -// @ts-ignore -import { normalizeIndexShards, normalizeNodeShards } from './normalize_shard_objects'; -// @ts-ignore -import { getShardAggs } from './get_shard_stat_aggs'; -// @ts-ignore import { calculateIndicesTotals } from './calculate_shard_stat_indices_totals'; -import { LegacyRequest } from '../../../types'; -import { ElasticsearchResponse, ElasticsearchModifiedSource } from '../../../../common/types/es'; -import { getNewIndexPatterns } from '../../cluster/get_index_patterns'; -import { Globals } from '../../../static_globals'; +import { getShardAggs } from './get_shard_stat_aggs'; +import { normalizeIndexShards, normalizeNodeShards } from './normalize_shard_objects'; export function handleResponse( resp: ElasticsearchResponse, @@ -58,7 +51,17 @@ export function handleResponse( export function getShardStats( req: LegacyRequest, cluster: ElasticsearchModifiedSource, - { includeNodes = false, includeIndices = false, indexName = null, nodeUuid = null } = {} + { + includeNodes = false, + includeIndices = false, + indexName = null, + nodeUuid = null, + }: { + includeNodes?: boolean; + includeIndices?: boolean; + indexName?: string | null; + nodeUuid?: string | null; + } = {} ) { const dataset = 'shard'; // data_stream.dataset const type = 'shards'; // legacy diff --git a/x-pack/plugins/monitoring/server/lib/logs/detect_reason.ts b/x-pack/plugins/monitoring/server/lib/logs/detect_reason.ts index 216d29d841a8..e9b906af677e 100644 --- a/x-pack/plugins/monitoring/server/lib/logs/detect_reason.ts +++ b/x-pack/plugins/monitoring/server/lib/logs/detect_reason.ts @@ -8,7 +8,7 @@ import { LegacyRequest } from '../../types'; import { createTimeFilter } from '../create_query'; -interface Opts { +export interface FilebeatIndexCheckOpts { start: number; end: number; clusterUuid?: string; @@ -19,7 +19,7 @@ interface Opts { async function doesFilebeatIndexExist( req: LegacyRequest, filebeatIndexPattern: string, - { start, end, clusterUuid, nodeUuid, indexUuid }: Opts + { start, end, clusterUuid, nodeUuid, indexUuid }: FilebeatIndexCheckOpts ) { const metric = { timestampField: '@timestamp' }; const filter = [createTimeFilter({ start, end, metric })]; @@ -142,6 +142,10 @@ async function doesFilebeatIndexExist( }; } -export async function detectReason(req: LegacyRequest, filebeatIndexPattern: string, opts: Opts) { +export async function detectReason( + req: LegacyRequest, + filebeatIndexPattern: string, + opts: FilebeatIndexCheckOpts +) { return await doesFilebeatIndexExist(req, filebeatIndexPattern, opts); } diff --git a/x-pack/plugins/monitoring/server/lib/logs/get_logs.ts b/x-pack/plugins/monitoring/server/lib/logs/get_logs.ts index 9e9f67831ba9..c243414c3649 100644 --- a/x-pack/plugins/monitoring/server/lib/logs/get_logs.ts +++ b/x-pack/plugins/monitoring/server/lib/logs/get_logs.ts @@ -6,17 +6,11 @@ */ import moment from 'moment'; -// @ts-ignore import { checkParam } from '../error_missing_required'; -// @ts-ignore import { createTimeFilter } from '../create_query'; -// @ts-ignore -import { detectReason } from './detect_reason'; -// @ts-ignore +import { detectReason, FilebeatIndexCheckOpts } from './detect_reason'; import { formatUTCTimestampForTimezone } from '../format_timezone'; -// @ts-ignore import { getTimezone } from '../get_timezone'; -// @ts-ignore import { detectReasonFromException } from './detect_reason_from_exception'; import { LegacyRequest } from '../../types'; import { FilebeatResponse } from '../../../common/types/filebeat'; @@ -36,7 +30,7 @@ async function handleResponse( response: FilebeatResponse, req: LegacyRequest, filebeatIndexPattern: string, - opts: { clusterUuid: string; nodeUuid: string; indexUuid: string; start: number; end: number } + opts: FilebeatIndexCheckOpts ) { const result: { enabled: boolean; logs: Log[]; reason?: any } = { enabled: false, @@ -73,13 +67,7 @@ export async function getLogs( config: MonitoringConfig, req: LegacyRequest, filebeatIndexPattern: string, - { - clusterUuid, - nodeUuid, - indexUuid, - start, - end, - }: { clusterUuid: string; nodeUuid: string; indexUuid: string; start: number; end: number } + { clusterUuid, nodeUuid, indexUuid, start, end }: FilebeatIndexCheckOpts ) { checkParam(filebeatIndexPattern, 'filebeatIndexPattern in logs/getLogs'); diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 433a3358558d..c81fb90e614c 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -328,7 +328,7 @@ export class MonitoringPlugin const plugins = (await getCoreServices())[1]; const coreContext = await context.core; const actionContext = await context.actions; - const legacyRequest: LegacyRequest = { + const legacyRequest: LegacyRequest = { ...req, logger: this.log, getLogger: this.getLogger, diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.ts b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.ts index 69b226ef3eae..7410a91293fb 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instance.ts @@ -19,12 +19,15 @@ import { MonitoringCore } from '../../../../types'; import { metricSet } from './metric_set_instance'; export function apmInstanceRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postApmInstanceRequestParamsRT); + const validateBody = createValidationFunction(postApmInstanceRequestPayloadRT); + server.route({ method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/apm/{apmUuid}', validate: { - params: createValidationFunction(postApmInstanceRequestParamsRT), - body: createValidationFunction(postApmInstanceRequestPayloadRT), + params: validateParams, + body: validateBody, }, async handler(req) { const apmUuid = req.params.apmUuid; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.ts b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.ts index 960a8dc3627b..6225bd4b553b 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/instances.ts @@ -17,12 +17,15 @@ import { handleError } from '../../../../lib/errors'; import { MonitoringCore } from '../../../../types'; export function apmInstancesRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postApmInstancesRequestParamsRT); + const validateBody = createValidationFunction(postApmInstancesRequestPayloadRT); + server.route({ method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/apm/instances', validate: { - params: createValidationFunction(postApmInstancesRequestParamsRT), - body: createValidationFunction(postApmInstancesRequestPayloadRT), + params: validateParams, + body: validateBody, }, async handler(req) { const config = server.config; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.ts b/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.ts index 532b5fa4dc4c..5e23b0138a72 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/apm/overview.ts @@ -17,12 +17,15 @@ import { metricSet } from './metric_set_overview'; import { getApmClusterStatus } from './_get_apm_cluster_status'; export function apmOverviewRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postApmOverviewRequestParamsRT); + const validateBody = createValidationFunction(postApmOverviewRequestPayloadRT); + server.route({ method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/apm', validate: { - params: createValidationFunction(postApmOverviewRequestParamsRT), - body: createValidationFunction(postApmOverviewRequestPayloadRT), + params: validateParams, + body: validateBody, }, async handler(req) { const config = server.config; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.ts b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.ts index 18f6de905dbf..26477b361136 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beat_detail.ts @@ -20,12 +20,15 @@ import { MonitoringCore } from '../../../../types'; import { metricSet } from './metric_set_detail'; export function beatsDetailRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postBeatDetailRequestParamsRT); + const validateBody = createValidationFunction(postBeatDetailRequestPayloadRT); + server.route({ method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/beats/beat/{beatUuid}', validate: { - params: createValidationFunction(postBeatDetailRequestParamsRT), - body: createValidationFunction(postBeatDetailRequestPayloadRT), + params: validateParams, + body: validateBody, }, async handler(req) { const clusterUuid = req.params.clusterUuid; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.ts b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.ts index 8bf73bb115f7..385950d23f83 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/beats.ts @@ -18,12 +18,15 @@ import { handleError } from '../../../../lib/errors'; import { MonitoringCore } from '../../../../types'; export function beatsListingRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postBeatsListingRequestParamsRT); + const validateBody = createValidationFunction(postBeatsListingRequestPayloadRT); + server.route({ method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/beats/beats', validate: { - params: createValidationFunction(postBeatsListingRequestParamsRT), - body: createValidationFunction(postBeatsListingRequestPayloadRT), + params: validateParams, + body: validateBody, }, async handler(req) { const config = server.config; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.ts b/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.ts index 6497c5568a1a..f36d6cd0b1ae 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/beats/overview.ts @@ -20,12 +20,15 @@ import { MonitoringCore } from '../../../../types'; import { metricSet } from './metric_set_overview'; export function beatsOverviewRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postBeatsOverviewRequestParamsRT); + const validateBody = createValidationFunction(postBeatsOverviewRequestPayloadRT); + server.route({ method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/beats', validate: { - params: createValidationFunction(postBeatsOverviewRequestParamsRT), - body: createValidationFunction(postBeatsOverviewRequestPayloadRT), + params: validateParams, + body: validateBody, }, async handler(req) { const config = server.config; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts index 3dd4feb3db80..726ed7b2500d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts @@ -5,21 +5,24 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; -import moment from 'moment'; import { get, groupBy } from 'lodash'; -// @ts-ignore -import { handleError } from '../../../../lib/errors/handle_error'; -// @ts-ignore import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { - ElasticsearchResponse, + postElasticsearchCcrRequestParamsRT, + postElasticsearchCcrRequestPayloadRT, + postElasticsearchCcrResponsePayloadRT, +} from '../../../../../common/http_api/elasticsearch'; +import { TimeRange } from '../../../../../common/http_api/shared'; +import { ElasticsearchLegacySource, ElasticsearchMetricbeatSource, + ElasticsearchResponse, } from '../../../../../common/types/es'; -import { LegacyRequest } from '../../../../types'; import { MonitoringConfig } from '../../../../config'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; +import { handleError } from '../../../../lib/errors/handle_error'; +import { LegacyRequest, MonitoringCore } from '../../../../types'; function getBucketScript(max: string, min: string) { return { @@ -33,9 +36,12 @@ function getBucketScript(max: string, min: string) { }; } -function buildRequest(req: LegacyRequest, config: MonitoringConfig, esIndexPattern: string) { - const min = moment.utc(req.payload.timeRange.min).valueOf(); - const max = moment.utc(req.payload.timeRange.max).valueOf(); +function buildRequest( + req: LegacyRequest, + config: MonitoringConfig, + esIndexPattern: string +) { + const { min, max } = req.payload.timeRange; const maxBucketSize = config.ui.max_bucket_size; const aggs = { ops_synced_max: { @@ -195,25 +201,18 @@ function buildRequest(req: LegacyRequest, config: MonitoringConfig, esIndexPatte }; } -export function ccrRoute(server: { route: (p: any) => void; config: MonitoringConfig }) { +export function ccrRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postElasticsearchCcrRequestParamsRT); + const validateBody = createValidationFunction(postElasticsearchCcrRequestPayloadRT); + server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/ccr', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: validateParams, + body: validateBody, }, - async handler(req: LegacyRequest) { + async handler(req) { const config = server.config; const ccs = req.payload.ccs; const esIndexPattern = prefixIndexPatternWithCcs(config, INDEX_PATTERN_ELASTICSEARCH, ccs); @@ -322,7 +321,7 @@ export function ccrRoute(server: { route: (p: any) => void; config: MonitoringCo return accum; }, []); - return { data }; + return postElasticsearchCcrResponsePayloadRT.encode({ data }); } catch (err) { return handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts index 44a1eb180759..797b8addd02c 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts @@ -5,18 +5,19 @@ * 2.0. */ -import moment from 'moment'; -import { schema } from '@kbn/config-schema'; -// @ts-ignore -import { handleError } from '../../../../lib/errors/handle_error'; -// @ts-ignore -import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; -// @ts-ignore -import { getMetrics } from '../../../../lib/details/get_metrics'; +import { + postElasticsearchCcrShardRequestParamsRT, + postElasticsearchCcrShardRequestPayloadRT, + postElasticsearchCcrShardResponsePayloadRT, +} from '../../../../../common/http_api/elasticsearch'; +import { TimeRange } from '../../../../../common/http_api/shared'; import { ElasticsearchResponse } from '../../../../../common/types/es'; -import { LegacyRequest } from '../../../../types'; import { getNewIndexPatterns } from '../../../../lib/cluster/get_index_patterns'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; +import { getMetrics } from '../../../../lib/details/get_metrics'; +import { handleError } from '../../../../lib/errors/handle_error'; import { Globals } from '../../../../static_globals'; +import { LegacyRequest, MonitoringCore } from '../../../../types'; function getFormattedLeaderIndex(leaderIndex: string) { let leader = leaderIndex; @@ -27,10 +28,12 @@ function getFormattedLeaderIndex(leaderIndex: string) { return leader; } -async function getCcrStat(req: LegacyRequest, esIndexPattern: string, filters: unknown[]) { - const min = moment.utc(req.payload.timeRange.min).valueOf(); - const max = moment.utc(req.payload.timeRange.max).valueOf(); - +async function getCcrStat( + req: LegacyRequest, + esIndexPattern: string, + filters: unknown[] +) { + const { min, max } = req.payload.timeRange; const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); const params = { @@ -78,27 +81,18 @@ async function getCcrStat(req: LegacyRequest, esIndexPattern: string, filters: u return await callWithRequest(req, 'search', params); } -export function ccrShardRoute(server: { route: (p: any) => void; config: () => {} }) { +export function ccrShardRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postElasticsearchCcrShardRequestParamsRT); + const validateBody = createValidationFunction(postElasticsearchCcrShardRequestPayloadRT); + server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/ccr/{index}/shard/{shardId}', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - index: schema.string(), - shardId: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: validateParams, + body: validateBody, }, - async handler(req: LegacyRequest) { + async handler(req) { const index = req.params.index; const shardId = req.params.shardId; const moduleType = 'elasticsearch'; @@ -171,7 +165,7 @@ export function ccrShardRoute(server: { route: (p: any) => void; config: () => { const leaderIndex = mbStat ? mbStat?.leader?.index : legacyStat?.leader_index; - return { + return postElasticsearchCcrShardResponsePayloadRT.encode({ metrics, stat: mbStat ?? legacyStat, formattedLeader: getFormattedLeaderIndex(leaderIndex ?? ''), @@ -179,7 +173,7 @@ export function ccrShardRoute(server: { route: (p: any) => void; config: () => { ccrResponse.hits?.hits[0]?._source['@timestamp'] ?? ccrResponse.hits?.hits[0]?._source.timestamp, oldestStat: oldestMBStat ?? oldestLegacyStat, - }; + }); } catch (err) { return handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index.ts similarity index 100% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index.ts diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.ts similarity index 81% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.ts index b4f317c9a435..1d31064f71eb 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.ts @@ -6,38 +6,35 @@ */ import { get } from 'lodash'; -import { schema } from '@kbn/config-schema'; +import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; +import { CCS_REMOTE_PATTERN } from '../../../../../common/constants'; +import { + postElasticsearchIndexDetailRequestParamsRT, + postElasticsearchIndexDetailRequestPayloadRT, + postElasticsearchIndexDetailResponsePayloadRT, +} from '../../../../../common/http_api/elasticsearch'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; -import { getIndexSummary } from '../../../../lib/elasticsearch/indices'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; import { getMetrics } from '../../../../lib/details/get_metrics'; +import { getIndexSummary } from '../../../../lib/elasticsearch/indices'; import { getShardAllocation, getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; -import { metricSet } from './metric_set_index_detail'; import { getLogs } from '../../../../lib/logs/get_logs'; -import { CCS_REMOTE_PATTERN } from '../../../../../common/constants'; +import { MonitoringCore } from '../../../../types'; +import { metricSets } from './metric_set_index_detail'; -const { advanced: metricSetAdvanced, overview: metricSetOverview } = metricSet; +const { advanced: metricSetAdvanced, overview: metricSetOverview } = metricSets; + +export function esIndexRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postElasticsearchIndexDetailRequestParamsRT); + const validateBody = createValidationFunction(postElasticsearchIndexDetailRequestPayloadRT); -export function esIndexRoute(server) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/indices/{id}', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - id: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - is_advanced: schema.boolean(), - }), - }, + validate: { + params: validateParams, + body: validateBody, }, handler: async (req) => { try { @@ -111,12 +108,12 @@ export function esIndexRoute(server) { }; } - return { + return postElasticsearchIndexDetailResponsePayloadRT.encode({ indexSummary, metrics, logs, ...shardAllocation, - }; + }); } catch (err) { throw handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.ts similarity index 59% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.ts index de4113779110..00956410d8c4 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.ts @@ -5,48 +5,46 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; +import { + postElasticsearchIndicesRequestParamsRT, + postElasticsearchIndicesRequestPayloadRT, + postElasticsearchIndicesRequestQueryRT, + postElasticsearchIndicesResponsePayloadRT, +} from '../../../../../common/http_api/elasticsearch'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; import { getIndices } from '../../../../lib/elasticsearch/indices'; -import { handleError } from '../../../../lib/errors/handle_error'; import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; +import { handleError } from '../../../../lib/errors/handle_error'; +import { MonitoringCore } from '../../../../types'; + +export function esIndicesRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postElasticsearchIndicesRequestParamsRT); + const validateQuery = createValidationFunction(postElasticsearchIndicesRequestQueryRT); + const validateBody = createValidationFunction(postElasticsearchIndicesRequestPayloadRT); -export function esIndicesRoute(server) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/indices', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - }), - query: schema.object({ - show_system_indices: schema.boolean(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: validateParams, + query: validateQuery, + body: validateBody, }, async handler(req) { const { clusterUuid } = req.params; const { show_system_indices: showSystemIndices } = req.query; - const { ccs } = req.payload; try { - const clusterStats = await getClusterStats(req, clusterUuid, ccs); + const clusterStats = await getClusterStats(req, clusterUuid); const indicesUnassignedShardStats = await getIndicesUnassignedShardStats(req, clusterStats); const indices = await getIndices(req, showSystemIndices, indicesUnassignedShardStats); - return { + return postElasticsearchIndicesResponsePayloadRT.encode({ clusterStatus: getClusterStatus(clusterStats, indicesUnassignedShardStats), indices, - }; + }); } catch (err) { throw handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_index_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_index_detail.ts similarity index 92% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_index_detail.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_index_detail.ts index fce09eac4918..36e3cef79796 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_index_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_index_detail.ts @@ -5,7 +5,12 @@ * 2.0. */ -export const metricSet = { +import { MetricDescriptor } from '../../../../lib/details/get_metrics'; + +export const metricSets: { + advanced: MetricDescriptor[]; + overview: MetricDescriptor[]; +} = { advanced: [ { keys: ['index_mem_fixed_bit_set', 'index_mem_versions'], diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_node_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_node_detail.ts similarity index 93% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_node_detail.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_node_detail.ts index 5303dc452f85..9de51af5ecba 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_node_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_node_detail.ts @@ -5,7 +5,12 @@ * 2.0. */ -export const metricSets = { +import { MetricDescriptor } from '../../../../lib/details/get_metrics'; + +export const metricSets: { + advanced: MetricDescriptor[]; + overview: MetricDescriptor[]; +} = { advanced: [ { keys: ['node_jvm_mem_max_in_bytes', 'node_jvm_mem_used_in_bytes'], diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_overview.ts similarity index 79% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_overview.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_overview.ts index 317486bce4ad..9ea00bf8a503 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/metric_set_overview.ts @@ -5,7 +5,9 @@ * 2.0. */ -export const metricSet = [ +import { MetricDescriptor } from '../../../../lib/details/get_metrics'; + +export const metricSet: MetricDescriptor[] = [ 'cluster_search_request_rate', 'cluster_query_latency', { diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.ts similarity index 62% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.ts index f2f0698bae18..f51ca41ee4e9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.ts @@ -5,30 +5,29 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; +import { + postElasticsearchMlJobsRequestParamsRT, + postElasticsearchMlJobsRequestPayloadRT, + postElasticsearchMlJobsResponsePayloadRT, +} from '../../../../../common/http_api/elasticsearch'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; import { getMlJobs } from '../../../../lib/elasticsearch/get_ml_jobs'; -import { handleError } from '../../../../lib/errors/handle_error'; import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; +import { handleError } from '../../../../lib/errors/handle_error'; +import { MonitoringCore } from '../../../../types'; + +export function mlJobRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postElasticsearchMlJobsRequestParamsRT); + const validateBody = createValidationFunction(postElasticsearchMlJobsRequestPayloadRT); -export function mlJobRoute(server) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/ml_jobs', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: validateParams, + body: validateBody, }, async handler(req) { const clusterUuid = req.params.clusterUuid; @@ -37,10 +36,10 @@ export function mlJobRoute(server) { const clusterStats = await getClusterStats(req, clusterUuid); const indicesUnassignedShardStats = await getIndicesUnassignedShardStats(req, clusterStats); const rows = await getMlJobs(req); - return { + return postElasticsearchMlJobsResponsePayloadRT.encode({ clusterStatus: getClusterStatus(clusterStats, indicesUnassignedShardStats), rows, - }; + }); } catch (err) { throw handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.ts similarity index 71% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.ts index 1504bb076101..85ba1d1a2bb8 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.ts @@ -6,44 +6,43 @@ */ import { get } from 'lodash'; -import { schema } from '@kbn/config-schema'; +import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; +import { CCS_REMOTE_PATTERN } from '../../../../../common/constants'; +import { + postElasticsearchNodeDetailRequestParamsRT, + postElasticsearchNodeDetailRequestPayloadRT, + postElasticsearchNodeDetailResponsePayloadRT, +} from '../../../../../common/http_api/elasticsearch'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; +import { + getMetrics, + MetricDescriptor, + NamedMetricDescriptor, +} from '../../../../lib/details/get_metrics'; import { getNodeSummary } from '../../../../lib/elasticsearch/nodes'; -import { getShardStats, getShardAllocation } from '../../../../lib/elasticsearch/shards'; -import { getMetrics } from '../../../../lib/details/get_metrics'; +import { getShardAllocation, getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; -import { metricSets } from './metric_set_node_detail'; import { getLogs } from '../../../../lib/logs/get_logs'; -import { CCS_REMOTE_PATTERN } from '../../../../../common/constants'; +import { MonitoringCore } from '../../../../types'; +import { metricSets } from './metric_set_node_detail'; const { advanced: metricSetAdvanced, overview: metricSetOverview } = metricSets; -export function esNodeRoute(server) { +export function esNodeRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postElasticsearchNodeDetailRequestParamsRT); + const validateBody = createValidationFunction(postElasticsearchNodeDetailRequestPayloadRT); + server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/nodes/{nodeUuid}', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - nodeUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - showSystemIndices: schema.boolean({ defaultValue: false }), // show/hide system indices in shard allocation table - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - is_advanced: schema.boolean(), - }), - }, + validate: { + params: validateParams, + body: validateBody, }, async handler(req) { const config = server.config; - const ccs = req.payload.ccs; - const showSystemIndices = req.payload.showSystemIndices; + const showSystemIndices = req.payload.showSystemIndices ?? false; const clusterUuid = req.params.clusterUuid; const nodeUuid = req.params.nodeUuid; const start = req.payload.timeRange.min; @@ -55,23 +54,27 @@ export function esNodeRoute(server) { ); const isAdvanced = req.payload.is_advanced; - let metricSet; + let metricSet: MetricDescriptor[]; if (isAdvanced) { metricSet = metricSetAdvanced; } else { metricSet = metricSetOverview; // set the cgroup option if needed const showCgroupMetricsElasticsearch = config.ui.container.elasticsearch.enabled; - const metricCpu = metricSet.find((m) => m.name === 'node_cpu_metric'); - if (showCgroupMetricsElasticsearch) { - metricCpu.keys = ['node_cgroup_quota_as_cpu_utilization']; - } else { - metricCpu.keys = ['node_cpu_utilization']; + const metricCpu = metricSet.find( + (m): m is NamedMetricDescriptor => typeof m === 'object' && m.name === 'node_cpu_metric' + ); + if (metricCpu) { + if (showCgroupMetricsElasticsearch) { + metricCpu.keys = ['node_cgroup_quota_as_cpu_utilization']; + } else { + metricCpu.keys = ['node_cpu_utilization']; + } } } try { - const cluster = await getClusterStats(req, clusterUuid, ccs); + const cluster = await getClusterStats(req, clusterUuid); const clusterState = get( cluster, @@ -132,12 +135,12 @@ export function esNodeRoute(server) { }); } - return { + return postElasticsearchNodeDetailResponsePayloadRT.encode({ nodeSummary, metrics, logs, ...shardAllocation, - }; + }); } catch (err) { throw handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.ts similarity index 66% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.ts index fa0329f957f5..f291af318bf9 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.ts @@ -5,45 +5,39 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; +import { + postElasticsearchNodesRequestParamsRT, + postElasticsearchNodesRequestPayloadRT, + postElasticsearchNodesResponsePayloadRT, +} from '../../../../../common/http_api/elasticsearch'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; import { getNodes } from '../../../../lib/elasticsearch/nodes'; -import { getNodesShardCount } from '../../../../lib/elasticsearch/shards/get_nodes_shard_count'; -import { handleError } from '../../../../lib/errors/handle_error'; import { getPaginatedNodes } from '../../../../lib/elasticsearch/nodes/get_nodes/get_paginated_nodes'; import { LISTING_METRICS_NAMES } from '../../../../lib/elasticsearch/nodes/get_nodes/nodes_listing_metrics'; import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; +import { getNodesShardCount } from '../../../../lib/elasticsearch/shards/get_nodes_shard_count'; +import { handleError } from '../../../../lib/errors/handle_error'; +import { MonitoringCore } from '../../../../types'; + +export function esNodesRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postElasticsearchNodesRequestParamsRT); + const validateBody = createValidationFunction(postElasticsearchNodesRequestPayloadRT); -export function esNodesRoute(server) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch/nodes', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - pagination: schema.object({ - index: schema.number(), - size: schema.number(), - }), - sort: schema.object({ - field: schema.string({ defaultValue: '' }), - direction: schema.string({ defaultValue: '' }), - }), - queryText: schema.string({ defaultValue: '' }), - }), - }, + validate: { + params: validateParams, + body: validateBody, }, async handler(req) { - const { ccs, pagination, sort, queryText } = req.payload; + const { + pagination, + sort: { field = '', direction = 'asc' } = {}, + queryText = '', + } = req.payload; const clusterUuid = req.params.clusterUuid; try { @@ -58,17 +52,23 @@ export function esNodesRoute(server) { { clusterUuid }, metricSet, pagination, - sort, + { + field, + direction, + }, queryText, { clusterStats, nodesShardCount, - }, - ccs + } ); const nodes = await getNodes(req, pageOfNodes, clusterStats, nodesShardCount); - return { clusterStatus, nodes, totalNodeCount }; + return postElasticsearchNodesResponsePayloadRT.encode({ + clusterStatus, + nodes, + totalNodeCount, + }); } catch (err) { throw handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.ts similarity index 72% rename from x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js rename to x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.ts index 35066bb33784..52410dd09f8d 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.ts @@ -5,35 +5,34 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; +import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; +import { CCS_REMOTE_PATTERN } from '../../../../../common/constants'; +import { + postElasticsearchOverviewRequestParamsRT, + postElasticsearchOverviewRequestPayloadRT, + postElasticsearchOverviewResponsePayloadRT, +} from '../../../../../common/http_api/elasticsearch'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; -import { getLastRecovery } from '../../../../lib/elasticsearch/get_last_recovery'; +import { createValidationFunction } from '../../../../lib/create_route_validation_function'; import { getMetrics } from '../../../../lib/details/get_metrics'; +import { getLastRecovery } from '../../../../lib/elasticsearch/get_last_recovery'; +import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; import { handleError } from '../../../../lib/errors/handle_error'; -import { prefixIndexPatternWithCcs } from '../../../../../common/ccs_utils'; -import { metricSet } from './metric_set_overview'; import { getLogs } from '../../../../lib/logs'; -import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; -import { CCS_REMOTE_PATTERN } from '../../../../../common/constants'; +import { MonitoringCore } from '../../../../types'; +import { metricSet } from './metric_set_overview'; + +export function esOverviewRoute(server: MonitoringCore) { + const validateParams = createValidationFunction(postElasticsearchOverviewRequestParamsRT); + const validateBody = createValidationFunction(postElasticsearchOverviewRequestPayloadRT); -export function esOverviewRoute(server) { server.route({ - method: 'POST', + method: 'post', path: '/api/monitoring/v1/clusters/{clusterUuid}/elasticsearch', - config: { - validate: { - params: schema.object({ - clusterUuid: schema.string(), - }), - body: schema.object({ - ccs: schema.maybe(schema.string()), - timeRange: schema.object({ - min: schema.string(), - max: schema.string(), - }), - }), - }, + validate: { + params: validateParams, + body: validateBody, }, async handler(req) { const config = server.config; @@ -43,9 +42,7 @@ export function esOverviewRoute(server) { config.ui.logs.index, CCS_REMOTE_PATTERN ); - - const start = req.payload.timeRange.min; - const end = req.payload.timeRange.max; + const { min: start, max: end } = req.payload.timeRange; try { const [clusterStats, metrics, shardActivity, logs] = await Promise.all([ @@ -63,7 +60,7 @@ export function esOverviewRoute(server) { logs, shardActivity, }; - return result; + return postElasticsearchOverviewResponsePayloadRT.encode(result); } catch (err) { throw handleError(err, req); } diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index bcd40fe38e41..597748451814 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -80,16 +80,22 @@ export interface RouteDependencies { logger: Logger; } -export type MonitoringRouteConfig = { - method: RouteMethod; -} & RouteConfig & { - handler: (request: LegacyRequest) => any; - }; +type LegacyHandler = (req: LegacyRequest) => Promise; + +export type MonitoringRouteConfig = RouteConfig< + Params, + Query, + Body, + Method +> & { + method: Method; + handler: LegacyHandler; +}; export interface MonitoringCore { config: MonitoringConfig; log: Logger; - route: ( + route: ( options: MonitoringRouteConfig ) => void; } @@ -112,15 +118,12 @@ export interface MonitoringPluginSetup { getKibanaStats: IBulkUploader['getKibanaStats']; } -export interface LegacyRequest { +export interface LegacyRequest { logger: Logger; getLogger: (...scopes: string[]) => Logger; - payload: { - [key: string]: any; - }; - params: { - [key: string]: string; - }; + payload: Body; + params: Params; + query: Query; getKibanaStatsCollector: () => any; getUiSettingsService: () => any; getActionTypeRegistry: () => any; diff --git a/x-pack/plugins/observability/public/config/translations.ts b/x-pack/plugins/observability/public/config/translations.ts index db1378d5b5a5..13f6470ecb20 100644 --- a/x-pack/plugins/observability/public/config/translations.ts +++ b/x-pack/plugins/observability/public/config/translations.ts @@ -65,6 +65,12 @@ export const translations = { defaultMessage: 'View rule details', } ), + viewAlertDetailsButtonText: i18n.translate( + 'xpack.observability.alertsTable.viewAlertDetailsButtonText', + { + defaultMessage: 'View alert details', + } + ), }, alertsFlyout: { statusLabel: i18n.translate('xpack.observability.alertsFlyout.statusLabel', { diff --git a/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts b/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts index 62edefc1b737..7e5ad089cd50 100644 --- a/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts +++ b/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts @@ -36,6 +36,7 @@ const triggersActionsUiStartMock = { createStart() { return { getAddAlertFlyout: jest.fn(), + getRuleTagBadge: jest.fn(), ruleTypeRegistry: { has: jest.fn(), register: jest.fn(), diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx index 32c604ab1973..686ae9a15d8d 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx @@ -234,8 +234,29 @@ function ObservabilityActions({ , ] : []), + + ...[ + { + closeActionsPopover(); + setFlyoutAlert(alert); + }} + > + {translations.alertsTable.viewAlertDetailsButtonText} + , + ], ]; - }, [casePermissions?.crud, handleAddToExistingCaseClick, handleAddToNewCaseClick, linkToRule]); + }, [ + casePermissions?.crud, + handleAddToExistingCaseClick, + handleAddToNewCaseClick, + linkToRule, + alert, + setFlyoutAlert, + closeActionsPopover, + ]); const actionsToolTip = actionsMenuItems.length <= 0 @@ -245,18 +266,6 @@ function ObservabilityActions({ return ( <> - - - setFlyoutAlert(alert)} - data-test-subj="openFlyoutButton" - aria-label={translations.alertsTable.viewDetailsTextLabel} - /> - - (null); const [rulesToDelete, setRulesToDelete] = useState([]); const [createRuleFlyoutVisibility, setCreateRuleFlyoutVisibility] = useState(false); + const [tagPopoverOpenIndex, setTagPopoverOpenIndex] = useState(-1); const isRuleTypeEditableInContext = (ruleTypeId: string) => ruleTypeRegistry.has(ruleTypeId) ? !ruleTypeRegistry.get(ruleTypeId).requiresAppContext : false; @@ -171,6 +172,23 @@ export function RulesPage() { 'data-test-subj': 'rulesTableCell-name', render: (name: string, rule: RuleTableItem) => , }, + { + field: 'tags', + name: '', + sortable: false, + width: '50px', + 'data-test-subj': 'rulesTableCell-tagsPopover', + render: (tags: string[], item: RuleTableItem) => { + return tags.length > 0 + ? triggersActionsUi.getRuleTagBadge({ + isOpen: tagPopoverOpenIndex === item.index, + tags, + onClick: () => setTagPopoverOpenIndex(item.index), + onClose: () => setTagPopoverOpenIndex(-1), + }) + : null; + }, + }, { field: 'executionStatus.lastExecutionDate', name: LAST_RUN_COLUMN_TITLE, diff --git a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson index 75bdecb5be42..f688dc0731c7 100644 --- a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson +++ b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson @@ -8,10 +8,7 @@ "version": "WzE5MjksMV0=", "attributes": { "name": "Test-rule", - "tags": [ - "__internal_rule_id:22308402-5e0e-421b-8d22-a47ddc4b0188", - "__internal_immutable:false" - ], + "tags": [], "alertTypeId": "siem.queryRule", "consumer": "siem", "params": { diff --git a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts b/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts index 1ddec794f41b..6a9d102784a5 100644 --- a/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/all/live_query.spec.ts @@ -42,10 +42,10 @@ describe('ALL - Live Query', () => { cy.contains('View in Lens').should('exist'); cy.react(RESULTS_TABLE_CELL_WRRAPER, { props: { id: 'osquery.days.number', index: 1 }, - }); + }).should('exist'); cy.react(RESULTS_TABLE_CELL_WRRAPER, { props: { id: 'osquery.hours.number', index: 2 }, - }); + }).should('exist'); getAdvancedButton().click(); typeInECSFieldInput('message{downArrow}{enter}'); @@ -58,9 +58,13 @@ describe('ALL - Live Query', () => { }); cy.react(RESULTS_TABLE_CELL_WRRAPER, { props: { id: 'message', index: 1 }, - }); + }).should('exist'); cy.react(RESULTS_TABLE_CELL_WRRAPER, { props: { id: 'osquery.days.number', index: 2 }, - }).react('EuiIconIndexMapping'); + }).within(() => { + cy.get('.euiToolTipAnchor').within(() => { + cy.get('svg').should('exist'); + }); + }); }); }); diff --git a/x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts b/x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts new file mode 100644 index 000000000000..5d25b6599b13 --- /dev/null +++ b/x-pack/plugins/osquery/cypress/integration/roles/alert_test.spec.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ROLES } from '../../test'; +import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; +import { login } from '../../tasks/login'; +import { findAndClickButton, findFormFieldByRowsLabelAndType } from '../../tasks/live_query'; +import { preparePack } from '../../tasks/packs'; +import { closeModalIfVisible } from '../../tasks/integrations'; +import { navigateTo } from '../../tasks/navigation'; + +describe('Alert_Test', () => { + before(() => { + runKbnArchiverScript(ArchiverMethod.LOAD, 'pack'); + runKbnArchiverScript(ArchiverMethod.LOAD, 'rule'); + }); + beforeEach(() => { + login(ROLES.alert_test); + }); + + after(() => { + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'pack'); + runKbnArchiverScript(ArchiverMethod.UNLOAD, 'rule'); + }); + + it('should be able to run live query', () => { + const PACK_NAME = 'testpack'; + const RULE_NAME = 'Test-rule'; + navigateTo('/app/osquery'); + preparePack(PACK_NAME); + findAndClickButton('Edit'); + cy.contains(`Edit ${PACK_NAME}`); + findFormFieldByRowsLabelAndType( + 'Scheduled agent policies (optional)', + 'fleet server {downArrow}{enter}' + ); + findAndClickButton('Update pack'); + closeModalIfVisible(); + cy.contains(PACK_NAME); + cy.visit('/app/security/rules'); + cy.contains(RULE_NAME).click(); + cy.wait(2000); + cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true'); + cy.getBySel('ruleSwitch').click(); + cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'false'); + cy.getBySel('ruleSwitch').click(); + cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true'); + cy.visit('/app/security/alerts'); + cy.getBySel('expand-event').first().click(); + cy.getBySel('take-action-dropdown-btn').click(); + cy.getBySel('osquery-action-item').click(); + + cy.contains('Run Osquery'); + cy.contains('Permission denied'); + }); +}); diff --git a/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts b/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts index 805eb134a44f..619865e50bb6 100644 --- a/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts +++ b/x-pack/plugins/osquery/cypress/integration/roles/t2_analyst.spec.ts @@ -74,10 +74,10 @@ describe('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => { cy.contains('View in Lens').should('not.exist'); cy.react('EuiDataGridHeaderCellWrapper', { props: { id: 'osquery.days.number', index: 1 }, - }); + }).should('exist'); cy.react('EuiDataGridHeaderCellWrapper', { props: { id: 'osquery.hours.number', index: 2 }, - }); + }).should('exist'); cy.react('EuiAccordion', { props: { buttonContent: 'Advanced' } }).click(); typeInECSFieldInput('message{downArrow}{enter}'); @@ -87,10 +87,14 @@ describe('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => { checkResults(); cy.react('EuiDataGridHeaderCellWrapper', { props: { id: 'message', index: 1 }, - }); + }).should('exist'); cy.react('EuiDataGridHeaderCellWrapper', { props: { id: 'osquery.days.number', index: 2 }, - }).react('EuiIconIndexMapping'); + }).within(() => { + cy.get('.euiToolTipAnchor').within(() => { + cy.get('svg').should('exist'); + }); + }); }); it('to click the edit button and edit pack', () => { navigateTo('/app/osquery/saved_queries'); diff --git a/x-pack/plugins/osquery/cypress/test/index.ts b/x-pack/plugins/osquery/cypress/test/index.ts index 53261d54e84b..11cca6c93c55 100644 --- a/x-pack/plugins/osquery/cypress/test/index.ts +++ b/x-pack/plugins/osquery/cypress/test/index.ts @@ -15,4 +15,5 @@ export enum ROLES { rule_author = 'rule_author', platform_engineer = 'platform_engineer', detections_admin = 'detections_admin', + alert_test = 'alert_test', } diff --git a/x-pack/plugins/osquery/public/assets/use_assets_status.ts b/x-pack/plugins/osquery/public/assets/use_assets_status.ts index fd6a2fecb279..a3ae65c964cc 100644 --- a/x-pack/plugins/osquery/public/assets/use_assets_status.ts +++ b/x-pack/plugins/osquery/public/assets/use_assets_status.ts @@ -18,6 +18,7 @@ export const useAssetsStatus = () => { () => http.get('/internal/osquery/assets'), { keepPreviousData: true, + retry: false, } ); }; diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index f082ca0baeeb..e63ad38aaa69 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -62,7 +62,7 @@ interface LiveQueryFormProps { ecsMappingField?: boolean; formType?: FormType; enabled?: boolean; - hideFullscreen?: true; + isExternal?: true; } const LiveQueryFormComponent: React.FC = ({ @@ -73,7 +73,7 @@ const LiveQueryFormComponent: React.FC = ({ ecsMappingField = true, formType = 'steps', enabled = true, - hideFullscreen, + isExternal, }) => { const ecsFieldRef = useRef(); const permissions = useKibana().services.application.capabilities.osquery; @@ -393,10 +393,10 @@ const LiveQueryFormComponent: React.FC = ({ actionId={actionId} endDate={data?.actions[0].expiration} agentIds={agentIds} - hideFullscreen={hideFullscreen} + isExternal={isExternal} /> ) : null, - [actionId, agentIds, data?.actions, hideFullscreen] + [actionId, agentIds, data?.actions, isExternal] ); const formSteps: EuiContainedStepProps[] = useMemo( @@ -467,6 +467,7 @@ const LiveQueryFormComponent: React.FC = ({ {showSavedQueryFlyout ? ( diff --git a/x-pack/plugins/osquery/public/live_queries/index.tsx b/x-pack/plugins/osquery/public/live_queries/index.tsx index fdde03d6076a..56cac9cdf5ec 100644 --- a/x-pack/plugins/osquery/public/live_queries/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/index.tsx @@ -28,7 +28,7 @@ interface LiveQueryProps { ecsMappingField?: boolean; enabled?: boolean; formType?: 'steps' | 'simple'; - hideFullscreen?: true; + isExternal?: true; } const LiveQueryComponent: React.FC = ({ @@ -45,7 +45,7 @@ const LiveQueryComponent: React.FC = ({ ecsMappingField, formType, enabled, - hideFullscreen, + isExternal, }) => { const { data: hasActionResultsPrivileges, isLoading } = useActionResultsPrivileges(); @@ -115,7 +115,7 @@ const LiveQueryComponent: React.FC = ({ onSuccess={onSuccess} formType={formType} enabled={enabled} - hideFullscreen={hideFullscreen} + isExternal={isExternal} /> ); }; diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx index dec1d08a0d01..9b3dc144a7e3 100644 --- a/x-pack/plugins/osquery/public/results/results_table.tsx +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -46,7 +46,7 @@ interface ResultsTableComponentProps { agentIds?: string[]; endDate?: string; startDate?: string; - hideFullscreen?: true; + isExternal?: true; } const ResultsTableComponent: React.FC = ({ @@ -54,7 +54,7 @@ const ResultsTableComponent: React.FC = ({ agentIds, startDate, endDate, - hideFullscreen, + isExternal, }) => { const [isLive, setIsLive] = useState(true); const { data: hasActionResultsPrivileges } = useActionResultsPrivileges(); @@ -312,7 +312,7 @@ const ResultsTableComponent: React.FC = ({ const toolbarVisibility = useMemo( () => ({ showDisplaySelector: false, - showFullScreenSelector: !hideFullscreen, + showFullScreenSelector: !isExternal, additionalControls: ( <> = ({ ), }), - [actionId, endDate, startDate, hideFullscreen] + [actionId, endDate, startDate, isExternal] ); useEffect( diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx index d0a48aa99781..0956dc6528a7 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/tabs.tsx @@ -17,7 +17,7 @@ interface ResultTabsProps { agentIds?: string[]; startDate?: string; endDate?: string; - hideFullscreen?: true; + isExternal?: true; } const ResultTabsComponent: React.FC = ({ @@ -25,7 +25,7 @@ const ResultTabsComponent: React.FC = ({ agentIds, endDate, startDate, - hideFullscreen, + isExternal, }) => { const tabs = useMemo( () => [ @@ -40,7 +40,7 @@ const ResultTabsComponent: React.FC = ({ agentIds={agentIds} startDate={startDate} endDate={endDate} - hideFullscreen={hideFullscreen} + isExternal={isExternal} /> ), @@ -60,7 +60,7 @@ const ResultTabsComponent: React.FC = ({ ), }, ], - [actionId, agentIds, endDate, startDate, hideFullscreen] + [actionId, agentIds, endDate, startDate, isExternal] ); return ( diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx index 69fe9442a1cb..899c27a1ef8f 100644 --- a/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/saved_query_flyout.tsx @@ -28,9 +28,16 @@ import { useCreateSavedQuery } from './use_create_saved_query'; interface AddQueryFlyoutProps { defaultValue: unknown; onClose: () => void; + isExternal?: true; } -const SavedQueryFlyoutComponent: React.FC = ({ defaultValue, onClose }) => { +const additionalZIndexStyle = { style: 'z-index: 6000' }; + +const SavedQueryFlyoutComponent: React.FC = ({ + defaultValue, + onClose, + isExternal, +}) => { const savedQueryFormRef = useRef(null); const createSavedQueryMutation = useCreateSavedQuery({ withRedirect: false }); @@ -53,8 +60,7 @@ const SavedQueryFlyoutComponent: React.FC = ({ defaultValue ownFocus onClose={onClose} aria-labelledby="flyoutTitle" - // eslint-disable-next-line react-perf/jsx-no-new-object-as-prop - maskProps={{ style: 'z-index: 6000' }} // For an edge case to display above the alerts flyout + maskProps={isExternal && additionalZIndexStyle} // For an edge case to display above the alerts flyout > diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx index a0ee8bf314e5..5fbc6caedcd1 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/index.tsx @@ -8,7 +8,13 @@ import { EuiErrorBoundary, EuiLoadingContent, EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import React, { useMemo } from 'react'; import { QueryClientProvider } from 'react-query'; -import { i18n } from '@kbn/i18n'; +import { + AGENT_STATUS_ERROR, + EMPTY_PROMPT, + NOT_AVAILABLE, + PERMISSION_DENIED, + SHORT_EMPTY_TITLE, +} from './translations'; import { KibanaContextProvider, useKibana } from '../../common/lib/kibana'; import { LiveQuery } from '../../live_queries'; @@ -20,36 +26,19 @@ import { useIsOsqueryAvailable } from './use_is_osquery_available'; interface OsqueryActionProps { agentId?: string; formType: 'steps' | 'simple'; - hideFullscreen?: true; + isExternal?: true; } -const OsqueryActionComponent: React.FC = ({ - agentId, - formType = 'simple', - hideFullscreen, -}) => { +const OsqueryActionComponent: React.FC = ({ agentId, formType = 'simple' }) => { const permissions = useKibana().services.application.capabilities.osquery; const emptyPrompt = useMemo( () => ( } - title={ -

- {i18n.translate('xpack.osquery.action.shortEmptyTitle', { - defaultMessage: 'Osquery is not available', - })} -

- } + title={

{SHORT_EMPTY_TITLE}

} titleSize="xs" - body={ -

- {i18n.translate('xpack.osquery.action.empty', { - defaultMessage: - 'An Elastic Agent is not installed on this host. To run queries, install Elastic Agent on the host, and then add the Osquery Manager integration to the agent policy in Fleet.', - })} -

- } + body={

{EMPTY_PROMPT}

} /> ), [] @@ -61,17 +50,14 @@ const OsqueryActionComponent: React.FC = ({ return emptyPrompt; } - if (!(permissions.runSavedQueries || permissions.writeLiveQueries)) { + if ( + (!permissions.runSavedQueries || !permissions.readSavedQueries) && + !permissions.writeLiveQueries + ) { return ( } - title={ -

- {i18n.translate('xpack.osquery.action.permissionDenied', { - defaultMessage: 'Permission denied', - })} -

- } + title={

{PERMISSION_DENIED}

} titleSize="xs" body={

@@ -95,22 +81,9 @@ const OsqueryActionComponent: React.FC = ({ return ( } - title={ -

- {i18n.translate('xpack.osquery.action.shortEmptyTitle', { - defaultMessage: 'Osquery is not available', - })} -

- } + title={

{SHORT_EMPTY_TITLE}

} titleSize="xs" - body={ -

- {i18n.translate('xpack.osquery.action.unavailable', { - defaultMessage: - 'The Osquery Manager integration is not added to the agent policy. To run queries on the host, add the Osquery Manager integration to the agent policy in Fleet.', - })} -

- } + body={

{NOT_AVAILABLE}

} /> ); } @@ -119,38 +92,25 @@ const OsqueryActionComponent: React.FC = ({ return ( } - title={ -

- {i18n.translate('xpack.osquery.action.shortEmptyTitle', { - defaultMessage: 'Osquery is not available', - })} -

- } + title={

{SHORT_EMPTY_TITLE}

} titleSize="xs" - body={ -

- {i18n.translate('xpack.osquery.action.agentStatus', { - defaultMessage: - 'To run queries on this host, the Elastic Agent must be active. Check the status of this agent in Fleet.', - })} -

- } + body={

{AGENT_STATUS_ERROR}

} /> ); } - return ; + return ; }; -const OsqueryAction = React.memo(OsqueryActionComponent); +export const OsqueryAction = React.memo(OsqueryActionComponent); // @ts-expect-error update types -const OsqueryActionWrapperComponent = ({ services, agentId, formType, hideFullscreen }) => ( +const OsqueryActionWrapperComponent = ({ services, agentId, formType, isExternal }) => ( - + diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/osquery_action.test.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_action/osquery_action.test.tsx new file mode 100644 index 000000000000..f6128642ba64 --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/osquery_action.test.tsx @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { render } from '@testing-library/react'; +import { QueryClientProvider } from 'react-query'; + +import { OsqueryAction } from '.'; +import { queryClient } from '../../query_client'; +import * as hooks from './use_is_osquery_available'; +import { useKibana } from '../../common/lib/kibana'; +import { AGENT_STATUS_ERROR, EMPTY_PROMPT, NOT_AVAILABLE, PERMISSION_DENIED } from './translations'; + +jest.mock('../../common/lib/kibana'); + +const useKibanaMock = useKibana as jest.MockedFunction; + +const defaultUseOsqueryAvailableResult = { + osqueryAvailable: true, + agentFetched: true, + isLoading: false, + policyFetched: true, + policyLoading: false, +}; + +const spyUseIsOsqueryAvailable = jest + .spyOn(hooks, 'useIsOsqueryAvailable') + .mockImplementation(() => ({ + ...defaultUseOsqueryAvailableResult, + agentData: {}, + })); + +const defaultPermissions = { + osquery: { + runSavedQueries: false, + readSavedQueries: false, + }, +}; + +const mockKibana = (permissionType: unknown = defaultPermissions) => { + useKibanaMock.mockReturnValue({ + services: { + application: { + capabilities: permissionType, + }, + }, + } as unknown as ReturnType); +}; + +const spyOsquery = (data: Record = {}) => { + spyUseIsOsqueryAvailable.mockImplementation(() => ({ + ...defaultUseOsqueryAvailableResult, + ...data, + })); +}; + +const properPermissions = { + osquery: { + runSavedQueries: true, + writeLiveQueries: true, + }, +}; + +const renderWithContext = (Element: React.ReactElement) => + render( + + {Element} + + ); + +describe('Osquery Action', () => { + it('should return empty prompt when agentFetched and no agentData', async () => { + spyOsquery(); + mockKibana(); + + const { getByText } = renderWithContext( + + ); + expect(getByText(EMPTY_PROMPT)).toBeInTheDocument(); + }); + it('should return empty prompt when no agentId', async () => { + spyOsquery(); + mockKibana(); + + const { getByText } = renderWithContext( + + ); + expect(getByText(EMPTY_PROMPT)).toBeInTheDocument(); + }); + it('should return permission denied when agentFetched and agentData available', async () => { + spyOsquery({ agentData: {} }); + mockKibana(); + + const { getByText } = renderWithContext( + + ); + expect(getByText(PERMISSION_DENIED)).toBeInTheDocument(); + }); + it('should return agent status error when permissions are ok and agent status is wrong', async () => { + spyOsquery({ agentData: {} }); + mockKibana(properPermissions); + const { getByText } = renderWithContext( + + ); + expect(getByText(AGENT_STATUS_ERROR)).toBeInTheDocument(); + }); + it('should return permission denied if just one permission (runSavedQueries) is available', async () => { + spyOsquery({ agentData: {} }); + mockKibana({ + osquery: { + runSavedQueries: true, + }, + }); + const { getByText } = renderWithContext( + + ); + expect(getByText(PERMISSION_DENIED)).toBeInTheDocument(); + }); + it('should return permission denied if just one permission (readSavedQueries) is available', async () => { + spyOsquery({ agentData: {} }); + mockKibana({ + osquery: { + readSavedQueries: true, + }, + }); + const { getByText } = renderWithContext( + + ); + expect(getByText(PERMISSION_DENIED)).toBeInTheDocument(); + }); + it('should return permission denied if no writeLiveQueries', async () => { + spyOsquery({ agentData: {} }); + mockKibana({ + osquery: { + writeLiveQueries: true, + }, + }); + const { getByText } = renderWithContext( + + ); + expect(getByText(AGENT_STATUS_ERROR)).toBeInTheDocument(); + }); + it('should return not available prompt if osquery is not available', async () => { + spyOsquery({ agentData: {}, osqueryAvailable: false }); + mockKibana({ + osquery: { + writeLiveQueries: true, + }, + }); + const { getByText } = renderWithContext( + + ); + expect(getByText(NOT_AVAILABLE)).toBeInTheDocument(); + }); + it('should not return any errors when all data is ok', async () => { + spyOsquery({ agentData: { status: 'online' } }); + mockKibana(properPermissions); + + const { queryByText } = renderWithContext( + + ); + expect(queryByText(EMPTY_PROMPT)).not.toBeInTheDocument(); + expect(queryByText(PERMISSION_DENIED)).not.toBeInTheDocument(); + expect(queryByText(AGENT_STATUS_ERROR)).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/translations.ts b/x-pack/plugins/osquery/public/shared_components/osquery_action/translations.ts new file mode 100644 index 000000000000..cd49b6de7b7c --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/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 SHORT_EMPTY_TITLE = i18n.translate('xpack.osquery.action.shortEmptyTitle', { + defaultMessage: 'Osquery is not available', +}); + +export const EMPTY_PROMPT = i18n.translate('xpack.osquery.action.empty', { + defaultMessage: + 'An Elastic Agent is not installed on this host. To run queries, install Elastic Agent on the host, and then add the Osquery Manager integration to the agent policy in Fleet.', +}); +export const PERMISSION_DENIED = i18n.translate('xpack.osquery.action.permissionDenied', { + defaultMessage: 'Permission denied', +}); + +export const NOT_AVAILABLE = i18n.translate('xpack.osquery.action.unavailable', { + defaultMessage: + 'The Osquery Manager integration is not added to the agent policy. To run queries on the host, add the Osquery Manager integration to the agent policy in Fleet.', +}); +export const AGENT_STATUS_ERROR = i18n.translate('xpack.osquery.action.agentStatus', { + defaultMessage: + 'To run queries on this host, the Elastic Agent must be active. Check the status of this agent in Fleet.', +}); diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/use_is_osquery_available.ts b/x-pack/plugins/osquery/public/shared_components/osquery_action/use_is_osquery_available.ts index 5cd84739cd7d..4fa52dcb75f0 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_action/use_is_osquery_available.ts +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/use_is_osquery_available.ts @@ -7,11 +7,24 @@ import { useMemo } from 'react'; import { find } from 'lodash'; +import { AgentStatus } from '@kbn/fleet-plugin/common'; import { useAgentDetails } from '../../agents/use_agent_details'; import { useAgentPolicy } from '../../agent_policies'; import { OSQUERY_INTEGRATION_NAME } from '../../../common'; -export const useIsOsqueryAvailable = (agentId?: string) => { +interface IIsOsqueryAvailable { + osqueryAvailable: boolean; + agentFetched: boolean; + isLoading: boolean; + policyFetched: boolean; + policyLoading: boolean; + agentData?: { + status?: AgentStatus; + policy_id?: string; + }; +} + +export const useIsOsqueryAvailable = (agentId?: string): IIsOsqueryAvailable => { const { data: agentData, isFetched: agentFetched, diff --git a/x-pack/plugins/osquery/scripts/roles_users/alert_test/delete_user.sh b/x-pack/plugins/osquery/scripts/roles_users/alert_test/delete_user.sh new file mode 100755 index 000000000000..f9198c39bdbc --- /dev/null +++ b/x-pack/plugins/osquery/scripts/roles_users/alert_test/delete_user.sh @@ -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. +# + +curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XDELETE ${ELASTICSEARCH_URL}/_security/user/alert_test diff --git a/x-pack/plugins/osquery/scripts/roles_users/alert_test/get_role.sh b/x-pack/plugins/osquery/scripts/roles_users/alert_test/get_role.sh new file mode 100755 index 000000000000..06400b4e7487 --- /dev/null +++ b/x-pack/plugins/osquery/scripts/roles_users/alert_test/get_role.sh @@ -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. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XGET ${KIBANA_URL}/api/security/role/alert_test | jq -S . diff --git a/x-pack/plugins/osquery/scripts/roles_users/alert_test/index.ts b/x-pack/plugins/osquery/scripts/roles_users/alert_test/index.ts new file mode 100644 index 000000000000..7f79ece04ccc --- /dev/null +++ b/x-pack/plugins/osquery/scripts/roles_users/alert_test/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as alertTestUser from './user.json'; +import * as alertTestRole from './role.json'; + +export { alertTestUser, alertTestRole }; diff --git a/x-pack/plugins/osquery/scripts/roles_users/alert_test/post_role.sh b/x-pack/plugins/osquery/scripts/roles_users/alert_test/post_role.sh new file mode 100755 index 000000000000..6150119851ab --- /dev/null +++ b/x-pack/plugins/osquery/scripts/roles_users/alert_test/post_role.sh @@ -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. +# + +ROLE_CONFIG=(${@:-./detections_role.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XPUT ${KIBANA_URL}/api/security/role/alert_test \ +-d @${ROLE_CONFIG} diff --git a/x-pack/plugins/osquery/scripts/roles_users/alert_test/post_user.sh b/x-pack/plugins/osquery/scripts/roles_users/alert_test/post_user.sh new file mode 100755 index 000000000000..c58aad8f7451 --- /dev/null +++ b/x-pack/plugins/osquery/scripts/roles_users/alert_test/post_user.sh @@ -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. +# + +USER=(${@:-./detections_user.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + ${ELASTICSEARCH_URL}/_security/user/alert_test \ +-d @${USER} diff --git a/x-pack/plugins/osquery/scripts/roles_users/alert_test/role.json b/x-pack/plugins/osquery/scripts/roles_users/alert_test/role.json new file mode 100644 index 000000000000..4b97c372a1d4 --- /dev/null +++ b/x-pack/plugins/osquery/scripts/roles_users/alert_test/role.json @@ -0,0 +1,33 @@ +{ + "elasticsearch": { + "cluster": ["manage"], + "indices": [ + { + "names": [".items-*", ".lists-*", ".alerts-security.alerts-*", ".siem-signals-*"], + "privileges": ["manage", "read", "write", "view_index_metadata", "maintenance"] + }, + { + "names": ["*"], + "privileges": ["read"] + }, + { + "names": ["logs-osquery_manager*"], + "privileges": ["read"] + } + ] + }, + "kibana": [ + { + "feature": { + "discover": ["read"], + "infrastructure": ["read"], + "ml": ["all"], + "siem": ["all"], + "osquery": ["read", "packs_all"], + "visualize": ["read"] + }, + "spaces": ["*"] + } + ] +} + diff --git a/x-pack/plugins/osquery/scripts/roles_users/alert_test/user.json b/x-pack/plugins/osquery/scripts/roles_users/alert_test/user.json new file mode 100644 index 000000000000..92eb767f964b --- /dev/null +++ b/x-pack/plugins/osquery/scripts/roles_users/alert_test/user.json @@ -0,0 +1,6 @@ +{ + "password": "changeme", + "roles": ["alert_test"], + "full_name": "Alert Test", + "email": "osquery@example.com" +} diff --git a/x-pack/plugins/osquery/scripts/roles_users/index.ts b/x-pack/plugins/osquery/scripts/roles_users/index.ts index 755b09387770..2bb4c9410837 100644 --- a/x-pack/plugins/osquery/scripts/roles_users/index.ts +++ b/x-pack/plugins/osquery/scripts/roles_users/index.ts @@ -10,3 +10,4 @@ export * from './reader'; export * from './t1_analyst'; export * from './t2_analyst'; export * from './soc_manager'; +export * from './alert_test'; diff --git a/x-pack/plugins/reporting/common/errors/errors.test.ts b/x-pack/plugins/reporting/common/errors/errors.test.ts index 37210373f4d6..d687d8b4adce 100644 --- a/x-pack/plugins/reporting/common/errors/errors.test.ts +++ b/x-pack/plugins/reporting/common/errors/errors.test.ts @@ -7,7 +7,9 @@ import * as errors from '.'; -describe('ReportingError', () => { +const { ReportingError: _, ...nonAbstractErrors } = errors; + +describe('Reporting error', () => { it('provides error code when stringified', () => { expect(new errors.AuthenticationExpiredError() + '').toBe( `ReportingError(code: authentication_expired_error)` @@ -18,10 +20,14 @@ describe('ReportingError', () => { `ReportingError(code: authentication_expired_error) "some details"` ); }); - it('has the expected code structure', () => { - const { ReportingError: _, ...nonAbstractErrors } = errors; + it('has the expected error code structure', () => { + Object.values(nonAbstractErrors).forEach((Ctor) => { + expect(Ctor.code).toMatch(/^[a-z_]+_error$/); + }); + }); + it('has the same error code values on static "code" and instance "code" properties', () => { Object.values(nonAbstractErrors).forEach((Ctor) => { - expect(new Ctor().code).toMatch(/^[a-z_]+_error$/); + expect(Ctor.code).toBe(new Ctor().code); }); }); }); diff --git a/x-pack/plugins/reporting/common/errors/index.ts b/x-pack/plugins/reporting/common/errors/index.ts index d5032c206a18..d2c5f0181df8 100644 --- a/x-pack/plugins/reporting/common/errors/index.ts +++ b/x-pack/plugins/reporting/common/errors/index.ts @@ -15,7 +15,7 @@ export abstract class ReportingError extends Error { * * @note Convention for codes: lower-case, snake-case and end in `_error`. */ - public abstract code: string; + public abstract get code(): string; constructor(public details?: string) { super(); @@ -38,22 +38,34 @@ export abstract class ReportingError extends Error { * access token expired. */ export class AuthenticationExpiredError extends ReportingError { - code = 'authentication_expired_error'; + static code = 'authentication_expired_error' as const; + public get code(): string { + return AuthenticationExpiredError.code; + } } export class QueueTimeoutError extends ReportingError { - code = 'queue_timeout_error'; + static code = 'queue_timeout_error' as const; + public get code(): string { + return QueueTimeoutError.code; + } } /** * An unknown error has occurred. See details. */ export class UnknownError extends ReportingError { - code = 'unknown_error'; + static code = 'unknown_error' as const; + public get code(): string { + return UnknownError.code; + } } export class PdfWorkerOutOfMemoryError extends ReportingError { - code = 'pdf_worker_out_of_memory_error'; + static code = 'pdf_worker_out_of_memory_error' as const; + public get code(): string { + return PdfWorkerOutOfMemoryError.code; + } details = i18n.translate('xpack.reporting.common.pdfWorkerOutOfMemoryErrorMessage', { defaultMessage: @@ -70,7 +82,10 @@ export class PdfWorkerOutOfMemoryError extends ReportingError { } export class BrowserCouldNotLaunchError extends ReportingError { - code = 'browser_could_not_launch_error'; + static code = 'browser_could_not_launch_error' as const; + public get code(): string { + return BrowserCouldNotLaunchError.code; + } details = i18n.translate('xpack.reporting.common.browserCouldNotLaunchErrorMessage', { defaultMessage: 'Cannot generate screenshots because the browser did not launch.', @@ -86,13 +101,42 @@ export class BrowserCouldNotLaunchError extends ReportingError { } export class BrowserUnexpectedlyClosedError extends ReportingError { - code = 'browser_unexpectedly_closed_error'; + static code = 'browser_unexpectedly_closed_error' as const; + public get code(): string { + return BrowserUnexpectedlyClosedError.code; + } } export class BrowserScreenshotError extends ReportingError { - code = 'browser_screenshot_error'; + static code = 'browser_screenshot_error' as const; + public get code(): string { + return BrowserScreenshotError.code; + } } export class KibanaShuttingDownError extends ReportingError { - code = 'kibana_shutting_down_error'; + static code = 'kibana_shutting_down_error' as const; + public get code(): string { + return KibanaShuttingDownError.code; + } +} + +/** + * Special error case that should only occur on Cloud when trying to generate + * a report on a Kibana instance that is too small to be running Chromium. + */ +export class VisualReportingSoftDisabledError extends ReportingError { + static code = 'visual_reporting_soft_disabled_error' as const; + public get code(): string { + return VisualReportingSoftDisabledError.code; + } + + details = i18n.translate('xpack.reporting.common.cloud.insufficientSystemMemoryError', { + defaultMessage: + 'This report cannot be generated because Kibana does not have sufficient memory.', + }); + + public override get message() { + return this.details; + } } diff --git a/x-pack/plugins/reporting/common/errors/map_to_reporting_error.ts b/x-pack/plugins/reporting/common/errors/map_to_reporting_error.ts index eafd4ed68362..1244737deee2 100644 --- a/x-pack/plugins/reporting/common/errors/map_to_reporting_error.ts +++ b/x-pack/plugins/reporting/common/errors/map_to_reporting_error.ts @@ -13,6 +13,7 @@ import { BrowserUnexpectedlyClosedError, BrowserScreenshotError, PdfWorkerOutOfMemoryError, + VisualReportingSoftDisabledError, } from '.'; export function mapToReportingError(error: unknown): ReportingError { @@ -28,6 +29,8 @@ export function mapToReportingError(error: unknown): ReportingError { return new BrowserCouldNotLaunchError(); case error instanceof errors.PdfWorkerOutOfMemoryError: return new PdfWorkerOutOfMemoryError(); + case error instanceof errors.InsufficientMemoryAvailableOnCloudError: + return new VisualReportingSoftDisabledError(); } return new UnknownError(); } diff --git a/x-pack/plugins/reporting/common/types/index.ts b/x-pack/plugins/reporting/common/types/index.ts index 8b95fdb54b1e..c8743fcf467a 100644 --- a/x-pack/plugins/reporting/common/types/index.ts +++ b/x-pack/plugins/reporting/common/types/index.ts @@ -156,6 +156,7 @@ export interface JobSummary { status: JobStatus; jobtype: ReportSource['jobtype']; title: ReportSource['payload']['title']; + errorCode?: ReportOutput['error_code']; maxSizeReached: TaskRunResult['max_size_reached']; csvContainsFormulas: TaskRunResult['csv_contains_formulas']; } diff --git a/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap b/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap index 50c867273316..4187db5b2064 100644 --- a/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap +++ b/x-pack/plugins/reporting/public/lib/__snapshots__/stream_handler.test.ts.snap @@ -5,6 +5,7 @@ Object { "completed": Array [ Object { "csvContainsFormulas": false, + "errorCode": undefined, "id": "job-source-mock1", "jobtype": undefined, "maxSizeReached": false, @@ -13,6 +14,7 @@ Object { }, Object { "csvContainsFormulas": true, + "errorCode": undefined, "id": "job-source-mock4", "jobtype": undefined, "maxSizeReached": false, @@ -23,6 +25,7 @@ Object { "failed": Array [ Object { "csvContainsFormulas": false, + "errorCode": undefined, "id": "job-source-mock2", "jobtype": undefined, "maxSizeReached": false, @@ -94,9 +97,7 @@ Array [ this is the failed report error diff --git a/x-pack/plugins/reporting/public/lib/job.tsx b/x-pack/plugins/reporting/public/lib/job.tsx index e875d00cabab..59a0446590dd 100644 --- a/x-pack/plugins/reporting/public/lib/job.tsx +++ b/x-pack/plugins/reporting/public/lib/job.tsx @@ -61,6 +61,8 @@ export class Job { public locatorParams?: BaseParamsV2['locatorParams']; + public error_code?: ReportOutput['error_code']; + constructor(report: ReportApiJSON) { this.id = report.id; this.index = report.index; @@ -90,6 +92,7 @@ export class Job { this.csv_contains_formulas = report.output?.csv_contains_formulas; this.max_size_reached = report.output?.max_size_reached; this.warnings = report.output?.warnings; + this.error_code = report.output?.error_code; this.locatorParams = (report.payload as BaseParamsV2).locatorParams; this.metrics = report.metrics; } diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts index 895266bdd568..6f575652450c 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.test.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.test.ts @@ -7,7 +7,7 @@ import sinon, { stub } from 'sinon'; import { NotificationsStart } from '@kbn/core/public'; -import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; +import { coreMock, themeServiceMock, docLinksServiceMock } from '@kbn/core/public/mocks'; import { JobSummary, ReportApiJSON } from '../../common/types'; import { Job } from './job'; import { ReportingAPIClient } from './reporting_api_client'; @@ -48,6 +48,7 @@ const notificationsMock = { } as unknown as NotificationsStart; const theme = themeServiceMock.createStartContract(); +const docLink = docLinksServiceMock.createStartContract(); describe('stream handler', () => { afterEach(() => { @@ -55,13 +56,23 @@ describe('stream handler', () => { }); it('constructs', () => { - const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock, theme); + const sh = new ReportingNotifierStreamHandler( + notificationsMock, + jobQueueClientMock, + theme, + docLink + ); expect(sh).not.toBe(null); }); describe('findChangedStatusJobs', () => { it('finds no changed status jobs from empty', (done) => { - const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock, theme); + const sh = new ReportingNotifierStreamHandler( + notificationsMock, + jobQueueClientMock, + theme, + docLink + ); const findJobs = sh.findChangedStatusJobs([]); findJobs.subscribe((data) => { expect(data).toEqual({ completed: [], failed: [] }); @@ -70,7 +81,12 @@ describe('stream handler', () => { }); it('finds changed status jobs', (done) => { - const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock, theme); + const sh = new ReportingNotifierStreamHandler( + notificationsMock, + jobQueueClientMock, + theme, + docLink + ); const findJobs = sh.findChangedStatusJobs([ 'job-source-mock1', 'job-source-mock2', @@ -87,7 +103,12 @@ describe('stream handler', () => { describe('showNotifications', () => { it('show success', (done) => { - const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock, theme); + const sh = new ReportingNotifierStreamHandler( + notificationsMock, + jobQueueClientMock, + theme, + docLink + ); sh.showNotifications({ completed: [ { @@ -108,7 +129,12 @@ describe('stream handler', () => { }); it('show max length warning', (done) => { - const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock, theme); + const sh = new ReportingNotifierStreamHandler( + notificationsMock, + jobQueueClientMock, + theme, + docLink + ); sh.showNotifications({ completed: [ { @@ -130,7 +156,12 @@ describe('stream handler', () => { }); it('show csv formulas warning', (done) => { - const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock, theme); + const sh = new ReportingNotifierStreamHandler( + notificationsMock, + jobQueueClientMock, + theme, + docLink + ); sh.showNotifications({ completed: [ { @@ -152,7 +183,12 @@ describe('stream handler', () => { }); it('show failed job toast', (done) => { - const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock, theme); + const sh = new ReportingNotifierStreamHandler( + notificationsMock, + jobQueueClientMock, + theme, + docLink + ); sh.showNotifications({ completed: [], failed: [ @@ -173,7 +209,12 @@ describe('stream handler', () => { }); it('show multiple toast', (done) => { - const sh = new ReportingNotifierStreamHandler(notificationsMock, jobQueueClientMock, theme); + const sh = new ReportingNotifierStreamHandler( + notificationsMock, + jobQueueClientMock, + theme, + docLink + ); sh.showNotifications({ completed: [ { diff --git a/x-pack/plugins/reporting/public/lib/stream_handler.ts b/x-pack/plugins/reporting/public/lib/stream_handler.ts index 2ff8b8bd4ebd..ba2c32de49f6 100644 --- a/x-pack/plugins/reporting/public/lib/stream_handler.ts +++ b/x-pack/plugins/reporting/public/lib/stream_handler.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import * as Rx from 'rxjs'; import { catchError, map } from 'rxjs/operators'; -import { NotificationsSetup, ThemeServiceStart } from '@kbn/core/public'; +import { NotificationsSetup, ThemeServiceStart, DocLinksStart } from '@kbn/core/public'; import { JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY, JOB_STATUSES } from '../../common/constants'; import { JobId, JobSummary, JobSummarySet } from '../../common/types'; import { @@ -34,6 +34,7 @@ function getReportStatus(src: Job): JobSummary { jobtype: src.prettyJobTypeName ?? src.jobtype, maxSizeReached: src.max_size_reached, csvContainsFormulas: src.csv_contains_formulas, + errorCode: src.error_code, }; } @@ -41,7 +42,8 @@ export class ReportingNotifierStreamHandler { constructor( private notifications: NotificationsSetup, private apiClient: ReportingAPIClient, - private theme: ThemeServiceStart + private theme: ThemeServiceStart, + private docLinks: DocLinksStart ) {} /* @@ -97,7 +99,13 @@ export class ReportingNotifierStreamHandler { for (const job of failedJobs) { const errorText = await this.apiClient.getError(job.id); this.notifications.toasts.addDanger( - getFailureToast(errorText, job, this.apiClient.getManagementLink, this.theme) + getFailureToast( + errorText, + job, + this.apiClient.getManagementLink, + this.theme, + this.docLinks + ) ); } return { completed: completedJobs, failed: failedJobs }; diff --git a/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx b/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx index 92b38d99cedd..780ea53d885a 100644 --- a/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx +++ b/x-pack/plugins/reporting/public/management/components/report_info_flyout_content.tsx @@ -17,10 +17,14 @@ import { import moment from 'moment'; import { USES_HEADLESS_JOB_TYPES } from '../../../common/constants'; +import { VisualReportingSoftDisabledError } from '../../../common/errors'; import type { Job } from '../../lib/job'; import { useKibana } from '../../shared_imports'; +import { sharedI18nTexts } from '../../shared_i18n_texts'; + +// TODO: Move all of these i18n texts to ./i18n_texts.tsx const NA = i18n.translate('xpack.reporting.listing.infoPanel.notApplicableLabel', { defaultMessage: 'N/A', }); @@ -40,7 +44,7 @@ const createDateFormatter = (format: string, tz: string) => (date: string) => { export const ReportInfoFlyoutContent: FunctionComponent = ({ info }) => { const { - services: { uiSettings }, + services: { uiSettings, docLinks }, } = useKibana(); const timezone = @@ -186,19 +190,31 @@ export const ReportInfoFlyoutContent: FunctionComponent = ({ info }) => { ]; const warnings = info.getWarnings(); - const errored = info.getError(); + const errored = + /* + * We link the user to documentation if they hit this error case. Note: this + * should only occur on cloud. + */ + info.error_code === VisualReportingSoftDisabledError.code + ? sharedI18nTexts.cloud.insufficientMemoryError( + docLinks.links.reporting.cloudMinimumRequirements + ) + : info.getError(); return ( <> {Boolean(errored) && ( - - {errored} - + <> + + {errored} + + + )} {Boolean(warnings) && ( <> diff --git a/x-pack/plugins/reporting/public/management/mount_management_section.tsx b/x-pack/plugins/reporting/public/management/mount_management_section.tsx index 9e709f317b7f..a3f238052f66 100644 --- a/x-pack/plugins/reporting/public/management/mount_management_section.tsx +++ b/x-pack/plugins/reporting/public/management/mount_management_section.tsx @@ -34,6 +34,7 @@ export async function mountManagementSection( http: coreSetup.http, application: coreStart.application, uiSettings: coreStart.uiSettings, + docLinks: coreStart.docLinks, }} > diff --git a/x-pack/plugins/reporting/public/notifier/job_failure.tsx b/x-pack/plugins/reporting/public/notifier/job_failure.tsx index e653c974861a..e7a6191c3083 100644 --- a/x-pack/plugins/reporting/public/notifier/job_failure.tsx +++ b/x-pack/plugins/reporting/public/notifier/job_failure.tsx @@ -6,18 +6,20 @@ */ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; -import { ThemeServiceStart, ToastInput } from '@kbn/core/public'; +import { DocLinksStart, ThemeServiceStart, ToastInput } from '@kbn/core/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import type { JobSummary, ManagementLinkFn } from '../../common/types'; +import * as errors from '../../common/errors'; +import { sharedI18nTexts } from '../shared_i18n_texts'; export const getFailureToast = ( errorText: string, job: JobSummary, getManagmenetLink: ManagementLinkFn, - theme: ThemeServiceStart + theme: ThemeServiceStart, + docLinks: DocLinksStart ): ToastInput => { return { title: toMountPoint( @@ -30,16 +32,12 @@ export const getFailureToast = ( ), text: toMountPoint( <> - - {errorText} + + {job.errorCode === errors.VisualReportingSoftDisabledError.code + ? sharedI18nTexts.cloud.insufficientMemoryError( + docLinks.links.reporting.cloudMinimumRequirements + ) + : errorText} diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index 252745c0cec1..df47c6f28a6e 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -254,9 +254,9 @@ export class ReportingPublicPlugin } public start(core: CoreStart) { - const { notifications } = core; + const { notifications, docLinks } = core; const apiClient = this.getApiClient(core.http, core.uiSettings); - const streamHandler = new StreamHandler(notifications, apiClient, core.theme); + const streamHandler = new StreamHandler(notifications, apiClient, core.theme, docLinks); const interval = durationToNumber(this.config.poll.jobsRefresh.interval); Rx.timer(0, interval) .pipe( diff --git a/x-pack/plugins/reporting/public/shared_i18n_texts.tsx b/x-pack/plugins/reporting/public/shared_i18n_texts.tsx new file mode 100644 index 000000000000..344fe5360fc3 --- /dev/null +++ b/x-pack/plugins/reporting/public/shared_i18n_texts.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 { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiLink } from '@elastic/eui'; + +export const sharedI18nTexts = { + cloud: { + insufficientMemoryError: (helpUrl: string) => ( + + {i18n.translate( + 'xpack.reporting.listing.infoPanel.callout.cloud.insufficientMemoryError.urlLink', + { defaultMessage: 'RAM requirements' } + )} + + ), + }} + /> + ), + }, +}; diff --git a/x-pack/plugins/reporting/public/types.ts b/x-pack/plugins/reporting/public/types.ts index 8a745a237ee5..3be354bada72 100644 --- a/x-pack/plugins/reporting/public/types.ts +++ b/x-pack/plugins/reporting/public/types.ts @@ -5,10 +5,11 @@ * 2.0. */ -import type { HttpSetup, ApplicationStart, CoreSetup } from '@kbn/core/public'; +import type { CoreSetup, CoreStart } from '@kbn/core/public'; export interface KibanaContext { - http: HttpSetup; - application: ApplicationStart; - uiSettings: CoreSetup['uiSettings']; + http: CoreSetup['http']; + application: CoreStart['application']; + uiSettings: CoreStart['uiSettings']; + docLinks: CoreStart['docLinks']; } diff --git a/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap b/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap index 0bfc8cb0df96..937fb0217f4b 100644 --- a/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap +++ b/x-pack/plugins/reporting/server/usage/__snapshots__/reporting_usage_collector.test.ts.snap @@ -48,6 +48,9 @@ Object { "unknown_error": Object { "type": "long", }, + "visual_reporting_soft_disabled_error": Object { + "type": "long", + }, }, "metrics": Object { "png_cpu": Object { @@ -149,6 +152,9 @@ Object { "unknown_error": Object { "type": "long", }, + "visual_reporting_soft_disabled_error": Object { + "type": "long", + }, }, "metrics": Object { "png_cpu": Object { @@ -416,6 +422,9 @@ Object { "unknown_error": Object { "type": "long", }, + "visual_reporting_soft_disabled_error": Object { + "type": "long", + }, }, "metrics": Object { "png_cpu": Object { @@ -517,6 +526,9 @@ Object { "unknown_error": Object { "type": "long", }, + "visual_reporting_soft_disabled_error": Object { + "type": "long", + }, }, "metrics": Object { "png_cpu": Object { @@ -803,6 +815,9 @@ Object { "unknown_error": Object { "type": "long", }, + "visual_reporting_soft_disabled_error": Object { + "type": "long", + }, }, "layout": Object { "canvas": Object { @@ -932,6 +947,9 @@ Object { "unknown_error": Object { "type": "long", }, + "visual_reporting_soft_disabled_error": Object { + "type": "long", + }, }, "layout": Object { "canvas": Object { @@ -1534,6 +1552,9 @@ Object { "unknown_error": Object { "type": "long", }, + "visual_reporting_soft_disabled_error": Object { + "type": "long", + }, }, "layout": Object { "canvas": Object { @@ -1663,6 +1684,9 @@ Object { "unknown_error": Object { "type": "long", }, + "visual_reporting_soft_disabled_error": Object { + "type": "long", + }, }, "layout": Object { "canvas": Object { diff --git a/x-pack/plugins/reporting/server/usage/get_export_stats.test.ts b/x-pack/plugins/reporting/server/usage/get_export_stats.test.ts index 069d5eb42d7f..f99c81ea39e2 100644 --- a/x-pack/plugins/reporting/server/usage/get_export_stats.test.ts +++ b/x-pack/plugins/reporting/server/usage/get_export_stats.test.ts @@ -331,6 +331,7 @@ test('Incorporate error code stats', () => { browser_could_not_launch_error: 2, browser_unexpectedly_closed_error: 8, browser_screenshot_error: 27, + visual_reporting_soft_disabled_error: 1, }, }, printable_pdf_v2: { @@ -349,6 +350,7 @@ test('Incorporate error code stats', () => { browser_could_not_launch_error: 2, browser_unexpectedly_closed_error: 8, browser_screenshot_error: 27, + visual_reporting_soft_disabled_error: 1, }, }, csv_searchsource_immediate: { @@ -378,6 +380,7 @@ test('Incorporate error code stats', () => { "kibana_shutting_down_error": 1, "queue_timeout_error": 1, "unknown_error": 0, + "visual_reporting_soft_disabled_error": 1, } `); expect(result.printable_pdf_v2.error_codes).toMatchInlineSnapshot(` @@ -390,6 +393,7 @@ test('Incorporate error code stats', () => { "pdf_worker_out_of_memory_error": 99, "queue_timeout_error": 1, "unknown_error": 0, + "visual_reporting_soft_disabled_error": 1, } `); diff --git a/x-pack/plugins/reporting/server/usage/schema.test.ts b/x-pack/plugins/reporting/server/usage/schema.test.ts index d188d6eb373d..f877b6251378 100644 --- a/x-pack/plugins/reporting/server/usage/schema.test.ts +++ b/x-pack/plugins/reporting/server/usage/schema.test.ts @@ -39,6 +39,7 @@ describe('Reporting telemetry schema', () => { "PNG.error_codes.kibana_shutting_down_error.type": "long", "PNG.error_codes.queue_timeout_error.type": "long", "PNG.error_codes.unknown_error.type": "long", + "PNG.error_codes.visual_reporting_soft_disabled_error.type": "long", "PNG.metrics.png_cpu.50.0.type": "long", "PNG.metrics.png_cpu.75.0.type": "long", "PNG.metrics.png_cpu.95.0.type": "long", @@ -68,6 +69,7 @@ describe('Reporting telemetry schema', () => { "PNGV2.error_codes.kibana_shutting_down_error.type": "long", "PNGV2.error_codes.queue_timeout_error.type": "long", "PNGV2.error_codes.unknown_error.type": "long", + "PNGV2.error_codes.visual_reporting_soft_disabled_error.type": "long", "PNGV2.metrics.png_cpu.50.0.type": "long", "PNGV2.metrics.png_cpu.75.0.type": "long", "PNGV2.metrics.png_cpu.95.0.type": "long", @@ -144,6 +146,7 @@ describe('Reporting telemetry schema', () => { "last7Days.PNG.error_codes.kibana_shutting_down_error.type": "long", "last7Days.PNG.error_codes.queue_timeout_error.type": "long", "last7Days.PNG.error_codes.unknown_error.type": "long", + "last7Days.PNG.error_codes.visual_reporting_soft_disabled_error.type": "long", "last7Days.PNG.metrics.png_cpu.50.0.type": "long", "last7Days.PNG.metrics.png_cpu.75.0.type": "long", "last7Days.PNG.metrics.png_cpu.95.0.type": "long", @@ -173,6 +176,7 @@ describe('Reporting telemetry schema', () => { "last7Days.PNGV2.error_codes.kibana_shutting_down_error.type": "long", "last7Days.PNGV2.error_codes.queue_timeout_error.type": "long", "last7Days.PNGV2.error_codes.unknown_error.type": "long", + "last7Days.PNGV2.error_codes.visual_reporting_soft_disabled_error.type": "long", "last7Days.PNGV2.metrics.png_cpu.50.0.type": "long", "last7Days.PNGV2.metrics.png_cpu.75.0.type": "long", "last7Days.PNGV2.metrics.png_cpu.95.0.type": "long", @@ -255,6 +259,7 @@ describe('Reporting telemetry schema', () => { "last7Days.printable_pdf.error_codes.pdf_worker_out_of_memory_error.type": "long", "last7Days.printable_pdf.error_codes.queue_timeout_error.type": "long", "last7Days.printable_pdf.error_codes.unknown_error.type": "long", + "last7Days.printable_pdf.error_codes.visual_reporting_soft_disabled_error.type": "long", "last7Days.printable_pdf.layout.canvas.type": "long", "last7Days.printable_pdf.layout.preserve_layout.type": "long", "last7Days.printable_pdf.layout.print.type": "long", @@ -292,6 +297,7 @@ describe('Reporting telemetry schema', () => { "last7Days.printable_pdf_v2.error_codes.pdf_worker_out_of_memory_error.type": "long", "last7Days.printable_pdf_v2.error_codes.queue_timeout_error.type": "long", "last7Days.printable_pdf_v2.error_codes.unknown_error.type": "long", + "last7Days.printable_pdf_v2.error_codes.visual_reporting_soft_disabled_error.type": "long", "last7Days.printable_pdf_v2.layout.canvas.type": "long", "last7Days.printable_pdf_v2.layout.preserve_layout.type": "long", "last7Days.printable_pdf_v2.layout.print.type": "long", @@ -461,6 +467,7 @@ describe('Reporting telemetry schema', () => { "printable_pdf.error_codes.pdf_worker_out_of_memory_error.type": "long", "printable_pdf.error_codes.queue_timeout_error.type": "long", "printable_pdf.error_codes.unknown_error.type": "long", + "printable_pdf.error_codes.visual_reporting_soft_disabled_error.type": "long", "printable_pdf.layout.canvas.type": "long", "printable_pdf.layout.preserve_layout.type": "long", "printable_pdf.layout.print.type": "long", @@ -498,6 +505,7 @@ describe('Reporting telemetry schema', () => { "printable_pdf_v2.error_codes.pdf_worker_out_of_memory_error.type": "long", "printable_pdf_v2.error_codes.queue_timeout_error.type": "long", "printable_pdf_v2.error_codes.unknown_error.type": "long", + "printable_pdf_v2.error_codes.visual_reporting_soft_disabled_error.type": "long", "printable_pdf_v2.layout.canvas.type": "long", "printable_pdf_v2.layout.preserve_layout.type": "long", "printable_pdf_v2.layout.print.type": "long", diff --git a/x-pack/plugins/reporting/server/usage/schema.ts b/x-pack/plugins/reporting/server/usage/schema.ts index a4cd8a3e51bf..f89d89d35503 100644 --- a/x-pack/plugins/reporting/server/usage/schema.ts +++ b/x-pack/plugins/reporting/server/usage/schema.ts @@ -88,6 +88,7 @@ const errorCodesSchemaPng: MakeSchemaFrom = { browser_could_not_launch_error: { type: 'long' }, browser_unexpectedly_closed_error: { type: 'long' }, browser_screenshot_error: { type: 'long' }, + visual_reporting_soft_disabled_error: { type: 'long' }, }; const errorCodesSchemaPdf: MakeSchemaFrom = { pdf_worker_out_of_memory_error: { type: 'long' }, @@ -98,6 +99,7 @@ const errorCodesSchemaPdf: MakeSchemaFrom = { diff --git a/x-pack/plugins/reporting/server/usage/types.ts b/x-pack/plugins/reporting/server/usage/types.ts index cf0acee312ae..9d7e08932cc1 100644 --- a/x-pack/plugins/reporting/server/usage/types.ts +++ b/x-pack/plugins/reporting/server/usage/types.ts @@ -203,6 +203,7 @@ export interface ErrorCodeStats { browser_unexpectedly_closed_error: number | null; browser_screenshot_error: number | null; kibana_shutting_down_error: number | null; + visual_reporting_soft_disabled_error: number | null; } export interface MetricsStats { diff --git a/x-pack/plugins/screenshotting/common/errors.ts b/x-pack/plugins/screenshotting/common/errors.ts index 50effb03ee1d..5284e808c799 100644 --- a/x-pack/plugins/screenshotting/common/errors.ts +++ b/x-pack/plugins/screenshotting/common/errors.ts @@ -13,3 +13,5 @@ export class FailedToSpawnBrowserError extends Error {} export class BrowserClosedUnexpectedly extends Error {} export class FailedToCaptureScreenshot extends Error {} + +export class InsufficientMemoryAvailableOnCloudError extends Error {} diff --git a/x-pack/plugins/screenshotting/kibana.json b/x-pack/plugins/screenshotting/kibana.json index f37640968731..37f962539446 100644 --- a/x-pack/plugins/screenshotting/kibana.json +++ b/x-pack/plugins/screenshotting/kibana.json @@ -7,6 +7,7 @@ "githubTeam": "kibana-reporting-services" }, "description": "Kibana Screenshotting Plugin", + "optionalPlugins": ["cloud"], "requiredPlugins": ["expressions", "screenshotMode"], "configPath": ["xpack", "screenshotting"], "server": true, diff --git a/x-pack/plugins/screenshotting/server/cloud/index.ts b/x-pack/plugins/screenshotting/server/cloud/index.ts new file mode 100644 index 000000000000..9ce60bf13c4b --- /dev/null +++ b/x-pack/plugins/screenshotting/server/cloud/index.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 { Logger } from '@kbn/core/server'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; + +const MIN_CLOUD_MEM_GB: number = 2; +const MIN_CLOUD_MEM_MB: number = MIN_CLOUD_MEM_GB * 1024; + +/** + * If we are on Cloud we need to ensure that we have sufficient memory available, + * if we do not Chromium cannot start. See {@link MIN_CLOUD_MEM_MB}. + * + */ +export function systemHasInsufficientMemory( + cloud: undefined | CloudSetup, + logger: Logger +): boolean { + if (cloud?.isCloudEnabled && typeof cloud?.instanceSizeMb === 'number') { + const instanceSizeMb = cloud.instanceSizeMb; + logger.info(`Memory limit read from cloud (in MB): ${instanceSizeMb}`); + return instanceSizeMb < MIN_CLOUD_MEM_MB; + } + return false; +} diff --git a/x-pack/plugins/screenshotting/server/plugin.ts b/x-pack/plugins/screenshotting/server/plugin.ts index 23f688206d39..27da8b3430e6 100755 --- a/x-pack/plugins/screenshotting/server/plugin.ts +++ b/x-pack/plugins/screenshotting/server/plugin.ts @@ -16,6 +16,7 @@ import type { PluginInitializerContext, } from '@kbn/core/server'; import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/server'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { ChromiumArchivePaths, HeadlessChromiumDriverFactory, install } from './browsers'; import { ConfigType, createConfig } from './config'; import { Screenshots } from './screenshots'; @@ -23,6 +24,7 @@ import { getChromiumPackage } from './utils'; interface SetupDeps { screenshotMode: ScreenshotModePluginSetup; + cloud?: CloudSetup; } /** @@ -57,7 +59,7 @@ export class ScreenshottingPlugin implements Plugin { const paths = new ChromiumArchivePaths(); @@ -85,7 +87,14 @@ export class ScreenshottingPlugin implements Plugin {}); diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts index 3833cfcc4593..1cc328509ab7 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.test.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.test.ts @@ -14,10 +14,12 @@ import { SCREENSHOTTING_EXPRESSION, SCREENSHOTTING_EXPRESSION_INPUT, } from '../../common'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { HeadlessChromiumDriverFactory } from '../browsers'; import { createMockBrowserDriver, createMockBrowserDriverFactory } from '../browsers/mock'; import type { ConfigType } from '../config'; import type { PngScreenshotOptions } from '../formats'; +import * as errors from '../../common/errors'; import * as Layouts from '../layouts/create_layout'; import { createMockLayout } from '../layouts/mock'; import { CONTEXT_ELEMENTATTRIBUTES } from './constants'; @@ -34,9 +36,15 @@ describe('Screenshot Observable Pipeline', () => { let packageInfo: Readonly; let options: ScreenshotOptions; let screenshots: Screenshots; + let cloud: CloudSetup; let config: ConfigType; beforeEach(async () => { + cloud = { + isCloudEnabled: false, + instanceSizeMb: 1024, // 1GB + apm: {}, + } as CloudSetup; driver = createMockBrowserDriver(); driverFactory = createMockBrowserDriverFactory(driver); http = httpServiceMock.createSetupContract(); @@ -71,7 +79,7 @@ describe('Screenshot Observable Pipeline', () => { browser: {} as ConfigType['browser'], }; - screenshots = new Screenshots(driverFactory, logger, packageInfo, http, config); + screenshots = new Screenshots(driverFactory, logger, packageInfo, http, config, cloud); jest.spyOn(Layouts, 'createLayout').mockReturnValue(layout); @@ -185,4 +193,41 @@ describe('Screenshot Observable Pipeline', () => { expect(result.results).toMatchSnapshot(); }); }); + + describe('cloud', () => { + beforeEach(() => { + cloud.isCloudEnabled = true; + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('throws an error when OS memory is under 1GB on cloud', async () => { + await expect( + lastValueFrom( + screenshots.getScreenshots({ + ...options, + expression: 'kibana', + input: 'something', + } as PngScreenshotOptions) + ) + ).rejects.toEqual(new errors.InsufficientMemoryAvailableOnCloudError()); + + expect(driver.open).toHaveBeenCalledTimes(0); + }); + + it('generates a report when OS memory is 2GB on cloud', async () => { + cloud.instanceSizeMb = 2048; + await lastValueFrom( + screenshots.getScreenshots({ + ...options, + expression: 'kibana', + input: 'something', + } as PngScreenshotOptions) + ); + + expect(driver.open).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/x-pack/plugins/screenshotting/server/screenshots/index.ts b/x-pack/plugins/screenshotting/server/screenshots/index.ts index 3fa0f1d222c3..b98270547dbe 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/index.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/index.ts @@ -12,7 +12,7 @@ import type { Transaction } from 'elastic-apm-node'; import apm from 'elastic-apm-node'; import ipaddr from 'ipaddr.js'; import { defaultsDeep, sum } from 'lodash'; -import { from, Observable, of } from 'rxjs'; +import { from, Observable, of, throwError } from 'rxjs'; import { catchError, concatMap, @@ -24,12 +24,15 @@ import { tap, toArray, } from 'rxjs/operators'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { LayoutParams, SCREENSHOTTING_APP_ID, SCREENSHOTTING_EXPRESSION, SCREENSHOTTING_EXPRESSION_INPUT, + errors, } from '../../common'; +import { systemHasInsufficientMemory } from '../cloud'; import type { HeadlessChromiumDriverFactory, PerformanceMetrics } from '../browsers'; import type { ConfigType } from '../config'; import { durationToNumber } from '../config'; @@ -101,7 +104,8 @@ export class Screenshots { private readonly logger: Logger, private readonly packageInfo: PackageInfo, private readonly http: HttpServiceSetup, - private readonly config: ConfigType + private readonly config: ConfigType, + private readonly cloud?: CloudSetup ) { this.semaphore = new Semaphore(config.poolSize); } @@ -228,10 +232,17 @@ export class Screenshots { ); } + systemHasInsufficientMemory(): boolean { + return systemHasInsufficientMemory(this.cloud, this.logger.get('cloud')); + } + getScreenshots(options: PngScreenshotOptions): Observable; getScreenshots(options: PdfScreenshotOptions): Observable; getScreenshots(options: ScreenshotOptions): Observable; getScreenshots(options: ScreenshotOptions): Observable { + if (this.systemHasInsufficientMemory()) { + return throwError(() => new errors.InsufficientMemoryAvailableOnCloudError()); + } const transaction = apm.startTransaction('screenshot-pipeline', 'screenshotting'); const layout = this.createLayout(transaction, options); const captureOptions = this.getCaptureOptions(options); diff --git a/x-pack/plugins/screenshotting/tsconfig.json b/x-pack/plugins/screenshotting/tsconfig.json index d385f72809ce..af98e0fcc324 100644 --- a/x-pack/plugins/screenshotting/tsconfig.json +++ b/x-pack/plugins/screenshotting/tsconfig.json @@ -16,5 +16,6 @@ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/expressions/tsconfig.json" }, { "path": "../../../src/plugins/screenshot_mode/tsconfig.json" }, + { "path": "../cloud/tsconfig.json" }, ] } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx index 20c2d3f4c4bc..7e1c5eb545a2 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx @@ -136,7 +136,6 @@ export class SpacesPopoverList extends Component { key={`spcMenuList`} data-search-term={searchTerm} className="spcMenu__spacesList" - hasFocus={this.state.allowSpacesListFocus} initialFocusedItemIndex={this.state.allowSpacesListFocus ? 0 : undefined} items={items} /> diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 3e871c497980..c801a1f8f4e1 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -85,7 +85,7 @@ describe('SecurityNavControlService', () => { >