>.
+
+[float]
+[[task-manager-cannot-operate-when-inline-scripts-are-disabled]]
+==== Inline scripts are disabled in {es}
+
+*Problem*:
+
+Tasks are not running, and the server logs contain the following error message:
+
+[source, txt]
+--------------------------------------------------
+[warning][plugins][taskManager] Task Manager cannot operate when inline scripts are disabled in {es}
+--------------------------------------------------
+
+*Solution*:
+
+Inline scripts are a hard requirement for Task Manager to function.
+To enable inline scripting, see the Elasticsearch documentation for {ref}/modules-scripting-security.html#allowed-script-types-setting[configuring allowed script types setting].
diff --git a/package.json b/package.json
index 66a6ef1d4558b..99591fdc1ea40 100644
--- a/package.json
+++ b/package.json
@@ -95,18 +95,23 @@
"yarn": "^1.21.1"
},
"dependencies": {
+ "@elastic/apm-rum": "^5.6.1",
+ "@elastic/apm-rum-react": "^1.2.5",
+ "@elastic/charts": "26.0.0",
"@elastic/datemath": "link:packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.4",
"@elastic/ems-client": "7.12.0",
- "@elastic/eui": "31.7.0",
+ "@elastic/eui": "31.10.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "^9.0.1-kibana3",
+ "@elastic/maki": "6.3.0",
"@elastic/node-crypto": "1.2.1",
"@elastic/numeral": "^2.5.0",
"@elastic/react-search-ui": "^1.5.1",
"@elastic/request-crypto": "1.1.4",
"@elastic/safer-lodash-set": "link:packages/elastic-safer-lodash-set",
"@elastic/search-ui-app-search-connector": "^1.5.0",
+ "@elastic/ui-ace": "0.2.3",
"@hapi/boom": "^9.1.1",
"@hapi/cookie": "^11.0.2",
"@hapi/good-squeeze": "6.0.0",
@@ -131,9 +136,15 @@
"@kbn/tinymath": "link:packages/kbn-tinymath",
"@kbn/ui-framework": "link:packages/kbn-ui-framework",
"@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps",
+ "@kbn/utility-types": "link:packages/kbn-utility-types",
"@kbn/utils": "link:packages/kbn-utils",
"@loaders.gl/core": "^2.3.1",
"@loaders.gl/json": "^2.3.1",
+ "@mapbox/geojson-rewind": "^0.5.0",
+ "@mapbox/mapbox-gl-draw": "^1.2.0",
+ "@mapbox/mapbox-gl-rtl-text": "^0.2.3",
+ "@mapbox/vector-tile": "1.3.1",
+ "@scant/router": "^0.1.1",
"@slack/webhook": "^5.0.4",
"@turf/along": "6.0.1",
"@turf/area": "6.0.1",
@@ -151,41 +162,60 @@
"accept": "3.0.2",
"ajv": "^6.12.4",
"angular": "^1.8.0",
+ "angular-aria": "^1.8.0",
"angular-elastic": "^2.5.1",
+ "angular-recursion": "^1.0.5",
"angular-resource": "1.8.0",
+ "angular-route": "^1.8.0",
"angular-sanitize": "^1.8.0",
+ "angular-sortable-view": "^0.0.17",
"angular-ui-ace": "0.2.3",
"antlr4ts": "^0.5.0-alpha.3",
"apollo-cache-inmemory": "1.6.2",
"apollo-client": "^2.3.8",
+ "apollo-link": "^1.2.3",
+ "apollo-link-error": "^1.1.7",
"apollo-link-http": "^1.5.16",
"apollo-link-http-common": "^0.2.15",
"apollo-link-schema": "^1.1.0",
+ "apollo-link-state": "^0.4.1",
"apollo-server-core": "^1.3.6",
"apollo-server-errors": "^2.0.2",
"apollo-server-hapi": "^1.3.6",
"archiver": "^5.2.0",
"axios": "^0.21.1",
+ "base64-js": "^1.3.1",
"bluebird": "3.5.5",
"brace": "0.11.1",
+ "broadcast-channel": "^3.0.3",
"chalk": "^4.1.0",
"check-disk-space": "^2.1.0",
+ "cheerio": "0.22.0",
"chokidar": "^3.4.3",
"chroma-js": "^1.4.1",
"classnames": "2.2.6",
"color": "1.0.3",
"commander": "^3.0.2",
+ "compare-versions": "3.5.1",
"concat-stream": "1.6.2",
+ "constate": "^1.3.2",
+ "cronstrue": "^1.51.0",
"content-disposition": "0.5.3",
+ "copy-to-clipboard": "^3.0.8",
"core-js": "^3.6.5",
+ "css-minimizer-webpack-plugin": "^1.3.0",
"custom-event-polyfill": "^0.3.0",
"cytoscape": "^3.10.0",
"cytoscape-dagre": "^2.2.2",
+ "d3": "3.5.17",
"d3-array": "1.2.4",
+ "d3-cloud": "1.2.5",
+ "d3-scale": "1.0.7",
"d3-shape": "^1.1.0",
"d3-time": "^1.1.0",
"dedent": "^0.7.0",
"deep-freeze-strict": "^1.1.1",
+ "deepmerge": "^4.2.2",
"del": "^5.1.0",
"elastic-apm-node": "^3.10.0",
"elasticsearch": "^16.7.0",
@@ -194,9 +224,11 @@
"expiry-js": "0.1.7",
"extract-zip": "^2.0.1",
"fast-deep-equal": "^3.1.1",
+ "file-saver": "^1.3.8",
"file-type": "^10.9.0",
"focus-trap-react": "^3.1.1",
"font-awesome": "4.7.0",
+ "formsy-react": "^1.1.5",
"fp-ts": "^2.3.1",
"geojson-vt": "^3.2.1",
"get-port": "^5.0.0",
@@ -212,31 +244,51 @@
"graphql-tag": "^2.10.3",
"graphql-tools": "^3.0.2",
"handlebars": "4.7.7",
+ "he": "^1.2.0",
"history": "^4.9.0",
+ "history-extra": "^5.0.1",
"hjson": "3.2.1",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^5.0.0",
+ "i18n-iso-countries": "^4.3.1",
+ "icalendar": "0.7.1",
"idx": "^2.5.6",
"immer": "^8.0.1",
"inline-style": "^2.0.0",
"intl": "^1.2.5",
"intl-format-cache": "^2.1.0",
"intl-messageformat": "^2.2.0",
+ "intl-messageformat-parser": "^1.4.0",
"intl-relativeformat": "^2.1.0",
"io-ts": "^2.0.5",
"ipaddr.js": "2.0.0",
"isbinaryfile": "4.0.2",
"joi": "^13.5.2",
"jquery": "^3.5.0",
+ "js-levenshtein": "^1.1.6",
+ "js-search": "^1.4.3",
"js-yaml": "^3.14.0",
"json-stable-stringify": "^1.0.1",
+ "json-stringify-pretty-compact": "1.2.0",
"json-stringify-safe": "5.0.1",
"jsonwebtoken": "^8.5.1",
+ "jsts": "^1.6.2",
+ "kea": "^2.3.0",
+ "leaflet": "1.5.1",
+ "leaflet-draw": "0.4.14",
+ "leaflet-responsive-popup": "0.6.4",
+ "leaflet.heat": "0.2.0",
+ "less": "npm:@elastic/less@2.7.3-kibana",
"load-json-file": "^6.2.0",
+ "loader-utils": "^1.2.3",
"lodash": "^4.17.21",
"lru-cache": "^4.1.5",
+ "lz-string": "^1.4.4",
"markdown-it": "^10.0.0",
+ "mapbox-gl": "1.13.1",
+ "mapbox-gl-draw-rectangle-mode": "^1.0.4",
"md5": "^2.1.0",
+ "memoize-one": "^5.0.0",
"mime": "^2.4.4",
"mime-types": "^2.1.27",
"mini-css-extract-plugin": "0.8.0",
@@ -261,38 +313,69 @@
"papaparse": "^5.2.0",
"pdfmake": "^0.1.65",
"pegjs": "0.10.0",
+ "p-limit": "^3.0.1",
+ "pluralize": "3.1.0",
"pngjs": "^3.4.0",
+ "polished": "^1.9.2",
"prop-types": "^15.7.2",
"proper-lockfile": "^3.2.0",
"proxy-from-env": "1.0.0",
+ "proxyquire": "1.8.0",
"puid": "1.0.7",
"puppeteer": "npm:@elastic/puppeteer@5.4.1-patch.1",
"query-string": "^6.13.2",
"raw-loader": "^3.1.0",
+ "rbush": "^3.0.1",
+ "re-resizable": "^6.1.1",
"re2": "^1.15.4",
"react": "^16.12.0",
"react-ace": "^5.9.0",
+ "react-apollo": "^2.1.4",
+ "react-beautiful-dnd": "^13.0.0",
"react-color": "^2.13.8",
"react-datetime": "^2.14.0",
"react-dom": "^16.12.0",
+ "react-dropzone": "^4.2.9",
+ "react-fast-compare": "^2.0.4",
+ "react-grid-layout": "^0.16.2",
"react-input-range": "^1.3.0",
"react-intl": "^2.8.0",
"react-is": "^16.8.0",
+ "react-markdown": "^4.3.1",
"react-moment-proptypes": "^1.7.0",
+ "react-monaco-editor": "^0.41.2",
+ "react-popper-tooltip": "^2.10.1",
"react-query": "^3.12.0",
+ "react-resize-detector": "^4.2.0",
+ "react-reverse-portal": "^1.0.4",
+ "react-router-redux": "^4.0.8",
+ "react-shortcuts": "^2.0.0",
+ "react-sizeme": "^2.3.6",
+ "react-syntax-highlighter": "^15.3.1",
"react-redux": "^7.2.0",
"react-resizable": "^1.7.5",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
+ "react-tiny-virtual-list": "^2.2.0",
+ "react-virtualized": "^9.21.2",
"react-use": "^15.3.8",
+ "react-vis": "^1.8.1",
+ "react-visibility-sensor": "^5.1.1",
+ "reactcss": "1.2.3",
"recompose": "^0.26.0",
+ "reduce-reducers": "^1.0.4",
"redux": "^4.0.5",
"redux-actions": "^2.6.5",
+ "redux-devtools-extension": "^2.13.8",
"redux-observable": "^1.2.0",
+ "redux-saga": "^1.1.3",
"redux-thunk": "^2.3.0",
+ "redux-thunks": "^1.0.0",
"regenerator-runtime": "^0.13.3",
"request": "^2.88.0",
"require-in-the-middle": "^5.0.2",
+ "reselect": "^4.0.0",
+ "resize-observer-polyfill": "^1.5.0",
"rison-node": "1.0.2",
"rxjs": "^6.5.5",
"seedrandom": "^3.0.5",
@@ -305,17 +388,30 @@
"style-it": "^2.1.3",
"styled-components": "^5.1.0",
"symbol-observable": "^1.2.0",
+ "suricata-sid-db": "^1.0.2",
"tabbable": "1.1.3",
"tar": "4.4.13",
+ "tinycolor2": "1.4.1",
"tinygradient": "0.4.3",
+ "topojson-client": "3.0.0",
"tree-kill": "^1.2.2",
"ts-easing": "^0.2.0",
"tslib": "^2.0.0",
"type-detect": "^4.0.8",
+ "typescript-fsa": "^3.0.0",
+ "typescript-fsa-reducers": "^1.2.2",
"ui-select": "0.19.8",
"unified": "^9.2.1",
+ "unstated": "^2.1.1",
+ "use-resize-observer": "^6.0.0",
"utility-types": "^3.10.0",
"uuid": "3.3.2",
+ "vega": "^5.19.1",
+ "vega-lite": "^4.17.0",
+ "vega-schema-url-parser": "^2.1.0",
+ "vega-spec-injector": "^0.0.2",
+ "vega-tooltip": "^0.25.0",
+ "venn.js": "0.2.20",
"vinyl": "^2.2.0",
"vt-pbf": "^3.1.1",
"wellknown": "^0.5.0",
@@ -347,13 +443,10 @@
"@cypress/webpack-preprocessor": "^5.5.0",
"@elastic/apm-rum": "^5.6.1",
"@elastic/apm-rum-react": "^1.2.5",
- "@elastic/charts": "25.3.0",
"@elastic/eslint-config-kibana": "link:packages/elastic-eslint-config-kibana",
"@elastic/eslint-plugin-eui": "0.0.2",
"@elastic/github-checks-reporter": "0.0.20b3",
"@elastic/makelogs": "^6.0.0",
- "@elastic/maki": "6.3.0",
- "@elastic/ui-ace": "0.2.3",
"@istanbuljs/schema": "^0.1.2",
"@jest/reporters": "^26.5.2",
"@kbn/babel-code-parser": "link:packages/kbn-babel-code-parser",
@@ -373,17 +466,11 @@
"@kbn/telemetry-tools": "link:packages/kbn-telemetry-tools",
"@kbn/test": "link:packages/kbn-test",
"@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector",
- "@kbn/utility-types": "link:packages/kbn-utility-types",
"@loaders.gl/polyfills": "^2.3.5",
- "@mapbox/geojson-rewind": "^0.5.0",
- "@mapbox/mapbox-gl-draw": "^1.2.0",
- "@mapbox/mapbox-gl-rtl-text": "^0.2.3",
- "@mapbox/vector-tile": "1.3.1",
"@microsoft/api-documenter": "7.7.2",
"@microsoft/api-extractor": "7.7.0",
"@octokit/rest": "^16.35.0",
"@percy/agent": "^0.28.6",
- "@scant/router": "^0.1.0",
"@storybook/addon-a11y": "^6.1.20",
"@storybook/addon-actions": "^6.1.20",
"@storybook/addon-docs": "^6.1.20",
@@ -456,7 +543,6 @@
"@types/he": "^1.1.1",
"@types/history": "^4.7.3",
"@types/hjson": "^2.4.2",
- "@types/hoist-non-react-statics": "^3.3.1",
"@types/http-proxy": "^1.17.4",
"@types/http-proxy-agent": "^2.0.2",
"@types/inquirer": "^7.3.1",
@@ -476,7 +562,6 @@
"@types/listr": "^0.14.0",
"@types/loader-utils": "^1.1.3",
"@types/lodash": "^4.14.159",
- "@types/log-symbols": "^2.0.0",
"@types/lru-cache": "^5.1.0",
"@types/mapbox-gl": "^1.9.1",
"@types/markdown-it": "^0.0.7",
@@ -563,21 +648,13 @@
"@types/zen-observable": "^0.8.0",
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
- "@welldone-software/why-did-you-render": "^5.0.0",
"@yarnpkg/lockfile": "^1.1.0",
"abab": "^2.0.4",
"aggregate-error": "^3.1.0",
- "angular-aria": "^1.8.0",
"angular-mocks": "^1.7.9",
- "angular-recursion": "^1.0.5",
- "angular-route": "^1.8.0",
- "angular-sortable-view": "^0.0.17",
"antlr4ts-cli": "^0.5.0-alpha.3",
"apidoc": "^0.25.0",
"apidoc-markdown": "^5.1.8",
- "apollo-link": "^1.2.3",
- "apollo-link-error": "^1.1.7",
- "apollo-link-state": "^0.4.1",
"argsplit": "^1.0.5",
"autoprefixer": "^9.7.4",
"axe-core": "^4.0.2",
@@ -590,34 +667,23 @@
"babel-plugin-styled-components": "^1.10.7",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"backport": "^5.6.6",
- "base64-js": "^1.3.1",
"base64url": "^3.0.1",
- "broadcast-channel": "^3.0.3",
"callsites": "^3.1.0",
"chai": "3.5.0",
"chance": "1.0.18",
- "cheerio": "0.22.0",
"chromedriver": "^89.0.0",
"clean-webpack-plugin": "^3.0.0",
"cmd-shim": "^2.1.0",
- "compare-versions": "3.5.1",
"compression-webpack-plugin": "^4.0.0",
- "constate": "^1.3.2",
- "copy-to-clipboard": "^3.0.8",
"copy-webpack-plugin": "^6.0.2",
"cpy": "^8.1.1",
- "cronstrue": "^1.51.0",
"css-loader": "^3.4.2",
"cypress": "^6.2.1",
"cypress-cucumber-preprocessor": "^2.5.2",
"cypress-multi-reporters": "^1.4.0",
"cypress-pipe": "^2.0.0",
"cypress-promise": "^1.1.0",
- "d3": "3.5.17",
- "d3-cloud": "1.2.5",
- "d3-scale": "1.0.7",
"debug": "^2.6.9",
- "deepmerge": "^4.2.2",
"del-cli": "^3.0.1",
"delete-empty": "^2.0.0",
"dependency-check": "^4.1.0",
@@ -654,9 +720,7 @@
"fast-glob": "2.2.7",
"fetch-mock": "^7.3.9",
"file-loader": "^4.2.0",
- "file-saver": "^1.3.8",
"form-data": "^4.0.0",
- "formsy-react": "^1.1.5",
"geckodriver": "^1.22.2",
"glob-watcher": "5.0.3",
"graphql-code-generator": "^0.18.2",
@@ -675,19 +739,10 @@
"gulp-zip": "^5.0.2",
"has-ansi": "^3.0.0",
"hdr-histogram-js": "^1.2.0",
- "he": "^1.2.0",
- "highlight.js": "^9.18.5",
- "history-extra": "^5.0.1",
- "hoist-non-react-statics": "^3.3.2",
"html": "1.0.0",
"html-loader": "^0.5.5",
"http-proxy": "^1.18.1",
- "i18n-iso-countries": "^4.3.1",
- "icalendar": "0.7.1",
- "iedriver": "^3.14.2",
- "imports-loader": "^0.8.0",
"inquirer": "^7.3.3",
- "intl-messageformat-parser": "^1.4.0",
"is-glob": "^4.0.1",
"is-path-inside": "^3.0.2",
"istanbul-instrumenter-loader": "^3.0.1",
@@ -705,31 +760,14 @@
"jest-styled-components": "^7.0.2",
"jest-when": "^2.7.2",
"jimp": "^0.14.0",
- "js-levenshtein": "^1.1.6",
- "js-search": "^1.4.3",
"jsdom": "13.1.0",
- "json-stringify-pretty-compact": "1.2.0",
"json5": "^1.0.1",
"jsondiffpatch": "0.4.1",
- "jsts": "^1.6.2",
- "kea": "^2.3.0",
- "keymirror": "0.1.1",
- "leaflet": "1.5.1",
- "leaflet-draw": "0.4.14",
- "leaflet-responsive-popup": "0.6.4",
- "leaflet.heat": "0.2.0",
- "less": "npm:@elastic/less@2.7.3-kibana",
"license-checker": "^16.0.0",
"listr": "^0.14.1",
"lmdb-store": "^0.9.0",
"load-grunt-config": "^3.0.1",
- "loader-utils": "^1.2.3",
- "log-symbols": "^2.2.0",
- "lz-string": "^1.4.4",
- "mapbox-gl": "1.13.1",
- "mapbox-gl-draw-rectangle-mode": "^1.0.4",
"marge": "^1.0.1",
- "memoize-one": "^5.0.0",
"micromatch": "3.1.10",
"minimist": "^1.2.5",
"mkdirp": "0.5.1",
@@ -741,8 +779,6 @@
"mock-http-server": "1.3.0",
"ms-chromium-edge-driver": "^0.2.3",
"multimatch": "^4.0.0",
- "multistream": "^2.1.1",
- "murmurhash3js": "3.0.1",
"mutation-observer": "^1.0.3",
"ncp": "^2.0.0",
"node-sass": "^4.14.1",
@@ -750,53 +786,19 @@
"nyc": "^15.0.1",
"oboe": "^2.1.4",
"ora": "^4.0.4",
- "p-limit": "^3.0.1",
"parse-link-header": "^1.0.1",
"pbf": "3.2.1",
"pirates": "^4.0.1",
"pixelmatch": "^5.1.0",
- "pkg-up": "^2.0.0",
- "pluralize": "3.1.0",
- "polished": "^1.9.2",
"postcss": "^7.0.32",
"postcss-loader": "^3.0.0",
"postcss-prefix-selector": "^1.7.2",
"prettier": "^2.2.0",
"pretty-ms": "5.0.0",
- "proxyquire": "1.8.0",
"q": "^1.5.1",
- "querystring": "^0.2.0",
- "rbush": "^3.0.1",
- "re-resizable": "^6.1.1",
- "react-apollo": "^2.1.4",
- "react-beautiful-dnd": "^13.0.0",
- "react-docgen-typescript-loader": "^3.1.1",
- "react-dropzone": "^4.2.9",
- "react-fast-compare": "^2.0.4",
- "react-grid-layout": "^0.16.2",
- "react-markdown": "^4.3.1",
- "react-monaco-editor": "^0.41.2",
- "react-popper-tooltip": "^2.10.1",
- "react-resize-detector": "^4.2.0",
- "react-reverse-portal": "^1.0.4",
- "react-router-redux": "^4.0.8",
- "react-shortcuts": "^2.0.0",
- "react-sizeme": "^2.3.6",
- "react-syntax-highlighter": "^15.3.1",
"react-test-renderer": "^16.12.0",
- "react-tiny-virtual-list": "^2.2.0",
- "react-virtualized": "^9.21.2",
- "react-vis": "^1.8.1",
- "react-visibility-sensor": "^5.1.1",
- "reactcss": "1.2.3",
"read-pkg": "^5.2.0",
- "reduce-reducers": "^1.0.4",
- "redux-devtools-extension": "^2.13.8",
- "redux-saga": "^1.1.3",
- "redux-thunks": "^1.0.0",
"regenerate": "^1.4.0",
- "reselect": "^4.0.0",
- "resize-observer-polyfill": "^1.5.0",
"resolve": "^1.7.1",
"rxjs-marbles": "^5.0.6",
"sass-loader": "^8.0.2",
@@ -816,31 +818,18 @@
"supertest": "^3.1.0",
"supertest-as-promised": "^4.0.2",
"supports-color": "^7.0.0",
- "suricata-sid-db": "^1.0.2",
"tape": "^5.0.1",
"tar-fs": "^2.1.0",
"tempy": "^0.3.0",
"terminal-link": "^2.1.1",
"terser-webpack-plugin": "^2.1.2",
- "tinycolor2": "1.4.1",
- "topojson-client": "3.0.0",
"ts-loader": "^7.0.5",
"ts-morph": "^9.1.0",
"tsd": "^0.13.1",
"typescript": "4.1.3",
- "typescript-fsa": "^3.0.0",
- "typescript-fsa-reducers": "^1.2.2",
"unlazy-loader": "^0.1.3",
- "unstated": "^2.1.1",
"url-loader": "^2.2.0",
- "use-resize-observer": "^6.0.0",
"val-loader": "^1.1.1",
- "vega": "^5.19.1",
- "vega-lite": "^4.17.0",
- "vega-schema-url-parser": "^2.1.0",
- "vega-spec-injector": "^0.0.2",
- "vega-tooltip": "^0.25.0",
- "venn.js": "0.2.20",
"vinyl-fs": "^3.0.3",
"wait-on": "^5.2.1",
"watchpack": "^1.6.0",
diff --git a/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json b/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json
index 99b108eb2e6b3..e9b87aa0f972f 100644
--- a/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json
+++ b/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a.json
@@ -2,6 +2,45 @@
"id": "pluginA",
"client": {
"classes": [
+ {
+ "id": "def-public.CrazyClass",
+ "type": "Class",
+ "tags": [],
+ "label": "CrazyClass",
+ "description": [],
+ "signature": [
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.CrazyClass",
+ "text": "CrazyClass"
+ },
+ " extends ",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.ExampleClass",
+ "text": "ExampleClass"
+ },
+ "<",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.WithGen",
+ "text": "WithGen"
+ },
+ "
>"
+ ],
+ "children": [],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
+ "lineNumber": 65
+ },
+ "initialIsOpen": false
+ },
{
"id": "def-public.ExampleClass",
"type": "Class",
@@ -35,8 +74,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 44,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L44"
+ "lineNumber": 44
},
"signature": [
"React.ComponentClass<{}, any> | React.FunctionComponent<{}> | undefined"
@@ -61,8 +99,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 46,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L46"
+ "lineNumber": 46
}
}
],
@@ -70,8 +107,7 @@
"returnComment": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 46,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L46"
+ "lineNumber": 46
}
},
{
@@ -94,8 +130,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 54,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L54"
+ "lineNumber": 54
}
}
],
@@ -123,8 +158,7 @@
"label": "arrowFn",
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 54,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L54"
+ "lineNumber": 54
},
"tags": [],
"returnComment": []
@@ -166,8 +200,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 60,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L60"
+ "lineNumber": 60
}
}
],
@@ -175,200 +208,18 @@
"returnComment": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 60,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L60"
+ "lineNumber": 60
}
}
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 38,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L38"
- },
- "initialIsOpen": false
- },
- {
- "id": "def-public.CrazyClass",
- "type": "Class",
- "tags": [],
- "label": "CrazyClass",
- "description": [],
- "signature": [
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.CrazyClass",
- "text": "CrazyClass"
- },
- "
extends ",
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.ExampleClass",
- "text": "ExampleClass"
- },
- "<",
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.WithGen",
- "text": "WithGen"
- },
- "
>"
- ],
- "children": [],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 65,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L65"
+ "lineNumber": 38
},
"initialIsOpen": false
}
],
"functions": [
- {
- "id": "def-public.notAnArrowFn",
- "type": "Function",
- "label": "notAnArrowFn",
- "signature": [
- "(a: string, b: number | undefined, c: ",
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.TypeWithGeneric",
- "text": "TypeWithGeneric"
- },
- ", d: ",
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.ImAType",
- "text": "ImAType"
- },
- ", e: string | undefined) => ",
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.TypeWithGeneric",
- "text": "TypeWithGeneric"
- },
- ""
- ],
- "description": [
- "\nThis is a non arrow function.\n"
- ],
- "children": [
- {
- "type": "string",
- "label": "a",
- "isRequired": true,
- "signature": [
- "string"
- ],
- "description": [
- "The letter A"
- ],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 22,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L22"
- }
- },
- {
- "type": "number",
- "label": "b",
- "isRequired": false,
- "signature": [
- "number | undefined"
- ],
- "description": [
- "Feed me to the function"
- ],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 23,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L23"
- }
- },
- {
- "type": "Array",
- "label": "c",
- "isRequired": true,
- "signature": [
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.TypeWithGeneric",
- "text": "TypeWithGeneric"
- },
- ""
- ],
- "description": [
- "So many params"
- ],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 24,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L24"
- }
- },
- {
- "type": "CompoundType",
- "label": "d",
- "isRequired": true,
- "signature": [
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.ImAType",
- "text": "ImAType"
- }
- ],
- "description": [
- "a great param"
- ],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 25,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L25"
- }
- },
- {
- "type": "string",
- "label": "e",
- "isRequired": false,
- "signature": [
- "string | undefined"
- ],
- "description": [
- "Another comment"
- ],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 26,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L26"
- }
- }
- ],
- "tags": [],
- "returnComment": [
- "something!"
- ],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 21,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L21"
- },
- "initialIsOpen": false
- },
{
"id": "def-public.arrowFn",
"type": "Function",
@@ -383,8 +234,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 42,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L42"
+ "lineNumber": 42
}
},
{
@@ -397,8 +247,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 43,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L43"
+ "lineNumber": 43
}
},
{
@@ -418,8 +267,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 44,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L44"
+ "lineNumber": 44
}
},
{
@@ -438,8 +286,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 45,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L45"
+ "lineNumber": 45
}
},
{
@@ -452,8 +299,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 46,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L46"
+ "lineNumber": 46
}
}
],
@@ -490,8 +336,7 @@
"label": "arrowFn",
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 41,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L41"
+ "lineNumber": 41
},
"tags": [],
"returnComment": [
@@ -518,15 +363,13 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 67,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L67"
+ "lineNumber": 67
}
}
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 67,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L67"
+ "lineNumber": 67
}
},
{
@@ -544,8 +387,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 68,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L68"
+ "lineNumber": 68
},
"signature": [
"(foo: { param: string; }) => number"
@@ -554,8 +396,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 68,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L68"
+ "lineNumber": 68
}
},
{
@@ -573,15 +414,13 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 69,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L69"
+ "lineNumber": 69
}
}
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 69,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L69"
+ "lineNumber": 69
}
}
],
@@ -594,8 +433,7 @@
"label": "crazyFunction",
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 66,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L66"
+ "lineNumber": 66
},
"tags": [],
"returnComment": [
@@ -617,8 +455,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 76,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L76"
+ "lineNumber": 76
}
}
],
@@ -629,102 +466,148 @@
"label": "fnWithNonExportedRef",
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 76,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L76"
+ "lineNumber": 76
},
"tags": [],
"returnComment": [],
"initialIsOpen": false
- }
- ],
- "interfaces": [
+ },
{
- "id": "def-public.SearchSpec",
- "type": "Interface",
- "label": "SearchSpec",
+ "id": "def-public.notAnArrowFn",
+ "type": "Function",
+ "label": "notAnArrowFn",
+ "signature": [
+ "(a: string, b: number | undefined, c: ",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.TypeWithGeneric",
+ "text": "TypeWithGeneric"
+ },
+ ", d: ",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.ImAType",
+ "text": "ImAType"
+ },
+ ", e: string | undefined) => ",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.TypeWithGeneric",
+ "text": "TypeWithGeneric"
+ },
+ ""
+ ],
"description": [
- "\nThe SearchSpec interface contains settings for creating a new SearchService, like\nusername and password."
+ "\nThis is a non arrow function.\n"
],
- "tags": [],
"children": [
{
- "tags": [],
- "id": "def-public.SearchSpec.username",
"type": "string",
- "label": "username",
+ "label": "a",
+ "isRequired": true,
+ "signature": [
+ "string"
+ ],
"description": [
- "\nStores the username. Duh,"
+ "The letter A"
],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 26,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L26"
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
+ "lineNumber": 22
}
},
{
- "tags": [],
- "id": "def-public.SearchSpec.password",
- "type": "string",
- "label": "password",
+ "type": "number",
+ "label": "b",
+ "isRequired": false,
+ "signature": [
+ "number | undefined"
+ ],
"description": [
- "\nStores the password. I hope it's encrypted!"
+ "Feed me to the function"
],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 30,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L30"
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
+ "lineNumber": 23
}
- }
- ],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 22,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L22"
- },
- "initialIsOpen": false
- },
- {
- "id": "def-public.WithGen",
- "type": "Interface",
- "label": "WithGen",
- "signature": [
+ },
{
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.WithGen",
- "text": "WithGen"
+ "type": "Array",
+ "label": "c",
+ "isRequired": true,
+ "signature": [
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.TypeWithGeneric",
+ "text": "TypeWithGeneric"
+ },
+ ""
+ ],
+ "description": [
+ "So many params"
+ ],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
+ "lineNumber": 24
+ }
},
- ""
- ],
- "description": [
- "\nAn interface with a generic."
- ],
- "tags": [],
- "children": [
{
- "tags": [],
- "id": "def-public.WithGen.t",
- "type": "Uncategorized",
- "label": "t",
- "description": [],
+ "type": "CompoundType",
+ "label": "d",
+ "isRequired": true,
+ "signature": [
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.ImAType",
+ "text": "ImAType"
+ }
+ ],
+ "description": [
+ "a great param"
+ ],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 31,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L31"
- },
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
+ "lineNumber": 25
+ }
+ },
+ {
+ "type": "string",
+ "label": "e",
+ "isRequired": false,
"signature": [
- "T"
- ]
+ "string | undefined"
+ ],
+ "description": [
+ "Another comment"
+ ],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
+ "lineNumber": 26
+ }
}
],
+ "tags": [],
+ "returnComment": [
+ "something!"
+ ],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 30,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L30"
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
+ "lineNumber": 21
},
"initialIsOpen": false
- },
+ }
+ ],
+ "interfaces": [
{
"id": "def-public.AnotherInterface",
"type": "Interface",
@@ -750,8 +633,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 35,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L35"
+ "lineNumber": 35
},
"signature": [
"T"
@@ -760,8 +642,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 34,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L34"
+ "lineNumber": 34
},
"initialIsOpen": false
},
@@ -802,8 +683,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 75,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L75"
+ "lineNumber": 75
},
"signature": [
"() => Promise"
@@ -819,8 +699,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 81,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L81"
+ "lineNumber": 81
},
"signature": [
"(t: T) => void"
@@ -841,47 +720,13 @@
"returnComment": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 86,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L86"
+ "lineNumber": 86
}
}
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 71,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L71"
- },
- "initialIsOpen": false
- },
- {
- "id": "def-public.IReturnAReactComponent",
- "type": "Interface",
- "label": "IReturnAReactComponent",
- "description": [
- "\nAn interface that has a react component."
- ],
- "tags": [],
- "children": [
- {
- "tags": [],
- "id": "def-public.IReturnAReactComponent.component",
- "type": "CompoundType",
- "label": "component",
- "description": [],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 93,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L93"
- },
- "signature": [
- "React.ComponentType<{}>"
- ]
- }
- ],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
- "lineNumber": 92,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts#L92"
+ "lineNumber": 71
},
"initialIsOpen": false
},
@@ -900,8 +745,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 44,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L44"
+ "lineNumber": 44
},
"signature": [
{
@@ -916,143 +760,154 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 43,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L43"
+ "lineNumber": 43
},
"initialIsOpen": false
- }
- ],
- "enums": [
+ },
{
- "id": "def-public.DayOfWeek",
- "type": "Enum",
- "label": "DayOfWeek",
- "tags": [],
+ "id": "def-public.IReturnAReactComponent",
+ "type": "Interface",
+ "label": "IReturnAReactComponent",
"description": [
- "\nComments on enums."
+ "\nAn interface that has a react component."
],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 31,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L31"
- },
- "initialIsOpen": false
- }
- ],
- "misc": [
- {
"tags": [],
- "id": "def-public.imAnAny",
- "type": "Any",
- "label": "imAnAny",
- "description": [],
+ "children": [
+ {
+ "tags": [],
+ "id": "def-public.IReturnAReactComponent.component",
+ "type": "CompoundType",
+ "label": "component",
+ "description": [],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
+ "lineNumber": 93
+ },
+ "signature": [
+ "React.ComponentType<{}>"
+ ]
+ }
+ ],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/index.ts",
- "lineNumber": 19,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/index.ts#L19"
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
+ "lineNumber": 92
},
- "signature": [
- "any"
- ],
"initialIsOpen": false
},
{
+ "id": "def-public.SearchSpec",
+ "type": "Interface",
+ "label": "SearchSpec",
+ "description": [
+ "\nThe SearchSpec interface contains settings for creating a new SearchService, like\nusername and password."
+ ],
"tags": [],
- "id": "def-public.imAnUnknown",
- "type": "Unknown",
- "label": "imAnUnknown",
- "description": [],
+ "children": [
+ {
+ "tags": [],
+ "id": "def-public.SearchSpec.username",
+ "type": "string",
+ "label": "username",
+ "description": [
+ "\nStores the username. Duh,"
+ ],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
+ "lineNumber": 26
+ }
+ },
+ {
+ "tags": [],
+ "id": "def-public.SearchSpec.password",
+ "type": "string",
+ "label": "password",
+ "description": [
+ "\nStores the password. I hope it's encrypted!"
+ ],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
+ "lineNumber": 30
+ }
+ }
+ ],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/index.ts",
- "lineNumber": 20,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/index.ts#L20"
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
+ "lineNumber": 22
},
- "signature": [
- "unknown"
- ],
"initialIsOpen": false
},
{
- "id": "def-public.NotAnArrowFnType",
- "type": "Type",
- "label": "NotAnArrowFnType",
- "tags": [],
- "description": [],
- "source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
- "lineNumber": 78,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts#L78"
- },
+ "id": "def-public.WithGen",
+ "type": "Interface",
+ "label": "WithGen",
"signature": [
- "(a: string, b: number | undefined, c: ",
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.TypeWithGeneric",
- "text": "TypeWithGeneric"
- },
- ", d: ",
{
"pluginId": "pluginA",
"scope": "public",
"docId": "kibPluginAPluginApi",
- "section": "def-public.ImAType",
- "text": "ImAType"
+ "section": "def-public.WithGen",
+ "text": "WithGen"
},
- ", e: string | undefined) => ",
+ ""
+ ],
+ "description": [
+ "\nAn interface with a generic."
+ ],
+ "tags": [],
+ "children": [
{
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.TypeWithGeneric",
- "text": "TypeWithGeneric"
- },
- ""
+ "tags": [],
+ "id": "def-public.WithGen.t",
+ "type": "Uncategorized",
+ "label": "t",
+ "description": [],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
+ "lineNumber": 31
+ },
+ "signature": [
+ "T"
+ ]
+ }
],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/classes.ts",
+ "lineNumber": 30
+ },
"initialIsOpen": false
- },
+ }
+ ],
+ "enums": [
{
+ "id": "def-public.DayOfWeek",
+ "type": "Enum",
+ "label": "DayOfWeek",
"tags": [],
- "id": "def-public.aUnionProperty",
- "type": "CompoundType",
- "label": "aUnionProperty",
"description": [
- "\nThis is a complicated union type"
+ "\nComments on enums."
],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 58,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L58"
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
+ "lineNumber": 31
},
- "signature": [
- "string | number | (() => string) | ",
- {
- "pluginId": "pluginA",
- "scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.CrazyClass",
- "text": "CrazyClass"
- },
- ""
- ],
"initialIsOpen": false
- },
+ }
+ ],
+ "misc": [
{
"tags": [],
- "id": "def-public.aStrArray",
- "type": "Array",
- "label": "aStrArray",
+ "id": "def-public.aNum",
+ "type": "number",
+ "label": "aNum",
"description": [
- "\nThis is an array of strings. The type is explicit."
+ "\nIt's a number. A special number."
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 63,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L63"
+ "lineNumber": 78
},
"signature": [
- "string[]"
+ "10"
],
"initialIsOpen": false
},
@@ -1066,8 +921,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 68,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L68"
+ "lineNumber": 68
},
"signature": [
"number[]"
@@ -1084,78 +938,104 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 73,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L73"
+ "lineNumber": 73
},
"initialIsOpen": false
},
{
"tags": [],
- "id": "def-public.aNum",
- "type": "number",
- "label": "aNum",
+ "id": "def-public.aStrArray",
+ "type": "Array",
+ "label": "aStrArray",
"description": [
- "\nIt's a number. A special number."
+ "\nThis is an array of strings. The type is explicit."
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 78,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L78"
+ "lineNumber": 63
},
"signature": [
- "10"
+ "string[]"
],
"initialIsOpen": false
},
{
"tags": [],
- "id": "def-public.literalString",
- "type": "string",
- "label": "literalString",
+ "id": "def-public.aUnionProperty",
+ "type": "CompoundType",
+ "label": "aUnionProperty",
"description": [
- "\nI'm a type of string, but more specifically, a literal string type."
+ "\nThis is a complicated union type"
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 83,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L83"
+ "lineNumber": 58
},
"signature": [
- "\"HI\""
+ "string | number | (() => string) | ",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.CrazyClass",
+ "text": "CrazyClass"
+ },
+ ""
],
"initialIsOpen": false
},
{
- "id": "def-public.StringOrUndefinedType",
+ "id": "def-public.FnWithGeneric",
"type": "Type",
- "label": "StringOrUndefinedType",
+ "label": "FnWithGeneric",
"tags": [],
"description": [
- "\nHow should a potentially undefined type show up."
+ "\nThis is a type that defines a function.\n"
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 15,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L15"
+ "lineNumber": 26
},
"signature": [
- "undefined | string"
+ "(t: T) => ",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.TypeWithGeneric",
+ "text": "TypeWithGeneric"
+ },
+ ""
],
"initialIsOpen": false
},
{
- "id": "def-public.TypeWithGeneric",
- "type": "Type",
- "label": "TypeWithGeneric",
"tags": [],
+ "id": "def-public.imAnAny",
+ "type": "Any",
+ "label": "imAnAny",
"description": [],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 17,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L17"
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/index.ts",
+ "lineNumber": 19
},
"signature": [
- "T[]"
+ "any"
+ ],
+ "initialIsOpen": false
+ },
+ {
+ "tags": [],
+ "id": "def-public.imAnUnknown",
+ "type": "Unknown",
+ "label": "imAnUnknown",
+ "description": [],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/index.ts",
+ "lineNumber": 20
+ },
+ "signature": [
+ "unknown"
],
"initialIsOpen": false
},
@@ -1167,8 +1047,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 19,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L19"
+ "lineNumber": 19
},
"signature": [
"string | number | ",
@@ -1199,28 +1078,41 @@
"initialIsOpen": false
},
{
- "id": "def-public.FnWithGeneric",
+ "id": "def-public.IRefANotExportedType",
"type": "Type",
- "label": "FnWithGeneric",
+ "label": "IRefANotExportedType",
"tags": [],
- "description": [
- "\nThis is a type that defines a function.\n"
- ],
+ "description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 26,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L26"
+ "lineNumber": 42
},
"signature": [
- "(t: T) => ",
{
"pluginId": "pluginA",
"scope": "public",
- "docId": "kibPluginAPluginApi",
- "section": "def-public.TypeWithGeneric",
- "text": "TypeWithGeneric"
+ "docId": "kibPluginAFooPluginApi",
+ "section": "def-public.ImNotExportedFromIndex",
+ "text": "ImNotExportedFromIndex"
},
- ""
+ " | { zed: \"hi\"; }"
+ ],
+ "initialIsOpen": false
+ },
+ {
+ "tags": [],
+ "id": "def-public.literalString",
+ "type": "string",
+ "label": "literalString",
+ "description": [
+ "\nI'm a type of string, but more specifically, a literal string type."
+ ],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
+ "lineNumber": 83
+ },
+ "signature": [
+ "\"HI\""
],
"initialIsOpen": false
},
@@ -1234,8 +1126,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 40,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L40"
+ "lineNumber": 40
},
"signature": [
"(typeof DayOfWeek)[]"
@@ -1243,25 +1134,73 @@
"initialIsOpen": false
},
{
- "id": "def-public.IRefANotExportedType",
+ "id": "def-public.NotAnArrowFnType",
"type": "Type",
- "label": "IRefANotExportedType",
+ "label": "NotAnArrowFnType",
"tags": [],
"description": [],
"source": {
- "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
- "lineNumber": 42,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts#L42"
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/fns.ts",
+ "lineNumber": 78
},
"signature": [
+ "(a: string, b: number | undefined, c: ",
{
"pluginId": "pluginA",
"scope": "public",
- "docId": "kibPluginAFooPluginApi",
- "section": "def-public.ImNotExportedFromIndex",
- "text": "ImNotExportedFromIndex"
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.TypeWithGeneric",
+ "text": "TypeWithGeneric"
},
- " | { zed: \"hi\"; }"
+ ", d: ",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.ImAType",
+ "text": "ImAType"
+ },
+ ", e: string | undefined) => ",
+ {
+ "pluginId": "pluginA",
+ "scope": "public",
+ "docId": "kibPluginAPluginApi",
+ "section": "def-public.TypeWithGeneric",
+ "text": "TypeWithGeneric"
+ },
+ ""
+ ],
+ "initialIsOpen": false
+ },
+ {
+ "id": "def-public.StringOrUndefinedType",
+ "type": "Type",
+ "label": "StringOrUndefinedType",
+ "tags": [],
+ "description": [
+ "\nHow should a potentially undefined type show up."
+ ],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
+ "lineNumber": 15
+ },
+ "signature": [
+ "undefined | string"
+ ],
+ "initialIsOpen": false
+ },
+ {
+ "id": "def-public.TypeWithGeneric",
+ "type": "Type",
+ "label": "TypeWithGeneric",
+ "tags": [],
+ "description": [],
+ "source": {
+ "path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/types.ts",
+ "lineNumber": 17
+ },
+ "signature": [
+ "T[]"
],
"initialIsOpen": false
}
@@ -1282,8 +1221,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 21,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L21"
+ "lineNumber": 21
},
"signature": [
"typeof ",
@@ -1306,8 +1244,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 26,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L26"
+ "lineNumber": 26
},
"signature": [
"typeof ",
@@ -1340,8 +1277,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 31,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L31"
+ "lineNumber": 31
}
}
],
@@ -1369,8 +1305,7 @@
"label": "aPropertyInlineFn",
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 31,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L31"
+ "lineNumber": 31
},
"tags": [],
"returnComment": []
@@ -1385,8 +1320,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 38,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L38"
+ "lineNumber": 38
}
},
{
@@ -1402,8 +1336,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 44,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L44"
+ "lineNumber": 44
}
}
],
@@ -1413,8 +1346,7 @@
"label": "nestedObj",
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 43,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L43"
+ "lineNumber": 43
}
}
],
@@ -1424,8 +1356,7 @@
"label": "aPretendNamespaceObj",
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts",
- "lineNumber": 17,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/const_vars.ts#L17"
+ "lineNumber": 17
},
"initialIsOpen": false
}
@@ -1451,8 +1382,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 101,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L101"
+ "lineNumber": 101
},
"signature": [
"(searchSpec: ",
@@ -1476,8 +1406,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 109,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L109"
+ "lineNumber": 109
},
"signature": [
"(searchSpec: { username: string; password: string; }) => string"
@@ -1495,8 +1424,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 122,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L122"
+ "lineNumber": 122
},
"signature": [
"(thingOne: number, thingTwo: string, thingThree: { nestedVar: number; }) => void"
@@ -1512,8 +1440,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 133,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L133"
+ "lineNumber": 133
},
"signature": [
"(obj: { fn: (foo: { param: string; }) => number; }) => () => { retFoo: () => string; }"
@@ -1529,15 +1456,13 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 140,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L140"
+ "lineNumber": 140
}
}
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 89,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L89"
+ "lineNumber": 89
},
"lifecycle": "setup",
"initialIsOpen": true
@@ -1559,8 +1484,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 68,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L68"
+ "lineNumber": 68
},
"signature": [
"() => ",
@@ -1576,8 +1500,7 @@
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts",
- "lineNumber": 64,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/plugin.ts#L64"
+ "lineNumber": 64
},
"lifecycle": "start",
"initialIsOpen": true
@@ -1610,15 +1533,13 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/common/index.ts",
- "lineNumber": 12,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/common/index.ts#L12"
+ "lineNumber": 12
}
}
],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/common/index.ts",
- "lineNumber": 11,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/common/index.ts#L11"
+ "lineNumber": 11
},
"initialIsOpen": false
}
diff --git a/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a_foo.json b/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a_foo.json
index 00fb2bd3aa7a9..a529d1a36657b 100644
--- a/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a_foo.json
+++ b/packages/kbn-docs-utils/src/api_docs/tests/snapshots/plugin_a_foo.json
@@ -14,8 +14,7 @@
"label": "doTheFooFnThing",
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index.ts",
- "lineNumber": 9,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index.ts#L9"
+ "lineNumber": 9
},
"tags": [],
"returnComment": [],
@@ -33,8 +32,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index.ts",
- "lineNumber": 11,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/public/foo/index.ts#L11"
+ "lineNumber": 11
},
"signature": [
"() => \"foo\""
@@ -66,8 +64,7 @@
"description": [],
"source": {
"path": "packages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/common/foo/index.ts",
- "lineNumber": 9,
- "link": "https://github.com/elastic/kibana/tree/masterpackages/kbn-docs-utils/src/api_docs/tests/__fixtures__/src/plugin_a/common/foo/index.ts#L9"
+ "lineNumber": 9
},
"signature": [
"\"COMMON VAR!\""
diff --git a/packages/kbn-test/src/jest/utils/enzyme_helpers.tsx b/packages/kbn-test/src/jest/utils/enzyme_helpers.tsx
index f517565434c18..686a201761dcd 100644
--- a/packages/kbn-test/src/jest/utils/enzyme_helpers.tsx
+++ b/packages/kbn-test/src/jest/utils/enzyme_helpers.tsx
@@ -85,6 +85,7 @@ export function mountWithIntl(
childContextTypes,
...props
}: {
+ attachTo?: HTMLElement;
context?: any;
childContextTypes?: ValidationMap;
} = {}
diff --git a/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js b/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js
index 43b6c90452b81..d472f27395ffb 100644
--- a/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js
+++ b/packages/kbn-test/src/legacy_es/legacy_es_test_cluster.js
@@ -12,9 +12,9 @@ import { get, toPath } from 'lodash';
import { Cluster } from '@kbn/es';
import { CI_PARALLEL_PROCESS_PREFIX } from '../ci_parallel_process_prefix';
import { esTestConfig } from './es_test_config';
+import { Client } from '@elastic/elasticsearch';
import { KIBANA_ROOT } from '../';
-import * as legacyElasticsearch from 'elasticsearch';
const path = require('path');
const del = require('del');
@@ -102,8 +102,8 @@ export function createLegacyEsTestCluster(options = {}) {
* Returns an ES Client to the configured cluster
*/
getClient() {
- return new legacyElasticsearch.Client({
- host: this.getUrl(),
+ return new Client({
+ node: this.getUrl(),
});
}
diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js
index 7a996e98762ce..135884fbf13e7 100644
--- a/packages/kbn-ui-shared-deps/webpack.config.js
+++ b/packages/kbn-ui-shared-deps/webpack.config.js
@@ -9,6 +9,9 @@
const Path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');
+
const CompressionPlugin = require('compression-webpack-plugin');
const { REPO_ROOT } = require('@kbn/utils');
const webpack = require('webpack');
@@ -105,6 +108,28 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({
},
optimization: {
+ minimizer: [
+ new CssMinimizerPlugin({
+ minimizerOptions: {
+ preset: [
+ 'default',
+ {
+ discardComments: false,
+ },
+ ],
+ },
+ }),
+ new TerserPlugin({
+ cache: false,
+ sourceMap: false,
+ extractComments: false,
+ parallel: false,
+ terserOptions: {
+ compress: true,
+ mangle: true,
+ },
+ }),
+ ],
noEmitOnErrors: true,
splitChunks: {
cacheGroups: {
diff --git a/packages/kbn-utility-types/package.json b/packages/kbn-utility-types/package.json
index a8f6e25276cec..33419ee0f1ec4 100644
--- a/packages/kbn-utility-types/package.json
+++ b/packages/kbn-utility-types/package.json
@@ -6,7 +6,7 @@
"main": "target",
"types": "target/index.d.ts",
"kibana": {
- "devOnly": true
+ "devOnly": false
},
"scripts": {
"build": "../../node_modules/.bin/tsc",
diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js
index 13c16691bf12a..34b78bbd7e51e 100644
--- a/src/cli/serve/serve.js
+++ b/src/cli/serve/serve.js
@@ -68,6 +68,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
if (opts.ssl) {
// @kbn/dev-utils is part of devDependencies
+ // eslint-disable-next-line import/no-extraneous-dependencies
const { CA_CERT_PATH, KBN_KEY_PATH, KBN_CERT_PATH } = require('@kbn/dev-utils');
const customElasticsearchHosts = opts.elasticsearch
? opts.elasticsearch.split(',')
diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
index 82a0419b1d0cf..00cc827a1e83f 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
@@ -4050,54 +4050,74 @@ exports[`Header renders 1`] = `
hasArrow={true}
id="headerHelpMenu"
isOpen={false}
- ownFocus={false}
+ ownFocus={true}
panelPaddingSize="m"
repositionOnScroll={true}
>
-
+
@@ -4194,26 +4214,81 @@ exports[`Header renders 1`] = `
data-test-subj="toggleNavButton"
onClick={[Function]}
>
- ,
+ }
+ }
className="euiHeaderSectionItem__button"
+ color="text"
data-test-subj="toggleNavButton"
onClick={[Function]}
- type="button"
>
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 6279d62d2c40e..ef3172b620b23 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -108,7 +108,9 @@ export class DocLinksService {
sum: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-sum-aggregation.html`,
top_hits: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-top-hits-aggregation.html`,
},
- runtimeFields: `${ELASTICSEARCH_DOCS}runtime.html`,
+ runtimeFields: {
+ mapping: `${ELASTICSEARCH_DOCS}runtime-mapping-fields.html`,
+ },
scriptedFields: {
scriptFields: `${ELASTICSEARCH_DOCS}search-request-script-fields.html`,
scriptAggs: `${ELASTICSEARCH_DOCS}search-aggregations.html`,
@@ -191,6 +193,7 @@ export class DocLinksService {
lens: `${ELASTIC_WEBSITE_URL}what-is/kibana-lens`,
lensPanels: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/lens.html`,
maps: `${ELASTIC_WEBSITE_URL}maps`,
+ vega: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/vega.html`,
},
observability: {
guide: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/index.html`,
@@ -215,12 +218,15 @@ export class DocLinksService {
guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/maps.html`,
},
monitoring: {
- alertsCluster: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/cluster-alerts.html`,
alertsKibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html`,
alertsKibanaCpuThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-cpu-threshold`,
alertsKibanaDiskThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-disk-usage-threshold`,
alertsKibanaJvmThreshold: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-jvm-memory-threshold`,
alertsKibanaMissingData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-missing-monitoring-data`,
+ alertsKibanaThreadpoolRejections: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-thread-pool-rejections`,
+ alertsKibanaCCRReadExceptions: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-ccr-read-exceptions`,
+ alertsKibanaLargeShardSize: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-large-shard-size`,
+ alertsKibanaClusterAlerts: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-alerts.html#kibana-alerts-cluster-alerts`,
metricbeatBlog: `${ELASTIC_WEBSITE_URL}blog/external-collection-for-elastic-stack-monitoring-is-now-available-via-metricbeat`,
monitorElasticsearch: `${ELASTICSEARCH_DOCS}configuring-metricbeat.html`,
monitorKibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/monitoring-metricbeat.html`,
@@ -231,7 +237,7 @@ export class DocLinksService {
apiKeyServiceSettings: `${ELASTICSEARCH_DOCS}security-settings.html#api-key-service-settings`,
clusterPrivileges: `${ELASTICSEARCH_DOCS}security-privileges.html#privileges-list-cluster`,
elasticsearchSettings: `${ELASTICSEARCH_DOCS}security-settings.html`,
- elasticsearchEnableSecurity: `${ELASTICSEARCH_DOCS}get-started-enable-security.html`,
+ elasticsearchEnableSecurity: `${ELASTICSEARCH_DOCS}configuring-stack-security.html`,
indicesPrivileges: `${ELASTICSEARCH_DOCS}security-privileges.html#privileges-list-indices`,
kibanaTLS: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/configuring-tls.html`,
kibanaPrivileges: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kibana-privileges.html`,
@@ -284,6 +290,7 @@ export class DocLinksService {
registerSourceOnly: `${ELASTICSEARCH_DOCS}snapshots-register-repository.html#snapshots-source-only-repository`,
registerUrl: `${ELASTICSEARCH_DOCS}snapshots-register-repository.html#snapshots-read-only-repository`,
restoreSnapshot: `${ELASTICSEARCH_DOCS}snapshots-restore-snapshot.html`,
+ restoreSnapshotApi: `${ELASTICSEARCH_DOCS}restore-snapshot-api.html#restore-snapshot-api-request-body`,
},
ingest: {
pipelines: `${ELASTICSEARCH_DOCS}ingest.html`,
@@ -379,7 +386,9 @@ export interface DocLinksStart {
readonly sum: string;
readonly top_hits: string;
};
- readonly runtimeFields: string;
+ readonly runtimeFields: {
+ readonly mapping: string;
+ };
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
index 7a1f936fe7f39..0d10ac47d0b75 100644
--- a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
+++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap
@@ -26,7 +26,7 @@ Array [
]
`;
-exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `""`;
+exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `""`;
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
Array [
@@ -59,4 +59,4 @@ Array [
]
`;
-exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `""`;
+exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `""`;
diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
index d52cc090d5d19..19ebb5a9113c3 100644
--- a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
+++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap
@@ -29,7 +29,7 @@ Array [
]
`;
-exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `""`;
+exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `""`;
exports[`ModalService openConfirm() renders a string confirm message 1`] = `
Array [
@@ -49,7 +49,7 @@ Array [
]
`;
-exports[`ModalService openConfirm() renders a string confirm message 2`] = `""`;
+exports[`ModalService openConfirm() renders a string confirm message 2`] = `""`;
exports[`ModalService openConfirm() with a currently active confirm replaces the current confirm with the new one 1`] = `
Array [
@@ -131,7 +131,7 @@ Array [
]
`;
-exports[`ModalService openModal() renders a modal to the DOM 2`] = `""`;
+exports[`ModalService openModal() renders a modal to the DOM 2`] = `""`;
exports[`ModalService openModal() with a currently active confirm replaces the current confirm with the new one 1`] = `
Array [
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 5c034e68a3736..5a5ae253bac7f 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -559,7 +559,9 @@ export interface DocLinksStart {
readonly sum: string;
readonly top_hits: string;
};
- readonly runtimeFields: string;
+ readonly runtimeFields: {
+ readonly mapping: string;
+ };
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
diff --git a/src/core/server/elasticsearch/integration_tests/client.test.ts b/src/core/server/elasticsearch/integration_tests/client.test.ts
new file mode 100644
index 0000000000000..3a4b7c5c4af22
--- /dev/null
+++ b/src/core/server/elasticsearch/integration_tests/client.test.ts
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import {
+ createTestServers,
+ TestElasticsearchUtils,
+ TestKibanaUtils,
+} from '../../../test_helpers/kbn_server';
+
+describe('elasticsearch clients', () => {
+ let esServer: TestElasticsearchUtils;
+ let kibanaServer: TestKibanaUtils;
+
+ beforeAll(async () => {
+ const { startES, startKibana } = createTestServers({
+ adjustTimeout: jest.setTimeout,
+ });
+
+ esServer = await startES();
+ kibanaServer = await startKibana();
+ });
+
+ afterAll(async () => {
+ await kibanaServer.stop();
+ await esServer.stop();
+ });
+
+ it('does not return deprecation warning when x-elastic-product-origin header is set', async () => {
+ // Header should be automatically set by Core
+ const resp1 = await kibanaServer.coreStart.elasticsearch.client.asInternalUser.indices.getSettings(
+ { index: '.kibana' }
+ );
+ expect(resp1.headers).not.toHaveProperty('warning');
+
+ // Also test setting it explicitly
+ const resp2 = await kibanaServer.coreStart.elasticsearch.client.asInternalUser.indices.getSettings(
+ { index: '.kibana' },
+ { headers: { 'x-elastic-product-origin': 'kibana' } }
+ );
+ expect(resp2.headers).not.toHaveProperty('warning');
+ });
+
+ it('returns deprecation warning when x-elastic-product-orign header is not set', async () => {
+ const resp = await kibanaServer.coreStart.elasticsearch.client.asInternalUser.indices.getSettings(
+ { index: '.kibana' },
+ { headers: { 'x-elastic-product-origin': null } }
+ );
+
+ expect(resp.headers).toHaveProperty('warning');
+ expect(resp.headers!.warning).toMatch('system indices');
+ });
+});
diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts
index 6c11534df0d11..af358caae8bfc 100644
--- a/src/core/server/http/integration_tests/core_services.test.ts
+++ b/src/core/server/http/integration_tests/core_services.test.ts
@@ -540,5 +540,50 @@ describe('http service', () => {
expect(header['www-authenticate']).toEqual('Basic realm="Authorization Required"');
});
+
+ it('provides error reason for Elasticsearch Response Errors', async () => {
+ const { http } = await root.setup();
+ const { createRouter } = http;
+ // eslint-disable-next-line prefer-const
+ let elasticsearch: InternalElasticsearchServiceStart;
+
+ esClient.ping.mockImplementation(() =>
+ elasticsearchClientMock.createErrorTransportRequestPromise(
+ new ResponseError({
+ statusCode: 404,
+ body: {
+ error: {
+ type: 'error_type',
+ reason: 'error_reason',
+ },
+ },
+ warnings: [],
+ headers: {},
+ meta: {} as any,
+ })
+ )
+ );
+
+ const router = createRouter('/new-platform');
+ router.get({ path: '/', validate: false }, async (context, req, res) => {
+ try {
+ const result = await elasticsearch.client.asScoped(req).asInternalUser.ping();
+ return res.ok({
+ body: result,
+ });
+ } catch (e) {
+ return res.badRequest({
+ body: e,
+ });
+ }
+ });
+
+ const coreStart = await root.start();
+ elasticsearch = coreStart.elasticsearch;
+
+ const { body } = await kbnTestServer.request.get(root, '/new-platform/').expect(400);
+
+ expect(body.message).toEqual('[error_type]: error_reason');
+ });
});
});
diff --git a/src/core/server/http/router/response_adapter.ts b/src/core/server/http/router/response_adapter.ts
index 15c29e261c30b..32a66adc697cf 100644
--- a/src/core/server/http/router/response_adapter.ts
+++ b/src/core/server/http/router/response_adapter.ts
@@ -14,6 +14,8 @@ import typeDetect from 'type-detect';
import Boom from '@hapi/boom';
import * as stream from 'stream';
+import { isResponseError as isElasticsearchResponseError } from '../../elasticsearch/client/errors';
+
import {
HttpResponsePayload,
KibanaResponse,
@@ -147,6 +149,11 @@ function getErrorMessage(payload?: ResponseError): string {
throw new Error('expected error message to be provided');
}
if (typeof payload === 'string') return payload;
+ // for ES response errors include nested error reason message. it doesn't contain sensitive data.
+ if (isElasticsearchResponseError(payload)) {
+ return `[${payload.message}]: ${payload.meta.body?.error?.reason}`;
+ }
+
return getErrorMessage(payload.message);
}
diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js
index 37572c83e4c88..ce48e8dc9a317 100644
--- a/src/core/server/saved_objects/service/lib/repository.test.js
+++ b/src/core/server/saved_objects/service/lib/repository.test.js
@@ -23,6 +23,7 @@ import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock
import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks';
import { esKuery } from '../../es_query';
import { errors as EsErrors } from '@elastic/elasticsearch';
+
const { nodeTypes } = esKuery;
jest.mock('./search_dsl/search_dsl', () => ({ getSearchDsl: jest.fn() }));
@@ -3654,6 +3655,33 @@ describe('SavedObjectsRepository', () => {
);
});
+ it(`uses the 'upsertAttributes' option when specified`, async () => {
+ const upsertAttributes = {
+ foo: 'bar',
+ hello: 'dolly',
+ };
+ await incrementCounterSuccess(type, id, counterFields, { namespace, upsertAttributes });
+ expect(client.update).toHaveBeenCalledWith(
+ expect.objectContaining({
+ body: expect.objectContaining({
+ upsert: expect.objectContaining({
+ [type]: {
+ foo: 'bar',
+ hello: 'dolly',
+ ...counterFields.reduce((aggs, field) => {
+ return {
+ ...aggs,
+ [field]: 1,
+ };
+ }, {}),
+ },
+ }),
+ }),
+ }),
+ expect.anything()
+ );
+ });
+
it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => {
await incrementCounterSuccess(type, id, counterFields, { namespace });
expect(client.update).toHaveBeenCalledWith(
diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts
index aa1e62c1652ca..6e2a1d6ec0511 100644
--- a/src/core/server/saved_objects/service/lib/repository.ts
+++ b/src/core/server/saved_objects/service/lib/repository.ts
@@ -76,10 +76,16 @@ import {
// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
-// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
-type Left = { tag: 'Left'; error: Record };
-// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
-type Right = { tag: 'Right'; value: Record };
+interface Left {
+ tag: 'Left';
+ error: Record;
+}
+
+interface Right {
+ tag: 'Right';
+ value: Record;
+}
+
type Either = Left | Right;
const isLeft = (either: Either): either is Left => either.tag === 'Left';
const isRight = (either: Either): either is Right => either.tag === 'Right';
@@ -98,7 +104,8 @@ export interface SavedObjectsRepositoryOptions {
/**
* @public
*/
-export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions {
+export interface SavedObjectsIncrementCounterOptions
+ extends SavedObjectsBaseOptions {
/**
* (default=false) If true, sets all the counter fields to 0 if they don't
* already exist. Existing fields will be left as-is and won't be incremented.
@@ -111,6 +118,10 @@ export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOpt
* operation. See {@link MutatingOperationRefreshSetting}
*/
refresh?: MutatingOperationRefreshSetting;
+ /**
+ * Attributes to use when upserting the document if it doesn't exist.
+ */
+ upsertAttributes?: Attributes;
}
/**
@@ -1694,6 +1705,20 @@ export class SavedObjectsRepository {
* .incrementCounter('dashboard_counter_type', 'counter_id', [
* 'stats.apiCalls',
* ])
+ *
+ * // Increment the apiCalls field counter by 4
+ * repository
+ * .incrementCounter('dashboard_counter_type', 'counter_id', [
+ * { fieldName: 'stats.apiCalls' incrementBy: 4 },
+ * ])
+ *
+ * // Initialize the document with arbitrary fields if not present
+ * repository.incrementCounter<{ appId: string }>(
+ * 'dashboard_counter_type',
+ * 'counter_id',
+ * [ 'stats.apiCalls'],
+ * { upsertAttributes: { appId: 'myId' } }
+ * )
* ```
*
* @param type - The type of saved object whose fields should be incremented
@@ -1706,7 +1731,7 @@ export class SavedObjectsRepository {
type: string,
id: string,
counterFields: Array,
- options: SavedObjectsIncrementCounterOptions = {}
+ options: SavedObjectsIncrementCounterOptions = {}
): Promise> {
if (typeof type !== 'string') {
throw new Error('"type" argument must be a string');
@@ -1728,12 +1753,16 @@ export class SavedObjectsRepository {
throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type);
}
- const { migrationVersion, refresh = DEFAULT_REFRESH_SETTING, initialize = false } = options;
+ const {
+ migrationVersion,
+ refresh = DEFAULT_REFRESH_SETTING,
+ initialize = false,
+ upsertAttributes,
+ } = options;
const normalizedCounterFields = counterFields.map((counterField) => {
const fieldName = typeof counterField === 'string' ? counterField : counterField.fieldName;
const incrementBy = typeof counterField === 'string' ? 1 : counterField.incrementBy || 1;
-
return {
fieldName,
incrementBy: initialize ? 0 : incrementBy,
@@ -1757,11 +1786,14 @@ export class SavedObjectsRepository {
type,
...(savedObjectNamespace && { namespace: savedObjectNamespace }),
...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }),
- attributes: normalizedCounterFields.reduce((acc, counterField) => {
- const { fieldName, incrementBy } = counterField;
- acc[fieldName] = incrementBy;
- return acc;
- }, {} as Record),
+ attributes: {
+ ...(upsertAttributes ?? {}),
+ ...normalizedCounterFields.reduce((acc, counterField) => {
+ const { fieldName, incrementBy } = counterField;
+ acc[fieldName] = incrementBy;
+ return acc;
+ }, {} as Record),
+ },
migrationVersion,
updated_at: time,
});
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 73f8a44075162..cf1647ef5cec3 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -2735,11 +2735,12 @@ export interface SavedObjectsIncrementCounterField {
}
// @public (undocumented)
-export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions {
+export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions {
initialize?: boolean;
// (undocumented)
migrationVersion?: SavedObjectsMigrationVersion;
refresh?: MutatingOperationRefreshSetting;
+ upsertAttributes?: Attributes;
}
// @public
@@ -2839,7 +2840,7 @@ export class SavedObjectsRepository {
// (undocumented)
find(options: SavedObjectsFindOptions): Promise>;
get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>;
- incrementCounter(type: string, id: string, counterFields: Array, options?: SavedObjectsIncrementCounterOptions): Promise>;
+ incrementCounter(type: string, id: string, counterFields: Array, options?: SavedObjectsIncrementCounterOptions): Promise>;
openPointInTimeForType(type: string | string[], { keepAlive, preference }?: SavedObjectsOpenPointInTimeOptions): Promise;
removeReferencesTo(type: string, id: string, options?: SavedObjectsRemoveReferencesToOptions): Promise;
resolve(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>;
diff --git a/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts b/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts
index c67cd325572ff..96725d4405112 100644
--- a/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts
+++ b/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts
@@ -29,9 +29,6 @@ export function convertPanelStateToSavedDashboardPanel(
panelState: DashboardPanelState,
version: string
): SavedDashboardPanel {
- const customTitle: string | undefined = panelState.explicitInput.title
- ? (panelState.explicitInput.title as string)
- : undefined;
const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId;
return {
version,
@@ -39,7 +36,7 @@ export function convertPanelStateToSavedDashboardPanel(
gridData: panelState.gridData,
panelIndex: panelState.explicitInput.id,
embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']),
- ...(customTitle && { title: customTitle }),
+ ...(panelState.explicitInput.title !== undefined && { title: panelState.explicitInput.title }),
...(savedObjectId !== undefined && { id: savedObjectId }),
};
}
diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
index a50aadc12e6c0..9e3018fb512c3 100644
--- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
+++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap
@@ -597,7 +597,7 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = `
restrictWidth="500px"
>
@@ -232,12 +232,12 @@ exports[`after fetch hideWriteControls 1`] = `
restrictWidth={true}
>
@@ -379,12 +379,12 @@ exports[`after fetch initialFilter 1`] = `
restrictWidth={true}
>
@@ -525,12 +525,12 @@ exports[`after fetch renders all table rows 1`] = `
restrictWidth={true}
>
@@ -671,12 +671,12 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = `
restrictWidth={true}
>
@@ -817,12 +817,12 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
restrictWidth={true}
>
diff --git a/src/plugins/data/README.mdx b/src/plugins/data/README.mdx
index 145aaa64fa3ad..60e74a3fa126c 100644
--- a/src/plugins/data/README.mdx
+++ b/src/plugins/data/README.mdx
@@ -21,7 +21,6 @@ It is wired into the `TopNavMenu` component, but can be used independently.
### Fetch Query Suggestions
The `getQuerySuggestions` function helps to construct a query.
-KQL suggestion functions are registered in X-Pack, so this API does not return results in OSS.
```.ts
diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts
index f63d2dfec142c..a7ba8ab9576b6 100644
--- a/src/plugins/data/common/search/session/types.ts
+++ b/src/plugins/data/common/search/session/types.ts
@@ -31,6 +31,13 @@ export interface SearchSessionSavedObjectAttributes {
* Expiration time of the session. Expiration itself is managed by Elasticsearch.
*/
expires: string;
+ /**
+ * Time of transition into completed state,
+ *
+ * Can be "null" in case already completed session
+ * transitioned into in-progress session
+ */
+ completed?: string | null;
/**
* status
*/
diff --git a/src/plugins/data/public/autocomplete/autocomplete_service.ts b/src/plugins/data/public/autocomplete/autocomplete_service.ts
index 6b288c4507f06..eb9d859664c4d 100644
--- a/src/plugins/data/public/autocomplete/autocomplete_service.ts
+++ b/src/plugins/data/public/autocomplete/autocomplete_service.ts
@@ -18,6 +18,11 @@ import {
import { ConfigSchema } from '../../config';
import { UsageCollectionSetup } from '../../../usage_collection/public';
import { createUsageCollector } from './collectors';
+import {
+ KUERY_LANGUAGE_NAME,
+ setupKqlQuerySuggestionProvider,
+} from './providers/kql_query_suggestion';
+import { DataPublicPluginStart, DataStartDependencies } from '../types';
export class AutocompleteService {
autocompleteConfig: ConfigSchema['autocomplete'];
@@ -31,12 +36,6 @@ export class AutocompleteService {
private readonly querySuggestionProviders: Map = new Map();
private getValueSuggestions?: ValueSuggestionsGetFn;
- private addQuerySuggestionProvider = (language: string, provider: QuerySuggestionGetFn): void => {
- if (language && provider && this.autocompleteConfig.querySuggestions.enabled) {
- this.querySuggestionProviders.set(language, provider);
- }
- };
-
private getQuerySuggestions: QuerySuggestionGetFn = (args) => {
const { language } = args;
const provider = this.querySuggestionProviders.get(language);
@@ -50,7 +49,7 @@ export class AutocompleteService {
/** @public **/
public setup(
- core: CoreSetup,
+ core: CoreSetup,
{
timefilter,
usageCollection,
@@ -62,11 +61,15 @@ export class AutocompleteService {
? setupValueSuggestionProvider(core, { timefilter, usageCollector })
: getEmptyValueSuggestions;
- return {
- addQuerySuggestionProvider: this.addQuerySuggestionProvider,
+ if (this.autocompleteConfig.querySuggestions.enabled) {
+ this.querySuggestionProviders.set(KUERY_LANGUAGE_NAME, setupKqlQuerySuggestionProvider(core));
+ }
- /** @obsolete **/
- /** please use "getProvider" only from the start contract **/
+ return {
+ /**
+ * @deprecated
+ * please use "getQuerySuggestions" from the start contract
+ */
getQuerySuggestions: this.getQuerySuggestions,
};
}
diff --git a/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/README.md b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/README.md
new file mode 100644
index 0000000000000..2ab87a7a490c1
--- /dev/null
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/README.md
@@ -0,0 +1 @@
+This is implementation of KQL query suggestions
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json
similarity index 100%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts
similarity index 92%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts
index 5e562ae63e91b..c1c44f1f55548 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 { setupGetConjunctionSuggestions } from './conjunction';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx
similarity index 67%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx
index 7efc2ea193abe..345f9f8051e5d 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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';
@@ -16,17 +17,17 @@ import {
const bothArgumentsText = (
);
const oneOrMoreArgumentsText = (
);
@@ -34,20 +35,20 @@ const conjunctions: Record = {
and: (
{bothArgumentsText},
}}
description="Full text: ' Requires both arguments to be true'. See
- 'xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part."
+ 'data.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part."
/>
),
or: (
= {
),
}}
description="Full text: 'Requires one or more arguments to be true'. See
- 'xpack.data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part."
+ 'data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part."
/>
),
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.test.ts
similarity index 97%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.test.ts
index afc55d13af9d9..f1eced06a33ea 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.test.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 indexPatternResponse from './__fixtures__/index_pattern_response.json';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.tsx
similarity index 92%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.tsx
index ac6f7de888320..5cafca168dfa2 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/field.tsx
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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';
@@ -22,7 +23,7 @@ const getDescription = (field: IFieldType) => {
return (
{field.name} }}
/>
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/index.ts
similarity index 85%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/index.ts
index 8b36480a35b17..c5c1626ae74f6 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/index.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { CoreSetup } from 'kibana/public';
@@ -17,6 +18,7 @@ import {
QuerySuggestion,
QuerySuggestionGetFnArgs,
QuerySuggestionGetFn,
+ DataPublicPluginStart,
} from '../../../../../../../src/plugins/data/public';
const cursorSymbol = '@kuery-cursor@';
@@ -26,7 +28,9 @@ const dedup = (suggestions: QuerySuggestion[]): QuerySuggestion[] =>
export const KUERY_LANGUAGE_NAME = 'kuery';
-export const setupKqlQuerySuggestionProvider = (core: CoreSetup): QuerySuggestionGetFn => {
+export const setupKqlQuerySuggestionProvider = (
+ core: CoreSetup
+): QuerySuggestionGetFn => {
const providers = {
field: setupGetFieldSuggestions(core),
value: setupGetValueSuggestions(core),
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
similarity index 92%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
index 0173617a99b1b..933449e779ef7 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 { escapeQuotes, escapeKuery } from './escape_kuery';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
similarity index 85%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
index 901e61bde455d..54f03803a893e 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 { flow } from 'lodash';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
similarity index 95%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
index bd021b0d0dac5..4debbc0843d51 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.test.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 indexPatternResponse from './__fixtures__/index_pattern_response.json';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.tsx
similarity index 65%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.tsx
index cfe935e4b1990..618e33ddf345a 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/operator.tsx
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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';
@@ -15,44 +16,44 @@ import { QuerySuggestionTypes } from '../../../../../../../src/plugins/data/publ
const equalsText = (
);
const lessThanOrEqualToText = (
);
const greaterThanOrEqualToText = (
);
const lessThanText = (
);
const greaterThanText = (
);
const existsText = (
);
@@ -60,11 +61,11 @@ const operators = {
':': {
description: (
{equalsText} }}
description="Full text: 'equals some value'. See
- 'xpack.data.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part."
+ 'data.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part."
/>
),
fieldTypes: [
@@ -83,7 +84,7 @@ const operators = {
'<=': {
description: (
),
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
@@ -99,7 +100,7 @@ const operators = {
'>=': {
description: (
),
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
@@ -115,11 +116,11 @@ const operators = {
'<': {
description: (
{lessThanText} }}
description="Full text: 'is less than some value'. See
- 'xpack.data.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part."
+ 'data.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part."
/>
),
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
@@ -127,13 +128,13 @@ const operators = {
'>': {
description: (
{greaterThanText},
}}
description="Full text: 'is greater than some value'. See
- 'xpack.data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part."
+ 'data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part."
/>
),
fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'],
@@ -141,11 +142,11 @@ const operators = {
': *': {
description: (
{existsText} }}
description="Full text: 'exists in any form'. See
- 'xpack.data.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part."
+ 'data.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part."
/>
),
fieldTypes: undefined,
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
similarity index 92%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
index aa236a45fa93c..f72fb75684105 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 { sortPrefixFirst } from './sort_prefix_first';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
similarity index 76%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
index c344197641ef4..25bc32d47f338 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts
@@ -1,8 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 { partition } from 'lodash';
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/types.ts
similarity index 65%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/types.ts
index b5abdbee51832..48e87a73f3671 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/types.ts
@@ -1,17 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
*/
import { CoreSetup } from 'kibana/public';
import {
+ DataPublicPluginStart,
KueryNode,
QuerySuggestionBasic,
QuerySuggestionGetFnArgs,
} from '../../../../../../../src/plugins/data/public';
export type KqlQuerySuggestionProvider = (
- core: CoreSetup
+ core: CoreSetup
) => (querySuggestionsGetFnArgs: QuerySuggestionGetFnArgs, kueryNode: KueryNode) => Promise;
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.test.ts
similarity index 93%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.test.ts
index 5744dad43dcdd..c434d9a8ef365 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.test.ts
@@ -1,15 +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.
+ * 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 { setupGetValueSuggestions } from './value';
import indexPatternResponse from './__fixtures__/index_pattern_response.json';
import { coreMock } from '../../../../../../../src/core/public/mocks';
import { QuerySuggestionGetFnArgs, KueryNode } from '../../../../../../../src/plugins/data/public';
-import { setAutocompleteService } from '../../../services';
const mockKueryNode = (kueryNode: Partial) => (kueryNode as unknown) as KueryNode;
@@ -19,11 +19,6 @@ describe('Kuery value suggestions', () => {
let autocompleteServiceMock: any;
beforeEach(() => {
- getSuggestions = setupGetValueSuggestions(coreMock.createSetup());
- querySuggestionsArgs = ({
- indexPatterns: [indexPatternResponse],
- } as unknown) as QuerySuggestionGetFnArgs;
-
autocompleteServiceMock = {
getValueSuggestions: jest.fn(({ field }) => {
let res: any[];
@@ -40,7 +35,16 @@ describe('Kuery value suggestions', () => {
return Promise.resolve(res);
}),
};
- setAutocompleteService(autocompleteServiceMock);
+
+ const coreSetup = coreMock.createSetup({
+ pluginStartContract: {
+ autocomplete: autocompleteServiceMock,
+ },
+ });
+ getSuggestions = setupGetValueSuggestions(coreSetup);
+ querySuggestionsArgs = ({
+ indexPatterns: [indexPatternResponse],
+ } as unknown) as QuerySuggestionGetFnArgs;
jest.clearAllMocks();
});
diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts
similarity index 79%
rename from x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts
rename to src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts
index 92fd4d7b71bdc..f8fc9d165fc6b 100644
--- a/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts
+++ b/src/plugins/data/public/autocomplete/providers/kql_query_suggestion/value.ts
@@ -1,15 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
+ * 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 { flatten } from 'lodash';
+import { CoreSetup } from 'kibana/public';
import { escapeQuotes } from './lib/escape_kuery';
import { KqlQuerySuggestionProvider } from './types';
-import { getAutocompleteService } from '../../../services';
import {
+ DataPublicPluginStart,
IFieldType,
IIndexPattern,
QuerySuggestion,
@@ -26,7 +28,12 @@ const wrapAsSuggestions = (start: number, end: number, query: string, values: st
end,
}));
-export const setupGetValueSuggestions: KqlQuerySuggestionProvider = () => {
+export const setupGetValueSuggestions: KqlQuerySuggestionProvider = (
+ core: CoreSetup
+) => {
+ const autoCompleteServicePromise = core
+ .getStartServices()
+ .then(([_, __, dataStart]) => dataStart.autocomplete);
return async (
{ indexPatterns, boolFilter, useTimeRange, signal },
{ start, end, prefix, suffix, fieldName, nestedPath }
@@ -41,7 +48,7 @@ export const setupGetValueSuggestions: KqlQuerySuggestionProvider = () => {
});
const query = `${prefix}${suffix}`.trim();
- const { getValueSuggestions } = getAutocompleteService();
+ const { getValueSuggestions } = await autoCompleteServicePromise;
const data = await Promise.all(
indexPatternFieldEntries.map(([indexPattern, field]) =>
diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts
index a3676c5116927..573820890de71 100644
--- a/src/plugins/data/public/mocks.ts
+++ b/src/plugins/data/public/mocks.ts
@@ -17,7 +17,6 @@ export type Setup = jest.Mocked>;
export type Start = jest.Mocked>;
const automcompleteSetupMock: jest.Mocked = {
- addQuerySuggestionProvider: jest.fn(),
getQuerySuggestions: jest.fn(),
};
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 4eae5629af3a6..e4085abe14050 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -23,7 +23,7 @@ import * as CSS from 'csstype';
import { Datatable as Datatable_2 } from 'src/plugins/expressions';
import { Datatable as Datatable_3 } from 'src/plugins/expressions/common';
import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions';
-import { DatatableColumnType } from 'src/plugins/expressions/common';
+import { DatatableColumnType as DatatableColumnType_2 } from 'src/plugins/expressions/common';
import { DetailedPeerCertificate } from 'tls';
import { Ensure } from '@kbn/utility-types';
import { EnvironmentMode } from '@kbn/config';
@@ -85,8 +85,8 @@ import { RequestAdapter } from 'src/plugins/inspector/common';
import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common';
import { Required } from '@kbn/utility-types';
import * as Rx from 'rxjs';
-import { SavedObject } from 'kibana/server';
-import { SavedObject as SavedObject_2 } from 'src/core/server';
+import { SavedObject } from 'src/core/server';
+import { SavedObject as SavedObject_2 } from 'kibana/server';
import { SavedObjectReference } from 'src/core/types';
import { SavedObjectsClientContract } from 'src/core/public';
import { SavedObjectsFindOptions } from 'kibana/public';
@@ -188,7 +188,7 @@ export class AggConfig {
// @deprecated (undocumented)
toJSON(): AggConfigSerialized;
// Warning: (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts
- toSerializedFieldFormat(): {} | Ensure, SerializableState>;
+ toSerializedFieldFormat(): {} | Ensure, SerializableState_2>;
// (undocumented)
get type(): IAggType;
set type(type: IAggType);
@@ -272,9 +272,9 @@ export type AggConfigSerialized = Ensure<{
type: string;
enabled?: boolean;
id?: string;
- params?: {} | SerializableState;
+ params?: {} | SerializableState_2;
schema?: string;
-}, SerializableState>;
+}, SerializableState_2>;
// Warning: (ae-missing-release-tag) "AggFunctionsMapping" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -1604,7 +1604,7 @@ export class IndexPatternsService {
// Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts
//
// (undocumented)
- getCache: () => Promise[] | null | undefined>;
+ getCache: () => Promise[] | null | undefined>;
getDefault: () => Promise;
getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise;
// Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts
@@ -1616,7 +1616,7 @@ export class IndexPatternsService {
}>>;
getTitles: (refresh?: boolean) => Promise;
refreshFields: (indexPattern: IndexPattern) => Promise;
- savedObjectToSpec: (savedObject: SavedObject_2) => IndexPatternSpec;
+ savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec;
setDefault: (id: string, force?: boolean) => Promise;
updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean): Promise;
}
@@ -2704,7 +2704,7 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:430:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/search/session/session_service.ts:55:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/search/session/session_service.ts:56:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts
index 8ee44cb2ca4ef..18d32463864e3 100644
--- a/src/plugins/data/public/search/session/mocks.ts
+++ b/src/plugins/data/public/search/session/mocks.ts
@@ -9,7 +9,7 @@
import { BehaviorSubject } from 'rxjs';
import { ISessionsClient } from './sessions_client';
import { ISessionService } from './session_service';
-import { SearchSessionState } from './search_session_state';
+import { SearchSessionState, SessionMeta } from './search_session_state';
export function getSessionsClientMock(): jest.Mocked {
return {
@@ -31,7 +31,9 @@ export function getSessionServiceMock(): jest.Mocked {
getSessionId: jest.fn(),
getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()),
state$: new BehaviorSubject(SearchSessionState.None).asObservable(),
- searchSessionName$: new BehaviorSubject(undefined).asObservable(),
+ sessionMeta$: new BehaviorSubject({
+ state: SearchSessionState.None,
+ }).asObservable(),
renameCurrentSession: jest.fn(),
trackSearch: jest.fn((searchDescriptor) => () => {}),
destroy: jest.fn(),
diff --git a/src/plugins/data/public/search/session/search_session_state.ts b/src/plugins/data/public/search/session/search_session_state.ts
index e58e1062091bf..bf9036d361a8f 100644
--- a/src/plugins/data/public/search/session/search_session_state.ts
+++ b/src/plugins/data/public/search/session/search_session_state.ts
@@ -7,6 +7,7 @@
*/
import uuid from 'uuid';
+import deepEqual from 'fast-deep-equal';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
import { createStateContainer, StateContainer } from '../../../../kibana_utils/public';
@@ -107,9 +108,19 @@ export interface SessionStateInternal {
isCanceled: boolean;
/**
- * Start time of current session
+ * Start time of the current session (from browser perspective)
*/
startTime?: Date;
+
+ /**
+ * Time when all the searches from the current session are completed (from browser perspective)
+ */
+ completedTime?: Date;
+
+ /**
+ * Time when the session was canceled by user, by hitting "stop"
+ */
+ canceledTime?: Date;
}
const createSessionDefaultState: <
@@ -170,12 +181,15 @@ export const sessionPureTransitions: SessionPureTransitions = {
...state,
isStarted: true,
pendingSearches: state.pendingSearches.concat(search),
+ completedTime: undefined,
};
},
unTrackSearch: (state) => (search) => {
+ const pendingSearches = state.pendingSearches.filter((s) => s !== search);
return {
...state,
- pendingSearches: state.pendingSearches.filter((s) => s !== search),
+ pendingSearches,
+ completedTime: pendingSearches.length === 0 ? new Date() : state.completedTime,
};
},
cancel: (state) => () => {
@@ -185,6 +199,7 @@ export const sessionPureTransitions: SessionPureTransitions = {
...state,
pendingSearches: [],
isCanceled: true,
+ canceledTime: new Date(),
isStored: false,
searchSessionSavedObject: undefined,
};
@@ -205,11 +220,24 @@ export const sessionPureTransitions: SessionPureTransitions = {
},
};
+/**
+ * Consolidate meta info about current seach session
+ * Contains both deferred properties and plain properties from state
+ */
+export interface SessionMeta {
+ state: SearchSessionState;
+ name?: string;
+ startTime?: Date;
+ canceledTime?: Date;
+ completedTime?: Date;
+}
+
export interface SessionPureSelectors<
SearchDescriptor = unknown,
S = SessionStateInternal
> {
getState: (state: S) => () => SearchSessionState;
+ getMeta: (state: S) => () => SessionMeta;
}
export const sessionPureSelectors: SessionPureSelectors = {
@@ -233,6 +261,21 @@ export const sessionPureSelectors: SessionPureSelectors = {
}
return SearchSessionState.None;
},
+ getMeta(state) {
+ const sessionState = this.getState(state)();
+
+ return () => ({
+ state: sessionState,
+ name: state.searchSessionSavedObject?.attributes.name,
+ startTime: state.searchSessionSavedObject?.attributes.created
+ ? new Date(state.searchSessionSavedObject?.attributes.created)
+ : state.startTime,
+ completedTime: state.searchSessionSavedObject?.attributes.completed
+ ? new Date(state.searchSessionSavedObject?.attributes.completed)
+ : state.completedTime,
+ canceledTime: state.canceledTime,
+ });
+ },
};
export type SessionStateContainer = StateContainer<
@@ -246,9 +289,7 @@ export const createSessionStateContainer = (
): {
stateContainer: SessionStateContainer;
sessionState$: Observable;
- sessionStartTime$: Observable;
- searchSessionSavedObject$: Observable;
- searchSessionName$: Observable;
+ sessionMeta$: Observable;
} => {
const stateContainer = createStateContainer(
createSessionDefaultState(),
@@ -257,33 +298,20 @@ export const createSessionStateContainer = (
freeze ? undefined : { freeze: (s) => s }
) as SessionStateContainer;
- const sessionState$: Observable = stateContainer.state$.pipe(
- map(() => stateContainer.selectors.getState()),
- distinctUntilChanged(),
- shareReplay(1)
- );
-
- const sessionStartTime$: Observable = stateContainer.state$.pipe(
- map(() => stateContainer.get().startTime),
- distinctUntilChanged(),
- shareReplay(1)
- );
-
- const searchSessionSavedObject$ = stateContainer.state$.pipe(
- map(() => stateContainer.get().searchSessionSavedObject),
- distinctUntilChanged(),
+ const sessionMeta$: Observable = stateContainer.state$.pipe(
+ map(() => stateContainer.selectors.getMeta()),
+ distinctUntilChanged(deepEqual),
shareReplay(1)
);
- const searchSessionName$ = searchSessionSavedObject$.pipe(
- map((savedObject) => savedObject?.attributes?.name)
+ const sessionState$: Observable = sessionMeta$.pipe(
+ map((meta) => meta.state),
+ distinctUntilChanged()
);
return {
stateContainer,
sessionState$,
- sessionStartTime$,
- searchSessionSavedObject$,
- searchSessionName$,
+ sessionMeta$,
};
};
diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts
index 785b9357fc895..381410574ecda 100644
--- a/src/plugins/data/public/search/session/session_service.ts
+++ b/src/plugins/data/public/search/session/session_service.ts
@@ -20,6 +20,7 @@ import { ConfigSchema } from '../../../config';
import {
createSessionStateContainer,
SearchSessionState,
+ SessionMeta,
SessionStateContainer,
} from './search_session_state';
import { ISessionsClient } from './sessions_client';
@@ -78,7 +79,7 @@ export class SessionService {
public readonly state$: Observable;
private readonly state: SessionStateContainer;
- public readonly searchSessionName$: Observable;
+ public readonly sessionMeta$: Observable;
private searchSessionInfoProvider?: SearchSessionInfoProvider;
private searchSessionIndicatorUiConfig?: Partial;
private subscription = new Subscription();
@@ -97,20 +98,24 @@ export class SessionService {
const {
stateContainer,
sessionState$,
- sessionStartTime$,
- searchSessionName$,
+ sessionMeta$,
} = createSessionStateContainer({
freeze: freezeState,
});
this.state$ = sessionState$;
this.state = stateContainer;
- this.searchSessionName$ = searchSessionName$;
+ this.sessionMeta$ = sessionMeta$;
this.subscription.add(
- sessionStartTime$.subscribe((startTime) => {
- if (startTime) this.nowProvider.set(startTime);
- else this.nowProvider.reset();
- })
+ sessionMeta$
+ .pipe(
+ map((meta) => meta.startTime),
+ distinctUntilChanged()
+ )
+ .subscribe((startTime) => {
+ if (startTime) this.nowProvider.set(startTime);
+ else this.nowProvider.reset();
+ })
);
getStartServices().then(([coreStart]) => {
diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
index a7d1471af3a77..837cff41ccd6b 100644
--- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
+++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap
@@ -664,96 +664,87 @@ exports[`Inspector Data View component should render single table without select
hasArrow={true}
id="inspectorDownloadData"
isOpen={false}
- ownFocus={false}
+ ownFocus={true}
panelPaddingSize="none"
repositionOnScroll={true}
>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
- Download CSV
-
-
+ Download CSV
+
-
-
-
-
-
+
+
+
+
+
-
+
@@ -1304,81 +1295,72 @@ exports[`Inspector Data View component should render single table without select
display="inlineBlock"
hasArrow={true}
isOpen={false}
- ownFocus={false}
+ ownFocus={true}
panelPaddingSize="none"
>
-
-
-
-
-
+
-
-
-
-
-
+
+
+
-
- Rows per page
-
- :
- 20
-
+ Rows per page
+
+ :
+ 20
-
-
-
-
+
+
+
+
-
+
@@ -1420,7 +1402,7 @@ exports[`Inspector Data View component should render single table without select
>
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
- Table 1
-
-
+ }
+ >
+ Table 1
+
-
-
-
-
+
+
+
+
-
+
@@ -2220,96 +2193,87 @@ exports[`Inspector Data View component should support multiple datatables 1`] =
hasArrow={true}
id="inspectorDownloadData"
isOpen={false}
- ownFocus={false}
+ ownFocus={true}
panelPaddingSize="none"
repositionOnScroll={true}
>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
- Download CSV
-
-
+ Download CSV
+
-
-
-
-
-
+
+
+
+
+
-
+
@@ -2885,81 +2849,72 @@ exports[`Inspector Data View component should support multiple datatables 1`] =
display="inlineBlock"
hasArrow={true}
isOpen={false}
- ownFocus={false}
+ ownFocus={true}
panelPaddingSize="none"
>
-
-
-
-
-
+
-
-
-
-
-
+
+
+
-
- Rows per page
-
- :
- 20
-
+ Rows per page
+
+ :
+ 20
-
-
-
-
+
+
+
+
-
+
@@ -3001,7 +2956,7 @@ exports[`Inspector Data View component should support multiple datatables 1`] =
>
diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
index 138122d80c765..1be42e1cd6b17 100644
--- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
+++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx
@@ -7,7 +7,8 @@
*/
import './discover_sidebar.scss';
-import React, { useCallback, useEffect, useState, useMemo } from 'react';
+import { throttle } from 'lodash';
+import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiAccordion,
@@ -18,7 +19,9 @@ import {
EuiSpacer,
EuiNotificationBadge,
EuiPageSideBar,
+ useResizeObserver,
} from '@elastic/eui';
+
import { isEqual, sortBy } from 'lodash';
import { FormattedMessage } from '@kbn/i18n/react';
import { DiscoverField } from './discover_field';
@@ -32,6 +35,11 @@ import { FieldFilterState, getDefaultFieldFilter, setFieldFilterProp } from './l
import { getIndexPatternFieldList } from './lib/get_index_pattern_field_list';
import { DiscoverSidebarResponsiveProps } from './discover_sidebar_responsive';
+/**
+ * Default number of available fields displayed and added on scroll
+ */
+const FIELDS_PER_PAGE = 50;
+
export interface DiscoverSidebarProps extends DiscoverSidebarResponsiveProps {
/**
* Current state of the field filter, filtering fields by name, type, ...
@@ -66,18 +74,25 @@ export function DiscoverSidebar({
unmappedFieldsConfig,
}: DiscoverSidebarProps) {
const [fields, setFields] = useState(null);
+ const [scrollContainer, setScrollContainer] = useState(null);
+ const [fieldsToRender, setFieldsToRender] = useState(FIELDS_PER_PAGE);
+ const [fieldsPerPage, setFieldsPerPage] = useState(FIELDS_PER_PAGE);
+ const availableFieldsContainer = useRef(null);
useEffect(() => {
const newFields = getIndexPatternFieldList(selectedIndexPattern, fieldCounts);
setFields(newFields);
}, [selectedIndexPattern, fieldCounts, hits]);
+ const scrollDimensions = useResizeObserver(scrollContainer);
+
const onChangeFieldSearch = useCallback(
(field: string, value: string | boolean | undefined) => {
const newState = setFieldFilterProp(fieldFilter, field, value);
setFieldFilter(newState);
+ setFieldsToRender(fieldsPerPage);
},
- [fieldFilter, setFieldFilter]
+ [fieldFilter, setFieldFilter, setFieldsToRender, fieldsPerPage]
);
const getDetailsByField = useCallback(
@@ -85,7 +100,10 @@ export function DiscoverSidebar({
[hits, columns, selectedIndexPattern]
);
- const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING);
+ const popularLimit = useMemo(() => services.uiSettings.get(FIELDS_LIMIT_SETTING), [
+ services.uiSettings,
+ ]);
+
const {
selected: selectedFields,
popular: popularFields,
@@ -112,6 +130,50 @@ export function DiscoverSidebar({
]
);
+ const paginate = useCallback(() => {
+ const newFieldsToRender = fieldsToRender + Math.round(fieldsPerPage * 0.5);
+ setFieldsToRender(Math.max(fieldsPerPage, Math.min(newFieldsToRender, unpopularFields.length)));
+ }, [setFieldsToRender, fieldsToRender, unpopularFields, fieldsPerPage]);
+
+ useEffect(() => {
+ if (scrollContainer && unpopularFields.length && availableFieldsContainer.current) {
+ const { clientHeight, scrollHeight } = scrollContainer;
+ const isScrollable = scrollHeight > clientHeight; // there is no scrolling currently
+ const allFieldsRendered = fieldsToRender >= unpopularFields.length;
+
+ if (!isScrollable && !allFieldsRendered) {
+ // Not all available fields were rendered with the given fieldsPerPage number
+ // and no scrolling is available due to the a high zoom out factor of the browser
+ // In this case the fieldsPerPage needs to be adapted
+ const fieldsRenderedHeight = availableFieldsContainer.current.clientHeight;
+ const avgHeightPerItem = Math.round(fieldsRenderedHeight / fieldsToRender);
+ const newFieldsPerPage = Math.round(clientHeight / avgHeightPerItem) + 10;
+ if (newFieldsPerPage >= FIELDS_PER_PAGE && newFieldsPerPage !== fieldsPerPage) {
+ setFieldsPerPage(newFieldsPerPage);
+ setFieldsToRender(newFieldsPerPage);
+ }
+ }
+ }
+ }, [
+ fieldsPerPage,
+ scrollContainer,
+ unpopularFields,
+ fieldsToRender,
+ setFieldsPerPage,
+ setFieldsToRender,
+ scrollDimensions,
+ ]);
+
+ const lazyScroll = useCallback(() => {
+ if (scrollContainer) {
+ const { scrollTop, clientHeight, scrollHeight } = scrollContainer;
+ const nearBottom = scrollTop + clientHeight > scrollHeight * 0.9;
+ if (nearBottom && unpopularFields) {
+ paginate();
+ }
+ }
+ }, [paginate, scrollContainer, unpopularFields]);
+
const fieldTypes = useMemo(() => {
const result = ['any'];
if (Array.isArray(fields)) {
@@ -145,12 +207,19 @@ export function DiscoverSidebar({
return map;
}, [fields, useNewFieldsApi, selectedFields]);
+ const getPaginated = useCallback(
+ (list) => {
+ return list.slice(0, fieldsToRender);
+ },
+ [fieldsToRender]
+ );
+
+ const filterChanged = useMemo(() => isEqual(fieldFilter, getDefaultFieldFilter()), [fieldFilter]);
+
if (!selectedIndexPattern || !fields) {
return null;
}
- const filterChanged = isEqual(fieldFilter, getDefaultFieldFilter());
-
if (useFlyout) {
return (
-
+
{
+ if (el && !el.dataset.dynamicScroll) {
+ el.dataset.dynamicScroll = 'true';
+ setScrollContainer(el);
+ }
+ }}
+ onScroll={throttle(lazyScroll, 100)}
+ className="eui-yScroll"
+ >
{fields.length > 0 && (
- <>
+
{selectedFields &&
selectedFields.length > 0 &&
selectedFields[0].displayName !== '_source' ? (
@@ -241,11 +319,7 @@ export function DiscoverSidebar({
>
{selectedFields.map((field: IndexPatternField) => {
return (
-
+
{popularFields.map((field: IndexPatternField) => {
return (
-
+
- {unpopularFields.map((field: IndexPatternField) => {
+ {getPaginated(unpopularFields).map((field: IndexPatternField) => {
return (
-
+
- >
+
)}
diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
index 18eccb4e87090..c57333d788ef5 100644
--- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
+++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
@@ -6,6 +6,8 @@
* Side Public License, v 1.
*/
+// eslint-disable-next-line import/no-extraneous-dependencies
export { registerTestBed, TestBed } from '@kbn/test/jest';
+// eslint-disable-next-line import/no-extraneous-dependencies
export { getRandomString } from '@kbn/test/jest';
diff --git a/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap
index d38c77faab7f8..6855e3a327c77 100644
--- a/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap
+++ b/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap
@@ -19,7 +19,7 @@ exports[`should render popover when appLinks is not empty 1`] = `
hasArrow={true}
id="sampleDataLinksecommerce"
isOpen={false}
- ownFocus={false}
+ ownFocus={true}
panelPaddingSize="none"
>
diff --git a/src/plugins/home/public/services/environment/index.ts b/src/plugins/home/public/services/environment/index.ts
index ab5297ed0e14c..6cbfffa1275e2 100644
--- a/src/plugins/home/public/services/environment/index.ts
+++ b/src/plugins/home/public/services/environment/index.ts
@@ -6,4 +6,5 @@
* Side Public License, v 1.
*/
-export { EnvironmentService, Environment, EnvironmentServiceSetup } from './environment';
+export { EnvironmentService } from './environment';
+export type { Environment, EnvironmentServiceSetup } from './environment';
diff --git a/src/plugins/home/public/services/feature_catalogue/index.ts b/src/plugins/home/public/services/feature_catalogue/index.ts
index fe1090e15a623..c89f26c8f8949 100644
--- a/src/plugins/home/public/services/feature_catalogue/index.ts
+++ b/src/plugins/home/public/services/feature_catalogue/index.ts
@@ -6,10 +6,10 @@
* Side Public License, v 1.
*/
-export {
- FeatureCatalogueCategory,
+export { FeatureCatalogueCategory, FeatureCatalogueRegistry } from './feature_catalogue_registry';
+
+export type {
FeatureCatalogueEntry,
FeatureCatalogueSolution,
- FeatureCatalogueRegistry,
FeatureCatalogueRegistrySetup,
} from './feature_catalogue_registry';
diff --git a/src/plugins/home/public/services/index.ts b/src/plugins/home/public/services/index.ts
index 29f3dee61537e..8cd4c8d84e0f7 100644
--- a/src/plugins/home/public/services/index.ts
+++ b/src/plugins/home/public/services/index.ts
@@ -6,6 +6,23 @@
* Side Public License, v 1.
*/
-export * from './feature_catalogue';
-export * from './environment';
-export * from './tutorials';
+export { FeatureCatalogueCategory, FeatureCatalogueRegistry } from './feature_catalogue';
+
+export type {
+ FeatureCatalogueEntry,
+ FeatureCatalogueSolution,
+ FeatureCatalogueRegistrySetup,
+} from './feature_catalogue';
+
+export { EnvironmentService } from './environment';
+export type { Environment, EnvironmentServiceSetup } from './environment';
+
+export { TutorialService } from './tutorials';
+
+export type {
+ TutorialVariables,
+ TutorialServiceSetup,
+ TutorialDirectoryNoticeComponent,
+ TutorialDirectoryHeaderLinkComponent,
+ TutorialModuleNoticeComponent,
+} from './tutorials';
diff --git a/src/plugins/home/public/services/tutorials/index.ts b/src/plugins/home/public/services/tutorials/index.ts
index cbfa95c753f4d..8de12c31249d8 100644
--- a/src/plugins/home/public/services/tutorials/index.ts
+++ b/src/plugins/home/public/services/tutorials/index.ts
@@ -6,8 +6,9 @@
* Side Public License, v 1.
*/
-export {
- TutorialService,
+export { TutorialService } from './tutorial_service';
+
+export type {
TutorialVariables,
TutorialServiceSetup,
TutorialDirectoryNoticeComponent,
diff --git a/src/plugins/home/server/index.ts b/src/plugins/home/server/index.ts
index fa150b2da8e5d..840a5944a1343 100644
--- a/src/plugins/home/server/index.ts
+++ b/src/plugins/home/server/index.ts
@@ -6,9 +6,9 @@
* Side Public License, v 1.
*/
-export { HomeServerPluginSetup, HomeServerPluginStart } from './plugin';
-export { TutorialProvider } from './services';
-export { SampleDatasetProvider, SampleDataRegistrySetup } from './services';
+export type { HomeServerPluginSetup, HomeServerPluginStart } from './plugin';
+export type { TutorialProvider } from './services';
+export type { SampleDatasetProvider, SampleDataRegistrySetup } from './services';
import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server';
import { HomeServerPlugin } from './plugin';
import { configSchema, ConfigSchema } from '../config';
@@ -26,4 +26,5 @@ export const config: PluginConfigDescriptor = {
export const plugin = (initContext: PluginInitializerContext) => new HomeServerPlugin(initContext);
export { INSTRUCTION_VARIANT } from '../common/instruction_variant';
-export { ArtifactsSchema, TutorialsCategory } from './services/tutorials';
+export { TutorialsCategory } from './services/tutorials';
+export type { ArtifactsSchema } from './services/tutorials';
diff --git a/src/plugins/home/server/services/index.ts b/src/plugins/home/server/services/index.ts
index 8f41ca0aae00c..7f26c886ab4b6 100644
--- a/src/plugins/home/server/services/index.ts
+++ b/src/plugins/home/server/services/index.ts
@@ -9,9 +9,12 @@
// provided to other plugins as APIs
// should model the plugin lifecycle
-export { TutorialsRegistry, TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials';
-export {
- TutorialsCategory,
+export { TutorialsRegistry } from './tutorials';
+export type { TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials';
+
+export { TutorialsCategory } from './tutorials';
+
+export type {
ParamTypes,
InstructionSetSchema,
ParamsSchema,
@@ -24,10 +27,8 @@ export {
ScopedTutorialContextFactory,
} from './tutorials';
-export {
- SampleDataRegistry,
- SampleDataRegistrySetup,
- SampleDataRegistryStart,
-} from './sample_data';
+export { SampleDataRegistry } from './sample_data';
+
+export type { SampleDataRegistrySetup, SampleDataRegistryStart } from './sample_data';
-export { SampleDatasetSchema, SampleDatasetProvider } from './sample_data';
+export type { SampleDatasetSchema, SampleDatasetProvider } from './sample_data';
diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
index 9b69dacd8fdb5..cfac42b97c686 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts
@@ -109,7 +109,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[eCommerce] Promotion Tracking',
}),
visState:
- '{"title":"[eCommerce] Promotion Tracking","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"ea20ae70-b88d-11e8-a451-f37365e9f268","color":"rgba(240,138,217,1)","split_mode":"everything","metrics":[{"id":"ea20ae71-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*trouser*","label":"Revenue Trousers","value_template":"${{value}}"},{"id":"062d77b0-b88e-11e8-a451-f37365e9f268","color":"rgba(191,240,129,1)","split_mode":"everything","metrics":[{"id":"062d77b1-b88e-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*watch*","label":"Revenue Watches","value_template":"${{value}}"},{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(23,233,230,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*bag*","label":"Revenue Bags","value_template":"${{value}}"},{"id":"faa2c170-b88d-11e8-a451-f37365e9f268","color":"rgba(235,186,180,1)","split_mode":"everything","metrics":[{"id":"faa2c171-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*cocktail dress*","label":"Revenue Cocktail Dresses","value_template":"${{value}}"}],"time_field":"order_date","index_pattern":"kibana_sample_data_ecommerce","interval":">=12h","axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"legend_position":"bottom","annotations":[{"fields":"taxful_total_price","template":"Ring the bell! ${{taxful_total_price}}","index_pattern":"kibana_sample_data_ecommerce","query_string":"taxful_total_price:>250","id":"c8c30be0-b88f-11e8-a451-f37365e9f268","color":"rgba(25,77,51,1)","time_field":"order_date","icon":"fa-bell","ignore_global_filters":1,"ignore_panel_filters":1}]},"aggs":[]}',
+ '{"title":"[eCommerce] Promotion Tracking","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"ea20ae70-b88d-11e8-a451-f37365e9f268","color":"rgba(240,138,217,1)","split_mode":"everything","metrics":[{"id":"ea20ae71-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*trouser*","label":"Revenue Trousers","value_template":"${{value}}"},{"id":"062d77b0-b88e-11e8-a451-f37365e9f268","color":"rgba(191,240,129,1)","split_mode":"everything","metrics":[{"id":"062d77b1-b88e-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*watch*","label":"Revenue Watches","value_template":"${{value}}"},{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(23,233,230,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*bag*","label":"Revenue Bags","value_template":"${{value}}"},{"id":"faa2c170-b88d-11e8-a451-f37365e9f268","color":"rgba(235,186,180,1)","split_mode":"everything","metrics":[{"id":"faa2c171-b88d-11e8-a451-f37365e9f268","type":"sum","field":"taxful_total_price"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":"0.7","stacked":"none","filter":"products.product_name:*cocktail dress*","label":"Revenue Cocktail Dresses","value_template":"${{value}}"}],"time_field":"order_date","index_pattern_ref_name":"ref_1_index_pattern","interval":">=12h","use_kibana_indexes":true,"axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"legend_position":"bottom","annotations":[{"fields":"taxful_total_price","template":"Ring the bell! ${{taxful_total_price}}","index_pattern_ref_name":"ref_2_index_pattern","query_string":"taxful_total_price:>250","id":"c8c30be0-b88f-11e8-a451-f37365e9f268","color":"rgba(25,77,51,1)","time_field":"order_date","icon":"fa-bell","ignore_global_filters":1,"ignore_panel_filters":1}]},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -117,7 +117,18 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
+ },
+ {
+ name: 'ref_2_index_pattern',
+ type: 'index_pattern',
+ id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
+ },
+ ],
},
{
id: '10f1a240-b891-11e8-a6d9-e546fe2bba5f',
@@ -152,7 +163,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[eCommerce] Sold Products per Day',
}),
visState:
- '{"title":"[eCommerce] Sold Products per Day","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"gauge","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"#68BC00","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Trxns / day"}],"time_field":"order_date","index_pattern":"kibana_sample_data_ecommerce","interval":"1d","axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"gauge_color_rules":[{"value":150,"id":"6da070c0-b891-11e8-b645-195edeb9de84","gauge":"rgba(104,188,0,1)","operator":"gte"},{"value":150,"id":"9b0cdbc0-b891-11e8-b645-195edeb9de84","gauge":"rgba(244,78,59,1)","operator":"lt"}],"gauge_width":"15","gauge_inner_width":10,"gauge_style":"half","filter":"","gauge_max":"300"},"aggs":[]}',
+ '{"title":"[eCommerce] Sold Products per Day","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"gauge","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"#68BC00","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count"}],"separate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Trxns / day"}],"time_field":"order_date","index_pattern_ref_name":"ref_1_index_pattern","interval":"1d","axis_position":"left","axis_formatter":"number","axis_scale":"normal","show_legend":1,"show_grid":1,"gauge_color_rules":[{"value":150,"id":"6da070c0-b891-11e8-b645-195edeb9de84","gauge":"rgba(104,188,0,1)","operator":"gte"},{"value":150,"id":"9b0cdbc0-b891-11e8-b645-195edeb9de84","gauge":"rgba(244,78,59,1)","operator":"lt"}],"gauge_width":"15","gauge_inner_width":10,"gauge_style":"half","filter":"","gauge_max":"300","use_kibana_indexes":true},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -160,7 +171,13 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
+ },
+ ],
},
{
id: '4b3ec120-b892-11e8-a6d9-e546fe2bba5f',
diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
index b316835029d7c..f16c1c7104417 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
@@ -144,7 +144,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Flights] Delays & Cancellations',
}),
visState:
- '{"title":"[Flights] Delays & Cancellations","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(0,156,224,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"filter_ratio","numerator":"FlightDelay:true"}],"separate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":0.5,"stacked":"none","label":"Percent Delays"}],"time_field":"timestamp","index_pattern":"kibana_sample_data_flights","interval":">=1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"FlightDelay,Cancelled,Carrier","template":"{{Carrier}}: Flight Delayed and Cancelled!","index_pattern":"kibana_sample_data_flights","query_string":"FlightDelay:true AND Cancelled:true","id":"53b7dff0-4c89-11e8-a66a-6989ad5a0a39","color":"rgba(0,98,177,1)","time_field":"timestamp","icon":"fa-exclamation-triangle","ignore_global_filters":1,"ignore_panel_filters":1}],"legend_position":"bottom"},"aggs":[]}',
+ '{"title":"[Flights] Delays & Cancellations","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(0,156,224,1)","split_mode":"everything","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"filter_ratio","numerator":"FlightDelay:true"}],"separate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":0.5,"stacked":"none","label":"Percent Delays"}],"time_field":"timestamp","index_pattern_ref_name":"ref_1_index_pattern","interval":">=1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"FlightDelay,Cancelled,Carrier","template":"{{Carrier}}: Flight Delayed and Cancelled!","index_pattern_ref_name":"ref_2_index_pattern","query_string":"FlightDelay:true AND Cancelled:true","id":"53b7dff0-4c89-11e8-a66a-6989ad5a0a39","color":"rgba(0,98,177,1)","time_field":"timestamp","icon":"fa-exclamation-triangle","ignore_global_filters":1,"ignore_panel_filters":1}],"legend_position":"bottom","use_kibana_indexes":true},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -152,7 +152,18 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d'
+ },
+ {
+ name: 'ref_2_index_pattern',
+ type: 'index_pattern',
+ id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d'
+ }
+ ]
},
{
id: '9886b410-4c8b-11e8-b3d7-01146121b73d',
diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts
index 0396cb58d3692..8a3469fe4f3c0 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts
@@ -89,7 +89,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Logs] Host, Visits and Bytes Table',
}),
visState:
- '{"title":"[Logs] Host, Visits and Bytes Table","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"table","series":[{"id":"bd09d600-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum","field":"bytes"},{"sigma":"","id":"c9514c90-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum_bucket","field":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Total)"},{"id":"b7672c30-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"b7672c31-a6df-11e8-8b18-1da1dfc50975","type":"sum","field":"bytes"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Last Hour)"},{"id":"f2c20700-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"f2c20701-a6df-11e8-8b18-1da1dfc50975","type":"cardinality","field":"ip"},{"sigma":"","id":"f46333e0-a6df-11e8-8b18-1da1dfc50975","type":"sum_bucket","field":"f2c20701-a6df-11e8-8b18-1da1dfc50975"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Total)","color_rules":[{"value":1000,"id":"2e963080-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":1000,"id":"3d4fb880-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":1500,"id":"435f8a20-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1},{"id":"46fd7fc0-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"46fd7fc1-e5b1-11e7-bfc2-a1f7e71965a1","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Last Hour)","color_rules":[{"value":10,"id":"4e90aeb0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":10,"id":"6d59b1c0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":25,"id":"77578670-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1}],"time_field":"timestamp","index_pattern":"kibana_sample_data_logs","interval":"1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"bar_color_rules":[{"id":"e9b4e490-e1c6-11e7-b4f6-0f68c45f7387"}],"pivot_id":"extension.keyword","pivot_label":"Type","drilldown_url":"","axis_scale":"normal"},"aggs":[]}',
+ '{"title":"[Logs] Host, Visits and Bytes Table","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"table","series":[{"id":"bd09d600-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum","field":"bytes"},{"sigma":"","id":"c9514c90-e5b1-11e7-bfc2-a1f7e71965a1","type":"sum_bucket","field":"bd09d601-e5b1-11e7-bfc2-a1f7e71965a1"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Total)"},{"id":"b7672c30-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"b7672c31-a6df-11e8-8b18-1da1dfc50975","type":"sum","field":"bytes"}],"seperate_axis":0,"axis_position":"right","formatter":"bytes","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","color_rules":[{"id":"c0c668d0-e5b1-11e7-bfc2-a1f7e71965a1"}],"label":"Bytes (Last Hour)"},{"id":"f2c20700-a6df-11e8-8b18-1da1dfc50975","color":"#68BC00","split_mode":"everything","metrics":[{"id":"f2c20701-a6df-11e8-8b18-1da1dfc50975","type":"cardinality","field":"ip"},{"sigma":"","id":"f46333e0-a6df-11e8-8b18-1da1dfc50975","type":"sum_bucket","field":"f2c20701-a6df-11e8-8b18-1da1dfc50975"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Total)","color_rules":[{"value":1000,"id":"2e963080-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":1000,"id":"3d4fb880-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":1500,"id":"435f8a20-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1},{"id":"46fd7fc0-e5b1-11e7-bfc2-a1f7e71965a1","color":"#68BC00","split_mode":"everything","metrics":[{"id":"46fd7fc1-e5b1-11e7-bfc2-a1f7e71965a1","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"number","chart_type":"line","line_width":1,"point_size":1,"fill":0.5,"stacked":"none","label":"Unique Visits (Last Hour)","color_rules":[{"value":10,"id":"4e90aeb0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(211,49,21,1)","operator":"lt"},{"value":10,"id":"6d59b1c0-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(252,196,0,1)","operator":"gte"},{"value":25,"id":"77578670-a6e0-11e8-8b18-1da1dfc50975","text":"rgba(104,188,0,1)","operator":"gte"}],"offset_time":"","value_template":"","trend_arrows":1}],"time_field":"timestamp","index_pattern_ref_name":"ref_1_index_pattern","use_kibana_indexes": true,"interval":"1h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"bar_color_rules":[{"id":"e9b4e490-e1c6-11e7-b4f6-0f68c45f7387"}],"pivot_id":"extension.keyword","pivot_label":"Type","drilldown_url":"","axis_scale":"normal"},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -97,7 +97,13 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ },
+ ],
},
{
id: '69a34b00-9ee8-11e7-8711-e7a007dcef99',
@@ -175,7 +181,7 @@ export const getSavedObjects = (): SavedObject[] => [
defaultMessage: '[Logs] Response Codes Over Time + Annotations',
}),
visState:
- '{"title":"[Logs] Response Codes Over Time + Annotations","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(115,216,255,1)","split_mode":"terms","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":"0.5","stacked":"percent","terms_field":"response.keyword","terms_order_by":"61ca57f2-469d-11e7-af02-69e470af7417","label":"Response Code Count","split_color_mode":"gradient"}],"time_field":"timestamp","index_pattern":"kibana_sample_data_logs","interval":">=4h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"geo.src, host","template":"Security Error from {{geo.src}} on {{host}}","index_pattern":"kibana_sample_data_logs","query_string":"tags:error AND tags:security","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","color":"rgba(211,49,21,1)","time_field":"timestamp","icon":"fa-asterisk","ignore_global_filters":1,"ignore_panel_filters":1}],"legend_position":"bottom","axis_scale":"normal","drop_last_bucket":0},"aggs":[]}',
+ '{"title":"[Logs] Response Codes Over Time + Annotations","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417","type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(115,216,255,1)","split_mode":"terms","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"cardinality","field":"ip"}],"seperate_axis":0,"axis_position":"right","formatter":"percent","chart_type":"line","line_width":"2","point_size":"0","fill":"0.5","stacked":"percent","terms_field":"response.keyword","terms_order_by":"61ca57f2-469d-11e7-af02-69e470af7417","label":"Response Code Count","split_color_mode":"gradient"}],"time_field":"timestamp","index_pattern_ref_name":"ref_1_index_pattern","use_kibana_indexes":true,"interval":">=4h","axis_position":"left","axis_formatter":"number","show_legend":1,"show_grid":1,"annotations":[{"fields":"geo.src, host","template":"Security Error from {{geo.src}} on {{host}}","index_pattern_ref_name":"ref_2_index_pattern","query_string":"tags:error AND tags:security","id":"bd7548a0-2223-11e8-832f-d5027f3c8a47","color":"rgba(211,49,21,1)","time_field":"timestamp","icon":"fa-asterisk","ignore_global_filters":1,"ignore_panel_filters":1}],"legend_position":"bottom","axis_scale":"normal","drop_last_bucket":0},"aggs":[]}',
uiStateJSON: '{}',
description: '',
version: 1,
@@ -183,7 +189,18 @@ export const getSavedObjects = (): SavedObject[] => [
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
},
- references: [],
+ references: [
+ {
+ name: 'ref_1_index_pattern',
+ type: 'index_pattern',
+ id: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ },
+ {
+ name: 'ref_2_index_pattern',
+ type: 'index_pattern',
+ id: '90943e30-9a47-11e8-b64d-95841ca0b247',
+ },
+ ],
},
{
id: '24a3e970-4257-11e8-b3aa-73fdaf54bfc9',
diff --git a/src/plugins/home/server/services/sample_data/index.ts b/src/plugins/home/server/services/sample_data/index.ts
index 8a504291a7262..30384dad8951d 100644
--- a/src/plugins/home/server/services/sample_data/index.ts
+++ b/src/plugins/home/server/services/sample_data/index.ts
@@ -6,10 +6,11 @@
* Side Public License, v 1.
*/
-export {
- SampleDataRegistry,
- SampleDataRegistrySetup,
- SampleDataRegistryStart,
-} from './sample_data_registry';
+export { SampleDataRegistry } from './sample_data_registry';
-export { SampleDatasetSchema, SampleDatasetProvider } from './lib/sample_dataset_registry_types';
+export type { SampleDataRegistrySetup, SampleDataRegistryStart } from './sample_data_registry';
+
+export type {
+ SampleDatasetSchema,
+ SampleDatasetProvider,
+} from './lib/sample_dataset_registry_types';
diff --git a/src/plugins/home/server/services/tutorials/index.ts b/src/plugins/home/server/services/tutorials/index.ts
index 89ac528eab06f..92f6de716185d 100644
--- a/src/plugins/home/server/services/tutorials/index.ts
+++ b/src/plugins/home/server/services/tutorials/index.ts
@@ -6,13 +6,12 @@
* Side Public License, v 1.
*/
-export {
- TutorialsRegistry,
- TutorialsRegistrySetup,
- TutorialsRegistryStart,
-} from './tutorials_registry';
-export {
- TutorialsCategory,
+export { TutorialsRegistry } from './tutorials_registry';
+export type { TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials_registry';
+
+export { TutorialsCategory } from './lib/tutorials_registry_types';
+
+export type {
ParamTypes,
InstructionSetSchema,
ParamsSchema,
diff --git a/src/plugins/home/tsconfig.json b/src/plugins/home/tsconfig.json
index b2613eeecdfb0..b15e1fc011b92 100644
--- a/src/plugins/home/tsconfig.json
+++ b/src/plugins/home/tsconfig.json
@@ -5,7 +5,8 @@
"outDir": "./target/types",
"emitDeclarationOnly": true,
"declaration": true,
- "declarationMap": true
+ "declarationMap": true,
+ "isolatedModules": true
},
"include": [
"common/**/*",
diff --git a/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx b/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx
index fca3eaf10b1ef..73a4837d6e0cc 100644
--- a/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx
+++ b/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx
@@ -6,12 +6,13 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiConfirmModal } from '@elastic/eui';
+import { EuiCallOut, EuiConfirmModal, EuiFieldText, EuiFormRow, EuiSpacer } from '@elastic/eui';
const geti18nTexts = (fieldsToDelete?: string[]) => {
let modalTitle = '';
+ let confirmButtonText = '';
if (fieldsToDelete) {
const isSingle = fieldsToDelete.length === 1;
@@ -19,27 +20,35 @@ const geti18nTexts = (fieldsToDelete?: string[]) => {
? i18n.translate(
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteSingleTitle',
{
- defaultMessage: `Remove field '{name}'?`,
+ defaultMessage: `Remove field '{name}'`,
values: { name: fieldsToDelete[0] },
}
)
: i18n.translate(
'indexPatternFieldEditor.deleteRuntimeField.confirmModal.deleteMultipleTitle',
{
- defaultMessage: `Remove {count} fields?`,
+ defaultMessage: `Remove {count} fields`,
values: { count: fieldsToDelete.length },
}
);
+ confirmButtonText = isSingle
+ ? i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeButtonLabel',
+ {
+ defaultMessage: `Remove field`,
+ }
+ )
+ : i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeMultipleButtonLabel',
+ {
+ defaultMessage: `Remove fields`,
+ }
+ );
}
return {
modalTitle,
- confirmButtonText: i18n.translate(
- 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.removeButtonLabel',
- {
- defaultMessage: 'Remove',
- }
- ),
+ confirmButtonText,
cancelButtonText: i18n.translate(
'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.cancelButtonLabel',
{
@@ -52,6 +61,19 @@ const geti18nTexts = (fieldsToDelete?: string[]) => {
defaultMessage: 'You are about to remove these runtime fields:',
}
),
+ typeConfirm: i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.typeConfirm',
+ {
+ defaultMessage: "Type 'REMOVE' to confirm",
+ }
+ ),
+ warningRemovingFields: i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.warningRemovingFields',
+ {
+ defaultMessage:
+ 'Warning: Removing fields may break searches or visualizations that rely on this field.',
+ }
+ ),
};
};
@@ -65,6 +87,7 @@ export function DeleteFieldModal({ fieldsToDelete, closeModal, confirmDelete }:
const i18nTexts = geti18nTexts(fieldsToDelete);
const { modalTitle, confirmButtonText, cancelButtonText, warningMultipleFields } = i18nTexts;
const isMultiple = Boolean(fieldsToDelete.length > 1);
+ const [confirmContent, setConfirmContent] = useState();
return (
- {isMultiple && (
- <>
- {warningMultipleFields}
-
- {fieldsToDelete.map((fieldName) => (
- {fieldName}
- ))}
-
- >
- )}
+
+ {isMultiple && (
+ <>
+ {warningMultipleFields}
+
+ {fieldsToDelete.map((fieldName) => (
+ {fieldName}
+ ))}
+
+ >
+ )}
+
+
+
+ setConfirmContent(e.target.value)}
+ data-test-subj="deleteModalConfirmText"
+ />
+
);
}
diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts
index e943dbdda998d..46414c264c6b7 100644
--- a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts
+++ b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.test.ts
@@ -68,7 +68,7 @@ describe(' ', () => {
const { find } = setup({ ...defaultProps, field });
- expect(find('flyoutTitle').text()).toBe(`Edit ${field.name} field`);
+ expect(find('flyoutTitle').text()).toBe(`Edit field 'foo'`);
expect(find('nameField.input').props().value).toBe(field.name);
expect(find('typeField').props().value).toBe(field.type);
expect(find('scriptField').props().value).toBe(field.script.source);
diff --git a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx
index 1511836da85e7..486df1a7707af 100644
--- a/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx
+++ b/src/plugins/index_pattern_field_editor/public/components/field_editor_flyout_content.tsx
@@ -8,6 +8,7 @@
import React, { useState, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiFlyoutHeader,
EuiFlyoutBody,
@@ -19,6 +20,10 @@ import {
EuiButton,
EuiCallOut,
EuiSpacer,
+ EuiText,
+ EuiConfirmModal,
+ EuiFieldText,
+ EuiFormRow,
} from '@elastic/eui';
import { DocLinksStart, CoreStart } from 'src/core/public';
@@ -30,16 +35,6 @@ import type { Props as FieldEditorProps, FieldEditorFormState } from './field_ed
const geti18nTexts = (field?: Field) => {
return {
- flyoutTitle: field
- ? i18n.translate('indexPatternFieldEditor.editor.flyoutEditFieldTitle', {
- defaultMessage: 'Edit {fieldName} field',
- values: {
- fieldName: field.name,
- },
- })
- : i18n.translate('indexPatternFieldEditor.editor.flyoutDefaultTitle', {
- defaultMessage: 'Create field',
- }),
closeButtonLabel: i18n.translate('indexPatternFieldEditor.editor.flyoutCloseButtonLabel', {
defaultMessage: 'Close',
}),
@@ -49,6 +44,31 @@ const geti18nTexts = (field?: Field) => {
formErrorsCalloutTitle: i18n.translate('indexPatternFieldEditor.editor.validationErrorTitle', {
defaultMessage: 'Fix errors in form before continuing.',
}),
+ cancelButtonText: i18n.translate(
+ 'indexPatternFieldEditor.saveRuntimeField.confirmationModal.cancelButtonLabel',
+ {
+ defaultMessage: 'Cancel',
+ }
+ ),
+ confirmButtonText: i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmationModal.saveButtonLabel',
+ {
+ defaultMessage: 'Save',
+ }
+ ),
+ warningChangingFields: i18n.translate(
+ 'indexPatternFieldEditor.deleteRuntimeField.confirmModal.warningChangingFields',
+ {
+ defaultMessage:
+ 'Warning: Changing name or type may break searches or visualizations that rely on this field.',
+ }
+ ),
+ typeConfirm: i18n.translate(
+ 'indexPatternFieldEditor.saveRuntimeField.confirmModal.typeConfirm',
+ {
+ defaultMessage: "Type 'CHANGE' to continue:",
+ }
+ ),
};
};
@@ -97,6 +117,7 @@ const FieldEditorFlyoutContentComponent = ({
runtimeFieldValidator,
isSavingField,
}: Props) => {
+ const isEditingExistingField = !!field;
const i18nTexts = geti18nTexts(field);
const [formState, setFormState] = useState({
@@ -112,6 +133,8 @@ const FieldEditorFlyoutContentComponent = ({
);
const [isValidating, setIsValidating] = useState(false);
+ const [isModalVisible, setIsModalVisible] = useState(false);
+ const [confirmContent, setConfirmContent] = useState();
const { submit, isValid: isFormValid, isSubmitted } = formState;
const { fields } = indexPattern;
@@ -129,6 +152,8 @@ const FieldEditorFlyoutContentComponent = ({
const onClickSave = useCallback(async () => {
const { isValid, data } = await submit();
+ const nameChange = field?.name !== data.name;
+ const typeChange = field?.type !== data.type;
if (isValid) {
if (data.script) {
@@ -147,9 +172,13 @@ const FieldEditorFlyoutContentComponent = ({
}
}
- onSave(data);
+ if (isEditingExistingField && (nameChange || typeChange)) {
+ setIsModalVisible(true);
+ } else {
+ onSave(data);
+ }
}
- }, [onSave, submit, runtimeFieldValidator]);
+ }, [onSave, submit, runtimeFieldValidator, field, isEditingExistingField]);
const namesNotAllowed = useMemo(() => fields.map((fld) => fld.name), [fields]);
@@ -180,12 +209,70 @@ const FieldEditorFlyoutContentComponent = ({
[fieldTypeToProcess, namesNotAllowed, existingConcreteFields]
);
+ const modal = isModalVisible ? (
+ {
+ setIsModalVisible(false);
+ setConfirmContent('');
+ }}
+ onConfirm={async () => {
+ const { data } = await submit();
+ onSave(data);
+ }}
+ >
+
+
+
+ setConfirmContent(e.target.value)}
+ data-test-subj="saveModalConfirmText"
+ />
+
+
+ ) : null;
return (
<>
-
- {i18nTexts.flyoutTitle}
+
+
+ {field ? (
+
+ ) : (
+
+ )}
+
+
+
+ {indexPattern.title},
+ }}
+ />
+
+
@@ -246,6 +333,7 @@ const FieldEditorFlyoutContentComponent = ({
>
)}
+ {modal}
>
);
};
diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap
index db527ea81b2cb..ca41dddf6197e 100644
--- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap
+++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap
@@ -61,7 +61,7 @@ exports[`IndicesList should change pages 1`] = `
hasArrow={true}
id="customizablePagination"
isOpen={false}
- ownFocus={false}
+ ownFocus={true}
panelPaddingSize="none"
>
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/constants.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/constants.ts
index 1910ba054bf8e..f072f044925bf 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/constants.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/constants.ts
@@ -12,15 +12,9 @@
export const ROLL_TOTAL_INDICES_INTERVAL = 24 * 60 * 60 * 1000;
/**
- * Roll daily indices every 30 minutes.
- * This means that, assuming a user can visit all the 44 apps we can possibly report
- * in the 3 minutes interval the browser reports to the server, up to 22 users can have the same
- * behaviour and we wouldn't need to paginate in the transactional documents (less than 10k docs).
- *
- * Based on a more normal expected use case, the users could visit up to 5 apps in those 3 minutes,
- * allowing up to 200 users before reaching the limit.
+ * Roll daily indices every 24h
*/
-export const ROLL_DAILY_INDICES_INTERVAL = 30 * 60 * 1000;
+export const ROLL_DAILY_INDICES_INTERVAL = 24 * 60 * 60 * 1000;
/**
* Start rolling indices after 5 minutes up
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/index.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/index.ts
index 676f5fddc16e1..2d2d07d9d1894 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/index.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/index.ts
@@ -7,3 +7,4 @@
*/
export { registerApplicationUsageCollector } from './telemetry_application_usage_collector';
+export { rollDailyData as migrateTransactionalDocs } from './rollups';
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.test.ts
similarity index 51%
rename from src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.test.ts
rename to src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.test.ts
index 7d86bc41e0b90..5acd1fb9c9c3a 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.test.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.test.ts
@@ -6,21 +6,16 @@
* Side Public License, v 1.
*/
-import { rollDailyData, rollTotals } from './rollups';
-import { savedObjectsRepositoryMock, loggingSystemMock } from '../../../../../core/server/mocks';
-import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
-import { SavedObjectsErrorHelpers } from '../../../../../core/server';
-import {
- SAVED_OBJECTS_DAILY_TYPE,
- SAVED_OBJECTS_TOTAL_TYPE,
- SAVED_OBJECTS_TRANSACTIONAL_TYPE,
-} from './saved_objects_types';
+import { savedObjectsRepositoryMock, loggingSystemMock } from '../../../../../../core/server/mocks';
+import { SavedObjectsErrorHelpers } from '../../../../../../core/server';
+import { SAVED_OBJECTS_DAILY_TYPE, SAVED_OBJECTS_TRANSACTIONAL_TYPE } from '../saved_objects_types';
+import { rollDailyData } from './daily';
describe('rollDailyData', () => {
const logger = loggingSystemMock.createLogger();
- test('returns undefined if no savedObjectsClient initialised yet', async () => {
- await expect(rollDailyData(logger, undefined)).resolves.toBe(undefined);
+ test('returns false if no savedObjectsClient initialised yet', async () => {
+ await expect(rollDailyData(logger, undefined)).resolves.toBe(false);
});
test('handle empty results', async () => {
@@ -33,7 +28,7 @@ describe('rollDailyData', () => {
throw new Error(`Unexpected type [${type}]`);
}
});
- await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(undefined);
+ await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(true);
expect(savedObjectClient.get).not.toBeCalled();
expect(savedObjectClient.bulkCreate).not.toBeCalled();
expect(savedObjectClient.delete).not.toBeCalled();
@@ -101,7 +96,7 @@ describe('rollDailyData', () => {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
});
- await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(undefined);
+ await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(true);
expect(savedObjectClient.get).toHaveBeenCalledTimes(2);
expect(savedObjectClient.get).toHaveBeenNthCalledWith(
1,
@@ -196,7 +191,7 @@ describe('rollDailyData', () => {
throw new Error('Something went terribly wrong');
});
- await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(undefined);
+ await expect(rollDailyData(logger, savedObjectClient)).resolves.toBe(false);
expect(savedObjectClient.get).toHaveBeenCalledTimes(1);
expect(savedObjectClient.get).toHaveBeenCalledWith(
SAVED_OBJECTS_DAILY_TYPE,
@@ -206,185 +201,3 @@ describe('rollDailyData', () => {
expect(savedObjectClient.delete).toHaveBeenCalledTimes(0);
});
});
-
-describe('rollTotals', () => {
- const logger = loggingSystemMock.createLogger();
-
- test('returns undefined if no savedObjectsClient initialised yet', async () => {
- await expect(rollTotals(logger, undefined)).resolves.toBe(undefined);
- });
-
- test('handle empty results', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => {
- switch (type) {
- case SAVED_OBJECTS_DAILY_TYPE:
- case SAVED_OBJECTS_TOTAL_TYPE:
- return { saved_objects: [], total: 0, page, per_page: perPage };
- default:
- throw new Error(`Unexpected type [${type}]`);
- }
- });
- await expect(rollTotals(logger, savedObjectClient)).resolves.toBe(undefined);
- expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(0);
- expect(savedObjectClient.delete).toHaveBeenCalledTimes(0);
- });
-
- test('migrate some documents', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => {
- switch (type) {
- case SAVED_OBJECTS_DAILY_TYPE:
- return {
- saved_objects: [
- {
- id: 'appId-2:2020-01-01',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-2',
- timestamp: '2020-01-01T10:31:00.000Z',
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- },
- {
- id: 'appId-1:2020-01-01',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-1',
- timestamp: '2020-01-01T11:31:00.000Z',
- minutesOnScreen: 2.5,
- numberOfClicks: 2,
- },
- },
- {
- id: 'appId-1:2020-01-01:viewId-1',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-1',
- viewId: 'viewId-1',
- timestamp: '2020-01-01T11:31:00.000Z',
- minutesOnScreen: 1,
- numberOfClicks: 1,
- },
- },
- ],
- total: 3,
- page,
- per_page: perPage,
- };
- case SAVED_OBJECTS_TOTAL_TYPE:
- return {
- saved_objects: [
- {
- id: 'appId-1',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-1',
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- },
- {
- id: 'appId-1___viewId-1',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-1',
- viewId: 'viewId-1',
- minutesOnScreen: 4,
- numberOfClicks: 2,
- },
- },
- {
- id: 'appId-2___viewId-1',
- type,
- score: 0,
- references: [],
- attributes: {
- appId: 'appId-2',
- viewId: 'viewId-1',
- minutesOnScreen: 1,
- numberOfClicks: 1,
- },
- },
- ],
- total: 3,
- page,
- per_page: perPage,
- };
- default:
- throw new Error(`Unexpected type [${type}]`);
- }
- });
- await expect(rollTotals(logger, savedObjectClient)).resolves.toBe(undefined);
- expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1);
- expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(
- [
- {
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id: 'appId-1',
- attributes: {
- appId: 'appId-1',
- viewId: MAIN_APP_DEFAULT_VIEW_ID,
- minutesOnScreen: 3.0,
- numberOfClicks: 3,
- },
- },
- {
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id: 'appId-1___viewId-1',
- attributes: {
- appId: 'appId-1',
- viewId: 'viewId-1',
- minutesOnScreen: 5.0,
- numberOfClicks: 3,
- },
- },
- {
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id: 'appId-2___viewId-1',
- attributes: {
- appId: 'appId-2',
- viewId: 'viewId-1',
- minutesOnScreen: 1.0,
- numberOfClicks: 1,
- },
- },
- {
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id: 'appId-2',
- attributes: {
- appId: 'appId-2',
- viewId: MAIN_APP_DEFAULT_VIEW_ID,
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- },
- ],
- { overwrite: true }
- );
- expect(savedObjectClient.delete).toHaveBeenCalledTimes(3);
- expect(savedObjectClient.delete).toHaveBeenCalledWith(
- SAVED_OBJECTS_DAILY_TYPE,
- 'appId-2:2020-01-01'
- );
- expect(savedObjectClient.delete).toHaveBeenCalledWith(
- SAVED_OBJECTS_DAILY_TYPE,
- 'appId-1:2020-01-01'
- );
- expect(savedObjectClient.delete).toHaveBeenCalledWith(
- SAVED_OBJECTS_DAILY_TYPE,
- 'appId-1:2020-01-01:viewId-1'
- );
- });
-});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.ts
similarity index 55%
rename from src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.ts
rename to src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.ts
index df7e7662b49cf..a7873c7d5dfe9 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/daily.ts
@@ -6,18 +6,20 @@
* Side Public License, v 1.
*/
-import { ISavedObjectsRepository, SavedObject, Logger } from 'kibana/server';
import moment from 'moment';
+import type { Logger } from '@kbn/logging';
+import {
+ ISavedObjectsRepository,
+ SavedObject,
+ SavedObjectsErrorHelpers,
+} from '../../../../../../core/server';
+import { getDailyId } from '../../../../../usage_collection/common/application_usage';
import {
ApplicationUsageDaily,
- ApplicationUsageTotal,
ApplicationUsageTransactional,
SAVED_OBJECTS_DAILY_TYPE,
- SAVED_OBJECTS_TOTAL_TYPE,
SAVED_OBJECTS_TRANSACTIONAL_TYPE,
-} from './saved_objects_types';
-import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server';
-import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
+} from '../saved_objects_types';
/**
* For Rolling the daily data, we only care about the stored attributes and the version (to avoid overwriting via concurrent requests)
@@ -27,18 +29,17 @@ type ApplicationUsageDailyWithVersion = Pick<
'version' | 'attributes'
>;
-export function serializeKey(appId: string, viewId: string) {
- return `${appId}___${viewId}`;
-}
-
/**
* Aggregates all the transactional events into daily aggregates
* @param logger
* @param savedObjectsClient
*/
-export async function rollDailyData(logger: Logger, savedObjectsClient?: ISavedObjectsRepository) {
+export async function rollDailyData(
+ logger: Logger,
+ savedObjectsClient?: ISavedObjectsRepository
+): Promise {
if (!savedObjectsClient) {
- return;
+ return false;
}
try {
@@ -58,10 +59,7 @@ export async function rollDailyData(logger: Logger, savedObjectsClient?: ISavedO
} = doc;
const dayId = moment(timestamp).format('YYYY-MM-DD');
- const dailyId =
- !viewId || viewId === MAIN_APP_DEFAULT_VIEW_ID
- ? `${appId}:${dayId}`
- : `${appId}:${dayId}:${viewId}`;
+ const dailyId = getDailyId({ dayId, appId, viewId });
const existingDoc =
toCreate.get(dailyId) ||
@@ -103,9 +101,11 @@ export async function rollDailyData(logger: Logger, savedObjectsClient?: ISavedO
}
}
} while (toCreate.size > 0);
+ return true;
} catch (err) {
logger.debug(`Failed to rollup transactional to daily entries`);
logger.debug(err);
+ return false;
}
}
@@ -125,7 +125,11 @@ async function getDailyDoc(
dayId: string
): Promise {
try {
- return await savedObjectsClient.get(SAVED_OBJECTS_DAILY_TYPE, id);
+ const { attributes, version } = await savedObjectsClient.get(
+ SAVED_OBJECTS_DAILY_TYPE,
+ id
+ );
+ return { attributes, version };
} catch (err) {
if (SavedObjectsErrorHelpers.isNotFoundError(err)) {
return {
@@ -142,91 +146,3 @@ async function getDailyDoc(
throw err;
}
}
-
-/**
- * Moves all the daily documents into aggregated "total" documents as we don't care about any granularity after 90 days
- * @param logger
- * @param savedObjectsClient
- */
-export async function rollTotals(logger: Logger, savedObjectsClient?: ISavedObjectsRepository) {
- if (!savedObjectsClient) {
- return;
- }
-
- try {
- const [
- { saved_objects: rawApplicationUsageTotals },
- { saved_objects: rawApplicationUsageDaily },
- ] = await Promise.all([
- savedObjectsClient.find({
- perPage: 10000,
- type: SAVED_OBJECTS_TOTAL_TYPE,
- }),
- savedObjectsClient.find({
- perPage: 10000,
- type: SAVED_OBJECTS_DAILY_TYPE,
- filter: `${SAVED_OBJECTS_DAILY_TYPE}.attributes.timestamp < now-90d`,
- }),
- ]);
-
- const existingTotals = rawApplicationUsageTotals.reduce(
- (
- acc,
- {
- attributes: { appId, viewId = MAIN_APP_DEFAULT_VIEW_ID, numberOfClicks, minutesOnScreen },
- }
- ) => {
- const key = viewId === MAIN_APP_DEFAULT_VIEW_ID ? appId : serializeKey(appId, viewId);
-
- return {
- ...acc,
- // No need to sum because there should be 1 document per appId only
- [key]: { appId, viewId, numberOfClicks, minutesOnScreen },
- };
- },
- {} as Record<
- string,
- { appId: string; viewId: string; minutesOnScreen: number; numberOfClicks: number }
- >
- );
-
- const totals = rawApplicationUsageDaily.reduce((acc, { attributes }) => {
- const {
- appId,
- viewId = MAIN_APP_DEFAULT_VIEW_ID,
- numberOfClicks,
- minutesOnScreen,
- } = attributes;
- const key = viewId === MAIN_APP_DEFAULT_VIEW_ID ? appId : serializeKey(appId, viewId);
- const existing = acc[key] || { minutesOnScreen: 0, numberOfClicks: 0 };
-
- return {
- ...acc,
- [key]: {
- appId,
- viewId,
- numberOfClicks: numberOfClicks + existing.numberOfClicks,
- minutesOnScreen: minutesOnScreen + existing.minutesOnScreen,
- },
- };
- }, existingTotals);
-
- await Promise.all([
- Object.entries(totals).length &&
- savedObjectsClient.bulkCreate(
- Object.entries(totals).map(([id, entry]) => ({
- type: SAVED_OBJECTS_TOTAL_TYPE,
- id,
- attributes: entry,
- })),
- { overwrite: true }
- ),
- ...rawApplicationUsageDaily.map(
- ({ id }) => savedObjectsClient.delete(SAVED_OBJECTS_DAILY_TYPE, id) // There is no bulkDelete :(
- ),
- ]);
- } catch (err) {
- logger.debug(`Failed to rollup daily entries to totals`);
- logger.debug(err);
- }
-}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/index.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/index.ts
new file mode 100644
index 0000000000000..8f3d83613aa9d
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/index.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { rollDailyData } from './daily';
+export { rollTotals } from './total';
+export { serializeKey } from './utils';
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.test.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.test.ts
new file mode 100644
index 0000000000000..9fea955ab5d8a
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.test.ts
@@ -0,0 +1,194 @@
+/*
+ * Copyright 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 { savedObjectsRepositoryMock, loggingSystemMock } from '../../../../../../core/server/mocks';
+import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../../usage_collection/common/constants';
+import { SAVED_OBJECTS_DAILY_TYPE, SAVED_OBJECTS_TOTAL_TYPE } from '../saved_objects_types';
+import { rollTotals } from './total';
+
+describe('rollTotals', () => {
+ const logger = loggingSystemMock.createLogger();
+
+ test('returns undefined if no savedObjectsClient initialised yet', async () => {
+ await expect(rollTotals(logger, undefined)).resolves.toBe(undefined);
+ });
+
+ test('handle empty results', async () => {
+ const savedObjectClient = savedObjectsRepositoryMock.create();
+ savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => {
+ switch (type) {
+ case SAVED_OBJECTS_DAILY_TYPE:
+ case SAVED_OBJECTS_TOTAL_TYPE:
+ return { saved_objects: [], total: 0, page, per_page: perPage };
+ default:
+ throw new Error(`Unexpected type [${type}]`);
+ }
+ });
+ await expect(rollTotals(logger, savedObjectClient)).resolves.toBe(undefined);
+ expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(0);
+ expect(savedObjectClient.delete).toHaveBeenCalledTimes(0);
+ });
+
+ test('migrate some documents', async () => {
+ const savedObjectClient = savedObjectsRepositoryMock.create();
+ savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => {
+ switch (type) {
+ case SAVED_OBJECTS_DAILY_TYPE:
+ return {
+ saved_objects: [
+ {
+ id: 'appId-2:2020-01-01',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-2',
+ timestamp: '2020-01-01T10:31:00.000Z',
+ minutesOnScreen: 0.5,
+ numberOfClicks: 1,
+ },
+ },
+ {
+ id: 'appId-1:2020-01-01',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-1',
+ timestamp: '2020-01-01T11:31:00.000Z',
+ minutesOnScreen: 2.5,
+ numberOfClicks: 2,
+ },
+ },
+ {
+ id: 'appId-1:2020-01-01:viewId-1',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-1',
+ viewId: 'viewId-1',
+ timestamp: '2020-01-01T11:31:00.000Z',
+ minutesOnScreen: 1,
+ numberOfClicks: 1,
+ },
+ },
+ ],
+ total: 3,
+ page,
+ per_page: perPage,
+ };
+ case SAVED_OBJECTS_TOTAL_TYPE:
+ return {
+ saved_objects: [
+ {
+ id: 'appId-1',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-1',
+ minutesOnScreen: 0.5,
+ numberOfClicks: 1,
+ },
+ },
+ {
+ id: 'appId-1___viewId-1',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-1',
+ viewId: 'viewId-1',
+ minutesOnScreen: 4,
+ numberOfClicks: 2,
+ },
+ },
+ {
+ id: 'appId-2___viewId-1',
+ type,
+ score: 0,
+ references: [],
+ attributes: {
+ appId: 'appId-2',
+ viewId: 'viewId-1',
+ minutesOnScreen: 1,
+ numberOfClicks: 1,
+ },
+ },
+ ],
+ total: 3,
+ page,
+ per_page: perPage,
+ };
+ default:
+ throw new Error(`Unexpected type [${type}]`);
+ }
+ });
+ await expect(rollTotals(logger, savedObjectClient)).resolves.toBe(undefined);
+ expect(savedObjectClient.bulkCreate).toHaveBeenCalledTimes(1);
+ expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(
+ [
+ {
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id: 'appId-1',
+ attributes: {
+ appId: 'appId-1',
+ viewId: MAIN_APP_DEFAULT_VIEW_ID,
+ minutesOnScreen: 3.0,
+ numberOfClicks: 3,
+ },
+ },
+ {
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id: 'appId-1___viewId-1',
+ attributes: {
+ appId: 'appId-1',
+ viewId: 'viewId-1',
+ minutesOnScreen: 5.0,
+ numberOfClicks: 3,
+ },
+ },
+ {
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id: 'appId-2___viewId-1',
+ attributes: {
+ appId: 'appId-2',
+ viewId: 'viewId-1',
+ minutesOnScreen: 1.0,
+ numberOfClicks: 1,
+ },
+ },
+ {
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id: 'appId-2',
+ attributes: {
+ appId: 'appId-2',
+ viewId: MAIN_APP_DEFAULT_VIEW_ID,
+ minutesOnScreen: 0.5,
+ numberOfClicks: 1,
+ },
+ },
+ ],
+ { overwrite: true }
+ );
+ expect(savedObjectClient.delete).toHaveBeenCalledTimes(3);
+ expect(savedObjectClient.delete).toHaveBeenCalledWith(
+ SAVED_OBJECTS_DAILY_TYPE,
+ 'appId-2:2020-01-01'
+ );
+ expect(savedObjectClient.delete).toHaveBeenCalledWith(
+ SAVED_OBJECTS_DAILY_TYPE,
+ 'appId-1:2020-01-01'
+ );
+ expect(savedObjectClient.delete).toHaveBeenCalledWith(
+ SAVED_OBJECTS_DAILY_TYPE,
+ 'appId-1:2020-01-01:viewId-1'
+ );
+ });
+});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts
new file mode 100644
index 0000000000000..e27c7b897d995
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { Logger } from '@kbn/logging';
+import type { ISavedObjectsRepository } from 'kibana/server';
+import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../../usage_collection/common/constants';
+import {
+ ApplicationUsageDaily,
+ ApplicationUsageTotal,
+ SAVED_OBJECTS_DAILY_TYPE,
+ SAVED_OBJECTS_TOTAL_TYPE,
+} from '../saved_objects_types';
+import { serializeKey } from './utils';
+
+/**
+ * Moves all the daily documents into aggregated "total" documents as we don't care about any granularity after 90 days
+ * @param logger
+ * @param savedObjectsClient
+ */
+export async function rollTotals(logger: Logger, savedObjectsClient?: ISavedObjectsRepository) {
+ if (!savedObjectsClient) {
+ return;
+ }
+
+ try {
+ const [
+ { saved_objects: rawApplicationUsageTotals },
+ { saved_objects: rawApplicationUsageDaily },
+ ] = await Promise.all([
+ savedObjectsClient.find({
+ perPage: 10000,
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ }),
+ savedObjectsClient.find({
+ perPage: 10000,
+ type: SAVED_OBJECTS_DAILY_TYPE,
+ filter: `${SAVED_OBJECTS_DAILY_TYPE}.attributes.timestamp < now-90d`,
+ }),
+ ]);
+
+ const existingTotals = rawApplicationUsageTotals.reduce(
+ (
+ acc,
+ {
+ attributes: { appId, viewId = MAIN_APP_DEFAULT_VIEW_ID, numberOfClicks, minutesOnScreen },
+ }
+ ) => {
+ const key = viewId === MAIN_APP_DEFAULT_VIEW_ID ? appId : serializeKey(appId, viewId);
+
+ return {
+ ...acc,
+ // No need to sum because there should be 1 document per appId only
+ [key]: { appId, viewId, numberOfClicks, minutesOnScreen },
+ };
+ },
+ {} as Record<
+ string,
+ { appId: string; viewId: string; minutesOnScreen: number; numberOfClicks: number }
+ >
+ );
+
+ const totals = rawApplicationUsageDaily.reduce((acc, { attributes }) => {
+ const {
+ appId,
+ viewId = MAIN_APP_DEFAULT_VIEW_ID,
+ numberOfClicks,
+ minutesOnScreen,
+ } = attributes;
+ const key = viewId === MAIN_APP_DEFAULT_VIEW_ID ? appId : serializeKey(appId, viewId);
+ const existing = acc[key] || { minutesOnScreen: 0, numberOfClicks: 0 };
+
+ return {
+ ...acc,
+ [key]: {
+ appId,
+ viewId,
+ numberOfClicks: numberOfClicks + existing.numberOfClicks,
+ minutesOnScreen: minutesOnScreen + existing.minutesOnScreen,
+ },
+ };
+ }, existingTotals);
+
+ await Promise.all([
+ Object.entries(totals).length &&
+ savedObjectsClient.bulkCreate(
+ Object.entries(totals).map(([id, entry]) => ({
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ id,
+ attributes: entry,
+ })),
+ { overwrite: true }
+ ),
+ ...rawApplicationUsageDaily.map(
+ ({ id }) => savedObjectsClient.delete(SAVED_OBJECTS_DAILY_TYPE, id) // There is no bulkDelete :(
+ ),
+ ]);
+ } catch (err) {
+ logger.debug(`Failed to rollup daily entries to totals`);
+ logger.debug(err);
+ }
+}
diff --git a/src/plugins/vis_type_timeseries/common/field_types.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/utils.ts
similarity index 73%
rename from src/plugins/vis_type_timeseries/common/field_types.ts
rename to src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/utils.ts
index f9ebc83b4a5db..8be00e6287883 100644
--- a/src/plugins/vis_type_timeseries/common/field_types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/utils.ts
@@ -6,10 +6,6 @@
* Side Public License, v 1.
*/
-export enum FIELD_TYPES {
- BOOLEAN = 'boolean',
- DATE = 'date',
- GEO = 'geo_point',
- NUMBER = 'number',
- STRING = 'string',
+export function serializeKey(appId: string, viewId: string) {
+ return `${appId}___${viewId}`;
}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts
index 9e71b5c3b032e..f2b996f3af97a 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { SavedObjectAttributes, SavedObjectsServiceSetup } from 'kibana/server';
+import type { SavedObjectAttributes, SavedObjectsServiceSetup } from 'kibana/server';
/**
* Used for accumulating the totals of all the stats older than 90d
@@ -17,6 +17,7 @@ export interface ApplicationUsageTotal extends SavedObjectAttributes {
minutesOnScreen: number;
numberOfClicks: number;
}
+
export const SAVED_OBJECTS_TOTAL_TYPE = 'application_usage_totals';
/**
@@ -25,6 +26,8 @@ export const SAVED_OBJECTS_TOTAL_TYPE = 'application_usage_totals';
export interface ApplicationUsageTransactional extends ApplicationUsageTotal {
timestamp: string;
}
+
+/** @deprecated transactional type is no longer used, and only preserved for backward compatibility */
export const SAVED_OBJECTS_TRANSACTIONAL_TYPE = 'application_usage_transactional';
/**
@@ -62,6 +65,7 @@ export function registerMappings(registerType: SavedObjectsServiceSetup['registe
});
// Type for storing ApplicationUsageTransactional (declaring empty mappings because we don't use the internal fields for query/aggregations)
+ // Remark: this type is deprecated and only here for BWC reasons.
registerType({
name: SAVED_OBJECTS_TRANSACTIONAL_TYPE,
hidden: false,
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
index 062d751ef454c..693e9132fe536 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
@@ -7,7 +7,7 @@
*/
import { MakeSchemaFrom } from 'src/plugins/usage_collection/server';
-import { ApplicationUsageTelemetryReport } from './telemetry_application_usage_collector';
+import { ApplicationUsageTelemetryReport } from './types';
const commonSchema: MakeSchemaFrom = {
appId: { type: 'keyword', _meta: { description: 'The application being tracked' } },
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts
index 3e8434d446033..f1b21af5506e6 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts
@@ -11,74 +11,99 @@ import {
Collector,
createUsageCollectionSetupMock,
} from '../../../../usage_collection/server/usage_collection.mock';
-
+import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
-import { ROLL_TOTAL_INDICES_INTERVAL, ROLL_INDICES_START } from './constants';
import {
registerApplicationUsageCollector,
transformByApplicationViews,
- ApplicationUsageViews,
} from './telemetry_application_usage_collector';
-import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
-import {
- SAVED_OBJECTS_DAILY_TYPE,
- SAVED_OBJECTS_TOTAL_TYPE,
- SAVED_OBJECTS_TRANSACTIONAL_TYPE,
-} from './saved_objects_types';
+import { ApplicationUsageViews } from './types';
-describe('telemetry_application_usage', () => {
- jest.useFakeTimers();
+import { SAVED_OBJECTS_DAILY_TYPE, SAVED_OBJECTS_TOTAL_TYPE } from './saved_objects_types';
- const logger = loggingSystemMock.createLogger();
+// use fake timers to avoid triggering rollups during tests
+jest.useFakeTimers();
+describe('telemetry_application_usage', () => {
+ let logger: ReturnType;
let collector: Collector;
+ let usageCollectionMock: ReturnType;
+ let savedObjectClient: ReturnType;
+ let getSavedObjectClient: jest.MockedFunction<() => undefined | typeof savedObjectClient>;
- const usageCollectionMock = createUsageCollectionSetupMock();
- usageCollectionMock.makeUsageCollector.mockImplementation((config) => {
- collector = new Collector(logger, config);
- return createUsageCollectionSetupMock().makeUsageCollector(config);
- });
-
- const getUsageCollector = jest.fn();
const registerType = jest.fn();
const mockedFetchContext = createCollectorFetchContextMock();
- beforeAll(() =>
- registerApplicationUsageCollector(logger, usageCollectionMock, registerType, getUsageCollector)
- );
- afterAll(() => jest.clearAllTimers());
+ beforeEach(() => {
+ logger = loggingSystemMock.createLogger();
+ usageCollectionMock = createUsageCollectionSetupMock();
+ savedObjectClient = savedObjectsRepositoryMock.create();
+ getSavedObjectClient = jest.fn().mockReturnValue(savedObjectClient);
+ usageCollectionMock.makeUsageCollector.mockImplementation((config) => {
+ collector = new Collector(logger, config);
+ return createUsageCollectionSetupMock().makeUsageCollector(config);
+ });
+ registerApplicationUsageCollector(
+ logger,
+ usageCollectionMock,
+ registerType,
+ getSavedObjectClient
+ );
+ });
+
+ afterEach(() => {
+ jest.clearAllTimers();
+ });
test('registered collector is set', () => {
expect(collector).not.toBeUndefined();
});
test('if no savedObjectClient initialised, return undefined', async () => {
+ getSavedObjectClient.mockReturnValue(undefined);
+
expect(collector.isReady()).toBe(false);
expect(await collector.fetch(mockedFetchContext)).toBeUndefined();
- jest.runTimersToTime(ROLL_INDICES_START);
});
- test('when savedObjectClient is initialised, return something', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- savedObjectClient.find.mockImplementation(
- async () =>
- ({
- saved_objects: [],
- total: 0,
- } as any)
+ test('calls `savedObjectsClient.find` with the correct parameters', async () => {
+ savedObjectClient.find.mockResolvedValue({
+ saved_objects: [],
+ total: 0,
+ per_page: 20,
+ page: 0,
+ });
+
+ await collector.fetch(mockedFetchContext);
+
+ expect(savedObjectClient.find).toHaveBeenCalledTimes(2);
+
+ expect(savedObjectClient.find).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: SAVED_OBJECTS_TOTAL_TYPE,
+ })
+ );
+ expect(savedObjectClient.find).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: SAVED_OBJECTS_DAILY_TYPE,
+ })
);
- getUsageCollector.mockImplementation(() => savedObjectClient);
+ });
- jest.runTimersToTime(ROLL_TOTAL_INDICES_INTERVAL); // Force rollTotals to run
+ test('when savedObjectClient is initialised, return something', async () => {
+ savedObjectClient.find.mockResolvedValue({
+ saved_objects: [],
+ total: 0,
+ per_page: 20,
+ page: 0,
+ });
expect(collector.isReady()).toBe(true);
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({});
expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled();
});
- test('it only gets 10k even when there are more documents (ES limitation)', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- const total = 10000;
+ test('it aggregates total and daily data', async () => {
savedObjectClient.find.mockImplementation(async (opts) => {
switch (opts.type) {
case SAVED_OBJECTS_TOTAL_TYPE:
@@ -95,18 +120,6 @@ describe('telemetry_application_usage', () => {
],
total: 1,
} as any;
- case SAVED_OBJECTS_TRANSACTIONAL_TYPE:
- const doc = {
- id: 'test-id',
- attributes: {
- appId: 'appId',
- timestamp: new Date().toISOString(),
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- };
- const savedObjects = new Array(total).fill(doc);
- return { saved_objects: savedObjects, total: total + 1 };
case SAVED_OBJECTS_DAILY_TYPE:
return {
saved_objects: [
@@ -125,122 +138,21 @@ describe('telemetry_application_usage', () => {
}
});
- getUsageCollector.mockImplementation(() => savedObjectClient);
-
- jest.runTimersToTime(ROLL_TOTAL_INDICES_INTERVAL); // Force rollTotals to run
-
expect(await collector.fetch(mockedFetchContext)).toStrictEqual({
appId: {
appId: 'appId',
viewId: 'main',
- clicks_total: total + 1 + 10,
- clicks_7_days: total + 1,
- clicks_30_days: total + 1,
- clicks_90_days: total + 1,
- minutes_on_screen_total: (total + 1) * 0.5 + 10,
- minutes_on_screen_7_days: (total + 1) * 0.5,
- minutes_on_screen_30_days: (total + 1) * 0.5,
- minutes_on_screen_90_days: (total + 1) * 0.5,
+ clicks_total: 1 + 10,
+ clicks_7_days: 1,
+ clicks_30_days: 1,
+ clicks_90_days: 1,
+ minutes_on_screen_total: 0.5 + 10,
+ minutes_on_screen_7_days: 0.5,
+ minutes_on_screen_30_days: 0.5,
+ minutes_on_screen_90_days: 0.5,
views: [],
},
});
- expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith(
- [
- {
- id: 'appId',
- type: SAVED_OBJECTS_TOTAL_TYPE,
- attributes: {
- appId: 'appId',
- viewId: 'main',
- minutesOnScreen: 10.5,
- numberOfClicks: 11,
- },
- },
- ],
- { overwrite: true }
- );
- expect(savedObjectClient.delete).toHaveBeenCalledTimes(1);
- expect(savedObjectClient.delete).toHaveBeenCalledWith(
- SAVED_OBJECTS_DAILY_TYPE,
- 'appId:YYYY-MM-DD'
- );
- });
-
- test('old transactional data not migrated yet', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
- savedObjectClient.find.mockImplementation(async (opts) => {
- switch (opts.type) {
- case SAVED_OBJECTS_TOTAL_TYPE:
- case SAVED_OBJECTS_DAILY_TYPE:
- return { saved_objects: [], total: 0 } as any;
- case SAVED_OBJECTS_TRANSACTIONAL_TYPE:
- return {
- saved_objects: [
- {
- id: 'test-id',
- attributes: {
- appId: 'appId',
- timestamp: new Date(0).toISOString(),
- minutesOnScreen: 0.5,
- numberOfClicks: 1,
- },
- },
- {
- id: 'test-id-2',
- attributes: {
- appId: 'appId',
- viewId: 'main',
- timestamp: new Date(0).toISOString(),
- minutesOnScreen: 2,
- numberOfClicks: 2,
- },
- },
- {
- id: 'test-id-3',
- attributes: {
- appId: 'appId',
- viewId: 'viewId-1',
- timestamp: new Date(0).toISOString(),
- minutesOnScreen: 1,
- numberOfClicks: 1,
- },
- },
- ],
- total: 1,
- };
- }
- });
-
- getUsageCollector.mockImplementation(() => savedObjectClient);
-
- expect(await collector.fetch(mockedFetchContext)).toStrictEqual({
- appId: {
- appId: 'appId',
- viewId: 'main',
- clicks_total: 3,
- clicks_7_days: 0,
- clicks_30_days: 0,
- clicks_90_days: 0,
- minutes_on_screen_total: 2.5,
- minutes_on_screen_7_days: 0,
- minutes_on_screen_30_days: 0,
- minutes_on_screen_90_days: 0,
- views: [
- {
- appId: 'appId',
- viewId: 'viewId-1',
- clicks_total: 1,
- clicks_7_days: 0,
- clicks_30_days: 0,
- clicks_90_days: 0,
- minutes_on_screen_total: 1,
- minutes_on_screen_7_days: 0,
- minutes_on_screen_30_days: 0,
- minutes_on_screen_90_days: 0,
- },
- ],
- },
- });
});
});
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts
index ee1b42e61a6ca..a01f1bca4f0e0 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts
@@ -11,57 +11,21 @@ import { timer } from 'rxjs';
import { ISavedObjectsRepository, Logger, SavedObjectsServiceSetup } from 'kibana/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { MAIN_APP_DEFAULT_VIEW_ID } from '../../../../usage_collection/common/constants';
-import { serializeKey } from './rollups';
-
import {
ApplicationUsageDaily,
ApplicationUsageTotal,
- ApplicationUsageTransactional,
registerMappings,
SAVED_OBJECTS_DAILY_TYPE,
SAVED_OBJECTS_TOTAL_TYPE,
- SAVED_OBJECTS_TRANSACTIONAL_TYPE,
} from './saved_objects_types';
import { applicationUsageSchema } from './schema';
-import { rollDailyData, rollTotals } from './rollups';
+import { rollTotals, rollDailyData, serializeKey } from './rollups';
import {
ROLL_TOTAL_INDICES_INTERVAL,
ROLL_DAILY_INDICES_INTERVAL,
ROLL_INDICES_START,
} from './constants';
-
-export interface ApplicationViewUsage {
- appId: string;
- viewId: string;
- clicks_total: number;
- clicks_7_days: number;
- clicks_30_days: number;
- clicks_90_days: number;
- minutes_on_screen_total: number;
- minutes_on_screen_7_days: number;
- minutes_on_screen_30_days: number;
- minutes_on_screen_90_days: number;
-}
-
-export interface ApplicationUsageViews {
- [serializedKey: string]: ApplicationViewUsage;
-}
-
-export interface ApplicationUsageTelemetryReport {
- [appId: string]: {
- appId: string;
- viewId: string;
- clicks_total: number;
- clicks_7_days: number;
- clicks_30_days: number;
- clicks_90_days: number;
- minutes_on_screen_total: number;
- minutes_on_screen_7_days: number;
- minutes_on_screen_30_days: number;
- minutes_on_screen_90_days: number;
- views?: ApplicationViewUsage[];
- };
-}
+import { ApplicationUsageTelemetryReport, ApplicationUsageViews } from './types';
export const transformByApplicationViews = (
report: ApplicationUsageViews
@@ -92,6 +56,21 @@ export function registerApplicationUsageCollector(
) {
registerMappings(registerType);
+ timer(ROLL_INDICES_START, ROLL_TOTAL_INDICES_INTERVAL).subscribe(() =>
+ rollTotals(logger, getSavedObjectsClient())
+ );
+
+ const dailyRollingSub = timer(ROLL_INDICES_START, ROLL_DAILY_INDICES_INTERVAL).subscribe(
+ async () => {
+ const success = await rollDailyData(logger, getSavedObjectsClient());
+ // we only need to roll the transactional documents once to assure BWC
+ // once we rolling succeeds, we can stop.
+ if (success) {
+ dailyRollingSub.unsubscribe();
+ }
+ }
+ );
+
const collector = usageCollection.makeUsageCollector(
{
type: 'application_usage',
@@ -105,7 +84,6 @@ export function registerApplicationUsageCollector(
const [
{ saved_objects: rawApplicationUsageTotals },
{ saved_objects: rawApplicationUsageDaily },
- { saved_objects: rawApplicationUsageTransactional },
] = await Promise.all([
savedObjectsClient.find({
type: SAVED_OBJECTS_TOTAL_TYPE,
@@ -115,10 +93,6 @@ export function registerApplicationUsageCollector(
type: SAVED_OBJECTS_DAILY_TYPE,
perPage: 10000, // We can have up to 44 apps * 91 days = 4004 docs. This limit is OK
}),
- savedObjectsClient.find({
- type: SAVED_OBJECTS_TRANSACTIONAL_TYPE,
- perPage: 10000, // If we have more than those, we won't report the rest (they'll be rolled up to the daily soon enough to become a problem)
- }),
]);
const applicationUsageFromTotals = rawApplicationUsageTotals.reduce(
@@ -156,10 +130,7 @@ export function registerApplicationUsageCollector(
const nowMinus30 = moment().subtract(30, 'days');
const nowMinus90 = moment().subtract(90, 'days');
- const applicationUsage = [
- ...rawApplicationUsageDaily,
- ...rawApplicationUsageTransactional,
- ].reduce(
+ const applicationUsage = rawApplicationUsageDaily.reduce(
(
acc,
{
@@ -224,11 +195,4 @@ export function registerApplicationUsageCollector(
);
usageCollection.registerCollector(collector);
-
- timer(ROLL_INDICES_START, ROLL_DAILY_INDICES_INTERVAL).subscribe(() =>
- rollDailyData(logger, getSavedObjectsClient())
- );
- timer(ROLL_INDICES_START, ROLL_TOTAL_INDICES_INTERVAL).subscribe(() =>
- rollTotals(logger, getSavedObjectsClient())
- );
}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/types.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/types.ts
new file mode 100644
index 0000000000000..bef835e922d8d
--- /dev/null
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/types.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 interface ApplicationViewUsage {
+ appId: string;
+ viewId: string;
+ clicks_total: number;
+ clicks_7_days: number;
+ clicks_30_days: number;
+ clicks_90_days: number;
+ minutes_on_screen_total: number;
+ minutes_on_screen_7_days: number;
+ minutes_on_screen_30_days: number;
+ minutes_on_screen_90_days: number;
+}
+
+export interface ApplicationUsageViews {
+ [serializedKey: string]: ApplicationViewUsage;
+}
+
+export interface ApplicationUsageTelemetryReport {
+ [appId: string]: {
+ appId: string;
+ viewId: string;
+ clicks_total: number;
+ clicks_7_days: number;
+ clicks_30_days: number;
+ clicks_90_days: number;
+ minutes_on_screen_total: number;
+ minutes_on_screen_7_days: number;
+ minutes_on_screen_30_days: number;
+ minutes_on_screen_90_days: number;
+ views?: ApplicationViewUsage[];
+ };
+}
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index 5959eb6aca4d4..41bb7c07bda7e 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -412,4 +412,8 @@ export const stackManagementSchema: MakeSchemaFrom = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
+ 'observability:enableInspectEsQueries': {
+ type: 'boolean',
+ _meta: { description: 'Non-default value of setting.' },
+ },
};
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
index fd63bb5bcaf43..c4a70f5065d8e 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -31,6 +31,7 @@ export interface UsageStats {
'apm:enableSignificantTerms': boolean;
'apm:enableServiceOverview': boolean;
'observability:enableAlertingExperience': boolean;
+ 'observability:enableInspectEsQueries': boolean;
'visualize:enableLabs': boolean;
'visualization:heatmap:maxBuckets': number;
'visualization:colorMapping': string;
diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap
index 67bbb46cfb607..bb426c91e827c 100644
--- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap
+++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap
@@ -60,7 +60,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
display="inlineBlock"
hasArrow={true}
isOpen={false}
- ownFocus={false}
+ ownFocus={true}
panelPaddingSize="m"
>
({
timelion: {
save: true,
+ show: true,
},
}));
core.savedObjects.registerType(timelionSheetSavedObjectType);
diff --git a/src/plugins/timelion/server/saved_objects/timelion_sheet.ts b/src/plugins/timelion/server/saved_objects/timelion_sheet.ts
index 52d7f59a7c734..231e049280bb1 100644
--- a/src/plugins/timelion/server/saved_objects/timelion_sheet.ts
+++ b/src/plugins/timelion/server/saved_objects/timelion_sheet.ts
@@ -12,6 +12,20 @@ export const timelionSheetSavedObjectType: SavedObjectsType = {
name: 'timelion-sheet',
hidden: false,
namespaceType: 'single',
+ management: {
+ icon: 'visTimelion',
+ defaultSearchField: 'title',
+ importableAndExportable: true,
+ getTitle(obj) {
+ return obj.attributes.title;
+ },
+ getInAppUrl(obj) {
+ return {
+ path: `/app/timelion#/${encodeURIComponent(obj.id)}`,
+ uiCapabilitiesPath: 'timelion.show',
+ };
+ },
+ },
mappings: {
properties: {
description: { type: 'text' },
diff --git a/src/plugins/usage_collection/common/application_usage.ts b/src/plugins/usage_collection/common/application_usage.ts
new file mode 100644
index 0000000000000..c9dd489000d35
--- /dev/null
+++ b/src/plugins/usage_collection/common/application_usage.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { MAIN_APP_DEFAULT_VIEW_ID } from './constants';
+
+export const getDailyId = ({
+ appId,
+ dayId,
+ viewId,
+}: {
+ viewId: string;
+ appId: string;
+ dayId: string;
+}) => {
+ return !viewId || viewId === MAIN_APP_DEFAULT_VIEW_ID
+ ? `${appId}:${dayId}`
+ : `${appId}:${dayId}:${viewId}`;
+};
diff --git a/src/plugins/usage_collection/server/report/schema.ts b/src/plugins/usage_collection/server/report/schema.ts
index 93203a33cd1e1..350ec8d90e765 100644
--- a/src/plugins/usage_collection/server/report/schema.ts
+++ b/src/plugins/usage_collection/server/report/schema.ts
@@ -9,6 +9,13 @@
import { schema, TypeOf } from '@kbn/config-schema';
import { METRIC_TYPE } from '@kbn/analytics';
+const applicationUsageReportSchema = schema.object({
+ minutesOnScreen: schema.number(),
+ numberOfClicks: schema.number(),
+ appId: schema.string(),
+ viewId: schema.string(),
+});
+
export const reportSchema = schema.object({
reportVersion: schema.maybe(schema.oneOf([schema.literal(3)])),
userAgent: schema.maybe(
@@ -38,17 +45,8 @@ export const reportSchema = schema.object({
})
)
),
- application_usage: schema.maybe(
- schema.recordOf(
- schema.string(),
- schema.object({
- minutesOnScreen: schema.number(),
- numberOfClicks: schema.number(),
- appId: schema.string(),
- viewId: schema.string(),
- })
- )
- ),
+ application_usage: schema.maybe(schema.recordOf(schema.string(), applicationUsageReportSchema)),
});
export type ReportSchemaType = TypeOf;
+export type ApplicationUsageReport = TypeOf;
diff --git a/src/plugins/usage_collection/server/report/store_application_usage.test.ts b/src/plugins/usage_collection/server/report/store_application_usage.test.ts
new file mode 100644
index 0000000000000..c4c9e5746e6cb
--- /dev/null
+++ b/src/plugins/usage_collection/server/report/store_application_usage.test.ts
@@ -0,0 +1,115 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 moment from 'moment';
+import { savedObjectsRepositoryMock } from '../../../../core/server/mocks';
+import { getDailyId } from '../../common/application_usage';
+import { storeApplicationUsage } from './store_application_usage';
+import { ApplicationUsageReport } from './schema';
+
+const createReport = (parts: Partial): ApplicationUsageReport => ({
+ appId: 'appId',
+ viewId: 'viewId',
+ numberOfClicks: 0,
+ minutesOnScreen: 0,
+ ...parts,
+});
+
+describe('storeApplicationUsage', () => {
+ let repository: ReturnType;
+ let timestamp: Date;
+
+ beforeEach(() => {
+ repository = savedObjectsRepositoryMock.create();
+ timestamp = new Date();
+ });
+
+ it('does not call `repository.incrementUsageCounters` when the report list is empty', async () => {
+ await storeApplicationUsage(repository, [], timestamp);
+ expect(repository.incrementCounter).not.toHaveBeenCalled();
+ });
+
+ it('calls `repository.incrementUsageCounters` with the correct parameters', async () => {
+ const report = createReport({
+ appId: 'app1',
+ viewId: 'view1',
+ numberOfClicks: 2,
+ minutesOnScreen: 5,
+ });
+
+ await storeApplicationUsage(repository, [report], timestamp);
+
+ expect(repository.incrementCounter).toHaveBeenCalledTimes(1);
+
+ expect(repository.incrementCounter).toHaveBeenCalledWith(
+ ...expectedIncrementParams(report, timestamp)
+ );
+ });
+
+ it('aggregates reports with the same appId/viewId tuple', async () => {
+ const report1 = createReport({
+ appId: 'app1',
+ viewId: 'view1',
+ numberOfClicks: 2,
+ minutesOnScreen: 5,
+ });
+ const report2 = createReport({
+ appId: 'app1',
+ viewId: 'view2',
+ numberOfClicks: 1,
+ minutesOnScreen: 7,
+ });
+ const report3 = createReport({
+ appId: 'app1',
+ viewId: 'view1',
+ numberOfClicks: 3,
+ minutesOnScreen: 9,
+ });
+
+ await storeApplicationUsage(repository, [report1, report2, report3], timestamp);
+
+ expect(repository.incrementCounter).toHaveBeenCalledTimes(2);
+
+ expect(repository.incrementCounter).toHaveBeenCalledWith(
+ ...expectedIncrementParams(
+ {
+ appId: 'app1',
+ viewId: 'view1',
+ numberOfClicks: report1.numberOfClicks + report3.numberOfClicks,
+ minutesOnScreen: report1.minutesOnScreen + report3.minutesOnScreen,
+ },
+ timestamp
+ )
+ );
+ expect(repository.incrementCounter).toHaveBeenCalledWith(
+ ...expectedIncrementParams(report2, timestamp)
+ );
+ });
+});
+
+const expectedIncrementParams = (
+ { appId, viewId, minutesOnScreen, numberOfClicks }: ApplicationUsageReport,
+ timestamp: Date
+) => {
+ const dayId = moment(timestamp).format('YYYY-MM-DD');
+ return [
+ 'application_usage_daily',
+ getDailyId({ appId, viewId, dayId }),
+ [
+ { fieldName: 'numberOfClicks', incrementBy: numberOfClicks },
+ { fieldName: 'minutesOnScreen', incrementBy: minutesOnScreen },
+ ],
+ {
+ upsertAttributes: {
+ appId,
+ viewId,
+ timestamp: moment(`${moment(dayId).format('YYYY-MM-DD')}T00:00:00Z`).toISOString(),
+ },
+ },
+ ];
+};
diff --git a/src/plugins/usage_collection/server/report/store_application_usage.ts b/src/plugins/usage_collection/server/report/store_application_usage.ts
new file mode 100644
index 0000000000000..2058b054fda8c
--- /dev/null
+++ b/src/plugins/usage_collection/server/report/store_application_usage.ts
@@ -0,0 +1,87 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 moment from 'moment';
+import { Writable } from '@kbn/utility-types';
+import { ISavedObjectsRepository } from 'src/core/server';
+import { ApplicationUsageReport } from './schema';
+import { getDailyId } from '../../common/application_usage';
+
+type WritableApplicationUsageReport = Writable;
+
+export const storeApplicationUsage = async (
+ repository: ISavedObjectsRepository,
+ appUsages: ApplicationUsageReport[],
+ timestamp: Date
+) => {
+ if (!appUsages.length) {
+ return;
+ }
+
+ const dayId = getDayId(timestamp);
+ const aggregatedReports = aggregateAppUsages(appUsages);
+
+ return Promise.allSettled(
+ aggregatedReports.map(async (report) => incrementUsageCounters(repository, report, dayId))
+ );
+};
+
+const aggregateAppUsages = (appUsages: ApplicationUsageReport[]) => {
+ return [
+ ...appUsages
+ .reduce((map, appUsage) => {
+ const key = getKey(appUsage);
+ const aggregated: WritableApplicationUsageReport = map.get(key) ?? {
+ appId: appUsage.appId,
+ viewId: appUsage.viewId,
+ minutesOnScreen: 0,
+ numberOfClicks: 0,
+ };
+
+ aggregated.minutesOnScreen += appUsage.minutesOnScreen;
+ aggregated.numberOfClicks += appUsage.numberOfClicks;
+
+ map.set(key, aggregated);
+ return map;
+ }, new Map())
+ .values(),
+ ];
+};
+
+const incrementUsageCounters = (
+ repository: ISavedObjectsRepository,
+ { appId, viewId, numberOfClicks, minutesOnScreen }: WritableApplicationUsageReport,
+ dayId: string
+) => {
+ const dailyId = getDailyId({ appId, viewId, dayId });
+
+ return repository.incrementCounter(
+ 'application_usage_daily',
+ dailyId,
+ [
+ { fieldName: 'numberOfClicks', incrementBy: numberOfClicks },
+ { fieldName: 'minutesOnScreen', incrementBy: minutesOnScreen },
+ ],
+ {
+ upsertAttributes: {
+ appId,
+ viewId,
+ timestamp: getTimestamp(dayId),
+ },
+ }
+ );
+};
+
+const getKey = ({ viewId, appId }: ApplicationUsageReport) => `${appId}___${viewId}`;
+
+const getDayId = (timestamp: Date) => moment(timestamp).format('YYYY-MM-DD');
+
+const getTimestamp = (dayId: string) => {
+ // Concatenating the day in YYYY-MM-DD form to T00:00:00Z to reduce the TZ effects
+ return moment(`${moment(dayId).format('YYYY-MM-DD')}T00:00:00Z`).toISOString();
+};
diff --git a/src/plugins/usage_collection/server/report/store_report.test.mocks.ts b/src/plugins/usage_collection/server/report/store_report.test.mocks.ts
new file mode 100644
index 0000000000000..d151e7d7a5ddd
--- /dev/null
+++ b/src/plugins/usage_collection/server/report/store_report.test.mocks.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export const storeApplicationUsageMock = jest.fn();
+jest.doMock('./store_application_usage', () => ({
+ storeApplicationUsage: storeApplicationUsageMock,
+}));
diff --git a/src/plugins/usage_collection/server/report/store_report.test.ts b/src/plugins/usage_collection/server/report/store_report.test.ts
index 7174a54067246..dfcdd1f8e7e42 100644
--- a/src/plugins/usage_collection/server/report/store_report.test.ts
+++ b/src/plugins/usage_collection/server/report/store_report.test.ts
@@ -6,6 +6,8 @@
* Side Public License, v 1.
*/
+import { storeApplicationUsageMock } from './store_report.test.mocks';
+
import { savedObjectsRepositoryMock } from '../../../../core/server/mocks';
import { storeReport } from './store_report';
import { ReportSchemaType } from './schema';
@@ -16,8 +18,17 @@ describe('store_report', () => {
const momentTimestamp = moment();
const date = momentTimestamp.format('DDMMYYYY');
+ let repository: ReturnType;
+
+ beforeEach(() => {
+ repository = savedObjectsRepositoryMock.create();
+ });
+
+ afterEach(() => {
+ storeApplicationUsageMock.mockReset();
+ });
+
test('stores report for all types of data', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
const report: ReportSchemaType = {
reportVersion: 3,
userAgent: {
@@ -53,9 +64,9 @@ describe('store_report', () => {
},
},
};
- await storeReport(savedObjectClient, report);
+ await storeReport(repository, report);
- expect(savedObjectClient.create).toHaveBeenCalledWith(
+ expect(repository.create).toHaveBeenCalledWith(
'ui-metric',
{ count: 1 },
{
@@ -63,51 +74,45 @@ describe('store_report', () => {
overwrite: true,
}
);
- expect(savedObjectClient.incrementCounter).toHaveBeenNthCalledWith(
+ expect(repository.incrementCounter).toHaveBeenNthCalledWith(
1,
'ui-metric',
'test-app-name:test-event-name',
[{ fieldName: 'count', incrementBy: 3 }]
);
- expect(savedObjectClient.incrementCounter).toHaveBeenNthCalledWith(
+ expect(repository.incrementCounter).toHaveBeenNthCalledWith(
2,
'ui-counter',
`test-app-name:${date}:${METRIC_TYPE.LOADED}:test-event-name`,
[{ fieldName: 'count', incrementBy: 1 }]
);
- expect(savedObjectClient.incrementCounter).toHaveBeenNthCalledWith(
+ expect(repository.incrementCounter).toHaveBeenNthCalledWith(
3,
'ui-counter',
`test-app-name:${date}:${METRIC_TYPE.CLICK}:test-event-name`,
[{ fieldName: 'count', incrementBy: 2 }]
);
- expect(savedObjectClient.bulkCreate).toHaveBeenNthCalledWith(1, [
- {
- type: 'application_usage_transactional',
- attributes: {
- numberOfClicks: 3,
- minutesOnScreen: 10,
- appId: 'appId',
- viewId: 'appId_view',
- timestamp: expect.any(Date),
- },
- },
- ]);
+
+ expect(storeApplicationUsageMock).toHaveBeenCalledTimes(1);
+ expect(storeApplicationUsageMock).toHaveBeenCalledWith(
+ repository,
+ Object.values(report.application_usage as Record),
+ expect.any(Date)
+ );
});
test('it should not fail if nothing to store', async () => {
- const savedObjectClient = savedObjectsRepositoryMock.create();
const report: ReportSchemaType = {
reportVersion: 3,
userAgent: void 0,
uiCounter: void 0,
application_usage: void 0,
};
- await storeReport(savedObjectClient, report);
+ await storeReport(repository, report);
- expect(savedObjectClient.bulkCreate).not.toHaveBeenCalled();
- expect(savedObjectClient.incrementCounter).not.toHaveBeenCalled();
- expect(savedObjectClient.create).not.toHaveBeenCalled();
- expect(savedObjectClient.create).not.toHaveBeenCalled();
+ expect(repository.bulkCreate).not.toHaveBeenCalled();
+ expect(repository.incrementCounter).not.toHaveBeenCalled();
+ expect(repository.create).not.toHaveBeenCalled();
+ expect(repository.create).not.toHaveBeenCalled();
});
});
diff --git a/src/plugins/usage_collection/server/report/store_report.ts b/src/plugins/usage_collection/server/report/store_report.ts
index c3e04990d5793..0545a54792d45 100644
--- a/src/plugins/usage_collection/server/report/store_report.ts
+++ b/src/plugins/usage_collection/server/report/store_report.ts
@@ -10,6 +10,7 @@ import { ISavedObjectsRepository } from 'src/core/server';
import moment from 'moment';
import { chain, sumBy } from 'lodash';
import { ReportSchemaType } from './schema';
+import { storeApplicationUsage } from './store_application_usage';
export async function storeReport(
internalRepository: ISavedObjectsRepository,
@@ -17,11 +18,11 @@ export async function storeReport(
) {
const uiCounters = report.uiCounter ? Object.entries(report.uiCounter) : [];
const userAgents = report.userAgent ? Object.entries(report.userAgent) : [];
- const appUsage = report.application_usage ? Object.values(report.application_usage) : [];
+ const appUsages = report.application_usage ? Object.values(report.application_usage) : [];
const momentTimestamp = moment();
- const timestamp = momentTimestamp.toDate();
const date = momentTimestamp.format('DDMMYYYY');
+ const timestamp = momentTimestamp.toDate();
return Promise.allSettled([
// User Agent
@@ -64,21 +65,6 @@ export async function storeReport(
];
}),
// Application Usage
- ...[
- (async () => {
- if (!appUsage.length) return [];
- const { saved_objects: savedObjects } = await internalRepository.bulkCreate(
- appUsage.map((metric) => ({
- type: 'application_usage_transactional',
- attributes: {
- ...metric,
- timestamp,
- },
- }))
- );
-
- return savedObjects;
- })(),
- ],
+ storeApplicationUsage(internalRepository, appUsages, timestamp),
]);
}
diff --git a/src/plugins/vis_type_timeseries/common/extract_index_patterns.test.ts b/src/plugins/vis_type_timeseries/common/extract_index_patterns.test.ts
deleted file mode 100644
index c4da2085855e6..0000000000000
--- a/src/plugins/vis_type_timeseries/common/extract_index_patterns.test.ts
+++ /dev/null
@@ -1,35 +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 { extractIndexPatterns } from './extract_index_patterns';
-import { PanelSchema } from './types';
-
-describe('extractIndexPatterns(vis)', () => {
- let panel: PanelSchema;
-
- beforeEach(() => {
- panel = {
- index_pattern: '*',
- series: [
- {
- override_index_pattern: 1,
- series_index_pattern: 'example-1-*',
- },
- {
- override_index_pattern: 1,
- series_index_pattern: 'example-2-*',
- },
- ],
- annotations: [{ index_pattern: 'notes-*' }, { index_pattern: 'example-1-*' }],
- } as PanelSchema;
- });
-
- test('should return index patterns', () => {
- expect(extractIndexPatterns(panel, '')).toEqual(['*', 'example-1-*', 'example-2-*', 'notes-*']);
- });
-});
diff --git a/src/plugins/vis_type_timeseries/common/extract_index_patterns.ts b/src/plugins/vis_type_timeseries/common/extract_index_patterns.ts
deleted file mode 100644
index c716ae7abb821..0000000000000
--- a/src/plugins/vis_type_timeseries/common/extract_index_patterns.ts
+++ /dev/null
@@ -1,43 +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 { uniq } from 'lodash';
-import { PanelSchema } from '../common/types';
-
-export function extractIndexPatterns(
- panel: PanelSchema,
- defaultIndex?: PanelSchema['default_index_pattern']
-) {
- const patterns: string[] = [];
-
- if (panel.index_pattern) {
- patterns.push(panel.index_pattern);
- }
-
- panel.series.forEach((series) => {
- const indexPattern = series.series_index_pattern;
- if (indexPattern && series.override_index_pattern) {
- patterns.push(indexPattern);
- }
- });
-
- if (panel.annotations) {
- panel.annotations.forEach((item) => {
- const indexPattern = item.index_pattern;
- if (indexPattern) {
- patterns.push(indexPattern);
- }
- });
- }
-
- if (patterns.length === 0 && defaultIndex) {
- patterns.push(defaultIndex);
- }
-
- return uniq(patterns).sort();
-}
diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.test.ts b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts
new file mode 100644
index 0000000000000..d1036aab2dc3e
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { toSanitizedFieldType } from './fields_utils';
+import type { FieldSpec, RuntimeField } from '../../data/common';
+
+describe('fields_utils', () => {
+ describe('toSanitizedFieldType', () => {
+ const mockedField = {
+ lang: 'lang',
+ conflictDescriptions: {},
+ aggregatable: true,
+ name: 'name',
+ type: 'type',
+ esTypes: ['long', 'geo'],
+ } as FieldSpec;
+
+ test('should sanitize fields ', async () => {
+ const fields = [mockedField] as FieldSpec[];
+
+ expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "label": "name",
+ "name": "name",
+ "type": "type",
+ },
+ ]
+ `);
+ });
+
+ test('should filter runtime fields', async () => {
+ const fields: FieldSpec[] = [
+ {
+ ...mockedField,
+ runtimeField: {} as RuntimeField,
+ },
+ ];
+
+ expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
+ });
+
+ test('should filter non-aggregatable fields', async () => {
+ const fields: FieldSpec[] = [
+ {
+ ...mockedField,
+ aggregatable: false,
+ },
+ ];
+
+ expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
+ });
+
+ test('should filter nested fields', async () => {
+ const fields: FieldSpec[] = [
+ {
+ ...mockedField,
+ subType: {
+ nested: {
+ path: 'path',
+ },
+ },
+ },
+ ];
+ expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
+ });
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.ts b/src/plugins/vis_type_timeseries/common/fields_utils.ts
new file mode 100644
index 0000000000000..04499d5320ab8
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/common/fields_utils.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { FieldSpec } from '../../data/common';
+import { isNestedField } from '../../data/common';
+import { SanitizedFieldType } from './types';
+
+export const toSanitizedFieldType = (fields: FieldSpec[]) => {
+ return fields
+ .filter(
+ (field) =>
+ // Make sure to only include mapped fields, e.g. no index pattern runtime fields
+ !field.runtimeField && field.aggregatable && !isNestedField(field)
+ )
+ .map(
+ (field) =>
+ ({
+ name: field.name,
+ label: field.customLabel ?? field.name,
+ type: field.type,
+ } as SanitizedFieldType)
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts
new file mode 100644
index 0000000000000..515fadffb6b32
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts
@@ -0,0 +1,139 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 {
+ extractIndexPatternValues,
+ isStringTypeIndexPattern,
+ fetchIndexPattern,
+} from './index_patterns_utils';
+import { PanelSchema } from './types';
+import { IndexPattern, IndexPatternsService } from '../../data/common';
+
+describe('isStringTypeIndexPattern', () => {
+ test('should returns true on string-based index', () => {
+ expect(isStringTypeIndexPattern('index')).toBeTruthy();
+ });
+ test('should returns false on object-based index', () => {
+ expect(isStringTypeIndexPattern({ id: 'id' })).toBeFalsy();
+ });
+});
+
+describe('extractIndexPatterns', () => {
+ let panel: PanelSchema;
+
+ beforeEach(() => {
+ panel = {
+ index_pattern: '*',
+ series: [
+ {
+ override_index_pattern: 1,
+ series_index_pattern: 'example-1-*',
+ },
+ {
+ override_index_pattern: 1,
+ series_index_pattern: 'example-2-*',
+ },
+ ],
+ annotations: [{ index_pattern: 'notes-*' }, { index_pattern: 'example-1-*' }],
+ } as PanelSchema;
+ });
+
+ test('should return index patterns', () => {
+ expect(extractIndexPatternValues(panel, '')).toEqual([
+ '*',
+ 'example-1-*',
+ 'example-2-*',
+ 'notes-*',
+ ]);
+ });
+});
+
+describe('fetchIndexPattern', () => {
+ let mockedIndices: IndexPattern[] | [];
+ let indexPatternsService: IndexPatternsService;
+
+ beforeEach(() => {
+ mockedIndices = [];
+
+ indexPatternsService = ({
+ getDefault: jest.fn(() => Promise.resolve({ id: 'default', title: 'index' })),
+ get: jest.fn(() => Promise.resolve(mockedIndices[0])),
+ find: jest.fn(() => Promise.resolve(mockedIndices || [])),
+ } as unknown) as IndexPatternsService;
+ });
+
+ test('should return default index on no input value', async () => {
+ const value = await fetchIndexPattern('', indexPatternsService);
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "default",
+ "title": "index",
+ },
+ "indexPatternString": "index",
+ }
+ `);
+ });
+
+ describe('text-based index', () => {
+ test('should return the Kibana index if it exists', async () => {
+ mockedIndices = [
+ {
+ id: 'indexId',
+ title: 'indexTitle',
+ },
+ ] as IndexPattern[];
+
+ const value = await fetchIndexPattern('indexTitle', indexPatternsService);
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "indexId",
+ "title": "indexTitle",
+ },
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+
+ test('should return only indexPatternString if Kibana index does not exist', async () => {
+ const value = await fetchIndexPattern('indexTitle', indexPatternsService);
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": undefined,
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+ });
+
+ describe('object-based index', () => {
+ test('should return the Kibana index if it exists', async () => {
+ mockedIndices = [
+ {
+ id: 'indexId',
+ title: 'indexTitle',
+ },
+ ] as IndexPattern[];
+
+ const value = await fetchIndexPattern({ id: 'indexId' }, indexPatternsService);
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "indexId",
+ "title": "indexTitle",
+ },
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts
new file mode 100644
index 0000000000000..398d1c30ed5a7
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { uniq } from 'lodash';
+import { PanelSchema, IndexPatternValue, FetchedIndexPattern } from '../common/types';
+import { IndexPatternsService } from '../../data/common';
+
+export const isStringTypeIndexPattern = (
+ indexPatternValue: IndexPatternValue
+): indexPatternValue is string => typeof indexPatternValue === 'string';
+
+export const getIndexPatternKey = (indexPatternValue: IndexPatternValue) =>
+ isStringTypeIndexPattern(indexPatternValue) ? indexPatternValue : indexPatternValue?.id ?? '';
+
+export const extractIndexPatternValues = (
+ panel: PanelSchema,
+ defaultIndex?: PanelSchema['default_index_pattern']
+) => {
+ const patterns: IndexPatternValue[] = [];
+
+ if (panel.index_pattern) {
+ patterns.push(panel.index_pattern);
+ }
+
+ panel.series.forEach((series) => {
+ const indexPattern = series.series_index_pattern;
+ if (indexPattern && series.override_index_pattern) {
+ patterns.push(indexPattern);
+ }
+ });
+
+ if (panel.annotations) {
+ panel.annotations.forEach((item) => {
+ const indexPattern = item.index_pattern;
+ if (indexPattern) {
+ patterns.push(indexPattern);
+ }
+ });
+ }
+
+ if (patterns.length === 0 && defaultIndex) {
+ patterns.push(defaultIndex);
+ }
+
+ return uniq(patterns).sort();
+};
+
+export const fetchIndexPattern = async (
+ indexPatternValue: IndexPatternValue | undefined,
+ indexPatternsService: Pick
+): Promise => {
+ let indexPattern: FetchedIndexPattern['indexPattern'];
+ let indexPatternString: string = '';
+
+ if (!indexPatternValue) {
+ indexPattern = await indexPatternsService.getDefault();
+ } else {
+ if (isStringTypeIndexPattern(indexPatternValue)) {
+ indexPattern = (await indexPatternsService.find(indexPatternValue)).find(
+ (index) => index.title === indexPatternValue
+ );
+
+ if (!indexPattern) {
+ indexPatternString = indexPatternValue;
+ }
+ } else if (indexPatternValue.id) {
+ indexPattern = await indexPatternsService.get(indexPatternValue.id);
+ }
+ }
+
+ return {
+ indexPattern,
+ indexPatternString: indexPattern?.title ?? indexPatternString,
+ };
+};
diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts
index 7d93232f310c9..1fe6196ad545b 100644
--- a/src/plugins/vis_type_timeseries/common/types.ts
+++ b/src/plugins/vis_type_timeseries/common/types.ts
@@ -13,10 +13,12 @@ import {
seriesItems,
visPayloadSchema,
fieldObject,
+ indexPattern,
annotationsItems,
} from './vis_schema';
import { PANEL_TYPES } from './panel_types';
import { TimeseriesUIRestrictions } from './ui_restrictions';
+import { IndexPattern } from '../../data/common';
export type AnnotationItemsSchema = TypeOf;
export type SeriesItemsSchema = TypeOf;
@@ -24,6 +26,12 @@ export type MetricsItemsSchema = TypeOf;
export type PanelSchema = TypeOf;
export type VisPayload = TypeOf;
export type FieldObject = TypeOf;
+export type IndexPatternValue = TypeOf;
+
+export interface FetchedIndexPattern {
+ indexPattern: IndexPattern | undefined | null;
+ indexPatternString: string | undefined;
+}
export interface PanelData {
id: string;
diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts
index a6bf70948bc1b..297b021fa9e77 100644
--- a/src/plugins/vis_type_timeseries/common/vis_schema.ts
+++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts
@@ -28,7 +28,7 @@ const numberOptional = schema.maybe(schema.number());
const queryObject = schema.object({
language: schema.string(),
- query: schema.string(),
+ query: schema.oneOf([schema.string(), schema.any()]),
});
const stringOrNumberOptionalNullable = schema.nullable(
schema.oneOf([stringOptionalNullable, numberOptional])
@@ -37,6 +37,13 @@ const numberOptionalOrEmptyString = schema.maybe(
schema.oneOf([numberOptional, schema.literal('')])
);
+export const indexPattern = schema.oneOf([
+ schema.maybe(schema.string()),
+ schema.object({
+ id: schema.string(),
+ }),
+]);
+
export const fieldObject = stringOptionalNullable;
export const annotationsItems = schema.object({
@@ -47,7 +54,7 @@ export const annotationsItems = schema.object({
id: schema.string(),
ignore_global_filters: numberIntegerOptional,
ignore_panel_filters: numberIntegerOptional,
- index_pattern: stringOptionalNullable,
+ index_pattern: indexPattern,
query_string: schema.maybe(queryObject),
template: stringOptionalNullable,
time_field: fieldObject,
@@ -68,6 +75,7 @@ const gaugeColorRulesItems = schema.object({
operator: stringOptionalNullable,
value: schema.maybe(schema.nullable(schema.number())),
});
+
export const metricsItems = schema.object({
field: fieldObject,
id: stringRequired,
@@ -167,7 +175,7 @@ export const seriesItems = schema.object({
point_size: numberOptionalOrEmptyString,
separate_axis: numberIntegerOptional,
seperate_axis: numberIntegerOptional,
- series_index_pattern: stringOptionalNullable,
+ series_index_pattern: indexPattern,
series_max_bars: numberIntegerOptional,
series_time_field: fieldObject,
series_interval: stringOptionalNullable,
@@ -195,6 +203,7 @@ export const seriesItems = schema.object({
});
export const panel = schema.object({
+ use_kibana_indexes: schema.maybe(schema.boolean()),
annotations: schema.maybe(schema.arrayOf(annotationsItems)),
axis_formatter: stringRequired,
axis_position: stringRequired,
@@ -218,7 +227,7 @@ export const panel = schema.object({
id: stringRequired,
ignore_global_filters: numberOptional,
ignore_global_filter: numberOptional,
- index_pattern: stringRequired,
+ index_pattern: indexPattern,
max_bars: numberIntegerOptional,
interval: stringRequired,
isModelInvalid: schema.maybe(schema.boolean()),
diff --git a/src/plugins/vis_type_timeseries/kibana.json b/src/plugins/vis_type_timeseries/kibana.json
index aa5eac84663ad..242b62a2c5ee4 100644
--- a/src/plugins/vis_type_timeseries/kibana.json
+++ b/src/plugins/vis_type_timeseries/kibana.json
@@ -5,5 +5,6 @@
"server": true,
"ui": true,
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "visualize"],
+ "optionalPlugins": ["usageCollection"],
"requiredBundles": ["kibanaUtils", "kibanaReact"]
}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
index 4fc7b89e23765..82989cc15d6c9 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx
@@ -10,8 +10,8 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiComboBox, EuiComboBoxProps, EuiComboBoxOptionOption } from '@elastic/eui';
import { METRIC_TYPES } from '../../../../common/metric_types';
-
-import type { SanitizedFieldType } from '../../../../common/types';
+import { getIndexPatternKey } from '../../../../common/index_patterns_utils';
+import type { SanitizedFieldType, IndexPatternValue } from '../../../../common/types';
import type { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions';
// @ts-ignore
@@ -20,7 +20,7 @@ import { isFieldEnabled } from '../../lib/check_ui_restrictions';
interface FieldSelectProps {
type: string;
fields: Record;
- indexPattern: string;
+ indexPattern: IndexPatternValue;
value?: string | null;
onChange: (options: Array>) => void;
disabled?: boolean;
@@ -62,8 +62,10 @@ export function FieldSelect({
const selectedOptions: Array> = [];
let newPlaceholder = placeholder;
+ const fieldsSelector = getIndexPatternKey(indexPattern);
+
const groupedOptions: EuiComboBoxProps['options'] = Object.values(
- (fields[indexPattern] || []).reduce>>(
+ (fields[fieldsSelector] || []).reduce>>(
(acc, field) => {
if (placeholder === field?.name) {
newPlaceholder = field.label ?? field.name;
diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js
index f95eeb4816128..ab0db6daae18a 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js
@@ -32,8 +32,8 @@ import {
EuiCode,
EuiText,
} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
+import { IndexPatternSelect } from './lib/index_pattern_select';
function newAnnotation() {
return {
@@ -91,7 +91,6 @@ export class AnnotationsEditor extends Component {
const htmlId = htmlIdGenerator(model.id);
const handleAdd = collectionActions.handleAdd.bind(null, this.props, newAnnotation);
const handleDelete = collectionActions.handleDelete.bind(null, this.props, model);
- const defaultIndexPattern = this.props.model.default_index_pattern;
return (
@@ -108,30 +107,11 @@ export class AnnotationsEditor extends Component {
-
- }
- helpText={
- defaultIndexPattern &&
- !model.index_pattern &&
- i18n.translate('visTypeTimeseries.annotationsEditor.searchByDefaultIndex', {
- defaultMessage: 'Default index pattern is used. To query all indexes use *',
- })
- }
- fullWidth
- >
-
-
+
{
const config = getUISettings();
const timeFieldName = `${prefix}time_field`;
@@ -89,13 +91,6 @@ export const IndexPattern = ({
const handleTextChange = createTextHandler(onChange);
const timeRangeOptions = [
- {
- label: i18n.translate('visTypeTimeseries.indexPattern.timeRange.lastValue', {
- defaultMessage: 'Last value',
- }),
- value: TIME_RANGE_DATA_MODES.LAST_VALUE,
- disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.LAST_VALUE, uiRestrictions),
- },
{
label: i18n.translate('visTypeTimeseries.indexPattern.timeRange.entireTimeRange', {
defaultMessage: 'Entire time range',
@@ -103,6 +98,13 @@ export const IndexPattern = ({
value: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE, uiRestrictions),
},
+ {
+ label: i18n.translate('visTypeTimeseries.indexPattern.timeRange.lastValue', {
+ defaultMessage: 'Last value',
+ }),
+ value: TIME_RANGE_DATA_MODES.LAST_VALUE,
+ disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.LAST_VALUE, uiRestrictions),
+ },
];
const defaults = {
@@ -139,6 +141,7 @@ export const IndexPattern = ({
})}
>
-
-
-
+
- {allowLevelofDetail && (
+ {allowLevelOfDetail && (
>;
+
+/** @internal **/
+type SelectedOptions = EuiComboBoxProps['selectedOptions'];
+
+const toComboBoxOptions = (options: IdsWithTitle) =>
+ options.map(({ title, id }) => ({ label: title, id }));
+
+export const ComboBoxSelect = ({
+ fetchedIndex,
+ onIndexChange,
+ onModeChange,
+ disabled,
+ placeholder,
+ allowSwitchMode,
+ 'data-test-subj': dataTestSubj,
+}: SelectIndexComponentProps) => {
+ const [availableIndexes, setAvailableIndexes] = useState([]);
+ const [selectedOptions, setSelectedOptions] = useState([]);
+
+ const onComboBoxChange: EuiComboBoxProps['onChange'] = useCallback(
+ ([selected]) => {
+ onIndexChange(selected ? { id: selected.id } : '');
+ },
+ [onIndexChange]
+ );
+
+ useEffect(() => {
+ let options: SelectedOptions = [];
+ const { indexPattern, indexPatternString } = fetchedIndex;
+
+ if (indexPattern || indexPatternString) {
+ if (!indexPattern) {
+ options = [{ label: indexPatternString ?? '' }];
+ } else {
+ options = [
+ {
+ id: indexPattern.id,
+ label: indexPattern.title,
+ },
+ ];
+ }
+ }
+ setSelectedOptions(options);
+ }, [fetchedIndex]);
+
+ useEffect(() => {
+ async function fetchIndexes() {
+ setAvailableIndexes(await getDataStart().indexPatterns.getIdsWithTitle());
+ }
+
+ fetchIndexes();
+ }, []);
+
+ return (
+
+ ),
+ })}
+ />
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/field_text_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/field_text_select.tsx
new file mode 100644
index 0000000000000..86d1758932301
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/field_text_select.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 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, { useCallback, useState, useEffect } from 'react';
+import useDebounce from 'react-use/lib/useDebounce';
+
+import { EuiFieldText, EuiFieldTextProps } from '@elastic/eui';
+import { SwitchModePopover } from './switch_mode_popover';
+
+import type { SelectIndexComponentProps } from './types';
+
+export const FieldTextSelect = ({
+ fetchedIndex,
+ onIndexChange,
+ disabled,
+ placeholder,
+ onModeChange,
+ allowSwitchMode,
+ 'data-test-subj': dataTestSubj,
+}: SelectIndexComponentProps) => {
+ const [inputValue, setInputValue] = useState();
+ const { indexPatternString } = fetchedIndex;
+
+ const onFieldTextChange: EuiFieldTextProps['onChange'] = useCallback((e) => {
+ setInputValue(e.target.value);
+ }, []);
+
+ useEffect(() => {
+ if (inputValue === undefined) {
+ setInputValue(indexPatternString ?? '');
+ }
+ }, [indexPatternString, inputValue]);
+
+ useDebounce(
+ () => {
+ if (inputValue !== indexPatternString) {
+ onIndexChange(inputValue);
+ }
+ },
+ 150,
+ [inputValue, onIndexChange]
+ );
+
+ return (
+
+ ),
+ })}
+ />
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts
new file mode 100644
index 0000000000000..584f13e7a025b
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { IndexPatternSelect } from './index_pattern_select';
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx
new file mode 100644
index 0000000000000..28b9c173a2b1b
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.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 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, { useState, useContext, useCallback, useEffect } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { EuiFormRow, EuiText, EuiLink, htmlIdGenerator } from '@elastic/eui';
+import { getCoreStart, getDataStart } from '../../../../services';
+import { PanelModelContext } from '../../../contexts/panel_model_context';
+
+import {
+ isStringTypeIndexPattern,
+ fetchIndexPattern,
+} from '../../../../../common/index_patterns_utils';
+
+import { FieldTextSelect } from './field_text_select';
+import { ComboBoxSelect } from './combo_box_select';
+
+import type { IndexPatternValue, FetchedIndexPattern } from '../../../../../common/types';
+
+const USE_KIBANA_INDEXES_KEY = 'use_kibana_indexes';
+
+interface IndexPatternSelectProps {
+ value: IndexPatternValue;
+ indexPatternName: string;
+ onChange: Function;
+ disabled?: boolean;
+ allowIndexSwitchingMode?: boolean;
+}
+
+const defaultIndexPatternHelpText = i18n.translate(
+ 'visTypeTimeseries.indexPatternSelect.defaultIndexPatternText',
+ {
+ defaultMessage: 'Default index pattern is used.',
+ }
+);
+
+const queryAllIndexesHelpText = i18n.translate(
+ 'visTypeTimeseries.indexPatternSelect.queryAllIndexesText',
+ {
+ defaultMessage: 'To query all indexes use *',
+ }
+);
+
+const indexPatternLabel = i18n.translate('visTypeTimeseries.indexPatternSelect.label', {
+ defaultMessage: 'Index pattern',
+});
+
+export const IndexPatternSelect = ({
+ value,
+ indexPatternName,
+ onChange,
+ disabled,
+ allowIndexSwitchingMode,
+}: IndexPatternSelectProps) => {
+ const htmlId = htmlIdGenerator();
+ const panelModel = useContext(PanelModelContext);
+ const [fetchedIndex, setFetchedIndex] = useState();
+ const useKibanaIndices = Boolean(panelModel?.[USE_KIBANA_INDEXES_KEY]);
+ const Component = useKibanaIndices ? ComboBoxSelect : FieldTextSelect;
+
+ const onIndexChange = useCallback(
+ (index: IndexPatternValue) => {
+ onChange({
+ [indexPatternName]: index,
+ });
+ },
+ [indexPatternName, onChange]
+ );
+
+ const onModeChange = useCallback(
+ (useKibanaIndexes: boolean, index?: FetchedIndexPattern) => {
+ onChange({
+ [USE_KIBANA_INDEXES_KEY]: useKibanaIndexes,
+ [indexPatternName]: index?.indexPattern?.id
+ ? {
+ id: index.indexPattern.id,
+ }
+ : '',
+ });
+ },
+ [onChange, indexPatternName]
+ );
+
+ const navigateToCreateIndexPatternPage = useCallback(() => {
+ const coreStart = getCoreStart();
+
+ coreStart.application.navigateToApp('management', {
+ path: `/kibana/indexPatterns/create?name=${fetchedIndex!.indexPatternString ?? ''}`,
+ });
+ }, [fetchedIndex]);
+
+ useEffect(() => {
+ async function fetchIndex() {
+ const { indexPatterns } = getDataStart();
+
+ setFetchedIndex(
+ value
+ ? await fetchIndexPattern(value, indexPatterns)
+ : {
+ indexPattern: undefined,
+ indexPatternString: undefined,
+ }
+ );
+ }
+
+ fetchIndex();
+ }, [value]);
+
+ if (!fetchedIndex) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ ) : null
+ }
+ >
+
+
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx
new file mode 100644
index 0000000000000..5f5506ce4a332
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/switch_mode_popover.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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, { useState, useCallback } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import {
+ EuiButtonIcon,
+ EuiPopover,
+ EuiPopoverTitle,
+ EuiSpacer,
+ EuiSwitch,
+ EuiText,
+} from '@elastic/eui';
+
+import type { PopoverProps } from './types';
+
+export const SwitchModePopover = ({ onModeChange, useKibanaIndices }: PopoverProps) => {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const closePopover = useCallback(() => setIsPopoverOpen(false), []);
+ const onButtonClick = useCallback(() => setIsPopoverOpen((isOpen) => !isOpen), []);
+
+ const switchMode = useCallback(() => {
+ onModeChange(!useKibanaIndices);
+ }, [onModeChange, useKibanaIndices]);
+
+ return (
+
+ }
+ isOpen={isPopoverOpen}
+ closePopover={closePopover}
+ style={{ height: 'auto' }}
+ >
+
+
+ {i18n.translate('visTypeTimeseries.indexPatternSelect.switchModePopover.title', {
+ defaultMessage: 'Index pattern selection mode',
+ })}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts
new file mode 100644
index 0000000000000..93b15402e3c24
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import type { Assign } from '@kbn/utility-types';
+import type { FetchedIndexPattern, IndexPatternValue } from '../../../../../common/types';
+
+/** @internal **/
+export interface SelectIndexComponentProps {
+ fetchedIndex: FetchedIndexPattern;
+ onIndexChange: (value: IndexPatternValue) => void;
+ onModeChange: (useKibanaIndexes: boolean, index?: FetchedIndexPattern) => void;
+ 'data-test-subj': string;
+ placeholder?: string;
+ disabled?: boolean;
+ allowSwitchMode?: boolean;
+}
+
+/** @internal **/
+export type PopoverProps = Assign<
+ Pick,
+ {
+ useKibanaIndices: boolean;
+ }
+>;
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx
index e302bbb9adb0b..f39ff6923f5ce 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/gauge.tsx
@@ -29,12 +29,11 @@ import type { Writable } from '@kbn/utility-types';
// @ts-ignore
import { SeriesEditor } from '../series_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { IndexPattern } from '../index_pattern';
import { createSelectHandler } from '../lib/create_select_handler';
import { ColorRules } from '../color_rules';
import { ColorPicker } from '../color_picker';
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
import { QueryBarWrapper } from '../query_bar_wrapper';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
import { YesNo } from '../yes_no';
@@ -128,6 +127,7 @@ export class GaugePanelConfig extends Component<
fields={this.props.fields}
model={this.props.model}
onChange={this.props.onChange}
+ allowIndexSwitchingMode={true}
/>
@@ -149,10 +149,10 @@ export class GaugePanelConfig extends Component<
language: model.filter?.language || getDefaultQueryLanguage(),
query: model.filter?.query || '',
}}
- onChange={(filter: PanelConfigProps['model']['filter']) =>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ onChange={(filter) => {
+ this.props.onChange({ filter });
+ }}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
@@ -321,6 +321,7 @@ export class GaugePanelConfig extends Component<
this.switchTab(PANEL_CONFIG_TABS.DATA)}
+ data-test-subj="gaugeEditorDataBtn"
>
this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
+ data-test-subj="gaugeEditorPanelOptionsBtn"
>
@@ -161,13 +161,13 @@ export class MarkdownPanelConfig extends Component<
>
{
+ this.props.onChange({ filter });
}}
- onChange={(filter: PanelConfigProps['model']['filter']) =>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx
index ec11f94d245a0..3ab49c1bef873 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/metric.tsx
@@ -25,12 +25,10 @@ import { FormattedMessage } from '@kbn/i18n/react';
// @ts-expect-error
import { SeriesEditor } from '../series_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { IndexPattern } from '../index_pattern';
import { ColorRules } from '../color_rules';
import { YesNo } from '../yes_no';
-
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
import { QueryBarWrapper } from '../query_bar_wrapper';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
import { limitOfSeries } from '../../../../common/ui_restrictions';
@@ -93,6 +91,7 @@ export class MetricPanelConfig extends Component<
fields={this.props.fields}
model={this.props.model}
onChange={this.props.onChange}
+ allowIndexSwitchingMode={true}
/>
@@ -111,13 +110,13 @@ export class MetricPanelConfig extends Component<
>
{
+ this.props.onChange({ filter });
}}
- onChange={(filter: PanelConfigProps['model']['filter']) =>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
@@ -166,6 +165,7 @@ export class MetricPanelConfig extends Component<
this.switchTab(PANEL_CONFIG_TABS.DATA)}
+ data-test-subj="metricEditorDataBtn"
>
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx
index 20e07be4e3fa4..f3d01df19666a 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx
@@ -31,16 +31,17 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { FieldSelect } from '../aggs/field_select';
// @ts-expect-error not typed yet
import { SeriesEditor } from '../series_editor';
-// @ts-ignore should be typed after https://github.com/elastic/kibana/pull/92812 to reduce conflicts
+// @ts-expect-error not typed yet
import { IndexPattern } from '../index_pattern';
import { YesNo } from '../yes_no';
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
+
import { QueryBarWrapper } from '../query_bar_wrapper';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
import { VisDataContext } from '../../contexts/vis_data_context';
import { BUCKET_TYPES } from '../../../../common/metric_types';
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
import { TimeseriesVisParams } from '../../../types';
+import { getIndexPatternKey } from '../../../../common/index_patterns_utils';
export class TablePanelConfig extends Component<
PanelConfigProps,
@@ -66,7 +67,7 @@ export class TablePanelConfig extends Component<
handlePivotChange = (selectedOption: Array>) => {
const { fields, model } = this.props;
const pivotId = get(selectedOption, '[0].value', null);
- const field = fields[model.index_pattern].find((f) => f.name === pivotId);
+ const field = fields[getIndexPatternKey(model.index_pattern)].find((f) => f.name === pivotId);
const pivotType = get(field, 'type', model.pivot_type);
this.props.onChange({
@@ -237,15 +238,13 @@ export class TablePanelConfig extends Component<
>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ onChange={(filter) => {
+ this.props.onChange({ filter });
+ }}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
@@ -274,6 +273,7 @@ export class TablePanelConfig extends Component<
this.switchTab(PANEL_CONFIG_TABS.DATA)}
+ data-test-subj="tableEditorDataBtn"
>
this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
+ data-test-subj="tableEditorPanelOptionsBtn"
>
-
@@ -202,13 +201,13 @@ export class TimeseriesPanelConfig extends Component<
>
{
+ this.props.onChange({ filter });
}}
- onChange={(filter: PanelConfigProps['model']['filter']) =>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx
index 184063f88ef03..78ac11eb39744 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/top_n.tsx
@@ -33,7 +33,6 @@ import { ColorRules } from '../color_rules';
import { ColorPicker } from '../color_picker';
import { YesNo } from '../yes_no';
import { getDefaultQueryLanguage } from '../lib/get_default_query_language';
-// @ts-ignore this is typed in https://github.com/elastic/kibana/pull/92812, remove ignore after merging
import { QueryBarWrapper } from '../query_bar_wrapper';
import { PanelConfigProps, PANEL_CONFIG_TABS } from './types';
import { TimeseriesVisParams } from '../../../types';
@@ -120,6 +119,7 @@ export class TopNPanelConfig extends Component<
fields={this.props.fields}
model={this.props.model}
onChange={this.props.onChange}
+ allowIndexSwitchingMode={true}
/>
@@ -138,13 +138,13 @@ export class TopNPanelConfig extends Component<
>
- this.props.onChange({ filter })
- }
- indexPatterns={[model.index_pattern || model.default_index_pattern]}
+ onChange={(filter: PanelConfigProps['model']['filter']) => {
+ this.props.onChange({ filter });
+ }}
+ indexPatterns={[model.index_pattern || model.default_index_pattern || '']}
/>
@@ -225,6 +225,7 @@ export class TopNPanelConfig extends Component<
this.switchTab(PANEL_CONFIG_TABS.DATA)}
+ data-test-subj="topNEditorDataBtn"
>
this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
+ data-test-subj="topNEditorPanelOptionsBtn"
>
& {
+ indexPatterns: IndexPatternValue[];
+};
+
+export function QueryBarWrapper({ query, onChange, indexPatterns }: QueryBarWrapperProps) {
+ const { indexPatterns: indexPatternsService } = getDataStart();
+ const [indexes, setIndexes] = useState([]);
+
+ const coreStartContext = useContext(CoreStartContext);
+
+ useEffect(() => {
+ async function fetchIndexes() {
+ const i: QueryStringInputProps['indexPatterns'] = [];
+
+ for (const index of indexPatterns ?? []) {
+ if (isStringTypeIndexPattern(index)) {
+ i.push(index);
+ } else if (index?.id) {
+ const fetchedIndex = await fetchIndexPattern(index, indexPatternsService);
+
+ if (fetchedIndex.indexPattern) {
+ i.push(fetchedIndex.indexPattern);
+ }
+ }
+ }
+ setIndexes(i);
+ }
+
+ fetchIndexes();
+ }, [indexPatterns, indexPatternsService]);
+
+ return (
+
+ );
+}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/series_config.js b/src/plugins/vis_type_timeseries/public/application/components/series_config.js
index 4e48ed4406ea5..3185503acb569 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/series_config.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/series_config.js
@@ -137,5 +137,5 @@ SeriesConfig.propTypes = {
panel: PropTypes.object,
model: PropTypes.object,
onChange: PropTypes.func,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/series_config_query_bar_with_ignore_global_filter.js b/src/plugins/vis_type_timeseries/public/application/components/series_config_query_bar_with_ignore_global_filter.js
index 0b67d52c23cd2..950101103b3a5 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/series_config_query_bar_with_ignore_global_filter.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/series_config_query_bar_with_ignore_global_filter.js
@@ -90,5 +90,5 @@ SeriesConfigQueryBarWithIgnoreGlobalFilter.propTypes = {
onChange: PropTypes.func,
model: PropTypes.object,
panel: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
index 5891320aa684f..b996abd6373ab 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js
@@ -25,7 +25,7 @@ import {
EuiFieldText,
} from '@elastic/eui';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
-import { FIELD_TYPES } from '../../../../common/field_types';
+import { KBN_FIELD_TYPES } from '../../../../../data/public';
import { STACKED_OPTIONS } from '../../visualizations/constants';
const DEFAULTS = { terms_direction: 'desc', terms_size: 10, terms_order_by: '_count' };
@@ -133,7 +133,7 @@ export const SplitByTermsUI = ({
- {selectedFieldType === FIELD_TYPES.STRING && (
+ {selectedFieldType === KBN_FIELD_TYPES.STRING && (
{
+ abortableFetchFields = (extractedIndexPatterns: IndexPatternValue[]) => {
this.abortControllerFetchFields?.abort();
this.abortControllerFetchFields = new AbortController();
@@ -202,7 +213,7 @@ export class VisEditor extends Component {
const defaultIndexTitle = index?.title ?? '';
- const indexPatterns = extractIndexPatterns(this.props.vis.params, defaultIndexTitle);
+ const indexPatterns = extractIndexPatternValues(this.props.vis.params, defaultIndexTitle);
const visFields = await fetchFields(indexPatterns);
this.setState((state) => ({
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js
index 2909167031d08..46cc8b6ebe635 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/gauge/series.js
@@ -198,7 +198,7 @@ GaugeSeriesUi.propTypes = {
visible: PropTypes.bool,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
export const GaugeSeries = injectI18n(GaugeSeriesUi);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js
index 6f00abe5aa2c0..f9817242a101a 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/markdown/series.js
@@ -200,7 +200,7 @@ MarkdownSeriesUi.propTypes = {
visible: PropTypes.bool,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
export const MarkdownSeries = injectI18n(MarkdownSeriesUi);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js
index 64425cf534226..5ec2378792812 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/metric/series.js
@@ -211,7 +211,7 @@ MetricSeriesUi.propTypes = {
togglePanelActivation: PropTypes.func,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
export const MetricSeries = injectI18n(MetricSeriesUi);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
index fecd6cde1dca8..0ba8d3e855365 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js
@@ -9,6 +9,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import uuid from 'uuid';
+import { i18n } from '@kbn/i18n';
+
import { DataFormatPicker } from '../../data_format_picker';
import { createSelectHandler } from '../../lib/create_select_handler';
import { createTextHandler } from '../../lib/create_text_handler';
@@ -28,11 +30,11 @@ import {
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
-import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
+import { FormattedMessage } from '@kbn/i18n/react';
import { getDefaultQueryLanguage } from '../../lib/get_default_query_language';
-
import { QueryBarWrapper } from '../../query_bar_wrapper';
-class TableSeriesConfigUI extends Component {
+
+export class TableSeriesConfig extends Component {
UNSAFE_componentWillMount() {
const { model } = this.props;
if (!model.color_rules || (model.color_rules && model.color_rules.length === 0)) {
@@ -48,68 +50,58 @@ class TableSeriesConfigUI extends Component {
const handleSelectChange = createSelectHandler(this.props.onChange);
const handleTextChange = createTextHandler(this.props.onChange);
const htmlId = htmlIdGenerator();
- const { intl } = this.props;
const functionOptions = [
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.sumLabel',
+ label: i18n.translate('visTypeTimeseries.table.sumLabel', {
defaultMessage: 'Sum',
}),
value: 'sum',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.maxLabel',
+ label: i18n.translate('visTypeTimeseries.table.maxLabel', {
defaultMessage: 'Max',
}),
value: 'max',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.minLabel',
+ label: i18n.translate('visTypeTimeseries.table.minLabel', {
defaultMessage: 'Min',
}),
value: 'min',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.avgLabel',
+ label: i18n.translate('visTypeTimeseries.table.avgLabel', {
defaultMessage: 'Avg',
}),
value: 'mean',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.overallSumLabel',
+ label: i18n.translate('visTypeTimeseries.table.overallSumLabel', {
defaultMessage: 'Overall Sum',
}),
value: 'overall_sum',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.overallMaxLabel',
+ label: i18n.translate('visTypeTimeseries.table.overallMaxLabel', {
defaultMessage: 'Overall Max',
}),
value: 'overall_max',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.overallMinLabel',
+ label: i18n.translate('visTypeTimeseries.table.overallMinLabel', {
defaultMessage: 'Overall Min',
}),
value: 'overall_min',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.overallAvgLabel',
+ label: i18n.translate('visTypeTimeseries.table.overallAvgLabel', {
defaultMessage: 'Overall Avg',
}),
value: 'overall_avg',
},
{
- label: intl.formatMessage({
- id: 'visTypeTimeseries.table.cumulativeSumLabel',
+ label: i18n.translate('visTypeTimeseries.table.cumulativeSumLabel', {
defaultMessage: 'Cumulative Sum',
}),
value: 'cumulative_sum',
@@ -170,11 +162,8 @@ class TableSeriesConfigUI extends Component {
>
this.props.onChange({ filter })}
indexPatterns={[this.props.indexPatternForQuery]}
@@ -259,11 +248,9 @@ class TableSeriesConfigUI extends Component {
}
}
-TableSeriesConfigUI.propTypes = {
+TableSeriesConfig.propTypes = {
fields: PropTypes.object,
model: PropTypes.object,
onChange: PropTypes.func,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
-
-export const TableSeriesConfig = injectI18n(TableSeriesConfigUI);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js
index a56afd1f817b3..acd2f4cc17d4a 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/series.js
@@ -186,7 +186,7 @@ TableSeriesUI.propTypes = {
togglePanelActivation: PropTypes.func,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
export const TableSeries = injectI18n(TableSeriesUI);
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js
index 3df12dafd5a66..22bf2fa4ca708 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js
@@ -542,7 +542,7 @@ export const TimeseriesConfig = injectI18n(function (props) {
{...props}
prefix="series_"
disabled={!model.override_index_pattern}
- allowLevelofDetail={true}
+ allowLevelOfDetail={true}
/>
@@ -555,6 +555,6 @@ TimeseriesConfig.propTypes = {
model: PropTypes.object,
panel: PropTypes.object,
onChange: PropTypes.func,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
seriesQuantity: PropTypes.object,
};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js
index 76df07ce7c8c4..bb10ac57c5ae9 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/series.js
@@ -209,7 +209,7 @@ TimeseriesSeriesUI.propTypes = {
togglePanelActivation: PropTypes.func,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
seriesQuantity: PropTypes.object,
};
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js
index bfe446a8226e8..61bb7e2473dd9 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/top_n/series.js
@@ -200,5 +200,5 @@ TopNSeries.propTypes = {
togglePanelActivation: PropTypes.func,
uiRestrictions: PropTypes.object,
dragHandleProps: PropTypes.object,
- indexPatternForQuery: PropTypes.string,
+ indexPatternForQuery: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
};
diff --git a/src/plugins/vis_type_timeseries/public/application/contexts/panel_model_context.ts b/src/plugins/vis_type_timeseries/public/application/contexts/panel_model_context.ts
new file mode 100644
index 0000000000000..534f686ca13fc
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/public/application/contexts/panel_model_context.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { PanelSchema } from '../../../common/types';
+
+export const PanelModelContext = React.createContext(null);
diff --git a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts
index 088930f90a765..af3ddd643cac8 100644
--- a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts
+++ b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts
@@ -9,12 +9,14 @@
import { i18n } from '@kbn/i18n';
import { getCoreStart, getDataStart } from '../../services';
import { ROUTES } from '../../../common/constants';
-import { SanitizedFieldType } from '../../../common/types';
+import { SanitizedFieldType, IndexPatternValue } from '../../../common/types';
+import { getIndexPatternKey } from '../../../common/index_patterns_utils';
+import { toSanitizedFieldType } from '../../../common/fields_utils';
export type VisFields = Record;
export async function fetchFields(
- indexes: string[] = [],
+ indexes: IndexPatternValue[] = [],
signal?: AbortSignal
): Promise {
const patterns = Array.isArray(indexes) ? indexes : [indexes];
@@ -25,26 +27,33 @@ export async function fetchFields(
const defaultIndexPattern = await dataStart.indexPatterns.getDefault();
const indexFields = await Promise.all(
patterns.map(async (pattern) => {
- return coreStart.http.get(ROUTES.FIELDS, {
- query: {
- index: pattern,
- },
- signal,
- });
+ if (typeof pattern !== 'string' && pattern?.id) {
+ return toSanitizedFieldType(
+ (await dataStart.indexPatterns.get(pattern.id)).getNonScriptedFields()
+ );
+ } else {
+ return coreStart.http.get(ROUTES.FIELDS, {
+ query: {
+ index: `${pattern ?? ''}`,
+ },
+ signal,
+ });
+ }
})
);
const fields: VisFields = patterns.reduce(
(cumulatedFields, currentPattern, index) => ({
...cumulatedFields,
- [currentPattern]: indexFields[index],
+ [getIndexPatternKey(currentPattern)]: indexFields[index],
}),
{}
);
- if (defaultIndexPattern?.title && patterns.includes(defaultIndexPattern.title)) {
- fields[''] = fields[defaultIndexPattern.title];
+ if (defaultIndexPattern) {
+ fields[''] = toSanitizedFieldType(await defaultIndexPattern.getNonScriptedFields());
}
+
return fields;
} catch (error) {
if (error.name !== 'AbortError') {
diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts
index 9e996fcc74833..5d5e082b2b7bb 100644
--- a/src/plugins/vis_type_timeseries/public/metrics_type.ts
+++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts
@@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import { TSVB_EDITOR_NAME } from './application';
import { PANEL_TYPES } from '../common/panel_types';
+import { isStringTypeIndexPattern } from '../common/index_patterns_utils';
import { toExpressionAst } from './to_ast';
import { VIS_EVENT_TO_TRIGGER, VisGroups, VisParams } from '../../visualizations/public';
import { getDataStart } from './services';
@@ -53,6 +54,7 @@ export const metricsVisDefinition = {
],
time_field: '',
index_pattern: '',
+ use_kibana_indexes: true,
interval: '',
axis_position: 'left',
axis_formatter: 'number',
@@ -77,7 +79,20 @@ export const metricsVisDefinition = {
inspectorAdapters: {},
getUsedIndexPattern: async (params: VisParams) => {
const { indexPatterns } = getDataStart();
+ const indexPatternValue = params.index_pattern;
- return params.index_pattern ? await indexPatterns.find(params.index_pattern) : [];
+ if (indexPatternValue) {
+ if (isStringTypeIndexPattern(indexPatternValue)) {
+ return await indexPatterns.find(indexPatternValue);
+ }
+
+ if (indexPatternValue.id) {
+ return [await indexPatterns.get(indexPatternValue.id)];
+ }
+ }
+
+ const defaultIndex = await indexPatterns.getDefault();
+
+ return defaultIndex ? [defaultIndex] : [];
},
};
diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
index f1bc5a11550e9..b0e85f8e44fbe 100644
--- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts
@@ -10,6 +10,7 @@ import { uniqBy } from 'lodash';
import { Framework } from '../plugin';
import { VisTypeTimeseriesFieldsRequest, VisTypeTimeseriesRequestHandlerContext } from '../types';
+import { getCachedIndexPatternFetcher } from './search_strategies/lib/cached_index_pattern_fetcher';
export async function getFields(
requestContext: VisTypeTimeseriesRequestHandlerContext,
@@ -17,26 +18,29 @@ export async function getFields(
framework: Framework,
indexPatternString: string
) {
+ const indexPatternsService = await framework.getIndexPatternsService(requestContext);
+ const cachedIndexPatternFetcher = getCachedIndexPatternFetcher(indexPatternsService);
+
if (!indexPatternString) {
- const indexPatternsService = await framework.getIndexPatternsService(requestContext);
const defaultIndexPattern = await indexPatternsService.getDefault();
indexPatternString = defaultIndexPattern?.title ?? '';
}
+ const fetchedIndex = await cachedIndexPatternFetcher(indexPatternString);
+
const {
searchStrategy,
capabilities,
} = (await framework.searchStrategyRegistry.getViableStrategy(
requestContext,
request,
- indexPatternString
+ fetchedIndex
))!;
const fields = await searchStrategy.getFieldsForWildcard(
- requestContext,
- request,
- indexPatternString,
+ fetchedIndex,
+ indexPatternsService,
capabilities
);
diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
index 0ad50a296b481..d91104fb299d7 100644
--- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
@@ -19,6 +19,7 @@ import type {
import { getSeriesData } from './vis_data/get_series_data';
import { getTableData } from './vis_data/get_table_data';
import { getEsQueryConfig } from './vis_data/helpers/get_es_query_uisettings';
+import { getCachedIndexPatternFetcher } from './search_strategies/lib/cached_index_pattern_fetcher';
export async function getVisData(
requestContext: VisTypeTimeseriesRequestHandlerContext,
@@ -29,12 +30,14 @@ export async function getVisData(
const esShardTimeout = await framework.getEsShardTimeout();
const indexPatternsService = await framework.getIndexPatternsService(requestContext);
const esQueryConfig = await getEsQueryConfig(uiSettings);
+
const services: VisTypeTimeseriesRequestServices = {
esQueryConfig,
esShardTimeout,
indexPatternsService,
uiSettings,
searchStrategyRegistry: framework.searchStrategyRegistry,
+ cachedIndexPatternFetcher: getCachedIndexPatternFetcher(indexPatternsService),
};
const promises = request.body.panels.map((panel) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts
new file mode 100644
index 0000000000000..aeaf3ca2cd327
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.test.ts
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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 { IndexPattern, IndexPatternsService } from 'src/plugins/data/server';
+import {
+ getCachedIndexPatternFetcher,
+ CachedIndexPatternFetcher,
+} from './cached_index_pattern_fetcher';
+
+describe('CachedIndexPatternFetcher', () => {
+ let mockedIndices: IndexPattern[] | [];
+ let cachedIndexPatternFetcher: CachedIndexPatternFetcher;
+
+ beforeEach(() => {
+ mockedIndices = [];
+
+ const indexPatternsService = ({
+ getDefault: jest.fn(() => Promise.resolve({ id: 'default', title: 'index' })),
+ get: jest.fn(() => Promise.resolve(mockedIndices[0])),
+ find: jest.fn(() => Promise.resolve(mockedIndices || [])),
+ } as unknown) as IndexPatternsService;
+
+ cachedIndexPatternFetcher = getCachedIndexPatternFetcher(indexPatternsService);
+ });
+
+ test('should return default index on no input value', async () => {
+ const value = await cachedIndexPatternFetcher('');
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "default",
+ "title": "index",
+ },
+ "indexPatternString": "index",
+ }
+ `);
+ });
+
+ describe('text-based index', () => {
+ test('should return the Kibana index if it exists', async () => {
+ mockedIndices = [
+ {
+ id: 'indexId',
+ title: 'indexTitle',
+ },
+ ] as IndexPattern[];
+
+ const value = await cachedIndexPatternFetcher('indexTitle');
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "indexId",
+ "title": "indexTitle",
+ },
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+
+ test('should return only indexPatternString if Kibana index does not exist', async () => {
+ const value = await cachedIndexPatternFetcher('indexTitle');
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": undefined,
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+ });
+
+ describe('object-based index', () => {
+ test('should return the Kibana index if it exists', async () => {
+ mockedIndices = [
+ {
+ id: 'indexId',
+ title: 'indexTitle',
+ },
+ ] as IndexPattern[];
+
+ const value = await cachedIndexPatternFetcher({ id: 'indexId' });
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": Object {
+ "id": "indexId",
+ "title": "indexTitle",
+ },
+ "indexPatternString": "indexTitle",
+ }
+ `);
+ });
+
+ test('should return default index if Kibana index not found', async () => {
+ const value = await cachedIndexPatternFetcher({ id: 'indexId' });
+
+ expect(value).toMatchInlineSnapshot(`
+ Object {
+ "indexPattern": undefined,
+ "indexPatternString": "",
+ }
+ `);
+ });
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts
new file mode 100644
index 0000000000000..68cbd93cdc614
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.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 { getIndexPatternKey, fetchIndexPattern } from '../../../../common/index_patterns_utils';
+
+import type { IndexPatternsService } from '../../../../../data/server';
+import type { IndexPatternValue, FetchedIndexPattern } from '../../../../common/types';
+
+export const getCachedIndexPatternFetcher = (indexPatternsService: IndexPatternsService) => {
+ const cache = new Map();
+
+ return async (indexPatternValue: IndexPatternValue): Promise => {
+ const key = getIndexPatternKey(indexPatternValue);
+
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
+
+ const fetchedIndex = fetchIndexPattern(indexPatternValue, indexPatternsService);
+
+ cache.set(indexPatternValue, fetchedIndex);
+
+ return fetchedIndex;
+ };
+};
+
+export type CachedIndexPatternFetcher = ReturnType;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
similarity index 57%
rename from src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts
rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
index f95667612efa4..9003eb7fc2ced 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts
@@ -6,21 +6,26 @@
* Side Public License, v 1.
*/
-import {
- VisTypeTimeseriesRequestHandlerContext,
- VisTypeTimeseriesVisDataRequest,
-} from '../../../types';
-import { AbstractSearchStrategy, DefaultSearchCapabilities } from '../../search_strategies';
+import type { VisTypeTimeseriesVisDataRequest } from '../../../types';
+import type { AbstractSearchStrategy, DefaultSearchCapabilities } from '../index';
+import type { IndexPatternsService } from '../../../../../data/common';
+import type { CachedIndexPatternFetcher } from './cached_index_pattern_fetcher';
export interface FieldsFetcherServices {
- requestContext: VisTypeTimeseriesRequestHandlerContext;
+ indexPatternsService: IndexPatternsService;
+ cachedIndexPatternFetcher: CachedIndexPatternFetcher;
searchStrategy: AbstractSearchStrategy;
capabilities: DefaultSearchCapabilities;
}
export const createFieldsFetcher = (
req: VisTypeTimeseriesVisDataRequest,
- { capabilities, requestContext, searchStrategy }: FieldsFetcherServices
+ {
+ capabilities,
+ indexPatternsService,
+ searchStrategy,
+ cachedIndexPatternFetcher,
+ }: FieldsFetcherServices
) => {
const fieldsCacheMap = new Map();
@@ -28,11 +33,11 @@ export const createFieldsFetcher = (
if (fieldsCacheMap.has(index)) {
return fieldsCacheMap.get(index);
}
+ const fetchedIndex = await cachedIndexPatternFetcher(index);
const fields = await searchStrategy.getFieldsForWildcard(
- requestContext,
- req,
- index,
+ fetchedIndex,
+ indexPatternsService,
capabilities
);
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/get_index_pattern.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/get_index_pattern.ts
deleted file mode 100644
index 512494de290fd..0000000000000
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/get_index_pattern.ts
+++ /dev/null
@@ -1,32 +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 { IndexPatternsService, IndexPattern } from '../../../../../data/server';
-
-interface IndexPatternObjectDependencies {
- indexPatternsService: IndexPatternsService;
-}
-export async function getIndexPatternObject(
- indexPatternString: string,
- { indexPatternsService }: IndexPatternObjectDependencies
-) {
- let indexPatternObject: IndexPattern | undefined | null;
-
- if (!indexPatternString) {
- indexPatternObject = await indexPatternsService.getDefault();
- } else {
- indexPatternObject = (await indexPatternsService.find(indexPatternString)).find(
- (index) => index.title === indexPatternString
- );
- }
-
- return {
- indexPatternObject,
- indexPatternString: indexPatternObject?.title || indexPatternString || '',
- };
-}
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
index f9a49bc322a29..a6e7c5b11ee64 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts
@@ -10,29 +10,27 @@ import { get } from 'lodash';
import { SearchStrategyRegistry } from './search_strategy_registry';
import { AbstractSearchStrategy, DefaultSearchStrategy } from './strategies';
import { DefaultSearchCapabilities } from './capabilities/default_search_capabilities';
-import { Framework } from '../../plugin';
import { VisTypeTimeseriesRequest, VisTypeTimeseriesRequestHandlerContext } from '../../types';
const getPrivateField = (registry: SearchStrategyRegistry, field: string) =>
get(registry, field) as T;
class MockSearchStrategy extends AbstractSearchStrategy {
- checkForViability() {
- return Promise.resolve({
+ async checkForViability() {
+ return {
isViable: true,
capabilities: {},
- });
+ };
}
}
describe('SearchStrategyRegister', () => {
- const framework = {} as Framework;
const requestContext = {} as VisTypeTimeseriesRequestHandlerContext;
let registry: SearchStrategyRegistry;
beforeAll(() => {
registry = new SearchStrategyRegistry();
- registry.addStrategy(new DefaultSearchStrategy(framework));
+ registry.addStrategy(new DefaultSearchStrategy());
});
test('should init strategies register', () => {
@@ -47,12 +45,11 @@ describe('SearchStrategyRegister', () => {
test('should return a DefaultSearchStrategy instance', async () => {
const req = {} as VisTypeTimeseriesRequest;
- const indexPattern = '*';
const { searchStrategy, capabilities } = (await registry.getViableStrategy(
requestContext,
req,
- indexPattern
+ { indexPatternString: '*', indexPattern: undefined }
))!;
expect(searchStrategy instanceof DefaultSearchStrategy).toBe(true);
@@ -60,7 +57,7 @@ describe('SearchStrategyRegister', () => {
});
test('should add a strategy if it is an instance of AbstractSearchStrategy', () => {
- const anotherSearchStrategy = new MockSearchStrategy(framework);
+ const anotherSearchStrategy = new MockSearchStrategy();
const addedStrategies = registry.addStrategy(anotherSearchStrategy);
expect(addedStrategies.length).toEqual(2);
@@ -69,14 +66,13 @@ describe('SearchStrategyRegister', () => {
test('should return a MockSearchStrategy instance', async () => {
const req = {} as VisTypeTimeseriesRequest;
- const indexPattern = '*';
- const anotherSearchStrategy = new MockSearchStrategy(framework);
+ const anotherSearchStrategy = new MockSearchStrategy();
registry.addStrategy(anotherSearchStrategy);
const { searchStrategy, capabilities } = (await registry.getViableStrategy(
requestContext,
req,
- indexPattern
+ { indexPatternString: '*', indexPattern: undefined }
))!;
expect(searchStrategy instanceof MockSearchStrategy).toBe(true);
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
index 11ff4b0a8a51f..4a013fd89735d 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts
@@ -6,14 +6,10 @@
* Side Public License, v 1.
*/
-import { extractIndexPatterns } from '../../../common/extract_index_patterns';
-import { PanelSchema } from '../../../common/types';
-import {
- VisTypeTimeseriesRequest,
- VisTypeTimeseriesRequestHandlerContext,
- VisTypeTimeseriesVisDataRequest,
-} from '../../types';
+import { VisTypeTimeseriesRequest, VisTypeTimeseriesRequestHandlerContext } from '../../types';
import { AbstractSearchStrategy } from './strategies';
+import { FetchedIndexPattern } from '../../../common/types';
+
export class SearchStrategyRegistry {
private strategies: AbstractSearchStrategy[] = [];
@@ -27,13 +23,13 @@ export class SearchStrategyRegistry {
async getViableStrategy(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesRequest,
- indexPattern: string
+ fetchedIndexPattern: FetchedIndexPattern
) {
for (const searchStrategy of this.strategies) {
const { isViable, capabilities } = await searchStrategy.checkForViability(
requestContext,
req,
- indexPattern
+ fetchedIndexPattern
);
if (isViable) {
@@ -44,14 +40,4 @@ export class SearchStrategyRegistry {
}
}
}
-
- async getViableStrategyForPanel(
- requestContext: VisTypeTimeseriesRequestHandlerContext,
- req: VisTypeTimeseriesVisDataRequest,
- panel: PanelSchema
- ) {
- const indexPattern = extractIndexPatterns(panel, panel.default_index_pattern).join(',');
-
- return this.getViableStrategy(requestContext, req, indexPattern);
- }
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
index e7282eba58ec7..fb66e32447c22 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts
@@ -6,48 +6,26 @@
* Side Public License, v 1.
*/
-const mockGetFieldsForWildcard = jest.fn(() => []);
-
-jest.mock('../../../../../data/server', () => ({
- indexPatterns: {
- isNestedField: jest.fn(() => false),
- },
- IndexPatternsFetcher: jest.fn().mockImplementation(() => ({
- getFieldsForWildcard: mockGetFieldsForWildcard,
- })),
-}));
+import { IndexPatternsService } from '../../../../../data/common';
import { from } from 'rxjs';
-import { AbstractSearchStrategy, toSanitizedFieldType } from './abstract_search_strategy';
+import { AbstractSearchStrategy } from './abstract_search_strategy';
import type { IFieldType } from '../../../../../data/common';
-import type { FieldSpec, RuntimeField } from '../../../../../data/common';
-import {
- VisTypeTimeseriesRequest,
+import type { CachedIndexPatternFetcher } from '../lib/cached_index_pattern_fetcher';
+import type {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
} from '../../../types';
-import { Framework } from '../../../plugin';
-import { indexPatterns } from '../../../../../data/server';
class FooSearchStrategy extends AbstractSearchStrategy {}
describe('AbstractSearchStrategy', () => {
let abstractSearchStrategy: AbstractSearchStrategy;
let mockedFields: IFieldType[];
- let indexPattern: string;
let requestContext: VisTypeTimeseriesRequestHandlerContext;
- let framework: Framework;
beforeEach(() => {
mockedFields = [];
- framework = ({
- getIndexPatternsService: jest.fn(() =>
- Promise.resolve({
- find: jest.fn(() => []),
- getDefault: jest.fn(() => {}),
- })
- ),
- } as unknown) as Framework;
requestContext = ({
core: {
elasticsearch: {
@@ -60,7 +38,7 @@ describe('AbstractSearchStrategy', () => {
search: jest.fn().mockReturnValue(from(Promise.resolve({}))),
},
} as unknown) as VisTypeTimeseriesRequestHandlerContext;
- abstractSearchStrategy = new FooSearchStrategy(framework);
+ abstractSearchStrategy = new FooSearchStrategy();
});
test('should init an AbstractSearchStrategy instance', () => {
@@ -71,17 +49,15 @@ describe('AbstractSearchStrategy', () => {
test('should return fields for wildcard', async () => {
const fields = await abstractSearchStrategy.getFieldsForWildcard(
- requestContext,
- {} as VisTypeTimeseriesRequest,
- indexPattern
+ { indexPatternString: '', indexPattern: undefined },
+ ({
+ getDefault: jest.fn(),
+ getFieldsForWildcard: jest.fn(() => Promise.resolve(mockedFields)),
+ } as unknown) as IndexPatternsService,
+ (() => Promise.resolve({}) as unknown) as CachedIndexPatternFetcher
);
expect(fields).toEqual(mockedFields);
- expect(mockGetFieldsForWildcard).toHaveBeenCalledWith({
- pattern: indexPattern,
- metaFields: [],
- fieldCapsOptions: { allow_no_indices: true },
- });
});
test('should return response', async () => {
@@ -117,68 +93,4 @@ describe('AbstractSearchStrategy', () => {
}
);
});
-
- describe('toSanitizedFieldType', () => {
- const mockedField = {
- lang: 'lang',
- conflictDescriptions: {},
- aggregatable: true,
- name: 'name',
- type: 'type',
- esTypes: ['long', 'geo'],
- } as FieldSpec;
-
- test('should sanitize fields ', async () => {
- const fields = [mockedField] as FieldSpec[];
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`
- Array [
- Object {
- "label": "name",
- "name": "name",
- "type": "type",
- },
- ]
- `);
- });
-
- test('should filter runtime fields', async () => {
- const fields: FieldSpec[] = [
- {
- ...mockedField,
- runtimeField: {} as RuntimeField,
- },
- ];
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
- });
-
- test('should filter non-aggregatable fields', async () => {
- const fields: FieldSpec[] = [
- {
- ...mockedField,
- aggregatable: false,
- },
- ];
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
- });
-
- test('should filter nested fields', async () => {
- const fields: FieldSpec[] = [
- {
- ...mockedField,
- subType: {
- nested: {
- path: 'path',
- },
- },
- },
- ];
- // @ts-expect-error
- indexPatterns.isNestedField.mockReturnValue(true);
-
- expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`);
- });
- });
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
index 5bc008091627f..26c3a6c7c8bf7 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts
@@ -6,37 +6,17 @@
* Side Public License, v 1.
*/
-import { indexPatterns, IndexPatternsFetcher } from '../../../../../data/server';
+import { IndexPatternsService } from '../../../../../data/server';
+import { toSanitizedFieldType } from '../../../../common/fields_utils';
-import type { Framework } from '../../../plugin';
-import type { FieldSpec } from '../../../../../data/common';
-import type { SanitizedFieldType } from '../../../../common/types';
+import type { FetchedIndexPattern } from '../../../../common/types';
import type {
VisTypeTimeseriesRequest,
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
} from '../../../types';
-import { getIndexPatternObject } from '../lib/get_index_pattern';
-
-export const toSanitizedFieldType = (fields: FieldSpec[]) => {
- return fields
- .filter(
- (field) =>
- // Make sure to only include mapped fields, e.g. no index pattern runtime fields
- !field.runtimeField && field.aggregatable && !indexPatterns.isNestedField(field)
- )
- .map(
- (field) =>
- ({
- name: field.name,
- label: field.customLabel ?? field.name,
- type: field.type,
- } as SanitizedFieldType)
- );
-};
export abstract class AbstractSearchStrategy {
- constructor(private framework: Framework) {}
async search(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesVisDataRequest,
@@ -66,35 +46,25 @@ export abstract class AbstractSearchStrategy {
checkForViability(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesRequest,
- indexPattern: string
+ fetchedIndexPattern: FetchedIndexPattern
): Promise<{ isViable: boolean; capabilities: any }> {
throw new TypeError('Must override method');
}
async getFieldsForWildcard(
- requestContext: VisTypeTimeseriesRequestHandlerContext,
- req: VisTypeTimeseriesRequest,
- indexPattern: string,
+ fetchedIndexPattern: FetchedIndexPattern,
+ indexPatternsService: IndexPatternsService,
capabilities?: unknown,
options?: Partial<{
type: string;
rollupIndex: string;
}>
) {
- const indexPatternsFetcher = new IndexPatternsFetcher(
- requestContext.core.elasticsearch.client.asCurrentUser
- );
- const indexPatternsService = await this.framework.getIndexPatternsService(requestContext);
- const { indexPatternObject } = await getIndexPatternObject(indexPattern, {
- indexPatternsService,
- });
-
return toSanitizedFieldType(
- indexPatternObject
- ? indexPatternObject.getNonScriptedFields()
- : await indexPatternsFetcher!.getFieldsForWildcard({
- pattern: indexPattern,
- fieldCapsOptions: { allow_no_indices: true },
+ fetchedIndexPattern.indexPattern
+ ? fetchedIndexPattern.indexPattern.getNonScriptedFields()
+ : await indexPatternsService.getFieldsForWildcard({
+ pattern: fetchedIndexPattern.indexPatternString ?? '',
metaFields: [],
...options,
})
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
index b9824355374e1..d7a4e6ddedc89 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts
@@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
-import { Framework } from '../../../plugin';
import {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
@@ -14,14 +13,13 @@ import {
import { DefaultSearchStrategy } from './default_search_strategy';
describe('DefaultSearchStrategy', () => {
- const framework = {} as Framework;
const requestContext = {} as VisTypeTimeseriesRequestHandlerContext;
let defaultSearchStrategy: DefaultSearchStrategy;
let req: VisTypeTimeseriesVisDataRequest;
beforeEach(() => {
req = {} as VisTypeTimeseriesVisDataRequest;
- defaultSearchStrategy = new DefaultSearchStrategy(framework);
+ defaultSearchStrategy = new DefaultSearchStrategy();
});
test('should init an DefaultSearchStrategy instance', () => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
index c925d8fcbb7c3..f95bf81b5c1d3 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts
@@ -8,25 +8,30 @@
import { AbstractSearchStrategy } from './abstract_search_strategy';
import { DefaultSearchCapabilities } from '../capabilities/default_search_capabilities';
-import { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRequest } from '../../../types';
+
+import type { IndexPatternsService } from '../../../../../data/server';
+import type { FetchedIndexPattern } from '../../../../common/types';
+import type {
+ VisTypeTimeseriesRequestHandlerContext,
+ VisTypeTimeseriesRequest,
+} from '../../../types';
export class DefaultSearchStrategy extends AbstractSearchStrategy {
- checkForViability(
+ async checkForViability(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesRequest
) {
- return Promise.resolve({
+ return {
isViable: true,
capabilities: new DefaultSearchCapabilities(req),
- });
+ };
}
async getFieldsForWildcard(
- requestContext: VisTypeTimeseriesRequestHandlerContext,
- req: VisTypeTimeseriesRequest,
- indexPattern: string,
+ fetchedIndexPattern: FetchedIndexPattern,
+ indexPatternsService: IndexPatternsService,
capabilities?: unknown
) {
- return super.getFieldsForWildcard(requestContext, req, indexPattern, capabilities);
+ return super.getFieldsForWildcard(fetchedIndexPattern, indexPatternsService, capabilities);
}
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
index 403013cfb9e10..c798f58b0b67b 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts
@@ -7,8 +7,10 @@
*/
import { RollupSearchStrategy } from './rollup_search_strategy';
-import { Framework } from '../../../plugin';
-import {
+
+import type { IndexPatternsService } from '../../../../../data/common';
+import type { CachedIndexPatternFetcher } from '../lib/cached_index_pattern_fetcher';
+import type {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
} from '../../../types';
@@ -49,12 +51,11 @@ describe('Rollup Search Strategy', () => {
},
},
} as unknown) as VisTypeTimeseriesRequestHandlerContext;
- const framework = {} as Framework;
const indexPattern = 'indexPattern';
test('should create instance of RollupSearchRequest', () => {
- const rollupSearchStrategy = new RollupSearchStrategy(framework);
+ const rollupSearchStrategy = new RollupSearchStrategy();
expect(rollupSearchStrategy).toBeDefined();
});
@@ -64,7 +65,7 @@ describe('Rollup Search Strategy', () => {
const rollupIndex = 'rollupIndex';
beforeEach(() => {
- rollupSearchStrategy = new RollupSearchStrategy(framework);
+ rollupSearchStrategy = new RollupSearchStrategy();
rollupSearchStrategy.getRollupData = jest.fn(() =>
Promise.resolve({
[rollupIndex]: {
@@ -99,7 +100,7 @@ describe('Rollup Search Strategy', () => {
const result = await rollupSearchStrategy.checkForViability(
requestContext,
{} as VisTypeTimeseriesVisDataRequest,
- (null as unknown) as string
+ { indexPatternString: (null as unknown) as string, indexPattern: undefined }
);
expect(result).toEqual({
@@ -113,7 +114,7 @@ describe('Rollup Search Strategy', () => {
let rollupSearchStrategy: RollupSearchStrategy;
beforeEach(() => {
- rollupSearchStrategy = new RollupSearchStrategy(framework);
+ rollupSearchStrategy = new RollupSearchStrategy();
});
test('should return rollup data', async () => {
@@ -140,7 +141,7 @@ describe('Rollup Search Strategy', () => {
const rollupIndex = 'rollupIndex';
beforeEach(() => {
- rollupSearchStrategy = new RollupSearchStrategy(framework);
+ rollupSearchStrategy = new RollupSearchStrategy();
fieldsCapabilities = {
[rollupIndex]: {
aggs: {
@@ -154,9 +155,9 @@ describe('Rollup Search Strategy', () => {
test('should return fields for wildcard', async () => {
const fields = await rollupSearchStrategy.getFieldsForWildcard(
- requestContext,
- {} as VisTypeTimeseriesVisDataRequest,
- indexPattern,
+ { indexPatternString: 'indexPattern', indexPattern: undefined },
+ {} as IndexPatternsService,
+ (() => Promise.resolve({}) as unknown) as CachedIndexPatternFetcher,
{
fieldsCapabilities,
rollupIndex,
diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
index 376d551624c8a..e6333ca420e0d 100644
--- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts
@@ -6,19 +6,20 @@
* Side Public License, v 1.
*/
-import { getCapabilitiesForRollupIndices } from '../../../../../data/server';
-import {
+import { getCapabilitiesForRollupIndices, IndexPatternsService } from '../../../../../data/server';
+import { AbstractSearchStrategy } from './abstract_search_strategy';
+import { RollupSearchCapabilities } from '../capabilities/rollup_search_capabilities';
+
+import type { FetchedIndexPattern } from '../../../../common/types';
+import type { CachedIndexPatternFetcher } from '../lib/cached_index_pattern_fetcher';
+import type {
VisTypeTimeseriesRequest,
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesVisDataRequest,
} from '../../../types';
-import { AbstractSearchStrategy } from './abstract_search_strategy';
-import { RollupSearchCapabilities } from '../capabilities/rollup_search_capabilities';
const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData);
const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*');
-const isIndexPatternValid = (indexPattern: string) =>
- indexPattern && typeof indexPattern === 'string' && !isIndexPatternContainsWildcard(indexPattern);
export class RollupSearchStrategy extends AbstractSearchStrategy {
async search(
@@ -33,24 +34,33 @@ export class RollupSearchStrategy extends AbstractSearchStrategy {
requestContext: VisTypeTimeseriesRequestHandlerContext,
indexPattern: string
) {
- return requestContext.core.elasticsearch.client.asCurrentUser.rollup
- .getRollupIndexCaps({
+ try {
+ const {
+ body,
+ } = await requestContext.core.elasticsearch.client.asCurrentUser.rollup.getRollupIndexCaps({
index: indexPattern,
- })
- .then((data) => data.body)
- .catch(() => Promise.resolve({}));
+ });
+
+ return body;
+ } catch (e) {
+ return {};
+ }
}
async checkForViability(
requestContext: VisTypeTimeseriesRequestHandlerContext,
req: VisTypeTimeseriesRequest,
- indexPattern: string
+ { indexPatternString, indexPattern }: FetchedIndexPattern
) {
let isViable = false;
let capabilities = null;
- if (isIndexPatternValid(indexPattern)) {
- const rollupData = await this.getRollupData(requestContext, indexPattern);
+ if (
+ indexPatternString &&
+ !isIndexPatternContainsWildcard(indexPatternString) &&
+ (!indexPattern || indexPattern.type === 'rollup')
+ ) {
+ const rollupData = await this.getRollupData(requestContext, indexPatternString);
const rollupIndices = getRollupIndices(rollupData);
isViable = rollupIndices.length === 1;
@@ -70,14 +80,14 @@ export class RollupSearchStrategy extends AbstractSearchStrategy {
}
async getFieldsForWildcard(
- requestContext: VisTypeTimeseriesRequestHandlerContext,
- req: VisTypeTimeseriesRequest,
- indexPattern: string,
+ fetchedIndexPattern: FetchedIndexPattern,
+ indexPatternsService: IndexPatternsService,
+ getCachedIndexPatternFetcher: CachedIndexPatternFetcher,
capabilities?: unknown
) {
- return super.getFieldsForWildcard(requestContext, req, indexPattern, capabilities, {
+ return super.getFieldsForWildcard(fetchedIndexPattern, indexPatternsService, capabilities, {
type: 'rollup',
- rollupIndex: indexPattern,
+ rollupIndex: fetchedIndexPattern.indexPatternString,
});
}
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
index c489a8d20b071..32086fbf4f5b4 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts
@@ -8,7 +8,6 @@
import { AnnotationItemsSchema, PanelSchema } from 'src/plugins/vis_type_timeseries/common/types';
import { buildAnnotationRequest } from './build_request_body';
-import { getIndexPatternObject } from '../../search_strategies/lib/get_index_pattern';
import {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesRequestServices,
@@ -30,21 +29,20 @@ export async function getAnnotationRequestParams(
esShardTimeout,
esQueryConfig,
capabilities,
- indexPatternsService,
uiSettings,
+ cachedIndexPatternFetcher,
}: AnnotationServices
) {
- const {
- indexPatternObject,
- indexPatternString,
- } = await getIndexPatternObject(annotation.index_pattern!, { indexPatternsService });
+ const { indexPattern, indexPatternString } = await cachedIndexPatternFetcher(
+ annotation.index_pattern
+ );
const request = await buildAnnotationRequest(
req,
panel,
annotation,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
);
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
index 9b371a8901e81..ebab984ff25aa 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js
@@ -10,8 +10,8 @@ import { AUTO_INTERVAL } from '../../../common/constants';
const DEFAULT_TIME_FIELD = '@timestamp';
-export function getIntervalAndTimefield(panel, series = {}, indexPatternObject) {
- const getDefaultTimeField = () => indexPatternObject?.timeFieldName ?? DEFAULT_TIME_FIELD;
+export function getIntervalAndTimefield(panel, series = {}, indexPattern) {
+ const getDefaultTimeField = () => indexPattern?.timeFieldName ?? DEFAULT_TIME_FIELD;
const timeField =
(series.override_index_pattern && series.series_time_field) ||
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
index f521de632b1f8..13dc1207f51de 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
@@ -21,6 +21,7 @@ import type {
VisTypeTimeseriesRequestServices,
} from '../../types';
import type { PanelSchema } from '../../../common/types';
+import { PANEL_TYPES } from '../../../common/panel_types';
export async function getSeriesData(
requestContext: VisTypeTimeseriesRequestHandlerContext,
@@ -28,10 +29,12 @@ export async function getSeriesData(
panel: PanelSchema,
services: VisTypeTimeseriesRequestServices
) {
- const strategy = await services.searchStrategyRegistry.getViableStrategyForPanel(
+ const panelIndex = await services.cachedIndexPatternFetcher(panel.index_pattern);
+
+ const strategy = await services.searchStrategyRegistry.getViableStrategy(
requestContext,
req,
- panel
+ panelIndex
);
if (!strategy) {
@@ -50,14 +53,15 @@ export async function getSeriesData(
try {
const bodiesPromises = getActiveSeries(panel).map((series) =>
- getSeriesRequestParams(req, panel, series, capabilities, services)
+ getSeriesRequestParams(req, panel, panelIndex, series, capabilities, services)
);
const searches = await Promise.all(bodiesPromises);
const data = await searchStrategy.search(requestContext, req, searches);
const handleResponseBodyFn = handleResponseBody(panel, req, {
- requestContext,
+ indexPatternsService: services.indexPatternsService,
+ cachedIndexPatternFetcher: services.cachedIndexPatternFetcher,
searchStrategy,
capabilities,
});
@@ -70,7 +74,7 @@ export async function getSeriesData(
let annotations = null;
- if (panel.annotations && panel.annotations.length) {
+ if (panel.type === PANEL_TYPES.TIMESERIES && panel.annotations && panel.annotations.length) {
annotations = await getAnnotations({
req,
panel,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
index a35a3246b0dd3..0cc1188086b7b 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
@@ -16,8 +16,8 @@ import { buildRequestBody } from './table/build_request_body';
import { handleErrorResponse } from './handle_error_response';
// @ts-expect-error
import { processBucket } from './table/process_bucket';
-import { getIndexPatternObject } from '../search_strategies/lib/get_index_pattern';
-import { createFieldsFetcher } from './helpers/fields_fetcher';
+
+import { createFieldsFetcher } from '../search_strategies/lib/fields_fetcher';
import { extractFieldLabel } from '../../../common/calculate_label';
import type {
VisTypeTimeseriesRequestHandlerContext,
@@ -32,12 +32,12 @@ export async function getTableData(
panel: PanelSchema,
services: VisTypeTimeseriesRequestServices
) {
- const panelIndexPattern = panel.index_pattern;
+ const panelIndex = await services.cachedIndexPatternFetcher(panel.index_pattern);
const strategy = await services.searchStrategyRegistry.getViableStrategy(
requestContext,
req,
- panelIndexPattern
+ panelIndex
);
if (!strategy) {
@@ -49,15 +49,17 @@ export async function getTableData(
}
const { searchStrategy, capabilities } = strategy;
- const { indexPatternObject } = await getIndexPatternObject(panelIndexPattern, {
+
+ const extractFields = createFieldsFetcher(req, {
indexPatternsService: services.indexPatternsService,
+ cachedIndexPatternFetcher: services.cachedIndexPatternFetcher,
+ searchStrategy,
+ capabilities,
});
- const extractFields = createFieldsFetcher(req, { requestContext, searchStrategy, capabilities });
-
const calculatePivotLabel = async () => {
- if (panel.pivot_id && indexPatternObject?.title) {
- const fields = await extractFields(indexPatternObject.title);
+ if (panel.pivot_id && panelIndex.indexPattern?.title) {
+ const fields = await extractFields(panelIndex.indexPattern.title);
return extractFieldLabel(fields, panel.pivot_id);
}
@@ -75,7 +77,7 @@ export async function getTableData(
req,
panel,
services.esQueryConfig,
- indexPatternObject,
+ panelIndex.indexPattern,
capabilities,
services.uiSettings
);
@@ -83,7 +85,7 @@ export async function getTableData(
const [resp] = await searchStrategy.search(requestContext, req, [
{
body,
- index: panelIndexPattern,
+ index: panelIndex.indexPatternString,
},
]);
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
index 0d100f6310b99..48b33c1e787e9 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js
@@ -18,7 +18,7 @@ export function dateHistogram(
panel,
annotation,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
index 9ff0325b60e82..dab9a24d06c0f 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js
@@ -19,7 +19,7 @@ export function dateHistogram(
panel,
series,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
@@ -27,11 +27,7 @@ export function dateHistogram(
const maxBarsUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS);
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { timeField, interval, maxBars } = getIntervalAndTimefield(
- panel,
- series,
- indexPatternObject
- );
+ const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, indexPattern);
const { bucketSize, intervalString } = getBucketSize(
req,
interval,
@@ -68,7 +64,7 @@ export function dateHistogram(
overwrite(doc, `aggs.${series.id}.meta`, {
timeField,
intervalString,
- index: indexPatternObject?.title,
+ index: indexPattern?.title,
bucketSize,
seriesId: series.id,
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
index d653f6acf6f3e..945c57b2341f3 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
@@ -16,7 +16,7 @@ describe('dateHistogram(req, panel, series)', () => {
let req;
let capabilities;
let config;
- let indexPatternObject;
+ let indexPattern;
let uiSettings;
beforeEach(() => {
@@ -39,7 +39,7 @@ describe('dateHistogram(req, panel, series)', () => {
allowLeadingWildcards: true,
queryStringOptions: {},
};
- indexPatternObject = {};
+ indexPattern = {};
capabilities = new DefaultSearchCapabilities(req);
uiSettings = {
get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50),
@@ -49,15 +49,9 @@ describe('dateHistogram(req, panel, series)', () => {
test('calls next when finished', async () => {
const next = jest.fn();
- await dateHistogram(
- req,
- panel,
- series,
- config,
- indexPatternObject,
- capabilities,
- uiSettings
- )(next)({});
+ await dateHistogram(req, panel, series, config, indexPattern, capabilities, uiSettings)(next)(
+ {}
+ );
expect(next.mock.calls.length).toEqual(1);
});
@@ -69,7 +63,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
@@ -110,7 +104,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
@@ -154,7 +148,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
@@ -198,7 +192,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
@@ -216,7 +210,7 @@ describe('dateHistogram(req, panel, series)', () => {
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
)(next)({});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
index 31ae988718a27..4639af9db83b8 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js
@@ -12,19 +12,19 @@ import { esQuery } from '../../../../../../data/server';
const filter = (metric) => metric.type === 'filter_ratio';
-export function ratios(req, panel, series, esQueryConfig, indexPatternObject) {
+export function ratios(req, panel, series, esQueryConfig, indexPattern) {
return (next) => (doc) => {
if (series.metrics.some(filter)) {
series.metrics.filter(filter).forEach((metric) => {
overwrite(
doc,
`aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.filter`,
- esQuery.buildEsQuery(indexPatternObject, metric.numerator, [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig)
);
overwrite(
doc,
`aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`,
- esQuery.buildEsQuery(indexPatternObject, metric.denominator, [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig)
);
let numeratorPath = `${metric.id}-numerator>_count`;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
index 9e0dd4f76c13f..345488ec01d5e 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js
@@ -13,7 +13,7 @@ describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () =>
let series;
let req;
let esQueryConfig;
- let indexPatternObject;
+ let indexPattern;
beforeEach(() => {
panel = {
time_field: 'timestamp',
@@ -47,18 +47,18 @@ describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () =>
queryStringOptions: { analyze_wildcard: true },
ignoreFilterIfFieldNotInIndex: false,
};
- indexPatternObject = {};
+ indexPattern = {};
});
test('calls next when finished', () => {
const next = jest.fn();
- ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({});
+ ratios(req, panel, series, esQueryConfig, indexPattern)(next)({});
expect(next.mock.calls.length).toEqual(1);
});
test('returns filter ratio aggs', () => {
const next = (doc) => doc;
- const doc = ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({});
+ const doc = ratios(req, panel, series, esQueryConfig, indexPattern)(next)({});
expect(doc).toEqual({
aggs: {
test: {
@@ -135,7 +135,7 @@ describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () =>
test('returns empty object when field is not set', () => {
delete series.metrics[0].field;
const next = (doc) => doc;
- const doc = ratios(req, panel, series, esQueryConfig, indexPatternObject)(next)({});
+ const doc = ratios(req, panel, series, esQueryConfig, indexPattern)(next)({});
expect(doc).toEqual({
aggs: {
test: {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
index 649b3cee6ea3e..86b691f6496c9 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js
@@ -17,14 +17,14 @@ export function metricBuckets(
panel,
series,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
series.metrics
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
index 1d67df7c92eb6..ce61374c0b124 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js
@@ -56,14 +56,14 @@ export function positiveRate(
panel,
series,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
if (series.metrics.some(filter)) {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
index cb12aa3513b91..d0e92c9157cb5 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js
@@ -10,16 +10,16 @@ import { offsetTime } from '../../offset_time';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { esQuery } from '../../../../../../data/server';
-export function query(req, panel, series, esQueryConfig, indexPatternObject) {
+export function query(req, panel, series, esQueryConfig, indexPattern) {
return (next) => (doc) => {
- const { timeField } = getIntervalAndTimefield(panel, series, indexPatternObject);
+ const { timeField } = getIntervalAndTimefield(panel, series, indexPattern);
const { from, to } = offsetTime(req, series.offset_time);
doc.size = 0;
const ignoreGlobalFilter = panel.ignore_global_filter || series.ignore_global_filter;
const queries = !ignoreGlobalFilter ? req.body.query : [];
const filters = !ignoreGlobalFilter ? req.body.filters : [];
- doc.query = esQuery.buildEsQuery(indexPatternObject, queries, filters, esQueryConfig);
+ doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig);
const timerange = {
range: {
@@ -34,13 +34,13 @@ export function query(req, panel, series, esQueryConfig, indexPatternObject) {
if (panel.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPatternObject, [panel.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)
);
}
if (series.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPatternObject, [series.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig)
);
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
index 315ccdfc13a47..401344d48f865 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js
@@ -17,13 +17,13 @@ export function siblingBuckets(
panel,
series,
esQueryConfig,
- indexPatternObject,
+ indexPattern,
capabilities,
uiSettings
) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, series, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, series, indexPattern);
const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
series.metrics
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
index 0ae6d113e28e4..5518065643172 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js
@@ -15,20 +15,13 @@ import { calculateAggRoot } from './calculate_agg_root';
import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server';
const { dateHistogramInterval } = search.aggs;
-export function dateHistogram(
- req,
- panel,
- esQueryConfig,
- indexPatternObject,
- capabilities,
- uiSettings
-) {
+export function dateHistogram(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPattern);
const meta = {
timeField,
- index: indexPatternObject?.title,
+ index: indexPattern?.title,
};
const getDateHistogramForLastBucketMode = () => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
index 7b3ac16cd6561..abb5971908771 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js
@@ -13,7 +13,7 @@ import { calculateAggRoot } from './calculate_agg_root';
const filter = (metric) => metric.type === 'filter_ratio';
-export function ratios(req, panel, esQueryConfig, indexPatternObject) {
+export function ratios(req, panel, esQueryConfig, indexPattern) {
return (next) => (doc) => {
panel.series.forEach((column) => {
const aggRoot = calculateAggRoot(doc, column);
@@ -22,12 +22,12 @@ export function ratios(req, panel, esQueryConfig, indexPatternObject) {
overwrite(
doc,
`${aggRoot}.timeseries.aggs.${metric.id}-numerator.filter`,
- esQuery.buildEsQuery(indexPatternObject, metric.numerator, [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig)
);
overwrite(
doc,
`${aggRoot}.timeseries.aggs.${metric.id}-denominator.filter`,
- esQuery.buildEsQuery(indexPatternObject, metric.denominator, [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig)
);
let numeratorPath = `${metric.id}-numerator>_count`;
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
index 53149a31603ef..5ce508bd9b279 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js
@@ -13,17 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { calculateAggRoot } from './calculate_agg_root';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function metricBuckets(
- req,
- panel,
- esQueryConfig,
- indexPatternObject,
- capabilities,
- uiSettings
-) {
+export function metricBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
index 8c7a0f5e2367f..176721e7b563a 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js
@@ -12,17 +12,10 @@ import { calculateAggRoot } from './calculate_agg_root';
import { createPositiveRate, filter } from '../series/positive_rate';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function positiveRate(
- req,
- panel,
- esQueryConfig,
- indexPatternObject,
- capabilities,
- uiSettings
-) {
+export function positiveRate(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
index a0118c5037d34..76df07b76e80e 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js
@@ -10,16 +10,16 @@ import { getTimerange } from '../../helpers/get_timerange';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { esQuery } from '../../../../../../data/server';
-export function query(req, panel, esQueryConfig, indexPatternObject) {
+export function query(req, panel, esQueryConfig, indexPattern) {
return (next) => (doc) => {
- const { timeField } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { timeField } = getIntervalAndTimefield(panel, {}, indexPattern);
const { from, to } = getTimerange(req);
doc.size = 0;
const queries = !panel.ignore_global_filter ? req.body.query : [];
const filters = !panel.ignore_global_filter ? req.body.filters : [];
- doc.query = esQuery.buildEsQuery(indexPatternObject, queries, filters, esQueryConfig);
+ doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig);
const timerange = {
range: {
@@ -33,7 +33,7 @@ export function query(req, panel, esQueryConfig, indexPatternObject) {
doc.query.bool.must.push(timerange);
if (panel.filter) {
doc.query.bool.must.push(
- esQuery.buildEsQuery(indexPatternObject, [panel.filter], [], esQueryConfig)
+ esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig)
);
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
index d205f0679a908..5539f16df41e0 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js
@@ -13,17 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
import { calculateAggRoot } from './calculate_agg_root';
import { UI_SETTINGS } from '../../../../../../data/common';
-export function siblingBuckets(
- req,
- panel,
- esQueryConfig,
- indexPatternObject,
- capabilities,
- uiSettings
-) {
+export function siblingBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) {
return (next) => async (doc) => {
const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET);
- const { interval } = getIntervalAndTimefield(panel, {}, indexPatternObject);
+ const { interval } = getIntervalAndTimefield(panel, {}, indexPattern);
const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings);
panel.series.forEach((column) => {
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts
index 968fe01565b04..d97af8ac748f4 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts
@@ -79,13 +79,13 @@ describe('buildRequestBody(req)', () => {
allowLeadingWildcards: true,
queryStringOptions: {},
};
- const indexPatternObject = {};
+ const indexPattern = {};
const doc = await buildRequestBody(
{ body },
panel,
series,
config,
- indexPatternObject,
+ indexPattern,
capabilities,
{
get: async () => 50,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
index ae846b5b4b817..1f2735da8fb06 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
@@ -6,43 +6,46 @@
* Side Public License, v 1.
*/
-import { PanelSchema, SeriesItemsSchema } from '../../../../common/types';
import { buildRequestBody } from './build_request_body';
-import { getIndexPatternObject } from '../../../lib/search_strategies/lib/get_index_pattern';
-import { VisTypeTimeseriesRequestServices, VisTypeTimeseriesVisDataRequest } from '../../../types';
-import { DefaultSearchCapabilities } from '../../search_strategies';
+
+import type { FetchedIndexPattern, PanelSchema, SeriesItemsSchema } from '../../../../common/types';
+import type {
+ VisTypeTimeseriesRequestServices,
+ VisTypeTimeseriesVisDataRequest,
+} from '../../../types';
+import type { DefaultSearchCapabilities } from '../../search_strategies';
export async function getSeriesRequestParams(
req: VisTypeTimeseriesVisDataRequest,
panel: PanelSchema,
+ panelIndex: FetchedIndexPattern,
series: SeriesItemsSchema,
capabilities: DefaultSearchCapabilities,
{
esQueryConfig,
esShardTimeout,
uiSettings,
- indexPatternsService,
+ cachedIndexPatternFetcher,
}: VisTypeTimeseriesRequestServices
) {
- const indexPattern =
- (series.override_index_pattern && series.series_index_pattern) || panel.index_pattern;
+ let seriesIndex = panelIndex;
- const { indexPatternObject, indexPatternString } = await getIndexPatternObject(indexPattern, {
- indexPatternsService,
- });
+ if (series.override_index_pattern) {
+ seriesIndex = await cachedIndexPatternFetcher(series.series_index_pattern ?? '');
+ }
const request = await buildRequestBody(
req,
panel,
series,
esQueryConfig,
- indexPatternObject,
+ seriesIndex.indexPattern,
capabilities,
uiSettings
);
return {
- index: indexPatternString,
+ index: seriesIndex.indexPatternString,
body: {
...request,
timeout: esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/handle_response_body.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/handle_response_body.ts
index 22e0372c23526..49f1ec0f93de5 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/handle_response_body.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/handle_response_body.ts
@@ -12,7 +12,10 @@ import { PanelSchema } from '../../../../common/types';
import { buildProcessorFunction } from '../build_processor_function';
// @ts-expect-error
import { processors } from '../response_processors/series';
-import { createFieldsFetcher, FieldsFetcherServices } from './../helpers/fields_fetcher';
+import {
+ createFieldsFetcher,
+ FieldsFetcherServices,
+} from '../../search_strategies/lib/fields_fetcher';
import { VisTypeTimeseriesVisDataRequest } from '../../../types';
export function handleResponseBody(
diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts
index 71b76dddbca6a..8bc752e944709 100644
--- a/src/plugins/vis_type_timeseries/server/plugin.ts
+++ b/src/plugins/vis_type_timeseries/server/plugin.ts
@@ -37,6 +37,8 @@ import {
} from './lib/search_strategies';
import { TimeseriesVisData, VisPayload } from '../common/types';
+import { registerTimeseriesUsageCollector } from './usage_collector';
+
export interface LegacySetup {
server: Server;
}
@@ -111,12 +113,16 @@ export class VisTypeTimeseriesPlugin implements Plugin {
},
};
- searchStrategyRegistry.addStrategy(new DefaultSearchStrategy(framework));
- searchStrategyRegistry.addStrategy(new RollupSearchStrategy(framework));
+ searchStrategyRegistry.addStrategy(new DefaultSearchStrategy());
+ searchStrategyRegistry.addStrategy(new RollupSearchStrategy());
visDataRoutes(router, framework);
fieldsRoutes(router, framework);
+ if (plugins.usageCollection) {
+ registerTimeseriesUsageCollector(plugins.usageCollection, globalConfig$);
+ }
+
return {
getVisData: async (
requestContext: VisTypeTimeseriesRequestHandlerContext,
diff --git a/src/plugins/vis_type_timeseries/server/types.ts b/src/plugins/vis_type_timeseries/server/types.ts
index da32669b3855d..7b42cf61d52b3 100644
--- a/src/plugins/vis_type_timeseries/server/types.ts
+++ b/src/plugins/vis_type_timeseries/server/types.ts
@@ -6,14 +6,19 @@
* Side Public License, v 1.
*/
+import { Observable } from 'rxjs';
+import { SharedGlobalConfig } from 'kibana/server';
import type { IRouter, IUiSettingsClient, KibanaRequest } from 'src/core/server';
import type {
DataRequestHandlerContext,
EsQueryConfig,
IndexPatternsService,
} from '../../data/server';
-import { VisPayload } from '../common/types';
-import { SearchStrategyRegistry } from './lib/search_strategies';
+import type { VisPayload } from '../common/types';
+import type { SearchStrategyRegistry } from './lib/search_strategies';
+import type { CachedIndexPatternFetcher } from './lib/search_strategies/lib/cached_index_pattern_fetcher';
+
+export type ConfigObservable = Observable;
export type VisTypeTimeseriesRequestHandlerContext = DataRequestHandlerContext;
export type VisTypeTimeseriesRouter = IRouter;
@@ -29,4 +34,5 @@ export interface VisTypeTimeseriesRequestServices {
uiSettings: IUiSettingsClient;
indexPatternsService: IndexPatternsService;
searchStrategyRegistry: SearchStrategyRegistry;
+ cachedIndexPatternFetcher: CachedIndexPatternFetcher;
}
diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.mock.ts b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.mock.ts
new file mode 100644
index 0000000000000..bb52d215c67e8
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.mock.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 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 const mockStats = { somestat: 1 };
+export const mockGetStats = jest.fn().mockResolvedValue(mockStats);
+
+jest.doMock('./get_usage_collector', () => ({
+ getStats: mockGetStats,
+}));
diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.test.ts b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.test.ts
new file mode 100644
index 0000000000000..8ecc02072905f
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.test.ts
@@ -0,0 +1,168 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { getStats } from './get_usage_collector';
+import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
+import { TIME_RANGE_DATA_MODES } from '../../common/timerange_data_modes';
+
+const mockedSavedObjects = [
+ {
+ _id: 'visualization:timeseries-123',
+ _source: {
+ type: 'visualization',
+ visualization: {
+ visState: JSON.stringify({
+ type: 'metrics',
+ title: 'TSVB visualization 1',
+ params: {
+ time_range_mode: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
+ },
+ }),
+ },
+ },
+ },
+ {
+ _id: 'visualization:timeseries-321',
+ _source: {
+ type: 'visualization',
+ visualization: {
+ visState: JSON.stringify({
+ type: 'metrics',
+ title: 'TSVB visualization 2',
+ params: {
+ time_range_mode: TIME_RANGE_DATA_MODES.LAST_VALUE,
+ },
+ }),
+ },
+ },
+ },
+ {
+ _id: 'visualization:timeseries-456',
+ _source: {
+ type: 'visualization',
+ visualization: {
+ visState: JSON.stringify({
+ type: 'metrics',
+ title: 'TSVB visualization 3',
+ params: {
+ time_range_mode: undefined,
+ },
+ }),
+ },
+ },
+ },
+];
+
+const mockedSavedObjectsByValue = [
+ {
+ attributes: {
+ panelsJSON: JSON.stringify({
+ type: 'visualization',
+ embeddableConfig: {
+ savedVis: {
+ type: 'metrics',
+ params: {
+ time_range_mode: TIME_RANGE_DATA_MODES.LAST_VALUE,
+ },
+ },
+ },
+ }),
+ },
+ },
+ {
+ attributes: {
+ panelsJSON: JSON.stringify({
+ type: 'visualization',
+ embeddableConfig: {
+ savedVis: {
+ type: 'metrics',
+ params: {
+ time_range_mode: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
+ },
+ },
+ },
+ }),
+ },
+ },
+];
+
+const getMockCollectorFetchContext = (hits?: unknown[], savedObjectsByValue: unknown[] = []) => {
+ const fetchParamsMock = createCollectorFetchContextMock();
+
+ fetchParamsMock.esClient.search = jest.fn().mockResolvedValue({ body: { hits: { hits } } });
+ fetchParamsMock.soClient.find = jest.fn().mockResolvedValue({
+ saved_objects: savedObjectsByValue,
+ });
+ return fetchParamsMock;
+};
+
+describe('Timeseries visualization usage collector', () => {
+ const mockIndex = 'mock_index';
+
+ test('Returns undefined when no results found (undefined)', async () => {
+ const mockCollectorFetchContext = getMockCollectorFetchContext([], []);
+ const result = await getStats(
+ mockCollectorFetchContext.esClient,
+ mockCollectorFetchContext.soClient,
+ mockIndex
+ );
+
+ expect(result).toBeUndefined();
+ });
+
+ test('Returns undefined when no timeseries saved objects found', async () => {
+ const mockCollectorFetchContext = getMockCollectorFetchContext(
+ [
+ {
+ _id: 'visualization:myvis-123',
+ _source: {
+ type: 'visualization',
+ visualization: { visState: '{"type": "area"}' },
+ },
+ },
+ ],
+ [
+ {
+ attributes: {
+ panelsJSON: JSON.stringify({
+ type: 'visualization',
+ embeddableConfig: {
+ savedVis: {
+ type: 'area',
+ },
+ },
+ }),
+ },
+ },
+ ]
+ );
+ const result = await getStats(
+ mockCollectorFetchContext.esClient,
+ mockCollectorFetchContext.soClient,
+ mockIndex
+ );
+
+ expect(result).toBeUndefined();
+ });
+
+ test('Summarizes visualizations response data', async () => {
+ const mockCollectorFetchContext = getMockCollectorFetchContext(
+ mockedSavedObjects,
+ mockedSavedObjectsByValue
+ );
+ const result = await getStats(
+ mockCollectorFetchContext.esClient,
+ mockCollectorFetchContext.soClient,
+ mockIndex
+ );
+
+ expect(result).toMatchObject({
+ timeseries_use_last_value_mode_total: 3,
+ });
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.ts b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.ts
new file mode 100644
index 0000000000000..c1a8715f72227
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/usage_collector/get_usage_collector.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { ElasticsearchClient } from 'src/core/server';
+import { SavedObjectsClientContract, ISavedObjectsRepository } from 'kibana/server';
+import { TIME_RANGE_DATA_MODES } from '../../common/timerange_data_modes';
+import { findByValueEmbeddables } from '../../../dashboard/server';
+
+export interface TimeseriesUsage {
+ timeseries_use_last_value_mode_total: number;
+}
+
+interface VisState {
+ type?: string;
+ params?: any;
+}
+
+export const getStats = async (
+ esClient: ElasticsearchClient,
+ soClient: SavedObjectsClientContract | ISavedObjectsRepository,
+ index: string
+): Promise => {
+ const timeseriesUsage = {
+ timeseries_use_last_value_mode_total: 0,
+ };
+
+ const searchParams = {
+ size: 10000,
+ index,
+ ignoreUnavailable: true,
+ filterPath: ['hits.hits._id', 'hits.hits._source.visualization'],
+ body: {
+ query: {
+ bool: {
+ filter: { term: { type: 'visualization' } },
+ },
+ },
+ },
+ };
+
+ const { body: esResponse } = await esClient.search<{
+ visualization: { visState: string };
+ updated_at: string;
+ }>(searchParams);
+
+ function telemetryUseLastValueMode(visState: VisState) {
+ if (
+ visState.type === 'metrics' &&
+ visState.params.type !== 'timeseries' &&
+ (!visState.params.time_range_mode ||
+ visState.params.time_range_mode === TIME_RANGE_DATA_MODES.LAST_VALUE)
+ ) {
+ timeseriesUsage.timeseries_use_last_value_mode_total++;
+ }
+ }
+
+ if (esResponse?.hits?.hits?.length) {
+ for (const hit of esResponse.hits.hits) {
+ if (hit._source && 'visualization' in hit._source) {
+ const { visualization } = hit._source!;
+
+ let visState: VisState = {};
+ try {
+ visState = JSON.parse(visualization?.visState ?? '{}');
+ } catch (e) {
+ // invalid visState
+ }
+
+ telemetryUseLastValueMode(visState);
+ }
+ }
+ }
+
+ const byValueVisualizations = await findByValueEmbeddables(soClient, 'visualization');
+
+ for (const item of byValueVisualizations) {
+ telemetryUseLastValueMode(item.savedVis as VisState);
+ }
+
+ return timeseriesUsage.timeseries_use_last_value_mode_total ? timeseriesUsage : undefined;
+};
diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/index.ts b/src/plugins/vis_type_timeseries/server/usage_collector/index.ts
new file mode 100644
index 0000000000000..7f72662e154ea
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/usage_collector/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { registerTimeseriesUsageCollector } from './register_timeseries_collector';
diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.test.ts b/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.test.ts
new file mode 100644
index 0000000000000..2612a3882af2d
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.test.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 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 { of } from 'rxjs';
+import { mockStats, mockGetStats } from './get_usage_collector.mock';
+import { createUsageCollectionSetupMock } from 'src/plugins/usage_collection/server/usage_collection.mock';
+import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
+import { registerTimeseriesUsageCollector } from './register_timeseries_collector';
+import { ConfigObservable } from '../types';
+
+describe('registerTimeseriesUsageCollector', () => {
+ const mockIndex = 'mock_index';
+ const mockConfig = of({ kibana: { index: mockIndex } }) as ConfigObservable;
+
+ it('makes a usage collector and registers it`', () => {
+ const mockCollectorSet = createUsageCollectionSetupMock();
+ registerTimeseriesUsageCollector(mockCollectorSet, mockConfig);
+ expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1);
+ expect(mockCollectorSet.registerCollector).toBeCalledTimes(1);
+ });
+
+ it('makeUsageCollector configs fit the shape', () => {
+ const mockCollectorSet = createUsageCollectionSetupMock();
+ registerTimeseriesUsageCollector(mockCollectorSet, mockConfig);
+ expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({
+ type: 'vis_type_timeseries',
+ isReady: expect.any(Function),
+ fetch: expect.any(Function),
+ schema: expect.any(Object),
+ });
+ const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
+ expect(usageCollectorConfig.isReady()).toBe(true);
+ });
+
+ it('makeUsageCollector config.isReady returns true', () => {
+ const mockCollectorSet = createUsageCollectionSetupMock();
+ registerTimeseriesUsageCollector(mockCollectorSet, mockConfig);
+ const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
+ expect(usageCollectorConfig.isReady()).toBe(true);
+ });
+
+ it('makeUsageCollector config.fetch calls getStats', async () => {
+ const mockCollectorSet = createUsageCollectionSetupMock();
+ registerTimeseriesUsageCollector(mockCollectorSet, mockConfig);
+ const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value;
+ const mockedCollectorFetchContext = createCollectorFetchContextMock();
+ const fetchResult = await usageCollector.fetch(mockedCollectorFetchContext);
+ expect(mockGetStats).toBeCalledTimes(1);
+ expect(mockGetStats).toBeCalledWith(
+ mockedCollectorFetchContext.esClient,
+ mockedCollectorFetchContext.soClient,
+ mockIndex
+ );
+ expect(fetchResult).toBe(mockStats);
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.ts b/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.ts
new file mode 100644
index 0000000000000..5edeb6654020e
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/usage_collector/register_timeseries_collector.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { first } from 'rxjs/operators';
+import { getStats, TimeseriesUsage } from './get_usage_collector';
+import { ConfigObservable } from '../types';
+
+export function registerTimeseriesUsageCollector(
+ collectorSet: UsageCollectionSetup,
+ config: ConfigObservable
+) {
+ const collector = collectorSet.makeUsageCollector({
+ type: 'vis_type_timeseries',
+ isReady: () => true,
+ schema: {
+ timeseries_use_last_value_mode_total: {
+ type: 'long',
+ _meta: { description: 'Number of TSVB visualizations using "last value" as a time range' },
+ },
+ },
+ fetch: async ({ esClient, soClient }) => {
+ const { index } = (await config.pipe(first()).toPromise()).kibana;
+
+ return await getStats(esClient, soClient, index);
+ },
+ });
+
+ collectorSet.registerCollector(collector);
+}
diff --git a/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx b/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx
index efb41c470024b..f5b0f614458fd 100644
--- a/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx
+++ b/src/plugins/vis_type_vega/public/components/vega_help_menu.tsx
@@ -11,6 +11,8 @@ import { EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } fr
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
+import { getDocLinks } from '../services';
+
function VegaHelpMenu() {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const onButtonClick = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]);
@@ -30,7 +32,7 @@ function VegaHelpMenu() {
const items = [
diff --git a/src/plugins/vis_type_vega/public/plugin.ts b/src/plugins/vis_type_vega/public/plugin.ts
index 0204c2c90b71b..f935362d21604 100644
--- a/src/plugins/vis_type_vega/public/plugin.ts
+++ b/src/plugins/vis_type_vega/public/plugin.ts
@@ -19,6 +19,7 @@ import {
setUISettings,
setInjectedMetadata,
setMapServiceSettings,
+ setDocLinks,
} from './services';
import { createVegaFn } from './vega_fn';
@@ -96,5 +97,6 @@ export class VegaPlugin implements Plugin {
setNotifications(core.notifications);
setData(data);
setInjectedMetadata(core.injectedMetadata);
+ setDocLinks(core.docLinks);
}
}
diff --git a/src/plugins/vis_type_vega/public/services.ts b/src/plugins/vis_type_vega/public/services.ts
index c47378282932b..f67fe4794e783 100644
--- a/src/plugins/vis_type_vega/public/services.ts
+++ b/src/plugins/vis_type_vega/public/services.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { CoreStart, NotificationsStart, IUiSettingsClient } from 'src/core/public';
+import { CoreStart, NotificationsStart, IUiSettingsClient, DocLinksStart } from 'src/core/public';
import { DataPublicPluginStart } from '../../data/public';
import { createGetterSetter } from '../../kibana_utils/public';
@@ -35,3 +35,5 @@ export const [getInjectedVars, setInjectedVars] = createGetterSetter<{
}>('InjectedVars');
export const getEnableExternalUrls = () => getInjectedVars().enableExternalUrls;
+
+export const [getDocLinks, setDocLinks] = createGetterSetter('docLinks');
diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
index 349e024f31c31..c2b9fcd77757a 100644
--- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
+++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx
@@ -12,6 +12,8 @@ import { first } from 'rxjs/operators';
import { EmbeddableStateWithType } from 'src/plugins/embeddable/common';
import { SavedObjectAttributes } from '../../../../core/public';
import { extractSearchSourceReferences } from '../../../data/public';
+import { SavedObjectReference } from '../../../../core/public';
+
import {
EmbeddableFactoryDefinition,
EmbeddableOutput,
@@ -38,6 +40,12 @@ import {
} from '../services';
import { showNewVisModal } from '../wizard';
import { convertToSerializedVis } from '../saved_visualizations/_saved_vis';
+import {
+ extractControlsReferences,
+ extractTimeSeriesReferences,
+ injectTimeSeriesReferences,
+ injectControlsReferences,
+} from '../saved_visualizations/saved_visualization_references';
import { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object';
import { StartServicesGetter } from '../../../kibana_utils/public';
import { VisualizationsStartDeps } from '../plugin';
@@ -239,6 +247,19 @@ export class VisualizeEmbeddableFactory
);
}
+ public inject(_state: EmbeddableStateWithType, references: SavedObjectReference[]) {
+ const state = (_state as unknown) as VisualizeInput;
+
+ const { type, params } = state.savedVis ?? {};
+
+ if (type && params) {
+ injectControlsReferences(type, params, references);
+ injectTimeSeriesReferences(type, params, references);
+ }
+
+ return _state;
+ }
+
public extract(_state: EmbeddableStateWithType) {
const state = (_state as unknown) as VisualizeInput;
const references = [];
@@ -259,19 +280,11 @@ export class VisualizeEmbeddableFactory
});
}
- if (state.savedVis?.params.controls) {
- const controls = state.savedVis.params.controls;
- controls.forEach((control: Record, i: number) => {
- if (!control.indexPattern) {
- return;
- }
- control.indexPatternRefName = `control_${i}_index_pattern`;
- references.push({
- name: control.indexPatternRefName,
- type: 'index-pattern',
- id: control.indexPattern,
- });
- });
+ const { type, params } = state.savedVis ?? {};
+
+ if (type && params) {
+ extractControlsReferences(type, params, references, `control_${state.id}`);
+ extractTimeSeriesReferences(type, params, references, `metrics_${state.id}`);
}
return { state: _state, references };
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts
new file mode 100644
index 0000000000000..d116fd2e2e9a7
--- /dev/null
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/controls_references.ts
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 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 { SavedObjectReference } from '../../../../../core/types';
+import { VisParams } from '../../../common';
+
+const isControlsVis = (visType: string) => visType === 'input_control_vis';
+
+export const extractControlsReferences = (
+ visType: string,
+ visParams: VisParams,
+ references: SavedObjectReference[] = [],
+ prefix: string = 'control'
+) => {
+ if (isControlsVis(visType)) {
+ (visParams?.controls ?? []).forEach((control: Record, i: number) => {
+ if (!control.indexPattern) {
+ return;
+ }
+ control.indexPatternRefName = `${prefix}_${i}_index_pattern`;
+ references.push({
+ name: control.indexPatternRefName,
+ type: 'index-pattern',
+ id: control.indexPattern,
+ });
+ delete control.indexPattern;
+ });
+ }
+};
+
+export const injectControlsReferences = (
+ visType: string,
+ visParams: VisParams,
+ references: SavedObjectReference[]
+) => {
+ if (isControlsVis(visType)) {
+ (visParams.controls ?? []).forEach((control: Record) => {
+ if (!control.indexPatternRefName) {
+ return;
+ }
+ const reference = references.find((ref) => ref.name === control.indexPatternRefName);
+ if (!reference) {
+ throw new Error(`Could not find index pattern reference "${control.indexPatternRefName}"`);
+ }
+ control.indexPattern = reference.id;
+ delete control.indexPatternRefName;
+ });
+ }
+};
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/index.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/index.ts
new file mode 100644
index 0000000000000..0acda1c0a0f80
--- /dev/null
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export { extractControlsReferences, injectControlsReferences } from './controls_references';
+export { extractTimeSeriesReferences, injectTimeSeriesReferences } from './timeseries_references';
+
+export { extractReferences, injectReferences } from './saved_visualization_references';
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.test.ts
similarity index 69%
rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts
rename to src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.test.ts
index f81054febcc44..867febd2544b0 100644
--- a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.test.ts
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.test.ts
@@ -7,8 +7,8 @@
*/
import { extractReferences, injectReferences } from './saved_visualization_references';
-import { VisSavedObject } from '../types';
-import { SavedVisState } from '../../common';
+import { VisSavedObject } from '../../types';
+import { SavedVisState } from '../../../common';
describe('extractReferences', () => {
test('extracts nothing if savedSearchId is empty', () => {
@@ -21,13 +21,13 @@ describe('extractReferences', () => {
};
const updatedDoc = extractReferences(doc);
expect(updatedDoc).toMatchInlineSnapshot(`
-Object {
- "attributes": Object {
- "foo": true,
- },
- "references": Array [],
-}
-`);
+ Object {
+ "attributes": Object {
+ "foo": true,
+ },
+ "references": Array [],
+ }
+ `);
});
test('extracts references from savedSearchId', () => {
@@ -41,20 +41,20 @@ Object {
};
const updatedDoc = extractReferences(doc);
expect(updatedDoc).toMatchInlineSnapshot(`
-Object {
- "attributes": Object {
- "foo": true,
- "savedSearchRefName": "search_0",
- },
- "references": Array [
- Object {
- "id": "123",
- "name": "search_0",
- "type": "search",
- },
- ],
-}
-`);
+ Object {
+ "attributes": Object {
+ "foo": true,
+ "savedSearchRefName": "search_0",
+ },
+ "references": Array [
+ Object {
+ "id": "123",
+ "name": "search_0",
+ "type": "search",
+ },
+ ],
+ }
+ `);
});
test('extracts references from controls', () => {
@@ -63,6 +63,7 @@ Object {
attributes: {
foo: true,
visState: JSON.stringify({
+ type: 'input_control_vis',
params: {
controls: [
{
@@ -81,20 +82,20 @@ Object {
const updatedDoc = extractReferences(doc);
expect(updatedDoc).toMatchInlineSnapshot(`
-Object {
- "attributes": Object {
- "foo": true,
- "visState": "{\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"bar\\":false}]}}",
- },
- "references": Array [
- Object {
- "id": "pattern*",
- "name": "control_0_index_pattern",
- "type": "index-pattern",
- },
- ],
-}
-`);
+ Object {
+ "attributes": Object {
+ "foo": true,
+ "visState": "{\\"type\\":\\"input_control_vis\\",\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"bar\\":false}]}}",
+ },
+ "references": Array [
+ Object {
+ "id": "pattern*",
+ "name": "control_0_index_pattern",
+ "type": "index-pattern",
+ },
+ ],
+ }
+ `);
});
});
@@ -106,11 +107,11 @@ describe('injectReferences', () => {
} as VisSavedObject;
injectReferences(context, []);
expect(context).toMatchInlineSnapshot(`
-Object {
- "id": "1",
- "title": "test",
-}
-`);
+ Object {
+ "id": "1",
+ "title": "test",
+ }
+ `);
});
test('injects references into context', () => {
@@ -119,6 +120,7 @@ Object {
title: 'test',
savedSearchRefName: 'search_0',
visState: ({
+ type: 'input_control_vis',
params: {
controls: [
{
@@ -146,25 +148,26 @@ Object {
];
injectReferences(context, references);
expect(context).toMatchInlineSnapshot(`
-Object {
- "id": "1",
- "savedSearchId": "123",
- "title": "test",
- "visState": Object {
- "params": Object {
- "controls": Array [
- Object {
- "foo": true,
- "indexPattern": "pattern*",
- },
- Object {
- "foo": false,
+ Object {
+ "id": "1",
+ "savedSearchId": "123",
+ "title": "test",
+ "visState": Object {
+ "params": Object {
+ "controls": Array [
+ Object {
+ "foo": true,
+ "indexPattern": "pattern*",
+ },
+ Object {
+ "foo": false,
+ },
+ ],
+ },
+ "type": "input_control_vis",
},
- ],
- },
- },
-}
-`);
+ }
+ `);
});
test(`fails when it can't find the saved search reference in the array`, () => {
@@ -183,6 +186,7 @@ Object {
id: '1',
title: 'test',
visState: ({
+ type: 'input_control_vis',
params: {
controls: [
{
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.ts
similarity index 67%
rename from src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts
rename to src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.ts
index 27b5a4542036b..6a4f9812db971 100644
--- a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/saved_visualization_references.ts
@@ -10,13 +10,16 @@ import {
SavedObjectAttribute,
SavedObjectAttributes,
SavedObjectReference,
-} from '../../../../core/public';
-import { VisSavedObject } from '../types';
+} from '../../../../../core/public';
+import { SavedVisState, VisSavedObject } from '../../types';
import {
extractSearchSourceReferences,
injectSearchSourceReferences,
SearchSourceFields,
-} from '../../../data/public';
+} from '../../../../data/public';
+
+import { extractTimeSeriesReferences, injectTimeSeriesReferences } from './timeseries_references';
+import { extractControlsReferences, injectControlsReferences } from './controls_references';
export function extractReferences({
attributes,
@@ -49,20 +52,13 @@ export function extractReferences({
// Extract index patterns from controls
if (updatedAttributes.visState) {
- const visState = JSON.parse(String(updatedAttributes.visState));
- const controls = (visState.params && visState.params.controls) || [];
- controls.forEach((control: Record, i: number) => {
- if (!control.indexPattern) {
- return;
- }
- control.indexPatternRefName = `control_${i}_index_pattern`;
- updatedReferences.push({
- name: control.indexPatternRefName,
- type: 'index-pattern',
- id: control.indexPattern,
- });
- delete control.indexPattern;
- });
+ const visState = JSON.parse(String(updatedAttributes.visState)) as SavedVisState;
+
+ if (visState.type && visState.params) {
+ extractControlsReferences(visState.type, visState.params, updatedReferences);
+ extractTimeSeriesReferences(visState.type, visState.params, updatedReferences);
+ }
+
updatedAttributes.visState = JSON.stringify(visState);
}
@@ -89,18 +85,11 @@ export function injectReferences(savedObject: VisSavedObject, references: SavedO
savedObject.savedSearchId = savedSearchReference.id;
delete savedObject.savedSearchRefName;
}
- if (savedObject.visState) {
- const controls = (savedObject.visState.params && savedObject.visState.params.controls) || [];
- controls.forEach((control: Record) => {
- if (!control.indexPatternRefName) {
- return;
- }
- const reference = references.find((ref) => ref.name === control.indexPatternRefName);
- if (!reference) {
- throw new Error(`Could not find index pattern reference "${control.indexPatternRefName}"`);
- }
- control.indexPattern = reference.id;
- delete control.indexPatternRefName;
- });
+
+ const { type, params } = savedObject.visState ?? {};
+
+ if (type && params) {
+ injectControlsReferences(type, params, references);
+ injectTimeSeriesReferences(type, params, references);
}
}
diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts
new file mode 100644
index 0000000000000..57706ee824e8d
--- /dev/null
+++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references/timeseries_references.ts
@@ -0,0 +1,83 @@
+/*
+ * Copyright 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 { SavedObjectReference } from '../../../../../core/types';
+import { VisParams } from '../../../common';
+
+/** @internal **/
+const REF_NAME_POSTFIX = '_ref_name';
+
+/** @internal **/
+const INDEX_PATTERN_REF_TYPE = 'index_pattern';
+
+/** @internal **/
+type Action = (object: Record, key: string) => void;
+
+const isMetricsVis = (visType: string) => visType === 'metrics';
+
+const doForExtractedIndices = (action: Action, visParams: VisParams) => {
+ action(visParams, 'index_pattern');
+
+ visParams.series.forEach((series: any) => {
+ if (series.override_index_pattern) {
+ action(series, 'series_index_pattern');
+ }
+ });
+
+ if (visParams.annotations) {
+ visParams.annotations.forEach((annotation: any) => {
+ action(annotation, 'index_pattern');
+ });
+ }
+};
+
+export const extractTimeSeriesReferences = (
+ visType: string,
+ visParams: VisParams,
+ references: SavedObjectReference[] = [],
+ prefix: string = 'metrics'
+) => {
+ let i = 0;
+ if (isMetricsVis(visType)) {
+ doForExtractedIndices((object, key) => {
+ if (object[key] && object[key].id) {
+ const name = `${prefix}_${i++}_index_pattern`;
+
+ object[key + REF_NAME_POSTFIX] = name;
+ references.push({
+ name,
+ type: INDEX_PATTERN_REF_TYPE,
+ id: object[key].id,
+ });
+ delete object[key];
+ }
+ }, visParams);
+ }
+};
+
+export const injectTimeSeriesReferences = (
+ visType: string,
+ visParams: VisParams,
+ references: SavedObjectReference[]
+) => {
+ if (isMetricsVis(visType)) {
+ doForExtractedIndices((object, key) => {
+ const refKey = key + REF_NAME_POSTFIX;
+
+ if (object[refKey]) {
+ const refValue = references.find((ref) => ref.name === object[refKey]);
+
+ if (refValue) {
+ object[key] = { id: refValue.id };
+ }
+
+ delete object[refKey];
+ }
+ }, visParams);
+ }
+};
diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
index afb59266d0dbf..ced33318413c5 100644
--- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
+++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts
@@ -790,6 +790,35 @@ const removeTSVBSearchSource: SavedObjectMigrationFn = (doc) => {
return doc;
};
+const addSupportOfDualIndexSelectionModeInTSVB: SavedObjectMigrationFn = (doc) => {
+ const visStateJSON = get(doc, 'attributes.visState');
+ let visState;
+
+ if (visStateJSON) {
+ try {
+ visState = JSON.parse(visStateJSON);
+ } catch (e) {
+ // Let it go, the data is invalid and we'll leave it as is
+ }
+ if (visState && visState.type === 'metrics') {
+ const { params } = visState;
+
+ if (typeof params?.index_pattern === 'string') {
+ params.use_kibana_indexes = false;
+ }
+
+ return {
+ ...doc,
+ attributes: {
+ ...doc.attributes,
+ visState: JSON.stringify(visState),
+ },
+ };
+ }
+ }
+ return doc;
+};
+
// [Data table visualization] Enable toolbar by default
const enableDataTableVisToolbar: SavedObjectMigrationFn = (doc) => {
let visState;
@@ -929,4 +958,5 @@ export const visualizationSavedObjectTypeMigrations = {
'7.10.0': flow(migrateFilterRatioQuery, removeTSVBSearchSource),
'7.11.0': flow(enableDataTableVisToolbar),
'7.12.0': flow(migrateVislibAreaLineBarTypes, migrateSchema),
+ '7.13.0': flow(addSupportOfDualIndexSelectionModeInTSVB),
};
diff --git a/src/setup_node_env/index.js b/src/setup_node_env/index.js
index 08664344db393..9ce60766997cc 100644
--- a/src/setup_node_env/index.js
+++ b/src/setup_node_env/index.js
@@ -7,4 +7,5 @@
*/
require('./no_transpilation');
+// eslint-disable-next-line import/no-extraneous-dependencies
require('@kbn/optimizer').registerNodeAutoTranspilation();
diff --git a/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js b/test/api_integration/apis/console/index.ts
similarity index 50%
rename from src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js
rename to test/api_integration/apis/console/index.ts
index af8404eb6da92..ad4f8256f97ad 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/query_bar_wrapper.js
+++ b/test/api_integration/apis/console/index.ts
@@ -6,12 +6,10 @@
* Side Public License, v 1.
*/
-import React, { useContext } from 'react';
-import { CoreStartContext } from '../contexts/query_input_bar_context';
-import { QueryStringInput } from '../../../../../plugins/data/public';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export function QueryBarWrapper(props) {
- const coreStartContext = useContext(CoreStartContext);
-
- return ;
+export default function ({ loadTestFile }: FtrProviderContext) {
+ describe('core', () => {
+ loadTestFile(require.resolve('./proxy_route'));
+ });
}
diff --git a/test/api_integration/apis/console/proxy_route.ts b/test/api_integration/apis/console/proxy_route.ts
new file mode 100644
index 0000000000000..d8a5f57a41a6e
--- /dev/null
+++ b/test/api_integration/apis/console/proxy_route.ts
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function ({ getService }: FtrProviderContext) {
+ const supertest = getService('supertest');
+
+ describe('POST /api/console/proxy', () => {
+ describe('system indices behavior', () => {
+ it('returns warning header when making requests to .kibana index', async () => {
+ return await supertest
+ .post('/api/console/proxy?method=GET&path=/.kibana/_settings')
+ .set('kbn-xsrf', 'true')
+ .then((response) => {
+ expect(response.header).to.have.property('warning');
+ const { warning } = response.header as { warning: string };
+ expect(warning.startsWith('299')).to.be(true);
+ expect(warning.includes('system indices')).to.be(true);
+ });
+ });
+
+ it('does not forward x-elastic-product-origin', async () => {
+ // If we pass the header and we still get the warning back, we assume that the header was not forwarded.
+ return await supertest
+ .post('/api/console/proxy?method=GET&path=/.kibana/_settings')
+ .set('kbn-xsrf', 'true')
+ .set('x-elastic-product-origin', 'kibana')
+ .then((response) => {
+ expect(response.header).to.have.property('warning');
+ const { warning } = response.header as { warning: string };
+ expect(warning.startsWith('299')).to.be(true);
+ expect(warning.includes('system indices')).to.be(true);
+ });
+ });
+ });
+ });
+}
diff --git a/test/api_integration/apis/index.ts b/test/api_integration/apis/index.ts
index 33495ad2c604b..0d87569cb8b97 100644
--- a/test/api_integration/apis/index.ts
+++ b/test/api_integration/apis/index.ts
@@ -10,6 +10,7 @@ import { FtrProviderContext } from '../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('apis', () => {
+ loadTestFile(require.resolve('./console'));
loadTestFile(require.resolve('./core'));
loadTestFile(require.resolve('./general'));
loadTestFile(require.resolve('./home'));
diff --git a/test/api_integration/apis/telemetry/telemetry_local.ts b/test/api_integration/apis/telemetry/telemetry_local.ts
index a7b4da566b143..d0a09ee58d335 100644
--- a/test/api_integration/apis/telemetry/telemetry_local.ts
+++ b/test/api_integration/apis/telemetry/telemetry_local.ts
@@ -156,7 +156,7 @@ export default function ({ getService }: FtrProviderContext) {
describe('application usage limits', () => {
function createSavedObject(viewId?: string) {
return supertest
- .post('/api/saved_objects/application_usage_transactional')
+ .post('/api/saved_objects/application_usage_daily')
.send({
attributes: {
appId: 'test-app',
@@ -184,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) {
await Promise.all(
savedObjectIds.map((savedObjectId) => {
return supertest
- .delete(`/api/saved_objects/application_usage_transactional/${savedObjectId}`)
+ .delete(`/api/saved_objects/application_usage_daily/${savedObjectId}`)
.expect(200);
})
);
@@ -230,7 +230,7 @@ export default function ({ getService }: FtrProviderContext) {
.post('/api/saved_objects/_bulk_create')
.send(
new Array(10001).fill(0).map(() => ({
- type: 'application_usage_transactional',
+ type: 'application_usage_daily',
attributes: {
appId: 'test-app',
minutesOnScreen: 1,
@@ -248,13 +248,12 @@ export default function ({ getService }: FtrProviderContext) {
// The SavedObjects API does not allow bulk deleting, and deleting one by one takes ages and the tests timeout
await es.deleteByQuery({
index: '.kibana',
- body: { query: { term: { type: 'application_usage_transactional' } } },
+ body: { query: { term: { type: 'application_usage_daily' } } },
conflicts: 'proceed',
});
});
- // flaky https://github.com/elastic/kibana/issues/94513
- it.skip("should only use the first 10k docs for the application_usage data (they'll be rolled up in a later process)", async () => {
+ it("should only use the first 10k docs for the application_usage data (they'll be rolled up in a later process)", async () => {
const stats = await retrieveTelemetry(supertest);
expect(stats.stack_stats.kibana.plugins.application_usage).to.eql({
'test-app': {
diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts
index def175474d40e..cc62608fbde6d 100644
--- a/test/functional/apps/discover/_discover.ts
+++ b/test/functional/apps/discover/_discover.ts
@@ -128,7 +128,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
return actualCount === expectedCount;
});
const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
- expect(Math.round(newDurationHours)).to.be(26);
+ expect(Math.round(newDurationHours)).to.be(27);
await retry.waitFor('doc table to contain the right search result', async () => {
const rowData = await PageObjects.discover.getDocTableField(1);
diff --git a/test/functional/apps/discover/_discover_histogram.ts b/test/functional/apps/discover/_discover_histogram.ts
index 2a6096f8d1a78..72deb74459ab9 100644
--- a/test/functional/apps/discover/_discover_histogram.ts
+++ b/test/functional/apps/discover/_discover_histogram.ts
@@ -22,7 +22,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const browser = getService('browser');
- describe('discover histogram', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/94532
+ describe.skip('discover histogram', function describeIndexTests() {
before(async () => {
await esArchiver.loadIfNeeded('logstash_functional');
await esArchiver.load('long_window_logstash');
diff --git a/test/functional/apps/discover/_huge_fields.ts b/test/functional/apps/discover/_huge_fields.ts
new file mode 100644
index 0000000000000..8cb39feb2e6bb
--- /dev/null
+++ b/test/functional/apps/discover/_huge_fields.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const esArchiver = getService('esArchiver');
+ const security = getService('security');
+ const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover', 'timePicker']);
+ const kibanaServer = getService('kibanaServer');
+ const testSubjects = getService('testSubjects');
+
+ describe('test large number of fields in sidebar', function () {
+ before(async function () {
+ await security.testUser.setRoles(['kibana_admin', 'test_testhuge_reader'], false);
+ await esArchiver.loadIfNeeded('large_fields');
+ await PageObjects.settings.navigateTo();
+ await kibanaServer.uiSettings.update({
+ 'timepicker:timeDefaults': `{ "from": "2016-10-05T00:00:00", "to": "2016-10-06T00:00:00"}`,
+ });
+ await PageObjects.settings.createIndexPattern('*huge*', 'date', true);
+ await PageObjects.common.navigateToApp('discover');
+ });
+
+ it('test_huge data should have expected number of fields', async function () {
+ await PageObjects.discover.selectIndexPattern('*huge*');
+ // initially this field should not be rendered
+ const fieldExistsBeforeScrolling = await testSubjects.exists('field-myvar1050');
+ expect(fieldExistsBeforeScrolling).to.be(false);
+ // scrolling down a little, should render this field
+ await testSubjects.scrollIntoView('fieldToggle-myvar1029');
+ const fieldExistsAfterScrolling = await testSubjects.exists('field-myvar1050');
+ expect(fieldExistsAfterScrolling).to.be(true);
+ });
+
+ after(async () => {
+ await security.testUser.restoreDefaults();
+ await esArchiver.unload('large_fields');
+ await kibanaServer.uiSettings.replace({});
+ });
+ });
+}
diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts
index 23f3af37bbdf6..9726b097c8f62 100644
--- a/test/functional/apps/discover/_saved_queries.ts
+++ b/test/functional/apps/discover/_saved_queries.ts
@@ -26,8 +26,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const savedQueryManagementComponent = getService('savedQueryManagementComponent');
const testSubjects = getService('testSubjects');
- // FLAKY: https://github.com/elastic/kibana/issues/89477
- describe.skip('saved queries saved objects', function describeIndexTests() {
+ describe('saved queries saved objects', function describeIndexTests() {
before(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.load('discover');
@@ -120,6 +119,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('does not allow saving a query with a non-unique name', async () => {
+ // this check allows this test to run stand alone, also should fix occacional flakiness
+ const savedQueryExists = await savedQueryManagementComponent.savedQueryExist('OkResponse');
+ if (!savedQueryExists) {
+ await savedQueryManagementComponent.saveNewQuery(
+ 'OkResponse',
+ '200 responses for .jpg over 24 hours',
+ true,
+ true
+ );
+ await savedQueryManagementComponent.clearCurrentlyLoadedQuery();
+ }
await savedQueryManagementComponent.saveNewQueryWithNameError('OkResponse');
});
diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts
index 5c319312c8137..e526cdaccbd4c 100644
--- a/test/functional/apps/discover/index.ts
+++ b/test/functional/apps/discover/index.ts
@@ -47,5 +47,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_data_grid_doc_navigation'));
loadTestFile(require.resolve('./_data_grid_doc_table'));
loadTestFile(require.resolve('./_indexpattern_with_unmapped_fields'));
+ loadTestFile(require.resolve('./_huge_fields'));
});
}
diff --git a/test/functional/apps/management/_runtime_fields.js b/test/functional/apps/management/_runtime_fields.js
index 4b3533f20c8dc..e3ff1819aed13 100644
--- a/test/functional/apps/management/_runtime_fields.js
+++ b/test/functional/apps/management/_runtime_fields.js
@@ -15,6 +15,7 @@ export default function ({ getService, getPageObjects }) {
const browser = getService('browser');
const retry = getService('retry');
const PageObjects = getPageObjects(['settings']);
+ const testSubjects = getService('testSubjects');
describe('runtime fields', function () {
this.tags(['skipFirefox']);
@@ -47,6 +48,20 @@ export default function ({ getService, getPageObjects }) {
expect(parseInt(await PageObjects.settings.getFieldsTabCount())).to.be(startingCount + 1);
});
});
+
+ it('should modify runtime field', async function () {
+ await PageObjects.settings.filterField(fieldName);
+ await testSubjects.click('editFieldFormat');
+ await PageObjects.settings.setFieldType('Long');
+ await PageObjects.settings.changeFieldScript('emit(6);');
+ await PageObjects.settings.clickSaveField();
+ await PageObjects.settings.confirmSave();
+ });
+
+ it('should delete runtime field', async function () {
+ await testSubjects.click('deleteField');
+ await PageObjects.settings.confirmDelete();
+ });
});
});
}
diff --git a/test/functional/apps/visualize/_heatmap_chart.ts b/test/functional/apps/visualize/_heatmap_chart.ts
index 660f45179631e..79a9a6cbd5aca 100644
--- a/test/functional/apps/visualize/_heatmap_chart.ts
+++ b/test/functional/apps/visualize/_heatmap_chart.ts
@@ -15,7 +15,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const inspector = getService('inspector');
const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']);
- describe('heatmap chart', function indexPatternCreation() {
+ // FLAKY: https://github.com/elastic/kibana/issues/95642
+ describe.skip('heatmap chart', function indexPatternCreation() {
const vizName1 = 'Visualization HeatmapChart';
before(async function () {
diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts
index ba500904d75c7..6b0080c3856fd 100644
--- a/test/functional/apps/visualize/_tsvb_chart.ts
+++ b/test/functional/apps/visualize/_tsvb_chart.ts
@@ -43,6 +43,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualBuilder.resetPage();
await PageObjects.visualBuilder.clickMetric();
await PageObjects.visualBuilder.checkMetricTabIsPresent();
+ await PageObjects.visualBuilder.clickPanelOptions('metric');
+ await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
+ await PageObjects.visualBuilder.clickDataTab('metric');
});
it('should not have inspector enabled', async () => {
@@ -81,12 +84,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualBuilder.checkGaugeTabIsPresent();
});
+ it('should "Entire time range" selected as timerange mode for new visualization', async () => {
+ await PageObjects.visualBuilder.clickPanelOptions('gauge');
+ await PageObjects.visualBuilder.checkSelectedDataTimerangeMode('Entire time range');
+ await PageObjects.visualBuilder.clickDataTab('gauge');
+ });
+
it('should verify gauge label and count display', async () => {
await PageObjects.visChart.waitForVisualizationRenderingStabilized();
const labelString = await PageObjects.visualBuilder.getGaugeLabel();
expect(labelString).to.be('Count');
const gaugeCount = await PageObjects.visualBuilder.getGaugeCount();
- expect(gaugeCount).to.be('156');
+ expect(gaugeCount).to.be('13,830');
});
});
@@ -95,6 +104,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualBuilder.resetPage();
await PageObjects.visualBuilder.clickTopN();
await PageObjects.visualBuilder.checkTopNTabIsPresent();
+ await PageObjects.visualBuilder.clickPanelOptions('topN');
+ await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
+ await PageObjects.visualBuilder.clickDataTab('topN');
});
it('should verify topN label and count display', async () => {
@@ -107,33 +119,51 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
describe('switch index patterns', () => {
+ before(async () => {
+ await esArchiver.loadIfNeeded('index_pattern_without_timefield');
+ });
+
beforeEach(async () => {
- log.debug('Load kibana_sample_data_flights data');
- await esArchiver.loadIfNeeded('kibana_sample_data_flights');
await PageObjects.visualBuilder.resetPage();
await PageObjects.visualBuilder.clickMetric();
await PageObjects.visualBuilder.checkMetricTabIsPresent();
+ await PageObjects.visualBuilder.clickPanelOptions('metric');
+ await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
+ await PageObjects.visualBuilder.clickDataTab('metric');
+ await PageObjects.timePicker.setAbsoluteRange(
+ 'Sep 22, 2019 @ 00:00:00.000',
+ 'Sep 23, 2019 @ 00:00:00.000'
+ );
});
+
after(async () => {
await security.testUser.restoreDefaults();
- await esArchiver.unload('kibana_sample_data_flights');
+ await esArchiver.unload('index_pattern_without_timefield');
});
- it('should be able to switch between index patterns', async () => {
- const value = await PageObjects.visualBuilder.getMetricValue();
- expect(value).to.eql('156');
+ const switchIndexTest = async (useKibanaIndexes: boolean) => {
await PageObjects.visualBuilder.clickPanelOptions('metric');
- const fromTime = 'Oct 22, 2018 @ 00:00:00.000';
- const toTime = 'Oct 28, 2018 @ 23:59:59.999';
- await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
+ await PageObjects.visualBuilder.setIndexPatternValue('', false);
+
+ const value = await PageObjects.visualBuilder.getMetricValue();
+ expect(value).to.eql('0');
+
// Sometimes popovers take some time to appear in Firefox (#71979)
await retry.tryForTime(20000, async () => {
- await PageObjects.visualBuilder.setIndexPatternValue('kibana_sample_data_flights');
+ await PageObjects.visualBuilder.setIndexPatternValue('with-timefield', useKibanaIndexes);
await PageObjects.visualBuilder.waitForIndexPatternTimeFieldOptionsLoaded();
await PageObjects.visualBuilder.selectIndexPatternTimeField('timestamp');
});
const newValue = await PageObjects.visualBuilder.getMetricValue();
- expect(newValue).to.eql('18');
+ expect(newValue).to.eql('1');
+ };
+
+ it('should be able to switch using text mode selection', async () => {
+ await switchIndexTest(false);
+ });
+
+ it('should be able to switch combo box mode selection', async () => {
+ await switchIndexTest(true);
});
});
diff --git a/test/functional/apps/visualize/_tsvb_markdown.ts b/test/functional/apps/visualize/_tsvb_markdown.ts
index caf9cab8b703a..b61fbf967a9bd 100644
--- a/test/functional/apps/visualize/_tsvb_markdown.ts
+++ b/test/functional/apps/visualize/_tsvb_markdown.ts
@@ -37,6 +37,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'Sep 22, 2015 @ 06:00:00.000',
'Sep 22, 2015 @ 11:00:00.000'
);
+ await visualBuilder.markdownSwitchSubTab('options');
+ await visualBuilder.setMetricsDataTimerangeMode('Last value');
+ await visualBuilder.markdownSwitchSubTab('markdown');
});
it('should render subtabs and table variables markdown components', async () => {
diff --git a/test/functional/apps/visualize/_tsvb_table.ts b/test/functional/apps/visualize/_tsvb_table.ts
index dfa232b6e527d..36c0e26430ff5 100644
--- a/test/functional/apps/visualize/_tsvb_table.ts
+++ b/test/functional/apps/visualize/_tsvb_table.ts
@@ -24,6 +24,9 @@ export default function ({ getPageObjects }: FtrProviderContext) {
await visualBuilder.clickTable();
await visualBuilder.checkTableTabIsPresent();
+ await visualBuilder.clickPanelOptions('table');
+ await visualBuilder.setMetricsDataTimerangeMode('Last value');
+ await visualBuilder.clickDataTab('table');
await visualBuilder.selectGroupByField('machine.os.raw');
await visualBuilder.setColumnLabelValue('OS');
await visChart.waitForVisualizationRenderingStabilized();
diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts
index c6412f55dffbf..6d9641a1a920e 100644
--- a/test/functional/page_objects/common_page.ts
+++ b/test/functional/page_objects/common_page.ts
@@ -463,6 +463,21 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
async getWelcomeText() {
return await testSubjects.getVisibleText('global-banner-item');
}
+
+ /**
+ * Clicks on an element, and validates that the desired effect has taken place
+ * by confirming the existence of a validator
+ */
+ async clickAndValidate(
+ clickTarget: string,
+ validator: string,
+ isValidatorCssString: boolean = false,
+ topOffset?: number
+ ) {
+ await testSubjects.click(clickTarget, undefined, topOffset);
+ const validate = isValidatorCssString ? find.byCssSelector : testSubjects.exists;
+ await validate(validator);
+ }
}
return new CommonPage();
diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts
index 4151a8c1a1893..14bd002ec9487 100644
--- a/test/functional/page_objects/settings_page.ts
+++ b/test/functional/page_objects/settings_page.ts
@@ -502,6 +502,16 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
await this.closeIndexPatternFieldEditor();
}
+ public async confirmSave() {
+ await testSubjects.setValue('saveModalConfirmText', 'change');
+ await testSubjects.click('confirmModalConfirmButton');
+ }
+
+ public async confirmDelete() {
+ await testSubjects.setValue('deleteModalConfirmText', 'remove');
+ await testSubjects.click('confirmModalConfirmButton');
+ }
+
async closeIndexPatternFieldEditor() {
await retry.waitFor('field editor flyout to close', async () => {
return !(await testSubjects.exists('euiFlyoutCloseButton'));
@@ -543,6 +553,17 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
browser.pressKeys(script);
}
+ async changeFieldScript(script: string) {
+ log.debug('set script = ' + script);
+ const formatRow = await testSubjects.find('valueRow');
+ const getMonacoTextArea = async () => (await formatRow.findAllByCssSelector('textarea'))[0];
+ retry.waitFor('monaco editor is ready', async () => !!(await getMonacoTextArea()));
+ const monacoTextArea = await getMonacoTextArea();
+ await monacoTextArea.focus();
+ browser.pressKeys(browser.keys.DELETE.repeat(30));
+ browser.pressKeys(script);
+ }
+
async clickAddScriptedField() {
log.debug('click Add Scripted Field');
await testSubjects.click('addScriptedFieldLink');
diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts
index d7bb84394ae3c..fbb2b101eb3af 100644
--- a/test/functional/page_objects/visual_builder_page.ts
+++ b/test/functional/page_objects/visual_builder_page.ts
@@ -431,10 +431,39 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
await PageObjects.header.waitUntilLoadingHasFinished();
}
- public async setIndexPatternValue(value: string) {
- const el = await testSubjects.find('metricsIndexPatternInput');
- await el.clearValue();
- await el.type(value, { charByChar: true });
+ public async clickDataTab(tabName: string) {
+ await testSubjects.click(`${tabName}EditorDataBtn`);
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ }
+
+ public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) {
+ await testSubjects.click('switchIndexPatternSelectionModePopover');
+ await testSubjects.setEuiSwitch(
+ 'switchIndexPatternSelectionMode',
+ useKibanaIndices ? 'check' : 'uncheck'
+ );
+ }
+
+ public async setIndexPatternValue(value: string, useKibanaIndices?: boolean) {
+ const metricsIndexPatternInput = 'metricsIndexPatternInput';
+
+ if (useKibanaIndices !== undefined) {
+ await this.switchIndexPatternSelectionMode(useKibanaIndices);
+ }
+
+ if (useKibanaIndices === false) {
+ const el = await testSubjects.find(metricsIndexPatternInput);
+ await el.clearValue();
+ if (value) {
+ await el.type(value, { charByChar: true });
+ }
+ } else {
+ await comboBox.clearInputField(metricsIndexPatternInput);
+ if (value) {
+ await comboBox.setCustom(metricsIndexPatternInput, value);
+ }
+ }
+
await PageObjects.header.waitUntilLoadingHasFinished();
}
@@ -614,6 +643,16 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
);
return await comboBox.isOptionSelected(groupBy, value);
}
+
+ public async setMetricsDataTimerangeMode(value: string) {
+ const dataTimeRangeMode = await testSubjects.find('dataTimeRangeMode');
+ return await comboBox.setElement(dataTimeRangeMode, value);
+ }
+
+ public async checkSelectedDataTimerangeMode(value: string) {
+ const dataTimeRangeMode = await testSubjects.find('dataTimeRangeMode');
+ return await comboBox.isOptionSelected(dataTimeRangeMode, value);
+ }
}
return new VisualBuilderPage();
diff --git a/test/functional/screenshots/baseline/tsvb_dashboard.png b/test/functional/screenshots/baseline/tsvb_dashboard.png
index e0d79c7234f6a..4280199e77d11 100644
Binary files a/test/functional/screenshots/baseline/tsvb_dashboard.png and b/test/functional/screenshots/baseline/tsvb_dashboard.png differ
diff --git a/test/functional/services/common/find.ts b/test/functional/services/common/find.ts
index 2a86efad1ea9d..0cd4c14683f6e 100644
--- a/test/functional/services/common/find.ts
+++ b/test/functional/services/common/find.ts
@@ -79,11 +79,11 @@ export async function FindProvider({ getService }: FtrProviderContext) {
return wrap(await driver.switchTo().activeElement());
}
- public async setValue(selector: string, text: string): Promise {
+ public async setValue(selector: string, text: string, topOffset?: number): Promise {
log.debug(`Find.setValue('${selector}', '${text}')`);
return await retry.try(async () => {
const element = await this.byCssSelector(selector);
- await element.click();
+ await element.click(topOffset);
// in case the input element is actually a child of the testSubject, we
// call clearValue() and type() on the element that is focused after
@@ -413,14 +413,15 @@ export async function FindProvider({ getService }: FtrProviderContext) {
public async clickByCssSelector(
selector: string,
- timeout: number = defaultFindTimeout
+ timeout: number = defaultFindTimeout,
+ topOffset?: number
): Promise {
log.debug(`Find.clickByCssSelector('${selector}') with timeout=${timeout}`);
await retry.try(async () => {
const element = await this.byCssSelector(selector, timeout);
if (element) {
// await element.moveMouseTo();
- await element.click();
+ await element.click(topOffset);
} else {
throw new Error(`Element with css='${selector}' is not found`);
}
diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts
index 28b37d9576e8c..111206ec9eafe 100644
--- a/test/functional/services/common/test_subjects.ts
+++ b/test/functional/services/common/test_subjects.ts
@@ -100,9 +100,13 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) {
await find.clickByCssSelectorWhenNotDisabled(testSubjSelector(selector), { timeout });
}
- public async click(selector: string, timeout: number = FIND_TIME): Promise {
+ public async click(
+ selector: string,
+ timeout: number = FIND_TIME,
+ topOffset?: number
+ ): Promise {
log.debug(`TestSubjects.click(${selector})`);
- await find.clickByCssSelector(testSubjSelector(selector), timeout);
+ await find.clickByCssSelector(testSubjSelector(selector), timeout, topOffset);
}
public async doubleClick(selector: string, timeout: number = FIND_TIME): Promise {
@@ -187,12 +191,13 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) {
public async setValue(
selector: string,
text: string,
- options: SetValueOptions = {}
+ options: SetValueOptions = {},
+ topOffset?: number
): Promise {
return await retry.try(async () => {
const { clearWithKeyboard = false, typeCharByChar = false } = options;
log.debug(`TestSubjects.setValue(${selector}, ${text})`);
- await this.click(selector);
+ await this.click(selector, undefined, topOffset);
// in case the input element is actually a child of the testSubject, we
// call clearValue() and type() on the element that is focused after
// clicking on the testSubject
diff --git a/test/functional/services/field_editor.ts b/test/functional/services/field_editor.ts
index 5cd1f2c4f6202..7d6dad4f7858e 100644
--- a/test/functional/services/field_editor.ts
+++ b/test/functional/services/field_editor.ts
@@ -10,7 +10,6 @@ import { FtrProviderContext } from '../ftr_provider_context';
export function FieldEditorProvider({ getService }: FtrProviderContext) {
const browser = getService('browser');
- const retry = getService('retry');
const testSubjects = getService('testSubjects');
class FieldEditor {
@@ -33,10 +32,17 @@ export function FieldEditorProvider({ getService }: FtrProviderContext) {
await browser.pressKeys(script);
}
public async save() {
- await retry.try(async () => {
- await testSubjects.click('fieldSaveButton');
- await testSubjects.missingOrFail('fieldSaveButton', { timeout: 2000 });
- });
+ await testSubjects.click('fieldSaveButton');
+ }
+
+ public async confirmSave() {
+ await testSubjects.setValue('saveModalConfirmText', 'change');
+ await testSubjects.click('confirmModalConfirmButton');
+ }
+
+ public async confirmDelete() {
+ await testSubjects.setValue('deleteModalConfirmText', 'remove');
+ await testSubjects.click('confirmModalConfirmButton');
}
}
diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
index 1a45aee877e1f..b1561b29342da 100644
--- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
+++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts
@@ -182,9 +182,9 @@ export class WebElementWrapper {
*
* @return {Promise}
*/
- public async click() {
+ public async click(topOffset?: number) {
await this.retryCall(async function click(wrapper) {
- await wrapper.scrollIntoViewIfNecessary();
+ await wrapper.scrollIntoViewIfNecessary(topOffset);
await wrapper._webElement.click();
});
}
@@ -693,11 +693,11 @@ export class WebElementWrapper {
* @nonstandard
* @return {Promise}
*/
- public async scrollIntoViewIfNecessary(): Promise {
+ public async scrollIntoViewIfNecessary(topOffset?: number): Promise {
await this.driver.executeScript(
scrollIntoViewIfNecessary,
this._webElement,
- this.fixedHeaderHeight
+ topOffset || this.fixedHeaderHeight
);
}
diff --git a/test/functional/services/saved_query_management_component.ts b/test/functional/services/saved_query_management_component.ts
index a39032af43295..7398e6ca8c12e 100644
--- a/test/functional/services/saved_query_management_component.ts
+++ b/test/functional/services/saved_query_management_component.ts
@@ -139,6 +139,13 @@ export function SavedQueryManagementComponentProvider({
await testSubjects.click('savedQueryFormSaveButton');
}
+ async savedQueryExist(title: string) {
+ await this.openSavedQueryManagementComponent();
+ const exists = testSubjects.exists(`~load-saved-query-${title}-button`);
+ await this.closeSavedQueryManagementComponent();
+ return exists;
+ }
+
async savedQueryExistOrFail(title: string) {
await this.openSavedQueryManagementComponent();
await testSubjects.existOrFail(`~load-saved-query-${title}-button`);
diff --git a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
index f8a91e3a0a67a..c338bbc998c49 100644
--- a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
+++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts
@@ -23,6 +23,7 @@ const byTypeSchema: MakeSchemaFrom['count_by_type'] = {
__servicenow: { type: 'long' },
__jira: { type: 'long' },
__resilient: { type: 'long' },
+ __teams: { type: 'long' },
};
export function createActionsUsageCollector(
diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts
index 884120d3d03df..59aeb4854d9f0 100644
--- a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts
+++ b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts
@@ -16,6 +16,7 @@ const byTypeSchema: MakeSchemaFrom['count_by_type'] = {
// Known alerts (searching the use of the alerts API `registerType`:
// Built-in
'__index-threshold': { type: 'long' },
+ '__es-query': { type: 'long' },
// APM
apm__error_rate: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
apm__transaction_error_rate: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
@@ -41,6 +42,10 @@ const byTypeSchema: MakeSchemaFrom['count_by_type'] = {
xpack__uptime__alerts__monitorStatus: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
xpack__uptime__alerts__tls: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
xpack__uptime__alerts__durationAnomaly: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention
+ // Maps
+ '__geo-containment': { type: 'long' },
+ // ML
+ xpack_ml_anomaly_detection_alert: { type: 'long' },
};
export function createAlertsUsageCollector(
diff --git a/x-pack/plugins/apm/common/apm_api/parse_endpoint.ts b/x-pack/plugins/apm/common/apm_api/parse_endpoint.ts
new file mode 100644
index 0000000000000..fb7ef6d36ce25
--- /dev/null
+++ b/x-pack/plugins/apm/common/apm_api/parse_endpoint.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.
+ */
+
+type Method = 'get' | 'post' | 'put' | 'delete';
+
+export function parseEndpoint(
+ endpoint: string,
+ pathParams: Record = {}
+) {
+ const [method, rawPathname] = endpoint.split(' ');
+
+ // replace template variables with path params
+ const pathname = Object.keys(pathParams).reduce((acc, paramName) => {
+ return acc.replace(`{${paramName}}`, pathParams[paramName]);
+ }, rawPathname);
+
+ return { method: parseMethod(method), pathname };
+}
+
+export function parseMethod(method: string) {
+ const res = method.trim().toLowerCase() as Method;
+
+ if (!['get', 'post', 'put', 'delete'].includes(res)) {
+ throw new Error('Endpoint was not prefixed with a valid HTTP method');
+ }
+
+ return res;
+}
diff --git a/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts b/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts
index 3316c74d52e38..4212e0430ff5f 100644
--- a/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts
+++ b/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts
@@ -45,10 +45,10 @@ describe('strictKeysRt', () => {
{
type: t.intersection([
t.type({ query: t.type({ bar: t.string }) }),
- t.partial({ query: t.partial({ _debug: t.boolean }) }),
+ t.partial({ query: t.partial({ _inspect: t.boolean }) }),
]),
- passes: [{ query: { bar: '', _debug: true } }],
- fails: [{ query: { _debug: true } }],
+ passes: [{ query: { bar: '', _inspect: true } }],
+ fails: [{ query: { _inspect: true } }],
},
];
@@ -91,12 +91,12 @@ describe('strictKeysRt', () => {
} as Record);
const typeB = t.partial({
- query: t.partial({ _debug: jsonRt.pipe(t.boolean) }),
+ query: t.partial({ _inspect: jsonRt.pipe(t.boolean) }),
});
const value = {
query: {
- _debug: 'true',
+ _inspect: 'true',
filterNames: JSON.stringify(['host', 'agentName']),
},
};
diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx
index b785fcc7dab08..7df6ca343426c 100644
--- a/x-pack/plugins/apm/public/application/application.test.tsx
+++ b/x-pack/plugins/apm/public/application/application.test.tsx
@@ -8,7 +8,7 @@
import { act } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { Observable } from 'rxjs';
-import { AppMountParameters, CoreStart, HttpSetup } from 'src/core/public';
+import { AppMountParameters, CoreStart } from 'src/core/public';
import { mockApmPluginContextValue } from '../context/apm_plugin/mock_apm_plugin_context';
import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin';
import { createCallApmApi } from '../services/rest/createCallApmApi';
@@ -72,7 +72,7 @@ describe('renderApp', () => {
embeddable,
};
jest.spyOn(window, 'scrollTo').mockReturnValueOnce(undefined);
- createCallApmApi((core.http as unknown) as HttpSetup);
+ createCallApmApi((core as unknown) as CoreStart);
jest
.spyOn(window.console, 'warn')
diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx
index 8ea4593bb89a7..787b15d0a5675 100644
--- a/x-pack/plugins/apm/public/application/csmApp.tsx
+++ b/x-pack/plugins/apm/public/application/csmApp.tsx
@@ -118,7 +118,7 @@ export const renderApp = (
) => {
const { element } = appMountParameters;
- createCallApmApi(core.http);
+ createCallApmApi(core);
// Automatically creates static index pattern and stores as saved object
createStaticIndexPattern().catch((e) => {
diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx
index 64600dd500bd5..bc14bc1531686 100644
--- a/x-pack/plugins/apm/public/application/index.tsx
+++ b/x-pack/plugins/apm/public/application/index.tsx
@@ -120,7 +120,7 @@ export const renderApp = (
// render APM feedback link in global help menu
setHelpExtension(core);
setReadonlyBadge(core);
- createCallApmApi(core.http);
+ createCallApmApi(core);
// Automatically creates static index pattern and stores as saved object
createStaticIndexPattern().catch((e) => {
diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx
index 29f74b26d310c..fdfed6eb0d685 100644
--- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx
@@ -107,7 +107,11 @@ export function ErrorCountAlertTrigger(props: Props) {
];
const chartPreview = (
-
+
);
return (
diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx
index 11aab788ec7f4..b4c78b54f329b 100644
--- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx
@@ -13,7 +13,6 @@ import { useParams } from 'react-router-dom';
import { ForLastExpression } from '../../../../../triggers_actions_ui/public';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { getDurationFormatter } from '../../../../common/utils/formatters';
-import { TimeSeries } from '../../../../typings/timeseries';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher';
@@ -116,9 +115,9 @@ export function TransactionDurationAlertTrigger(props: Props) {
]
);
- const maxY = getMaxY([
- { data: data ?? [] } as TimeSeries<{ x: number; y: number | null }>,
- ]);
+ const latencyChartPreview = data?.latencyChartPreview ?? [];
+
+ const maxY = getMaxY([{ data: latencyChartPreview }]);
const formatter = getDurationFormatter(maxY);
const yTickFormat = getResponseTimeTickFormatter(formatter);
@@ -127,7 +126,7 @@ export function TransactionDurationAlertTrigger(props: Props) {
const chartPreview = (
diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx
index de30af4a4707f..c6f9c4efd98b6 100644
--- a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx
@@ -132,7 +132,7 @@ export function TransactionErrorRateAlertTrigger(props: Props) {
const chartPreview = (
asPercent(d, 1)}
threshold={thresholdAsPercent}
/>
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/BreakdownSeries.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/BreakdownSeries.tsx
index 6c94b895f6924..db5932a96fb12 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/BreakdownSeries.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/BreakdownSeries.tsx
@@ -35,7 +35,7 @@ export function BreakdownSeries({
? EUI_CHARTS_THEME_DARK
: EUI_CHARTS_THEME_LIGHT;
- const { data, status } = useBreakdowns({
+ const { breakdowns, status } = useBreakdowns({
field,
value,
percentileRange,
@@ -49,7 +49,7 @@ export function BreakdownSeries({
// so don't user that here
return (
<>
- {data?.map(({ data: seriesData, name }, sortIndex) => (
+ {breakdowns.map(({ data: seriesData, name }, sortIndex) => (
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
index 5af7f0682db19..e21aaa08c432d 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts
@@ -17,12 +17,10 @@ interface Props {
export const useBreakdowns = ({ percentileRange, field, value }: Props) => {
const { urlParams, uiFilters } = useUrlParams();
-
const { start, end, searchTerm } = urlParams;
-
const { min: minP, max: maxP } = percentileRange ?? {};
- return useFetcher(
+ const { data, status } = useFetcher(
(callApmApi) => {
if (start && end && field && value) {
return callApmApi({
@@ -47,4 +45,6 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => {
},
[end, start, uiFilters, field, value, minP, maxP, searchTerm]
);
+
+ return { breakdowns: data?.pageLoadDistBreakdown ?? [], status };
};
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
index e3e2a979c48d3..d04bcb79a53e1 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx
@@ -38,6 +38,7 @@ export function MainFilters() {
[start, end]
);
+ const rumServiceNames = data?.rumServices ?? [];
const { isSmall } = useBreakPoints();
// on mobile we want it to take full width
@@ -48,7 +49,7 @@ export function MainFilters() {
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
index c40f6ba2b8850..8ae4c9dc0e01d 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useLocalUIFilters.ts
@@ -68,7 +68,7 @@ export function useLocalUIFilters({
});
};
- const { data = getInitialData(filterNames), status } = useFetcher(
+ const { data, status } = useFetcher(
(callApmApi) => {
if (shouldFetch && urlParams.start && urlParams.end) {
return callApmApi({
@@ -96,7 +96,8 @@ export function useLocalUIFilters({
]
);
- const filters = data.map((filter) => ({
+ const localUiFilters = data?.localUiFilters ?? getInitialData(filterNames);
+ const filters = localUiFilters.map((filter) => ({
...filter,
value: values[filter.name] || [],
}));
diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/use_call_api.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/use_call_api.ts
index 5b448871804eb..f932cec3cacb6 100644
--- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/use_call_api.ts
+++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/use_call_api.ts
@@ -11,9 +11,9 @@ import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plug
import { FetchOptions } from '../../../../../common/fetch_options';
export function useCallApi() {
- const { http } = useApmPluginContext().core;
+ const { core } = useApmPluginContext();
return useMemo(() => {
- return (options: FetchOptions) => callApi(http, options);
- }, [http]);
+ return (options: FetchOptions) => callApi(core, options);
+ }, [core]);
}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
index d754710dc84fa..ac1846155569a 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
@@ -6,7 +6,7 @@
*/
import cytoscape from 'cytoscape';
-import { HttpSetup } from 'kibana/public';
+import { CoreStart } from 'kibana/public';
import React, { ComponentType } from 'react';
import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
@@ -21,19 +21,21 @@ export default {
component: Popover,
decorators: [
(Story: ComponentType) => {
- const httpMock = ({
- get: async () => ({
- avgCpuUsage: 0.32809666568309237,
- avgErrorRate: 0.556068173242986,
- avgMemoryUsage: 0.5504868173242986,
- transactionStats: {
- avgRequestsPerMinute: 164.47222031860858,
- avgTransactionDuration: 61634.38905590272,
- },
- }),
- } as unknown) as HttpSetup;
+ const coreMock = ({
+ http: {
+ get: async () => ({
+ avgCpuUsage: 0.32809666568309237,
+ avgErrorRate: 0.556068173242986,
+ avgMemoryUsage: 0.5504868173242986,
+ transactionStats: {
+ avgRequestsPerMinute: 164.47222031860858,
+ avgTransactionDuration: 61634.38905590272,
+ },
+ }),
+ },
+ } as unknown) as CoreStart;
- createCallApmApi(httpMock);
+ createCallApmApi(coreMock);
return (
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx
index e762f517ce1b5..71355a84d28d4 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx
@@ -33,7 +33,7 @@ interface Props {
}
export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) {
- const { data: serviceNames = [], status: serviceNamesStatus } = useFetcher(
+ const { data: serviceNamesData, status: serviceNamesStatus } = useFetcher(
(callApmApi) => {
return callApmApi({
endpoint: 'GET /api/apm/settings/agent-configuration/services',
@@ -43,8 +43,9 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) {
[],
{ preservePreviousData: false }
);
+ const serviceNames = serviceNamesData?.serviceNames ?? [];
- const { data: environments = [], status: environmentStatus } = useFetcher(
+ const { data: environmentsData, status: environmentsStatus } = useFetcher(
(callApmApi) => {
if (newConfig.service.name) {
return callApmApi({
@@ -59,6 +60,8 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) {
{ preservePreviousData: false }
);
+ const environments = environmentsData?.environments ?? [];
+
const { status: agentNameStatus } = useFetcher(
async (callApmApi) => {
const serviceName = newConfig.service.name;
@@ -153,11 +156,11 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) {
'xpack.apm.agentConfig.servicePage.environment.fieldLabel',
{ defaultMessage: 'Service environment' }
)}
- isLoading={environmentStatus === FETCH_STATUS.LOADING}
+ isLoading={environmentsStatus === FETCH_STATUS.LOADING}
options={environmentOptions}
value={newConfig.service.environment}
disabled={
- !newConfig.service.name || environmentStatus === FETCH_STATUS.LOADING
+ !newConfig.service.name || environmentsStatus === FETCH_STATUS.LOADING
}
onChange={(e) => {
e.preventDefault();
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
index 4d2754a677bf7..cd5fa5db89a31 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
@@ -7,7 +7,7 @@
import { storiesOf } from '@storybook/react';
import React from 'react';
-import { HttpSetup } from 'kibana/public';
+import { CoreStart } from 'kibana/public';
import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common';
import { AgentConfiguration } from '../../../../../../common/agent_configuration/configuration_types';
import { FETCH_STATUS } from '../../../../../hooks/use_fetcher';
@@ -23,10 +23,10 @@ storiesOf(
module
)
.addDecorator((storyFn) => {
- const httpMock = {};
+ const coreMock = ({} as unknown) as CoreStart;
// mock
- createCallApmApi((httpMock as unknown) as HttpSetup);
+ createCallApmApi(coreMock);
const contextMock = {
core: {
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx
index 081a3dbc907c5..3e3bc892e6518 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx
@@ -16,7 +16,7 @@ import {
} from '../../../../../services/rest/createCallApmApi';
import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
-type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>[0];
+type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>['configurations'][0];
interface Props {
config: Config;
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
index bef0dfc22280c..c098be41968dd 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx
@@ -32,15 +32,19 @@ import { ITableColumn, ManagedTable } from '../../../../shared/ManagedTable';
import { TimestampTooltip } from '../../../../shared/TimestampTooltip';
import { ConfirmDeleteModal } from './ConfirmDeleteModal';
-type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>[0];
+type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>['configurations'][0];
interface Props {
status: FETCH_STATUS;
- data: Config[];
+ configurations: Config[];
refetch: () => void;
}
-export function AgentConfigurationList({ status, data, refetch }: Props) {
+export function AgentConfigurationList({
+ status,
+ configurations,
+ refetch,
+}: Props) {
const { core } = useApmPluginContext();
const canSave = core.application.capabilities.apm.save;
const { basePath } = core.http;
@@ -113,7 +117,7 @@ export function AgentConfigurationList({ status, data, refetch }: Props) {
return failurePrompt;
}
- if (status === FETCH_STATUS.SUCCESS && isEmpty(data)) {
+ if (status === FETCH_STATUS.SUCCESS && isEmpty(configurations)) {
return emptyStatePrompt;
}
@@ -231,7 +235,7 @@ export function AgentConfigurationList({ status, data, refetch }: Props) {
}
columns={columns}
- items={data}
+ items={configurations}
initialSortField="service.name"
initialSortDirection="asc"
initialPageSize={20}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
index 8aa0c35f36717..3225951fd6c70 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx
@@ -25,8 +25,10 @@ import { useFetcher } from '../../../../hooks/use_fetcher';
import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks';
import { AgentConfigurationList } from './List';
+const INITIAL_DATA = { configurations: [] };
+
export function AgentConfigurations() {
- const { refetch, data = [], status } = useFetcher(
+ const { refetch, data = INITIAL_DATA, status } = useFetcher(
(callApmApi) =>
callApmApi({ endpoint: 'GET /api/apm/settings/agent-configuration' }),
[],
@@ -36,7 +38,7 @@ export function AgentConfigurations() {
useTrackPageview({ app: 'apm', path: 'agent_configuration' });
useTrackPageview({ app: 'apm', path: 'agent_configuration', delay: 15000 });
- const hasConfigurations = !isEmpty(data);
+ const hasConfigurations = !isEmpty(data.configurations);
return (
<>
@@ -72,7 +74,11 @@ export function AgentConfigurations() {
-
+
>
);
diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
index 9722c99990e3f..9d2b4bba22afb 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx
@@ -24,7 +24,10 @@ import React, { useEffect, useState } from 'react';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { clearCache } from '../../../../services/rest/callApi';
-import { callApmApi } from '../../../../services/rest/createCallApmApi';
+import {
+ APIReturnType,
+ callApmApi,
+} from '../../../../services/rest/createCallApmApi';
const APM_INDEX_LABELS = [
{
@@ -84,8 +87,10 @@ async function saveApmIndices({
clearCache();
}
+type ApiResponse = APIReturnType<`GET /api/apm/settings/apm-index-settings`>;
+
// avoid infinite loop by initializing the state outside the component
-const INITIAL_STATE = [] as [];
+const INITIAL_STATE: ApiResponse = { apmIndexSettings: [] };
export function ApmIndices() {
const { core } = useApmPluginContext();
@@ -108,7 +113,7 @@ export function ApmIndices() {
useEffect(() => {
setApmIndices(
- data.reduce(
+ data.apmIndexSettings.reduce(
(acc, { configurationName, savedValue }) => ({
...acc,
[configurationName]: savedValue,
@@ -190,7 +195,7 @@ export function ApmIndices() {
{APM_INDEX_LABELS.map(({ configurationName, label }) => {
- const matchedConfiguration = data.find(
+ const matchedConfiguration = data.apmIndexSettings.find(
({ configurationName: configName }) =>
configName === configurationName
);
diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
index 0dbc8f6235342..77835afef863a 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx
@@ -24,20 +24,12 @@ import {
} from '../../../../../utils/testHelpers';
import * as saveCustomLink from './CreateEditCustomLinkFlyout/saveCustomLink';
-const data = [
- {
- id: '1',
- label: 'label 1',
- url: 'url 1',
- 'service.name': 'opbeans-java',
- },
- {
- id: '2',
- label: 'label 2',
- url: 'url 2',
- 'transaction.type': 'request',
- },
-];
+const data = {
+ customLinks: [
+ { id: '1', label: 'label 1', url: 'url 1', 'service.name': 'opbeans-java' },
+ { id: '2', label: 'label 2', url: 'url 2', 'transaction.type': 'request' },
+ ],
+};
function getMockAPMContext({ canSave }: { canSave: boolean }) {
return ({
@@ -69,7 +61,7 @@ describe('CustomLink', () => {
describe('empty prompt', () => {
beforeAll(() => {
jest.spyOn(hooks, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: hooks.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
@@ -290,7 +282,7 @@ describe('CustomLink', () => {
describe('invalid license', () => {
beforeAll(() => {
jest.spyOn(hooks, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: hooks.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
index 4b4bc2e8feeab..49fa3eab47862 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx
@@ -35,7 +35,7 @@ export function CustomLinkOverview() {
CustomLink | undefined
>();
- const { data: customLinks = [], status, refetch } = useFetcher(
+ const { data, status, refetch } = useFetcher(
async (callApmApi) => {
if (hasValidLicense) {
return callApmApi({
@@ -46,6 +46,8 @@ export function CustomLinkOverview() {
[hasValidLicense]
);
+ const customLinks = data?.customLinks ?? [];
+
useEffect(() => {
if (customLinkSelected) {
setIsFlyoutOpen(true);
diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
index 6a11f862994e2..bf9062418313a 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx
@@ -21,6 +21,7 @@ import {
EuiEmptyPrompt,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { APIReturnType } from '../../../../services/rest/createCallApmApi';
import { ML_ERRORS } from '../../../../../common/anomaly_detection';
import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
@@ -33,6 +34,10 @@ interface Props {
onCreateJobSuccess: () => void;
onCancel: () => void;
}
+
+type ApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection/environments'>;
+const INITIAL_DATA: ApiResponse = { environments: [] };
+
export function AddEnvironments({
currentEnvironments,
onCreateJobSuccess,
@@ -42,7 +47,7 @@ export function AddEnvironments({
const { anomalyDetectionJobsRefetch } = useAnomalyDetectionJobsContext();
const canCreateJob = !!application.capabilities.ml.canCreateJob;
const { toasts } = notifications;
- const { data = [], status } = useFetcher(
+ const { data = INITIAL_DATA, status } = useFetcher(
(callApmApi) =>
callApmApi({
endpoint: `GET /api/apm/settings/anomaly-detection/environments`,
@@ -51,7 +56,7 @@ export function AddEnvironments({
{ preservePreviousData: false }
);
- const environmentOptions = data.map((env) => ({
+ const environmentOptions = data.environments.map((env) => ({
label: getEnvironmentLabel(env),
value: env,
disabled: currentEnvironments.includes(env),
diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
index 7526eaf1aad64..22a12db680334 100644
--- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
+++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap
@@ -1722,7 +1722,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
>
List should render with data 1`] = `
;
+type ErrorGroupItem = APIReturnType<'GET /api/apm/services/{serviceName}/errors'>['errorGroups'][0];
interface Props {
- items: ErrorGroupListAPIResponse;
+ items: ErrorGroupItem[];
serviceName: string;
}
@@ -128,7 +128,7 @@ function ErrorGroupList({ items, serviceName }: Props) {
field: 'message',
sortable: false,
width: '50%',
- render: (message: string, item: ErrorGroupListAPIResponse[0]) => {
+ render: (message: string, item: ErrorGroupItem) => {
return (
diff --git a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx
index f4870439fe478..fc218f3ba6df3 100644
--- a/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_node_overview/index.tsx
@@ -39,7 +39,7 @@ function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) {
urlParams: { kuery, start, end },
} = useUrlParams();
- const { data: items = [] } = useFetcher(
+ const { data } = useFetcher(
(callApmApi) => {
if (!start || !end) {
return undefined;
@@ -61,6 +61,7 @@ function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) {
[kuery, serviceName, start, end]
);
+ const items = data?.serviceNodes ?? [];
const columns: Array> = [
{
name: (
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx
index f6ffec46f9f51..b30faac7a65af 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx
@@ -91,7 +91,7 @@ describe('ServiceOverview', () => {
isAggregationAccurate: true,
},
'GET /api/apm/services/{serviceName}/dependencies': [],
- 'GET /api/apm/services/{serviceName}/service_overview_instances': [],
+ 'GET /api/apm/services/{serviceName}/service_overview_instances/primary_statistics': [],
};
/* eslint-enable @typescript-eslint/naming-convention */
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
index a4647bc148b1e..4ff42b151dc8e 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
@@ -164,7 +164,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) {
},
];
- const { data = [], status } = useFetcher(
+ const { data, status } = useFetcher(
(callApmApi) => {
if (!start || !end) {
return;
@@ -188,8 +188,10 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) {
[start, end, serviceName, environment]
);
+ const serviceDependencies = data?.serviceDependencies ?? [];
+
// need top-level sortable fields for the managed table
- const items = data.map((item) => ({
+ const items = serviceDependencies.map((item) => ({
...item,
errorRateValue: item.errorRate.value,
latencyValue: item.latency.value,
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
index 435def8bb9a91..13322b094c65e 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx
@@ -6,11 +6,18 @@
*/
import { EuiFlexItem, EuiPanel } from '@elastic/eui';
-import React from 'react';
+import { orderBy } from 'lodash';
+import React, { useState } from 'react';
+import uuid from 'uuid';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
-import { useFetcher } from '../../../hooks/use_fetcher';
-import { ServiceOverviewInstancesTable } from './service_overview_instances_table';
+import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
+import { APIReturnType } from '../../../services/rest/createCallApmApi';
+import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison';
+import {
+ ServiceOverviewInstancesTable,
+ TableOptions,
+} from './service_overview_instances_table';
// We're hiding this chart until these issues are resolved in the 7.13 timeframe:
//
@@ -24,17 +31,77 @@ interface ServiceOverviewInstancesChartAndTableProps {
serviceName: string;
}
+export interface PrimaryStatsServiceInstanceItem {
+ serviceNodeName: string;
+ errorRate: number;
+ throughput: number;
+ latency: number;
+ cpuUsage: number;
+ memoryUsage: number;
+}
+
+const INITIAL_STATE_PRIMARY_STATS = {
+ primaryStatsItems: [] as PrimaryStatsServiceInstanceItem[],
+ primaryStatsRequestId: undefined,
+ primaryStatsItemCount: 0,
+};
+
+type ApiResponseComparisonStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics'>;
+
+const INITIAL_STATE_COMPARISON_STATISTICS: ApiResponseComparisonStats = {
+ currentPeriod: {},
+ previousPeriod: {},
+};
+
+export type SortField =
+ | 'serviceNodeName'
+ | 'latency'
+ | 'throughput'
+ | 'errorRate'
+ | 'cpuUsage'
+ | 'memoryUsage';
+
+export type SortDirection = 'asc' | 'desc';
+export const PAGE_SIZE = 5;
+const DEFAULT_SORT = {
+ direction: 'desc' as const,
+ field: 'throughput' as const,
+};
+
export function ServiceOverviewInstancesChartAndTable({
chartHeight,
serviceName,
}: ServiceOverviewInstancesChartAndTableProps) {
const { transactionType } = useApmServiceContext();
+ const [tableOptions, setTableOptions] = useState({
+ pageIndex: 0,
+ sort: DEFAULT_SORT,
+ });
+
+ const { pageIndex, sort } = tableOptions;
+ const { direction, field } = sort;
const {
- urlParams: { environment, kuery, latencyAggregationType, start, end },
+ urlParams: {
+ environment,
+ kuery,
+ latencyAggregationType,
+ start,
+ end,
+ comparisonType,
+ },
} = useUrlParams();
- const { data = [], status } = useFetcher(
+ const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
+ start,
+ end,
+ comparisonType,
+ });
+
+ const {
+ data: primaryStatsData = INITIAL_STATE_PRIMARY_STATS,
+ status: primaryStatsStatus,
+ } = useFetcher(
(callApmApi) => {
if (!start || !end || !transactionType || !latencyAggregationType) {
return;
@@ -42,7 +109,7 @@ export function ServiceOverviewInstancesChartAndTable({
return callApmApi({
endpoint:
- 'GET /api/apm/services/{serviceName}/service_overview_instances',
+ 'GET /api/apm/services/{serviceName}/service_overview_instances/primary_statistics',
params: {
path: {
serviceName,
@@ -54,11 +121,32 @@ export function ServiceOverviewInstancesChartAndTable({
start,
end,
transactionType,
- numBuckets: 20,
},
},
+ }).then((response) => {
+ const primaryStatsItems = orderBy(
+ // need top-level sortable fields for the managed table
+ response.serviceInstances.map((item) => ({
+ ...item,
+ latency: item.latency ?? 0,
+ throughput: item.throughput ?? 0,
+ errorRate: item.errorRate ?? 0,
+ cpuUsage: item.cpuUsage ?? 0,
+ memoryUsage: item.memoryUsage ?? 0,
+ })),
+ field,
+ direction
+ ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);
+
+ return {
+ primaryStatsRequestId: uuid(),
+ primaryStatsItems,
+ primaryStatsItemCount: response.serviceInstances.length,
+ };
});
},
+ // comparisonType is listed as dependency even thought it is not used. This is needed to trigger the comparison api when it is changed.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
[
environment,
kuery,
@@ -67,24 +155,97 @@ export function ServiceOverviewInstancesChartAndTable({
end,
serviceName,
transactionType,
+ pageIndex,
+ field,
+ direction,
+ comparisonType,
]
);
+ const {
+ primaryStatsItems,
+ primaryStatsRequestId,
+ primaryStatsItemCount,
+ } = primaryStatsData;
+
+ const {
+ data: comparisonStatsData = INITIAL_STATE_COMPARISON_STATISTICS,
+ status: comparisonStatisticsStatus,
+ } = useFetcher(
+ (callApmApi) => {
+ if (
+ !start ||
+ !end ||
+ !transactionType ||
+ !latencyAggregationType ||
+ !primaryStatsItemCount
+ ) {
+ return;
+ }
+
+ return callApmApi({
+ endpoint:
+ 'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics',
+ params: {
+ path: {
+ serviceName,
+ },
+ query: {
+ environment,
+ kuery,
+ latencyAggregationType,
+ start,
+ end,
+ numBuckets: 20,
+ transactionType,
+ serviceNodeIds: JSON.stringify(
+ primaryStatsItems.map((item) => item.serviceNodeName)
+ ),
+ comparisonStart,
+ comparisonEnd,
+ },
+ },
+ });
+ },
+ // only fetches comparison statistics when requestId is invalidated by primary statistics api call
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [primaryStatsRequestId],
+ { preservePreviousData: false }
+ );
+
return (
<>
{/*
*/}
{
+ setTableOptions({
+ pageIndex: newTableOptions.page?.index ?? 0,
+ sort: newTableOptions.sort
+ ? {
+ field: newTableOptions.sort.field as SortField,
+ direction: newTableOptions.sort.direction,
+ }
+ : DEFAULT_SORT,
+ });
+ }}
/>
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx
new file mode 100644
index 0000000000000..b88172a162063
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx
@@ -0,0 +1,211 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiBasicTableColumn } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types';
+import { isJavaAgentName } from '../../../../../common/agent_name';
+import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n';
+import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes';
+import {
+ asMillisecondDuration,
+ asPercent,
+ asTransactionRate,
+} from '../../../../../common/utils/formatters';
+import { APIReturnType } from '../../../../services/rest/createCallApmApi';
+import { px, unit } from '../../../../style/variables';
+import { SparkPlot } from '../../../shared/charts/spark_plot';
+import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink';
+import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink';
+import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip';
+import { getLatencyColumnLabel } from '../get_latency_column_label';
+import { PrimaryStatsServiceInstanceItem } from '../service_overview_instances_chart_and_table';
+
+type ServiceInstanceComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics'>;
+
+export function getColumns({
+ serviceName,
+ agentName,
+ latencyAggregationType,
+ comparisonStatsData,
+ comparisonEnabled,
+}: {
+ serviceName: string;
+ agentName?: string;
+ latencyAggregationType?: LatencyAggregationType;
+ comparisonStatsData?: ServiceInstanceComparisonStatistics;
+ comparisonEnabled?: boolean;
+}): Array> {
+ return [
+ {
+ field: 'serviceNodeName',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnNodeName',
+ { defaultMessage: 'Node name' }
+ ),
+ render: (_, item) => {
+ const { serviceNodeName } = item;
+ const isMissingServiceNodeName =
+ serviceNodeName === SERVICE_NODE_NAME_MISSING;
+ const text = isMissingServiceNodeName
+ ? UNIDENTIFIED_SERVICE_NODES_LABEL
+ : serviceNodeName;
+
+ const link = isJavaAgentName(agentName) ? (
+
+ {text}
+
+ ) : (
+ ({
+ ...query,
+ kuery: isMissingServiceNodeName
+ ? `NOT (service.node.name:*)`
+ : `service.node.name:"${item.serviceNodeName}"`,
+ })}
+ >
+ {text}
+
+ );
+
+ return ;
+ },
+ sortable: true,
+ },
+ {
+ field: 'latencyValue',
+ name: getLatencyColumnLabel(latencyAggregationType),
+ width: px(unit * 10),
+ render: (_, { serviceNodeName, latency }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.latency;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.latency;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ {
+ field: 'throughputValue',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnThroughput',
+ { defaultMessage: 'Throughput' }
+ ),
+ width: px(unit * 10),
+ render: (_, { serviceNodeName, throughput }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.throughput;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.throughput;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ {
+ field: 'errorRateValue',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnErrorRate',
+ { defaultMessage: 'Error rate' }
+ ),
+ width: px(unit * 8),
+ render: (_, { serviceNodeName, errorRate }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.errorRate;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.errorRate;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ {
+ field: 'cpuUsageValue',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnCpuUsage',
+ { defaultMessage: 'CPU usage (avg.)' }
+ ),
+ width: px(unit * 8),
+ render: (_, { serviceNodeName, cpuUsage }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.cpuUsage;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.cpuUsage;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ {
+ field: 'memoryUsageValue',
+ name: i18n.translate(
+ 'xpack.apm.serviceOverview.instancesTableColumnMemoryUsage',
+ { defaultMessage: 'Memory usage (avg.)' }
+ ),
+ width: px(unit * 9),
+ render: (_, { serviceNodeName, memoryUsage }) => {
+ const currentPeriodTimestamp =
+ comparisonStatsData?.currentPeriod?.[serviceNodeName]?.memoryUsage;
+ const previousPeriodTimestamp =
+ comparisonStatsData?.previousPeriod?.[serviceNodeName]?.memoryUsage;
+ return (
+
+ );
+ },
+ sortable: true,
+ },
+ ];
+}
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx
index 83ad506e8659b..28654acbefa46 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/index.tsx
@@ -6,208 +6,82 @@
*/
import {
- EuiBasicTableColumn,
+ EuiBasicTable,
EuiFlexGroup,
EuiFlexItem,
- EuiInMemoryTable,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
-import { ValuesType } from 'utility-types';
-import { isJavaAgentName } from '../../../../../common/agent_name';
-import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n';
-import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes';
-import {
- asMillisecondDuration,
- asPercent,
- asTransactionRate,
-} from '../../../../../common/utils/formatters';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
-import { px, unit } from '../../../../style/variables';
-import { SparkPlot } from '../../../shared/charts/spark_plot';
-import { MetricOverviewLink } from '../../../shared/Links/apm/MetricOverviewLink';
-import { ServiceNodeMetricOverviewLink } from '../../../shared/Links/apm/ServiceNodeMetricOverviewLink';
import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper';
-import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip';
-import { getLatencyColumnLabel } from '../get_latency_column_label';
+import {
+ PAGE_SIZE,
+ PrimaryStatsServiceInstanceItem,
+ SortDirection,
+ SortField,
+} from '../service_overview_instances_chart_and_table';
import { ServiceOverviewTableContainer } from '../service_overview_table_container';
+import { getColumns } from './get_columns';
+
+type ServiceInstanceComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/comparison_statistics'>;
-type ServiceInstanceItem = ValuesType<
- APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'>
->;
+export interface TableOptions {
+ pageIndex: number;
+ sort: {
+ direction: SortDirection;
+ field: SortField;
+ };
+}
interface Props {
- items?: ServiceInstanceItem[];
+ primaryStatsItems: PrimaryStatsServiceInstanceItem[];
serviceName: string;
- status: FETCH_STATUS;
+ primaryStatsStatus: FETCH_STATUS;
+ primaryStatsItemCount: number;
+ tableOptions: TableOptions;
+ onChangeTableOptions: (newTableOptions: {
+ page?: { index: number };
+ sort?: { field: string; direction: SortDirection };
+ }) => void;
+ comparisonStatsData?: ServiceInstanceComparisonStatistics;
+ isLoading: boolean;
}
-
export function ServiceOverviewInstancesTable({
- items = [],
+ primaryStatsItems = [],
+ primaryStatsItemCount,
serviceName,
- status,
+ primaryStatsStatus: status,
+ tableOptions,
+ onChangeTableOptions,
+ comparisonStatsData: comparisonStatsData,
+ isLoading,
}: Props) {
const { agentName } = useApmServiceContext();
const {
- urlParams: { latencyAggregationType },
+ urlParams: { latencyAggregationType, comparisonEnabled },
} = useUrlParams();
- const columns: Array> = [
- {
- field: 'name',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnNodeName',
- {
- defaultMessage: 'Node name',
- }
- ),
- render: (_, item) => {
- const { serviceNodeName } = item;
- const isMissingServiceNodeName =
- serviceNodeName === SERVICE_NODE_NAME_MISSING;
- const text = isMissingServiceNodeName
- ? UNIDENTIFIED_SERVICE_NODES_LABEL
- : serviceNodeName;
-
- const link = isJavaAgentName(agentName) ? (
-
- {text}
-
- ) : (
- ({
- ...query,
- kuery: isMissingServiceNodeName
- ? `NOT (service.node.name:*)`
- : `service.node.name:"${item.serviceNodeName}"`,
- })}
- >
- {text}
-
- );
+ const { pageIndex, sort } = tableOptions;
+ const { direction, field } = sort;
- return ;
- },
- sortable: true,
- },
- {
- field: 'latencyValue',
- name: getLatencyColumnLabel(latencyAggregationType),
- width: px(unit * 10),
- render: (_, { latency }) => {
- return (
-
- );
- },
- sortable: true,
- },
- {
- field: 'throughputValue',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnThroughput',
- { defaultMessage: 'Throughput' }
- ),
- width: px(unit * 10),
- render: (_, { throughput }) => {
- return (
-
- );
- },
- sortable: true,
- },
- {
- field: 'errorRateValue',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnErrorRate',
- {
- defaultMessage: 'Error rate',
- }
- ),
- width: px(unit * 8),
- render: (_, { errorRate }) => {
- return (
-
- );
- },
- sortable: true,
- },
- {
- field: 'cpuUsageValue',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnCpuUsage',
- {
- defaultMessage: 'CPU usage (avg.)',
- }
- ),
- width: px(unit * 8),
- render: (_, { cpuUsage }) => {
- return (
-
- );
- },
- sortable: true,
- },
- {
- field: 'memoryUsageValue',
- name: i18n.translate(
- 'xpack.apm.serviceOverview.instancesTableColumnMemoryUsage',
- {
- defaultMessage: 'Memory usage (avg.)',
- }
- ),
- width: px(unit * 9),
- render: (_, { memoryUsage }) => {
- return (
-
- );
- },
- sortable: true,
- },
- ];
+ const columns = getColumns({
+ agentName,
+ serviceName,
+ latencyAggregationType,
+ comparisonStatsData,
+ comparisonEnabled,
+ });
- // need top-level sortable fields for the managed table
- const tableItems = items.map((item) => ({
- ...item,
- latencyValue: item.latency?.value ?? 0,
- throughputValue: item.throughput?.value ?? 0,
- errorRateValue: item.errorRate?.value ?? 0,
- cpuUsageValue: item.cpuUsage?.value ?? 0,
- memoryUsageValue: item.memoryUsage?.value ?? 0,
- }));
-
- const isLoading = status === FETCH_STATUS.LOADING;
+ const pagination = {
+ pageIndex,
+ pageSize: PAGE_SIZE,
+ totalItemCount: primaryStatsItemCount,
+ hidePerPageOptions: true,
+ };
return (
@@ -223,24 +97,15 @@ export function ServiceOverviewInstancesTable({
-
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx
index 02f60eab2cb88..121b96b0361b2 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx
@@ -15,6 +15,7 @@ import { i18n } from '@kbn/i18n';
import { orderBy } from 'lodash';
import React, { useState } from 'react';
import uuid from 'uuid';
+import { APIReturnType } from '../../../../services/rest/createCallApmApi';
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
@@ -28,8 +29,9 @@ interface Props {
serviceName: string;
}
+type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>;
const INITIAL_STATE = {
- transactionGroups: [],
+ transactionGroups: [] as ApiResponse['transactionGroups'],
isAggregationAccurate: true,
requestId: '',
transactionGroupsTotalItems: 0,
diff --git a/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx b/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx
index 23adbb23b2322..94391b5b2fb06 100644
--- a/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_profiling/index.tsx
@@ -19,6 +19,7 @@ import {
} from '../../../../common/profiling';
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { useFetcher } from '../../../hooks/use_fetcher';
+import { APIReturnType } from '../../../services/rest/createCallApmApi';
import { SearchBar } from '../../shared/search_bar';
import { ServiceProfilingFlamegraph } from './service_profiling_flamegraph';
import { ServiceProfilingTimeline } from './service_profiling_timeline';
@@ -28,6 +29,9 @@ interface ServiceProfilingProps {
environment?: string;
}
+type ApiResponse = APIReturnType<'GET /api/apm/services/{serviceName}/profiling/timeline'>;
+const DEFAULT_DATA: ApiResponse = { profilingTimeline: [] };
+
export function ServiceProfiling({
serviceName,
environment,
@@ -36,7 +40,7 @@ export function ServiceProfiling({
urlParams: { kuery, start, end },
} = useUrlParams();
- const { data = [] } = useFetcher(
+ const { data = DEFAULT_DATA } = useFetcher(
(callApmApi) => {
if (!start || !end) {
return;
@@ -58,14 +62,16 @@ export function ServiceProfiling({
[kuery, start, end, serviceName, environment]
);
+ const { profilingTimeline } = data;
+
const [valueType, setValueType] = useState();
useEffect(() => {
- if (!data.length) {
+ if (!profilingTimeline.length) {
return;
}
- const availableValueTypes = data.reduce((set, point) => {
+ const availableValueTypes = profilingTimeline.reduce((set, point) => {
(Object.keys(point.valueTypes).filter(
(type) => type !== 'unknown'
) as ProfilingValueType[])
@@ -80,7 +86,7 @@ export function ServiceProfiling({
if (!valueType || !availableValueTypes.has(valueType)) {
setValueType(Array.from(availableValueTypes)[0]);
}
- }, [data, valueType]);
+ }, [profilingTimeline, valueType]);
return (
<>
@@ -103,7 +109,7 @@ export function ServiceProfiling({
{
setValueType(type);
}}
diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx
index 3cd858aceaa90..4bc9764b704b0 100644
--- a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx
+++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx
@@ -8,7 +8,7 @@
import { EuiTitle } from '@elastic/eui';
import React, { ComponentType } from 'react';
import { MemoryRouter } from 'react-router-dom';
-import { HttpSetup } from '../../../../../../../src/core/public';
+import { CoreStart } from '../../../../../../../src/core/public';
import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider';
@@ -20,7 +20,7 @@ export default {
component: ApmHeader,
decorators: [
(Story: ComponentType) => {
- createCallApmApi(({} as unknown) as HttpSetup);
+ createCallApmApi(({} as unknown) as CoreStart);
return (
diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx
index 6f2910a2a5ef7..a624c220a0e4c 100644
--- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx
@@ -9,7 +9,6 @@ import { act, fireEvent, render } from '@testing-library/react';
import React, { ReactNode } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { CustomLinkMenuSection } from '.';
-import { CustomLink as CustomLinkType } from '../../../../../common/custom_link/custom_link_types';
import { Transaction } from '../../../../../typings/es_schemas/ui/transaction';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
import * as useFetcher from '../../../../hooks/use_fetcher';
@@ -40,7 +39,7 @@ const transaction = ({
describe('Custom links', () => {
it('shows empty message when no custom link is available', () => {
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: useFetcher.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
@@ -58,7 +57,7 @@ describe('Custom links', () => {
it('shows loading while custom links are fetched', () => {
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: useFetcher.FETCH_STATUS.LOADING,
refetch: jest.fn(),
});
@@ -71,12 +70,14 @@ describe('Custom links', () => {
});
it('shows first 3 custom links available', () => {
- const customLinks = [
- { id: '1', label: 'foo', url: 'foo' },
- { id: '2', label: 'bar', url: 'bar' },
- { id: '3', label: 'baz', url: 'baz' },
- { id: '4', label: 'qux', url: 'qux' },
- ] as CustomLinkType[];
+ const customLinks = {
+ customLinks: [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' },
+ ],
+ };
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
data: customLinks,
@@ -93,15 +94,17 @@ describe('Custom links', () => {
});
it('clicks "show all" and "show fewer"', () => {
- const customLinks = [
- { id: '1', label: 'foo', url: 'foo' },
- { id: '2', label: 'bar', url: 'bar' },
- { id: '3', label: 'baz', url: 'baz' },
- { id: '4', label: 'qux', url: 'qux' },
- ] as CustomLinkType[];
+ const data = {
+ customLinks: [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' },
+ ],
+ };
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: customLinks,
+ data,
status: useFetcher.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
@@ -125,7 +128,7 @@ describe('Custom links', () => {
describe('create custom link buttons', () => {
it('shows create button below empty message', () => {
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: [],
+ data: { customLinks: [] },
status: useFetcher.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
@@ -140,15 +143,17 @@ describe('Custom links', () => {
});
it('shows create button besides the title', () => {
- const customLinks = [
- { id: '1', label: 'foo', url: 'foo' },
- { id: '2', label: 'bar', url: 'bar' },
- { id: '3', label: 'baz', url: 'baz' },
- { id: '4', label: 'qux', url: 'qux' },
- ] as CustomLinkType[];
+ const data = {
+ customLinks: [
+ { id: '1', label: 'foo', url: 'foo' },
+ { id: '2', label: 'bar', url: 'bar' },
+ { id: '3', label: 'baz', url: 'baz' },
+ { id: '4', label: 'qux', url: 'qux' },
+ ],
+ };
jest.spyOn(useFetcher, 'useFetcher').mockReturnValue({
- data: customLinks,
+ data,
status: useFetcher.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx
index 7d2e4a13278ec..cbbf34c78c4af 100644
--- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx
@@ -58,7 +58,7 @@ export function CustomLinkMenuSection({
[transaction]
);
- const { data: customLinks = [], status, refetch } = useFetcher(
+ const { data, status, refetch } = useFetcher(
(callApmApi) =>
callApmApi({
isCachable: false,
@@ -68,6 +68,8 @@ export function CustomLinkMenuSection({
[filters]
);
+ const customLinks = data?.customLinks ?? [];
+
return (
<>
{isCreateEditFlyoutOpen && (
diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts
index b0ac35cc3667a..b8d67f71a9baa 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts
+++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.test.ts
@@ -7,7 +7,7 @@
import { onBrushEnd, isTimeseriesEmpty } from './helper';
import { History } from 'history';
-import { TimeSeries } from '../../../../../typings/timeseries';
+import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
describe('Chart helper', () => {
describe('onBrushEnd', () => {
@@ -52,7 +52,7 @@ describe('Chart helper', () => {
type: 'line',
color: 'red',
},
- ] as TimeSeries[];
+ ] as Array>;
expect(isTimeseriesEmpty(timeseries)).toBeTruthy();
});
it('returns true when y coordinate is null', () => {
@@ -63,7 +63,7 @@ describe('Chart helper', () => {
type: 'line',
color: 'red',
},
- ] as TimeSeries[];
+ ] as Array>;
expect(isTimeseriesEmpty(timeseries)).toBeTruthy();
});
it('returns true when y coordinate is undefined', () => {
@@ -74,7 +74,7 @@ describe('Chart helper', () => {
type: 'line',
color: 'red',
},
- ] as TimeSeries[];
+ ] as Array>;
expect(isTimeseriesEmpty(timeseries)).toBeTruthy();
});
it('returns false when at least one coordinate is filled', () => {
@@ -91,7 +91,7 @@ describe('Chart helper', () => {
type: 'line',
color: 'green',
},
- ] as TimeSeries[];
+ ] as Array>;
expect(isTimeseriesEmpty(timeseries)).toBeFalsy();
});
});
diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
index 3b93cb1f402e8..d94f2ce8f5c5d 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
+++ b/x-pack/plugins/apm/public/components/shared/charts/helper/helper.ts
@@ -7,7 +7,7 @@
import { XYBrushArea } from '@elastic/charts';
import { History } from 'history';
-import { TimeSeries } from '../../../../../typings/timeseries';
+import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
import { fromQuery, toQuery } from '../../Links/url_helpers';
export const onBrushEnd = ({
@@ -36,15 +36,12 @@ export const onBrushEnd = ({
}
};
-export function isTimeseriesEmpty(timeseries?: TimeSeries[]) {
+export function isTimeseriesEmpty(timeseries?: Array>) {
return (
!timeseries ||
timeseries
.map((serie) => serie.data)
.flat()
- .every(
- ({ y }: { x?: number | null; y?: number | null }) =>
- y === null || y === undefined
- )
+ .every(({ y }: Coordinate) => y === null || y === undefined)
);
}
diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
index aa353b40d464a..5bcf0d161653e 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx
@@ -23,13 +23,13 @@ import {
} from '../../../../../common/utils/formatters';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { useTheme } from '../../../../hooks/use_theme';
-import { APIReturnType } from '../../../../services/rest/createCallApmApi';
+import { PrimaryStatsServiceInstanceItem } from '../../../app/service_overview/service_overview_instances_chart_and_table';
import { ChartContainer } from '../chart_container';
import { getResponseTimeTickFormatter } from '../transaction_charts/helper';
interface InstancesLatencyDistributionChartProps {
height: number;
- items?: APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'>;
+ items?: PrimaryStatsServiceInstanceItem[];
status: FETCH_STATUS;
}
@@ -44,15 +44,11 @@ export function InstancesLatencyDistributionChart({
const chartTheme = {
...useChartTheme(),
bubbleSeriesStyle: {
- point: {
- strokeWidth: 0,
- fill: theme.eui.euiColorVis1,
- radius: 4,
- },
+ point: { strokeWidth: 0, fill: theme.eui.euiColorVis1, radius: 4 },
},
};
- const maxLatency = Math.max(...items.map((item) => item.latency?.value ?? 0));
+ const maxLatency = Math.max(...items.map((item) => item.latency ?? 0));
const latencyFormatter = getDurationFormatter(maxLatency);
return (
@@ -79,9 +75,9 @@ export function InstancesLatencyDistributionChart({
'xpack.apm.instancesLatencyDistributionChartLegend',
{ defaultMessage: 'Instances' }
)}
- xAccessor={(item) => item.throughput?.value}
+ xAccessor={(item) => item.throughput}
xScaleType={ScaleType.Linear}
- yAccessors={[(item) => item.latency?.value]}
+ yAccessors={[(item) => item.latency]}
yScaleType={ScaleType.Linear}
/>
>;
/**
* Formatter for y-axis tick values
*/
@@ -85,12 +89,10 @@ export function TimeseriesChart({
const max = Math.max(...xValues);
const xFormatter = niceTimeFormatter([min, max]);
-
const isEmpty = isTimeseriesEmpty(timeseries);
-
const annotationColor = theme.eui.euiColorSecondary;
-
const allSeries = [...timeseries, ...(anomalyTimeseries?.boundaries ?? [])];
+ const xDomain = isEmpty ? { min: 0, max: 1 } : { min, max };
return (
@@ -111,7 +113,7 @@ export function TimeseriesChart({
showLegend
showLegendExtra
legendPosition={Position.Bottom}
- xDomain={{ min, max }}
+ xDomain={xDomain}
onLegendItemClick={(legend) => {
if (onToggleLegend) {
onToggleLegend(legend);
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx
index f55389ec2d5f7..23016cc5dd8e9 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx
@@ -28,7 +28,7 @@ import {
asAbsoluteDateTime,
asPercent,
} from '../../../../../common/utils/formatters';
-import { TimeSeries } from '../../../../../typings/timeseries';
+import { Coordinate, TimeSeries } from '../../../../../typings/timeseries';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { useTheme } from '../../../../hooks/use_theme';
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
@@ -42,7 +42,7 @@ interface Props {
fetchStatus: FETCH_STATUS;
height?: number;
showAnnotations: boolean;
- timeseries?: TimeSeries[];
+ timeseries?: Array>;
}
export function TransactionBreakdownChartContents({
diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/helper.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/helper.tsx
index 6c46580f4738e..31d18b7a9709d 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/helper.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/helper.tsx
@@ -6,14 +6,14 @@
*/
import { isFiniteNumber } from '../../../../../common/utils/is_finite_number';
-import { APMChartSpec, Coordinate } from '../../../../../typings/timeseries';
+import { Coordinate } from '../../../../../typings/timeseries';
import { TimeFormatter } from '../../../../../common/utils/formatters';
export function getResponseTimeTickFormatter(formatter: TimeFormatter) {
return (t: number) => formatter(t).formatted;
}
-export function getMaxY(specs?: Array>) {
+export function getMaxY(specs?: Array<{ data: Coordinate[] }>) {
const values = specs
?.flatMap((spec) => spec.data)
.map((coord) => coord.y)
diff --git a/x-pack/plugins/apm/public/components/shared/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar.tsx
index 2bd3fef8c0e88..aeb2a2c6390fc 100644
--- a/x-pack/plugins/apm/public/components/shared/search_bar.tsx
+++ b/x-pack/plugins/apm/public/components/shared/search_bar.tsx
@@ -7,14 +7,21 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiCallOut } from '@elastic/eui';
+import { EuiLink } from '@elastic/eui';
+import { enableInspectEsQueries } from '../../../../observability/public';
import { euiStyled } from '../../../../../../src/plugins/kibana_react/common';
import { px, unit } from '../../style/variables';
import { DatePicker } from './DatePicker';
import { KueryBar } from './KueryBar';
import { TimeComparison } from './time_comparison';
import { useBreakPoints } from '../../hooks/use_break_points';
+import { useKibanaUrl } from '../../hooks/useKibanaUrl';
+import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context';
-const SearchBarFlexGroup = euiStyled(EuiFlexGroup)`
+const EuiFlexGroupSpaced = euiStyled(EuiFlexGroup)`
margin: ${({ theme }) =>
`${theme.eui.euiSizeS} ${theme.eui.euiSizeS} -${theme.eui.gutterTypes.gutterMedium} ${theme.eui.euiSizeS}`};
`;
@@ -29,6 +36,52 @@ function getRowDirection(showColumn: boolean) {
return showColumn ? 'column' : 'row';
}
+function DebugQueryCallout() {
+ const { uiSettings } = useApmPluginContext().core;
+ const advancedSettingsUrl = useKibanaUrl('/app/management/kibana/settings', {
+ query: {
+ query: 'category:(observability)',
+ },
+ });
+
+ if (!uiSettings.get(enableInspectEsQueries)) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.searchBar.inspectEsQueriesEnabled.callout.description.advancedSettings',
+ { defaultMessage: 'Advanced Settings' }
+ )}
+
+ ),
+ }}
+ />
+
+
+
+ );
+}
+
export function SearchBar({
prepend,
showTimeComparison = false,
@@ -38,26 +91,29 @@ export function SearchBar({
const itemsStyle = { marginBottom: isLarge ? px(unit) : 0 };
return (
-
-
-
-
-
-
- {showTimeComparison && (
-
-
+ <>
+
+
+
+
+
+
+
+ {showTimeComparison && (
+
+
+
+ )}
+
+
- )}
-
-
-
-
-
-
+
+
+
+ >
);
}
diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
index 024deca558497..9a910787d5fe8 100644
--- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
+++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
@@ -116,8 +116,8 @@ export function MockApmPluginContextWrapper({
children?: React.ReactNode;
value?: ApmPluginContextValue;
}) {
- if (value.core?.http) {
- createCallApmApi(value.core?.http);
+ if (value.core) {
+ createCallApmApi(value.core);
}
return (
{
if (start && end) {
return callApmApi({
@@ -51,9 +53,9 @@ export function useEnvironmentsFetcher({
);
const environmentOptions = useMemo(
- () => getEnvironmentOptions(environments),
- [environments]
+ () => getEnvironmentOptions(data.environments),
+ [data?.environments]
);
- return { environments, status, environmentOptions };
+ return { environments: data.environments, status, environmentOptions };
}
diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts
index 4ddd10ecc1476..382053f133950 100644
--- a/x-pack/plugins/apm/public/plugin.ts
+++ b/x-pack/plugins/apm/public/plugin.ts
@@ -9,7 +9,7 @@ import { ConfigSchema } from '.';
import {
FetchDataParams,
HasDataParams,
- ObservabilityPluginSetup,
+ ObservabilityPublicSetup,
} from '../../observability/public';
import {
AppMountParameters,
@@ -52,7 +52,7 @@ export interface ApmPluginSetupDeps {
home?: HomePublicPluginSetup;
licensing: LicensingPluginSetup;
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
- observability?: ObservabilityPluginSetup;
+ observability?: ObservabilityPublicSetup;
}
export interface ApmPluginStartDeps {
@@ -85,19 +85,19 @@ export class ApmPlugin implements Plugin {
const getApmDataHelper = async () => {
const {
fetchObservabilityOverviewPageData,
- hasData,
+ getHasData,
createCallApmApi,
} = await import('./services/rest/apm_observability_overview_fetchers');
// have to do this here as well in case app isn't mounted yet
- createCallApmApi(core.http);
+ createCallApmApi(core);
- return { fetchObservabilityOverviewPageData, hasData };
+ return { fetchObservabilityOverviewPageData, getHasData };
};
plugins.observability.dashboard.register({
appName: 'apm',
hasData: async () => {
const dataHelper = await getApmDataHelper();
- return await dataHelper.hasData();
+ return await dataHelper.getHasData();
},
fetchData: async (params: FetchDataParams) => {
const dataHelper = await getApmDataHelper();
@@ -112,7 +112,7 @@ export class ApmPlugin implements Plugin {
createCallApmApi,
} = await import('./components/app/RumDashboard/ux_overview_fetchers');
// have to do this here as well in case app isn't mounted yet
- createCallApmApi(core.http);
+ createCallApmApi(core);
return { fetchUxOverviewDate, hasRumData };
};
diff --git a/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts b/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts
index f9e72bff231f4..f334212536778 100644
--- a/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts
+++ b/x-pack/plugins/apm/public/selectors/throughput_chart_selectors.ts
@@ -8,14 +8,14 @@
import { difference, zipObject } from 'lodash';
import { EuiTheme } from '../../../../../src/plugins/kibana_react/common';
import { asTransactionRate } from '../../common/utils/formatters';
-import { TimeSeries } from '../../typings/timeseries';
+import { Coordinate, TimeSeries } from '../../typings/timeseries';
import { APIReturnType } from '../services/rest/createCallApmApi';
import { httpStatusCodeToColor } from '../utils/httpStatusCodeToColor';
export type ThroughputChartsResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/throughput'>;
export interface ThroughputChart {
- throughputTimeseries: TimeSeries[];
+ throughputTimeseries: Array>;
}
export function getThroughputChartSelector({
diff --git a/x-pack/plugins/apm/public/services/callApi.test.ts b/x-pack/plugins/apm/public/services/callApi.test.ts
index cdd9cb5b08a32..5f0be1b6fadbb 100644
--- a/x-pack/plugins/apm/public/services/callApi.test.ts
+++ b/x-pack/plugins/apm/public/services/callApi.test.ts
@@ -7,49 +7,51 @@
import { mockNow } from '../utils/testHelpers';
import { clearCache, callApi } from './rest/callApi';
-import { SessionStorageMock } from './__mocks__/SessionStorageMock';
-import { HttpSetup } from 'kibana/public';
+import { CoreStart, HttpSetup } from 'kibana/public';
-type HttpMock = HttpSetup & {
- get: jest.SpyInstance;
+type CoreMock = CoreStart & {
+ http: {
+ get: jest.SpyInstance;
+ };
};
describe('callApi', () => {
- let http: HttpMock;
+ let core: CoreMock;
beforeEach(() => {
- http = ({
- get: jest.fn().mockReturnValue({
- my_key: 'hello_world',
- }),
- } as unknown) as HttpMock;
-
- // @ts-expect-error
- global.sessionStorage = new SessionStorageMock();
+ core = ({
+ http: {
+ get: jest.fn().mockReturnValue({
+ my_key: 'hello_world',
+ }),
+ },
+ uiSettings: { get: () => false }, // disable `observability:enableInspectEsQueries` setting
+ } as unknown) as CoreMock;
});
afterEach(() => {
- http.get.mockClear();
+ core.http.get.mockClear();
clearCache();
});
- describe('apm_debug', () => {
+ describe('_inspect', () => {
beforeEach(() => {
- sessionStorage.setItem('apm_debug', 'true');
+ // @ts-expect-error
+ core.uiSettings.get = () => true; // enable `observability:enableInspectEsQueries` setting
});
it('should add debug param for APM endpoints', async () => {
- await callApi(http, { pathname: `/api/apm/status/server` });
+ await callApi(core, { pathname: `/api/apm/status/server` });
- expect(http.get).toHaveBeenCalledWith('/api/apm/status/server', {
- query: { _debug: true },
+ expect(core.http.get).toHaveBeenCalledWith('/api/apm/status/server', {
+ query: { _inspect: true },
});
});
it('should not add debug param for non-APM endpoints', async () => {
- await callApi(http, { pathname: `/api/kibana` });
+ await callApi(core, { pathname: `/api/kibana` });
- expect(http.get).toHaveBeenCalledWith('/api/kibana', { query: {} });
+ expect(core.http.get).toHaveBeenCalledWith('/api/kibana', { query: {} });
});
});
@@ -65,138 +67,138 @@ describe('callApi', () => {
describe('when the call does not contain start/end params', () => {
it('should not return cached response for identical calls', async () => {
- await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
- await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
- await callApi(http, { pathname: `/api/kibana`, query: { foo: 'bar' } });
+ await callApi(core, { pathname: `/api/kibana`, query: { foo: 'bar' } });
+ await callApi(core, { pathname: `/api/kibana`, query: { foo: 'bar' } });
+ await callApi(core, { pathname: `/api/kibana`, query: { foo: 'bar' } });
- expect(http.get).toHaveBeenCalledTimes(3);
+ expect(core.http.get).toHaveBeenCalledTimes(3);
});
});
describe('when the call contains start/end params', () => {
it('should return cached response for identical calls', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- expect(http.get).toHaveBeenCalledTimes(1);
+ expect(core.http.get).toHaveBeenCalledTimes(1);
});
it('should not return cached response for subsequent calls if arguments change', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011', foo: 'bar1' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011', foo: 'bar2' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2010', end: '2011', foo: 'bar3' },
});
- expect(http.get).toHaveBeenCalledTimes(3);
+ expect(core.http.get).toHaveBeenCalledTimes(3);
});
it('should not return cached response if `end` is a future timestamp', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { end: '2030' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { end: '2030' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { end: '2030' },
});
- expect(http.get).toHaveBeenCalledTimes(3);
+ expect(core.http.get).toHaveBeenCalledTimes(3);
});
it('should return cached response if calls contain `end` param in the past', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2009', end: '2010' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2009', end: '2010' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2009', end: '2010' },
});
- expect(http.get).toHaveBeenCalledTimes(1);
+ expect(core.http.get).toHaveBeenCalledTimes(1);
});
it('should return cached response even if order of properties change', async () => {
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { end: '2010', start: '2009' },
});
- await callApi(http, {
+ await callApi(core, {
pathname: `/api/kibana`,
query: { start: '2009', end: '2010' },
});
- await callApi(http, {
+ await callApi(core, {
query: { start: '2009', end: '2010' },
pathname: `/api/kibana`,
});
- expect(http.get).toHaveBeenCalledTimes(1);
+ expect(core.http.get).toHaveBeenCalledTimes(1);
});
it('should not return cached response with `isCachable: false` option', async () => {
- await callApi(http, {
+ await callApi(core, {
isCachable: false,
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- await callApi(http, {
+ await callApi(core, {
isCachable: false,
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- await callApi(http, {
+ await callApi(core, {
isCachable: false,
pathname: `/api/kibana`,
query: { start: '2010', end: '2011' },
});
- expect(http.get).toHaveBeenCalledTimes(3);
+ expect(core.http.get).toHaveBeenCalledTimes(3);
});
it('should return cached response with `isCachable: true` option', async () => {
- await callApi(http, {
+ await callApi(core, {
isCachable: true,
pathname: `/api/kibana`,
query: { end: '2030' },
});
- await callApi(http, {
+ await callApi(core, {
isCachable: true,
pathname: `/api/kibana`,
query: { end: '2030' },
});
- await callApi(http, {
+ await callApi(core, {
isCachable: true,
pathname: `/api/kibana`,
query: { end: '2030' },
});
- expect(http.get).toHaveBeenCalledTimes(1);
+ expect(core.http.get).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/x-pack/plugins/apm/public/services/callApmApi.test.ts b/x-pack/plugins/apm/public/services/callApmApi.test.ts
index 25d34b5d102f5..56146c49fc57d 100644
--- a/x-pack/plugins/apm/public/services/callApmApi.test.ts
+++ b/x-pack/plugins/apm/public/services/callApmApi.test.ts
@@ -7,7 +7,7 @@
import * as callApiExports from './rest/callApi';
import { createCallApmApi, callApmApi } from './rest/createCallApmApi';
-import { HttpSetup } from 'kibana/public';
+import { CoreStart } from 'kibana/public';
const callApi = jest
.spyOn(callApiExports, 'callApi')
@@ -15,7 +15,7 @@ const callApi = jest
describe('callApmApi', () => {
beforeEach(() => {
- createCallApmApi({} as HttpSetup);
+ createCallApmApi({} as CoreStart);
});
afterEach(() => {
@@ -79,7 +79,7 @@ describe('callApmApi', () => {
{},
expect.objectContaining({
pathname: '/api/apm',
- method: 'POST',
+ method: 'post',
body: {
foo: 'bar',
bar: 'foo',
diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts
index b0bae6aa91a3d..1821e92ee5a78 100644
--- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts
+++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts
@@ -8,7 +8,7 @@
import moment from 'moment';
import {
fetchObservabilityOverviewPageData,
- hasData,
+ getHasData,
} from './apm_observability_overview_fetchers';
import * as createCallApmApi from './createCallApmApi';
@@ -31,12 +31,12 @@ describe('Observability dashboard data', () => {
describe('hasData', () => {
it('returns false when no data is available', async () => {
callApmApiMock.mockImplementation(() => Promise.resolve(false));
- const response = await hasData();
+ const response = await getHasData();
expect(response).toBeFalsy();
});
it('returns true when data is available', async () => {
- callApmApiMock.mockImplementation(() => Promise.resolve(true));
- const response = await hasData();
+ callApmApiMock.mockResolvedValue({ hasData: true });
+ const response = await getHasData();
expect(response).toBeTruthy();
});
});
diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts
index 6d630ede1cb11..55ead8d942aca 100644
--- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts
+++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts
@@ -58,9 +58,11 @@ export const fetchObservabilityOverviewPageData = async ({
};
};
-export async function hasData() {
- return await callApmApi({
+export async function getHasData() {
+ const res = await callApmApi({
endpoint: 'GET /api/apm/observability_overview/has_data',
signal: null,
});
+
+ return res.hasData;
}
diff --git a/x-pack/plugins/apm/public/services/rest/callApi.ts b/x-pack/plugins/apm/public/services/rest/callApi.ts
index f5106fce78cc7..f623872303c5b 100644
--- a/x-pack/plugins/apm/public/services/rest/callApi.ts
+++ b/x-pack/plugins/apm/public/services/rest/callApi.ts
@@ -5,15 +5,19 @@
* 2.0.
*/
-import { HttpSetup } from 'kibana/public';
+import { CoreSetup, CoreStart } from 'kibana/public';
import { isString, startsWith } from 'lodash';
import LRU from 'lru-cache';
import hash from 'object-hash';
+import { enableInspectEsQueries } from '../../../../observability/public';
import { FetchOptions } from '../../../common/fetch_options';
-function fetchOptionsWithDebug(fetchOptions: FetchOptions) {
+function fetchOptionsWithDebug(
+ fetchOptions: FetchOptions,
+ inspectableEsQueriesEnabled: boolean
+) {
const debugEnabled =
- sessionStorage.getItem('apm_debug') === 'true' &&
+ inspectableEsQueriesEnabled &&
startsWith(fetchOptions.pathname, '/api/apm');
const { body, ...rest } = fetchOptions;
@@ -23,7 +27,7 @@ function fetchOptionsWithDebug(fetchOptions: FetchOptions) {
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
query: {
...fetchOptions.query,
- ...(debugEnabled ? { _debug: true } : {}),
+ ...(debugEnabled ? { _inspect: true } : {}),
},
};
}
@@ -37,9 +41,12 @@ export function clearCache() {
export type CallApi = typeof callApi;
export async function callApi(
- http: HttpSetup,
+ { http, uiSettings }: CoreStart | CoreSetup,
fetchOptions: FetchOptions
): Promise {
+ const inspectableEsQueriesEnabled: boolean = uiSettings.get(
+ enableInspectEsQueries
+ );
const cacheKey = getCacheKey(fetchOptions);
const cacheResponse = cache.get(cacheKey);
if (cacheResponse) {
@@ -47,7 +54,8 @@ export async function callApi(
}
const { pathname, method = 'get', ...options } = fetchOptionsWithDebug(
- fetchOptions
+ fetchOptions,
+ inspectableEsQueriesEnabled
);
const lowercaseMethod = method.toLowerCase() as
diff --git a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts
index c6d55a85dd70e..b0cce3296fe21 100644
--- a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts
+++ b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts
@@ -5,13 +5,14 @@
* 2.0.
*/
-import { HttpSetup } from 'kibana/public';
+import { CoreSetup, CoreStart } from 'kibana/public';
+import { parseEndpoint } from '../../../common/apm_api/parse_endpoint';
import { FetchOptions } from '../../../common/fetch_options';
import { callApi } from './callApi';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { APMAPI } from '../../../server/routes/create_apm_api';
+import type { APMAPI } from '../../../server/routes/create_apm_api';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { Client } from '../../../server/routes/typings';
+import type { Client } from '../../../server/routes/typings';
export type APMClient = Client;
export type AutoAbortedAPMClient = Client;
@@ -24,8 +25,8 @@ export type APMClientOptions = Omit<
signal: AbortSignal | null;
params?: {
body?: any;
- query?: any;
- path?: any;
+ query?: Record;
+ path?: Record;
};
};
@@ -35,23 +36,17 @@ export let callApmApi: APMClient = () => {
);
};
-export function createCallApmApi(http: HttpSetup) {
+export function createCallApmApi(core: CoreStart | CoreSetup) {
callApmApi = ((options: APMClientOptions) => {
- const { endpoint, params = {}, ...opts } = options;
+ const { endpoint, params, ...opts } = options;
+ const { method, pathname } = parseEndpoint(endpoint, params?.path);
- const path = (params.path || {}) as Record;
- const [method, pathname] = endpoint.split(' ');
-
- const formattedPathname = Object.keys(path).reduce((acc, paramName) => {
- return acc.replace(`{${paramName}}`, path[paramName]);
- }, pathname);
-
- return callApi(http, {
+ return callApi(core, {
...opts,
method,
- pathname: formattedPathname,
- body: params.body,
- query: params.query,
+ pathname,
+ body: params?.body,
+ query: params?.query,
});
}) as APMClient;
}
diff --git a/x-pack/plugins/apm/readme.md b/x-pack/plugins/apm/readme.md
index b35024844a892..ef2675f4f6c65 100644
--- a/x-pack/plugins/apm/readme.md
+++ b/x-pack/plugins/apm/readme.md
@@ -31,19 +31,19 @@ _Docker Compose is required_
## Testing
-### E2E (Cypress) tests
+### Cypress tests
```sh
-x-pack/plugins/apm/e2e/run-e2e.sh
+node x-pack/plugins/apm/scripts/ftr_e2e/cypress_run.js
```
_Starts Kibana (:5701), APM Server (:8201) and Elasticsearch (:9201). Ingests sample data into Elasticsearch via APM Server and runs the Cypress tests_
-### Unit testing
+### Jest tests
Note: Run the following commands from `kibana/x-pack/plugins/apm`.
-#### Run unit tests
+#### Run
```
npx jest --watch
@@ -82,8 +82,11 @@ For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
### API integration tests
-Our tests are separated in two suites: one suite runs with a basic license, and the other
-with a trial license (the equivalent of gold+). This requires separate test servers and test runners.
+API tests are separated in two suites:
+ - a basic license test suite
+ - a trial license test suite (the equivalent of gold+)
+
+This requires separate test servers and test runners.
**Basic**
@@ -109,7 +112,10 @@ node scripts/functional_test_runner --config x-pack/test/apm_api_integration/tri
The API tests for "trial" are located in `x-pack/test/apm_api_integration/trial/tests`.
-For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
+
+**API Test tips**
+ - For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
+ - To update snapshots append `--updateSnapshots` to the functional_test_runner command
## Linting
@@ -154,10 +160,10 @@ The users will be created with the password specified in kibana.dev.yml for `ela
## Debugging Elasticsearch queries
-All APM api endpoints accept `_debug=true` as a query param that will result in the underlying ES query being outputted in the Kibana backend process.
+All APM api endpoints accept `_inspect=true` as a query param that will result in the underlying ES query being outputted in the Kibana backend process.
Example:
-`/api/apm/services/my_service?_debug=true`
+`/api/apm/services/my_service?_inspect=true`
## Storybook
diff --git a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts
index 50613c10ff7c0..88b1cf3a344ed 100644
--- a/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/get_correlations_for_slow_transactions/get_latency_distribution.ts
@@ -106,7 +106,7 @@ export async function getLatencyDistribution({
type Agg = NonNullable;
if (!response.aggregations) {
- return;
+ return {};
}
function formatDistribution(distribution: Agg['distribution']) {
diff --git a/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts b/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts
index 94ed3dc3b6999..cc1e32e47973d 100644
--- a/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts
+++ b/x-pack/plugins/apm/server/lib/correlations/process_significant_term_aggs.ts
@@ -47,10 +47,15 @@ function getMaxImpactScore(scores: number[]) {
export function processSignificantTermAggs({
sigTermAggs,
}: {
- sigTermAggs: Record;
+ sigTermAggs: Record;
}) {
- const significantTerms = Object.entries(sigTermAggs).flatMap(
- ([fieldName, agg]) => {
+ const significantTerms = Object.entries(sigTermAggs)
+ // filter entries with buckets, i.e. Significant terms aggs
+ .filter((entry): entry is [string, SigTermAgg] => {
+ const [, agg] = entry;
+ return 'buckets' in agg;
+ })
+ .flatMap(([fieldName, agg]) => {
return agg.buckets.map((bucket) => ({
fieldName,
fieldValue: bucket.key,
@@ -58,8 +63,7 @@ export function processSignificantTermAggs({
valueCount: bucket.doc_count,
score: bucket.score,
}));
- }
- );
+ });
const maxImpactScore = getMaxImpactScore(
significantTerms.map(({ score }) => score)
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
index aa41880fba444..1f0aa401bcab0 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
@@ -7,8 +7,10 @@
/* eslint-disable no-console */
+import { omit } from 'lodash';
import chalk from 'chalk';
import { KibanaRequest } from '../../../../../../../src/core/server';
+import { inspectableEsQueriesMap } from '../../../routes/create_api';
function formatObj(obj: Record) {
return JSON.stringify(obj, null, 2);
@@ -18,10 +20,18 @@ export async function callAsyncWithDebug({
cb,
getDebugMessage,
debug,
+ request,
+ requestType,
+ requestParams,
+ isCalledWithInternalUser,
}: {
cb: () => Promise;
getDebugMessage: () => { body: string; title: string };
debug: boolean;
+ request: KibanaRequest;
+ requestType: string;
+ requestParams: Record;
+ isCalledWithInternalUser: boolean; // only allow inspection of queries that were retrieved with credentials of the end user
}) {
if (!debug) {
return cb();
@@ -41,16 +51,27 @@ export async function callAsyncWithDebug({
if (debug) {
const highlightColor = esError ? 'bgRed' : 'inverse';
const diff = process.hrtime(startTime);
- const duration = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`;
+ const duration = Math.round(diff[0] * 1000 + diff[1] / 1e6); // duration in ms
const { title, body } = getDebugMessage();
console.log(
- chalk.bold[highlightColor](`=== Debug: ${title} (${duration}) ===`)
+ chalk.bold[highlightColor](`=== Debug: ${title} (${duration}ms) ===`)
);
console.log(body);
console.log(`\n`);
+
+ const inspectableEsQueries = inspectableEsQueriesMap.get(request);
+ if (!isCalledWithInternalUser && inspectableEsQueries) {
+ inspectableEsQueries.push({
+ response: res,
+ duration,
+ requestType,
+ requestParams: omit(requestParams, 'headers'),
+ esError: esError?.response ?? esError?.message,
+ });
+ }
}
if (esError) {
@@ -62,13 +83,13 @@ export async function callAsyncWithDebug({
export const getDebugBody = (
params: Record,
- operationName: string
+ requestType: string
) => {
- if (operationName === 'search') {
+ if (requestType === 'search') {
return `GET ${params.index}/_search\n${formatObj(params.body)}`;
}
- return `${chalk.bold('ES operation:')} ${operationName}\n${chalk.bold(
+ return `${chalk.bold('ES operation:')} ${requestType}\n${chalk.bold(
'ES query:'
)}\n${formatObj(params)}`;
};
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
index e20103cc6ddca..b8a14253a229a 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
@@ -93,6 +93,9 @@ export function createApmEventClient({
ignore_unavailable: true,
};
+ // only "search" operation is currently supported
+ const requestType = 'search';
+
return callAsyncWithDebug({
cb: () => {
const searchPromise = cancelEsRequestOnAbort(
@@ -103,10 +106,14 @@ export function createApmEventClient({
return unwrapEsResponse(searchPromise);
},
getDebugMessage: () => ({
- body: getDebugBody(searchParams, 'search'),
+ body: getDebugBody(searchParams, requestType),
title: getDebugTitle(request),
}),
+ isCalledWithInternalUser: false,
debug,
+ request,
+ requestType,
+ requestParams: searchParams,
});
},
};
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
index 2e83baece01a9..45e17c1678518 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
@@ -40,10 +40,10 @@ export function createInternalESClient({
function callEs({
cb,
- operationName,
+ requestType,
params,
}: {
- operationName: string;
+ requestType: string;
cb: () => TransportRequestPromise;
params: Record;
}) {
@@ -51,9 +51,13 @@ export function createInternalESClient({
cb: () => unwrapEsResponse(cancelEsRequestOnAbort(cb(), request)),
getDebugMessage: () => ({
title: getDebugTitle(request),
- body: getDebugBody(params, operationName),
+ body: getDebugBody(params, requestType),
}),
- debug: context.params.query._debug,
+ debug: context.params.query._inspect,
+ isCalledWithInternalUser: true,
+ request,
+ requestType,
+ requestParams: params,
});
}
@@ -65,28 +69,28 @@ export function createInternalESClient({
params: TSearchRequest
): Promise> => {
return callEs({
- operationName: 'search',
+ requestType: 'search',
cb: () => asInternalUser.search(params),
params,
});
},
index: (params: APMIndexDocumentParams) => {
return callEs({
- operationName: 'index',
+ requestType: 'index',
cb: () => asInternalUser.index(params),
params,
});
},
delete: (params: DeleteRequest): Promise<{ result: string }> => {
return callEs({
- operationName: 'delete',
+ requestType: 'delete',
cb: () => asInternalUser.delete(params),
params,
});
},
indicesCreate: (params: CreateIndexRequest) => {
return callEs({
- operationName: 'indices.create',
+ requestType: 'indices.create',
cb: () => asInternalUser.indices.create(params),
params,
});
diff --git a/x-pack/plugins/apm/server/lib/helpers/input_validation.ts b/x-pack/plugins/apm/server/lib/helpers/input_validation.ts
index 5c188ff0d093e..0a34711b9b40d 100644
--- a/x-pack/plugins/apm/server/lib/helpers/input_validation.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/input_validation.ts
@@ -14,7 +14,7 @@ export const withDefaultValidators = (
validators: { [key: string]: Schema } = {}
) => {
return Joi.object().keys({
- _debug: Joi.bool(),
+ _inspect: Joi.bool(),
start: dateValidation,
end: dateValidation,
uiFilters: Joi.string(),
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
index 51f386d59c04a..c0707d0286180 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts
@@ -51,7 +51,7 @@ function getMockRequest() {
) as APMConfig,
params: {
query: {
- _debug: false,
+ _inspect: false,
},
},
core: {
diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
index 60fb9a8bfa85a..fff661250c6df 100644
--- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts
@@ -45,7 +45,7 @@ export interface SetupTimeRange {
interface SetupRequestParams {
query?: {
- _debug?: boolean;
+ _inspect?: boolean;
/**
* Timestamp in ms since epoch
@@ -88,7 +88,7 @@ export async function setupRequest(
indices,
apmEventClient: createApmEventClient({
esClient: context.core.elasticsearch.client.asCurrentUser,
- debug: context.params.query._debug,
+ debug: context.params.query._inspect,
request,
indices,
options: { includeFrozen },
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
index 8d0acb7f85f5d..0b7f82c0b8388 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts
@@ -21,20 +21,20 @@ export async function createStaticIndexPattern(
setup: Setup,
context: APMRequestHandlerContext,
savedObjectsClient: InternalSavedObjectsClient
-): Promise {
+): Promise {
return withApmSpan('create_static_index_pattern', async () => {
const { config } = context;
// don't autocreate APM index pattern if it's been disabled via the config
if (!config['xpack.apm.autocreateApmIndexPattern']) {
- return;
+ return false;
}
// Discover and other apps will throw errors if an index pattern exists without having matching indices.
// The following ensures the index pattern is only created if APM data is found
const hasData = await hasHistoricalAgentData(setup);
if (!hasData) {
- return;
+ return false;
}
try {
@@ -49,12 +49,12 @@ export async function createStaticIndexPattern(
{ id: APM_STATIC_INDEX_PATTERN_ID, overwrite: false }
)
);
- return;
+ return true;
} catch (e) {
// if the index pattern (saved object) already exists a conflict error (code: 409) will be thrown
// that error should be silenced
if (SavedObjectsErrorHelpers.isConflictError(e)) {
- return;
+ return false;
}
throw e;
}
diff --git a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
index abdc8da78502c..bbe13874d7d3b 100644
--- a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
+++ b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts
@@ -9,7 +9,7 @@ import { ProcessorEvent } from '../../../common/processor_event';
import { withApmSpan } from '../../utils/with_apm_span';
import { Setup } from '../helpers/setup_request';
-export function hasData({ setup }: { setup: Setup }) {
+export function getHasData({ setup }: { setup: Setup }) {
return withApmSpan('observability_overview_has_apm_data', async () => {
const { apmEventClient } = setup;
try {
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/comparison_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/comparison_statistics.ts
new file mode 100644
index 0000000000000..6fca42723b9cc
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/comparison_statistics.ts
@@ -0,0 +1,166 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { keyBy } from 'lodash';
+import { Coordinate } from '../../../../typings/timeseries';
+import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
+import { joinByKey } from '../../../../common/utils/join_by_key';
+import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate';
+import { withApmSpan } from '../../../utils/with_apm_span';
+import { Setup, SetupTimeRange } from '../../helpers/setup_request';
+import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics';
+import { getServiceInstancesTransactionStatistics } from './get_service_instances_transaction_statistics';
+
+interface ServiceInstanceComparisonStatisticsParams {
+ environment?: string;
+ kuery?: string;
+ latencyAggregationType: LatencyAggregationType;
+ setup: Setup;
+ serviceName: string;
+ transactionType: string;
+ searchAggregatedTransactions: boolean;
+ numBuckets: number;
+ start: number;
+ end: number;
+ serviceNodeIds: string[];
+}
+
+async function getServiceInstancesComparisonStatistics(
+ params: ServiceInstanceComparisonStatisticsParams
+): Promise<
+ Array<{
+ serviceNodeName: string;
+ errorRate?: Coordinate[];
+ latency?: Coordinate[];
+ throughput?: Coordinate[];
+ cpuUsage?: Coordinate[];
+ memoryUsage?: Coordinate[];
+ }>
+> {
+ return withApmSpan(
+ 'get_service_instances_comparison_statistics',
+ async () => {
+ const [transactionStats, systemMetricStats = []] = await Promise.all([
+ getServiceInstancesTransactionStatistics({
+ ...params,
+ isComparisonSearch: true,
+ }),
+ getServiceInstancesSystemMetricStatistics({
+ ...params,
+ isComparisonSearch: true,
+ }),
+ ]);
+
+ const stats = joinByKey(
+ [...transactionStats, ...systemMetricStats],
+ 'serviceNodeName'
+ );
+
+ return stats;
+ }
+ );
+}
+
+export async function getServiceInstancesComparisonStatisticsPeriods({
+ environment,
+ kuery,
+ latencyAggregationType,
+ setup,
+ serviceName,
+ transactionType,
+ searchAggregatedTransactions,
+ numBuckets,
+ serviceNodeIds,
+ comparisonStart,
+ comparisonEnd,
+}: {
+ environment?: string;
+ kuery?: string;
+ latencyAggregationType: LatencyAggregationType;
+ setup: Setup & SetupTimeRange;
+ serviceName: string;
+ transactionType: string;
+ searchAggregatedTransactions: boolean;
+ numBuckets: number;
+ serviceNodeIds: string[];
+ comparisonStart?: number;
+ comparisonEnd?: number;
+}) {
+ return withApmSpan(
+ 'get_service_instances_comparison_statistics_periods',
+ async () => {
+ const { start, end } = setup;
+
+ const commonParams = {
+ environment,
+ kuery,
+ latencyAggregationType,
+ setup,
+ serviceName,
+ transactionType,
+ searchAggregatedTransactions,
+ numBuckets,
+ serviceNodeIds,
+ };
+
+ const currentPeriodPromise = getServiceInstancesComparisonStatistics({
+ ...commonParams,
+ start,
+ end,
+ });
+
+ const previousPeriodPromise =
+ comparisonStart && comparisonEnd
+ ? getServiceInstancesComparisonStatistics({
+ ...commonParams,
+ start: comparisonStart,
+ end: comparisonEnd,
+ })
+ : [];
+ const [currentPeriod, previousPeriod] = await Promise.all([
+ currentPeriodPromise,
+ previousPeriodPromise,
+ ]);
+
+ const firtCurrentPeriod = currentPeriod.length
+ ? currentPeriod[0]
+ : undefined;
+
+ return {
+ currentPeriod: keyBy(currentPeriod, 'serviceNodeName'),
+ previousPeriod: keyBy(
+ previousPeriod.map((data) => {
+ return {
+ ...data,
+ cpuUsage: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.cpuUsage,
+ previousPeriodTimeseries: data.cpuUsage,
+ }),
+ errorRate: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.errorRate,
+ previousPeriodTimeseries: data.errorRate,
+ }),
+ latency: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.latency,
+ previousPeriodTimeseries: data.latency,
+ }),
+ memoryUsage: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.memoryUsage,
+ previousPeriodTimeseries: data.memoryUsage,
+ }),
+ throughput: offsetPreviousPeriodCoordinates({
+ currentPeriodTimeseries: firtCurrentPeriod?.throughput,
+ previousPeriodTimeseries: data.throughput,
+ }),
+ };
+ }),
+ 'serviceNodeName'
+ ),
+ };
+ }
+ );
+}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts
deleted file mode 100644
index 6a72f817b3f69..0000000000000
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_system_metric_stats.ts
+++ /dev/null
@@ -1,161 +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 { AggregationOptionsByType } from '../../../../../../../typings/elasticsearch';
-import {
- environmentQuery,
- rangeQuery,
- kqlQuery,
-} from '../../../../server/utils/queries';
-import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
-import {
- METRIC_CGROUP_MEMORY_USAGE_BYTES,
- METRIC_PROCESS_CPU_PERCENT,
- METRIC_SYSTEM_FREE_MEMORY,
- METRIC_SYSTEM_TOTAL_MEMORY,
- SERVICE_NAME,
- SERVICE_NODE_NAME,
-} from '../../../../common/elasticsearch_fieldnames';
-import { ProcessorEvent } from '../../../../common/processor_event';
-import { ServiceInstanceParams } from '.';
-import { getBucketSize } from '../../helpers/get_bucket_size';
-import {
- percentCgroupMemoryUsedScript,
- percentSystemMemoryUsedScript,
-} from '../../metrics/by_agent/shared/memory';
-import { withApmSpan } from '../../../utils/with_apm_span';
-
-export async function getServiceInstanceSystemMetricStats({
- environment,
- kuery,
- setup,
- serviceName,
- size,
- numBuckets,
-}: ServiceInstanceParams) {
- return withApmSpan('get_service_instance_system_metric_stats', async () => {
- const { apmEventClient, start, end } = setup;
-
- const { intervalString } = getBucketSize({ start, end, numBuckets });
-
- const systemMemoryFilter = {
- bool: {
- filter: [
- { exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
- { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
- ],
- },
- };
-
- const cgroupMemoryFilter = {
- exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES },
- };
-
- const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } };
-
- function withTimeseries(agg: T) {
- return {
- avg: { avg: agg },
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: start,
- max: end,
- },
- },
- aggs: {
- avg: { avg: agg },
- },
- },
- };
- }
-
- const subAggs = {
- memory_usage_cgroup: {
- filter: cgroupMemoryFilter,
- aggs: withTimeseries({ script: percentCgroupMemoryUsedScript }),
- },
- memory_usage_system: {
- filter: systemMemoryFilter,
- aggs: withTimeseries({ script: percentSystemMemoryUsedScript }),
- },
- cpu_usage: {
- filter: cpuUsageFilter,
- aggs: withTimeseries({ field: METRIC_PROCESS_CPU_PERCENT }),
- },
- };
-
- const response = await apmEventClient.search({
- apm: {
- events: [ProcessorEvent.metric],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
- should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter],
- minimum_should_match: 1,
- },
- },
- aggs: {
- [SERVICE_NODE_NAME]: {
- terms: {
- field: SERVICE_NODE_NAME,
- missing: SERVICE_NODE_NAME_MISSING,
- size,
- },
- aggs: subAggs,
- },
- },
- },
- });
-
- return (
- response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
- (serviceNodeBucket) => {
- const hasCGroupData =
- serviceNodeBucket.memory_usage_cgroup.avg.value !== null;
-
- const memoryMetricsKey = hasCGroupData
- ? 'memory_usage_cgroup'
- : 'memory_usage_system';
-
- return {
- serviceNodeName: String(serviceNodeBucket.key),
- cpuUsage: {
- value: serviceNodeBucket.cpu_usage.avg.value,
- timeseries: serviceNodeBucket.cpu_usage.timeseries.buckets.map(
- (dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.avg.value,
- })
- ),
- },
- memoryUsage: {
- value: serviceNodeBucket[memoryMetricsKey].avg.value,
- timeseries: serviceNodeBucket[
- memoryMetricsKey
- ].timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.avg.value,
- })),
- },
- };
- }
- ) ?? []
- );
- });
-}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts
deleted file mode 100644
index 94a5e54e9ace5..0000000000000
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instance_transaction_stats.ts
+++ /dev/null
@@ -1,166 +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 { EventOutcome } from '../../../../common/event_outcome';
-import {
- environmentQuery,
- rangeQuery,
- kqlQuery,
-} from '../../../../server/utils/queries';
-import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
-import {
- EVENT_OUTCOME,
- SERVICE_NAME,
- SERVICE_NODE_NAME,
- TRANSACTION_TYPE,
-} from '../../../../common/elasticsearch_fieldnames';
-import { ServiceInstanceParams } from '.';
-import { getBucketSize } from '../../helpers/get_bucket_size';
-import {
- getProcessorEventForAggregatedTransactions,
- getTransactionDurationFieldForAggregatedTransactions,
-} from '../../helpers/aggregated_transactions';
-import { calculateThroughput } from '../../helpers/calculate_throughput';
-import { withApmSpan } from '../../../utils/with_apm_span';
-import {
- getLatencyAggregation,
- getLatencyValue,
-} from '../../helpers/latency_aggregation_type';
-
-export async function getServiceInstanceTransactionStats({
- environment,
- kuery,
- latencyAggregationType,
- setup,
- transactionType,
- serviceName,
- size,
- searchAggregatedTransactions,
- numBuckets,
-}: ServiceInstanceParams) {
- return withApmSpan('get_service_instance_transaction_stats', async () => {
- const { apmEventClient, start, end } = setup;
-
- const { intervalString, bucketSize } = getBucketSize({
- start,
- end,
- numBuckets,
- });
-
- const field = getTransactionDurationFieldForAggregatedTransactions(
- searchAggregatedTransactions
- );
-
- const subAggs = {
- ...getLatencyAggregation(latencyAggregationType, field),
- failures: {
- filter: {
- term: {
- [EVENT_OUTCOME]: EventOutcome.failure,
- },
- },
- },
- };
-
- const response = await apmEventClient.search({
- apm: {
- events: [
- getProcessorEventForAggregatedTransactions(
- searchAggregatedTransactions
- ),
- ],
- },
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ...rangeQuery(start, end),
- ...environmentQuery(environment),
- ...kqlQuery(kuery),
- ],
- },
- },
- aggs: {
- [SERVICE_NODE_NAME]: {
- terms: {
- field: SERVICE_NODE_NAME,
- missing: SERVICE_NODE_NAME_MISSING,
- size,
- },
- aggs: {
- ...subAggs,
- timeseries: {
- date_histogram: {
- field: '@timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: start,
- max: end,
- },
- },
- aggs: {
- ...subAggs,
- },
- },
- },
- },
- },
- },
- });
-
- const bucketSizeInMinutes = bucketSize / 60;
-
- return (
- response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
- (serviceNodeBucket) => {
- const {
- doc_count: count,
- latency,
- key,
- failures,
- timeseries,
- } = serviceNodeBucket;
-
- return {
- serviceNodeName: String(key),
- errorRate: {
- value: failures.doc_count / count,
- timeseries: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.failures.doc_count / dateBucket.doc_count,
- })),
- },
- throughput: {
- value: calculateThroughput({ start, end, value: count }),
- timeseries: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: dateBucket.doc_count / bucketSizeInMinutes,
- })),
- },
- latency: {
- value: getLatencyValue({
- aggregation: latency,
- latencyAggregationType,
- }),
- timeseries: timeseries.buckets.map((dateBucket) => ({
- x: dateBucket.key,
- y: getLatencyValue({
- aggregation: dateBucket.latency,
- latencyAggregationType,
- }),
- })),
- },
- };
- }
- ) ?? []
- );
- });
-}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts
new file mode 100644
index 0000000000000..1a33e9810dd5e
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_system_metric_statistics.ts
@@ -0,0 +1,208 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { AggregationOptionsByType } from 'typings/elasticsearch';
+import {
+ METRIC_CGROUP_MEMORY_USAGE_BYTES,
+ METRIC_PROCESS_CPU_PERCENT,
+ METRIC_SYSTEM_FREE_MEMORY,
+ METRIC_SYSTEM_TOTAL_MEMORY,
+ SERVICE_NAME,
+ SERVICE_NODE_NAME,
+} from '../../../../common/elasticsearch_fieldnames';
+import { ProcessorEvent } from '../../../../common/processor_event';
+import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
+import { Coordinate } from '../../../../typings/timeseries';
+import { environmentQuery, kqlQuery, rangeQuery } from '../../../utils/queries';
+import { getBucketSize } from '../../helpers/get_bucket_size';
+import { Setup } from '../../helpers/setup_request';
+import {
+ percentCgroupMemoryUsedScript,
+ percentSystemMemoryUsedScript,
+} from '../../metrics/by_agent/shared/memory';
+import { withApmSpan } from '../../../utils/with_apm_span';
+
+interface ServiceInstanceSystemMetricPrimaryStatistics {
+ serviceNodeName: string;
+ cpuUsage: number | null;
+ memoryUsage: number | null;
+}
+
+interface ServiceInstanceSystemMetricComparisonStatistics {
+ serviceNodeName: string;
+ cpuUsage: Coordinate[];
+ memoryUsage: Coordinate[];
+}
+
+type ServiceInstanceSystemMetricStatistics = T extends true
+ ? ServiceInstanceSystemMetricComparisonStatistics
+ : ServiceInstanceSystemMetricPrimaryStatistics;
+
+export async function getServiceInstancesSystemMetricStatistics<
+ T extends true | false
+>({
+ environment,
+ kuery,
+ setup,
+ serviceName,
+ size,
+ start,
+ end,
+ serviceNodeIds,
+ numBuckets,
+ isComparisonSearch,
+}: {
+ setup: Setup;
+ serviceName: string;
+ start: number;
+ end: number;
+ numBuckets?: number;
+ serviceNodeIds?: string[];
+ environment?: string;
+ kuery?: string;
+ size?: number;
+ isComparisonSearch: T;
+}): Promise>> {
+ return withApmSpan(
+ 'get_service_instances_system_metric_statistics',
+ async () => {
+ const { apmEventClient } = setup;
+
+ const { intervalString } = getBucketSize({ start, end, numBuckets });
+
+ const systemMemoryFilter = {
+ bool: {
+ filter: [
+ { exists: { field: METRIC_SYSTEM_FREE_MEMORY } },
+ { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } },
+ ],
+ },
+ };
+
+ const cgroupMemoryFilter = {
+ exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES },
+ };
+
+ const cpuUsageFilter = { exists: { field: METRIC_PROCESS_CPU_PERCENT } };
+
+ function withTimeseries(
+ agg: TParams
+ ) {
+ return {
+ ...(isComparisonSearch
+ ? {
+ avg: { avg: agg },
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: {
+ min: start,
+ max: end,
+ },
+ },
+ aggs: { avg: { avg: agg } },
+ },
+ }
+ : { avg: { avg: agg } }),
+ };
+ }
+
+ const subAggs = {
+ memory_usage_cgroup: {
+ filter: cgroupMemoryFilter,
+ aggs: withTimeseries({ script: percentCgroupMemoryUsedScript }),
+ },
+ memory_usage_system: {
+ filter: systemMemoryFilter,
+ aggs: withTimeseries({ script: percentSystemMemoryUsedScript }),
+ },
+ cpu_usage: {
+ filter: cpuUsageFilter,
+ aggs: withTimeseries({ field: METRIC_PROCESS_CPU_PERCENT }),
+ },
+ };
+
+ const response = await apmEventClient.search({
+ apm: {
+ events: [ProcessorEvent.metric],
+ },
+ body: {
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ...(isComparisonSearch && serviceNodeIds
+ ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
+ : []),
+ ],
+ should: [cgroupMemoryFilter, systemMemoryFilter, cpuUsageFilter],
+ minimum_should_match: 1,
+ },
+ },
+ aggs: {
+ [SERVICE_NODE_NAME]: {
+ terms: {
+ field: SERVICE_NODE_NAME,
+ missing: SERVICE_NODE_NAME_MISSING,
+ ...(size ? { size } : {}),
+ ...(isComparisonSearch ? { include: serviceNodeIds } : {}),
+ },
+ aggs: subAggs,
+ },
+ },
+ },
+ });
+
+ return (
+ (response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
+ (serviceNodeBucket) => {
+ const serviceNodeName = String(serviceNodeBucket.key);
+ const hasCGroupData =
+ serviceNodeBucket.memory_usage_cgroup.avg.value !== null;
+
+ const memoryMetricsKey = hasCGroupData
+ ? 'memory_usage_cgroup'
+ : 'memory_usage_system';
+
+ const cpuUsage =
+ // Timeseries is available when isComparisonSearch is true
+ 'timeseries' in serviceNodeBucket.cpu_usage
+ ? serviceNodeBucket.cpu_usage.timeseries.buckets.map(
+ (dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.avg.value,
+ })
+ )
+ : serviceNodeBucket.cpu_usage.avg.value;
+
+ const memoryUsageValue = serviceNodeBucket[memoryMetricsKey];
+ const memoryUsage =
+ // Timeseries is available when isComparisonSearch is true
+ 'timeseries' in memoryUsageValue
+ ? memoryUsageValue.timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.avg.value,
+ }))
+ : serviceNodeBucket[memoryMetricsKey].avg.value;
+
+ return {
+ serviceNodeName,
+ cpuUsage,
+ memoryUsage,
+ };
+ }
+ ) as Array>) || []
+ );
+ }
+ );
+}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
new file mode 100644
index 0000000000000..ad54a231b52ef
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/get_service_instances_transaction_statistics.ts
@@ -0,0 +1,202 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import {
+ EVENT_OUTCOME,
+ SERVICE_NAME,
+ SERVICE_NODE_NAME,
+ TRANSACTION_TYPE,
+} from '../../../../common/elasticsearch_fieldnames';
+import { EventOutcome } from '../../../../common/event_outcome';
+import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
+import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes';
+import { Coordinate } from '../../../../typings/timeseries';
+import { environmentQuery, kqlQuery, rangeQuery } from '../../../utils/queries';
+import {
+ getProcessorEventForAggregatedTransactions,
+ getTransactionDurationFieldForAggregatedTransactions,
+} from '../../helpers/aggregated_transactions';
+import { calculateThroughput } from '../../helpers/calculate_throughput';
+import { getBucketSize } from '../../helpers/get_bucket_size';
+import {
+ getLatencyAggregation,
+ getLatencyValue,
+} from '../../helpers/latency_aggregation_type';
+import { Setup } from '../../helpers/setup_request';
+import { withApmSpan } from '../../../utils/with_apm_span';
+
+interface ServiceInstanceTransactionPrimaryStatistics {
+ serviceNodeName: string;
+ errorRate: number;
+ latency: number;
+ throughput: number;
+}
+
+interface ServiceInstanceTransactionComparisonStatistics {
+ serviceNodeName: string;
+ errorRate: Coordinate[];
+ latency: Coordinate[];
+ throughput: Coordinate[];
+}
+
+type ServiceInstanceTransactionStatistics = T extends true
+ ? ServiceInstanceTransactionComparisonStatistics
+ : ServiceInstanceTransactionPrimaryStatistics;
+
+export async function getServiceInstancesTransactionStatistics<
+ T extends true | false
+>({
+ environment,
+ kuery,
+ latencyAggregationType,
+ setup,
+ transactionType,
+ serviceName,
+ size,
+ searchAggregatedTransactions,
+ start,
+ end,
+ serviceNodeIds,
+ numBuckets,
+ isComparisonSearch,
+}: {
+ latencyAggregationType: LatencyAggregationType;
+ setup: Setup;
+ serviceName: string;
+ transactionType: string;
+ searchAggregatedTransactions: boolean;
+ start: number;
+ end: number;
+ isComparisonSearch: T;
+ serviceNodeIds?: string[];
+ environment?: string;
+ kuery?: string;
+ size?: number;
+ numBuckets?: number;
+}): Promise>> {
+ return withApmSpan(
+ 'get_service_instances_transaction_statistics',
+ async () => {
+ const { apmEventClient } = setup;
+
+ const { intervalString, bucketSize } = getBucketSize({
+ start,
+ end,
+ numBuckets,
+ });
+
+ const field = getTransactionDurationFieldForAggregatedTransactions(
+ searchAggregatedTransactions
+ );
+
+ const subAggs = {
+ ...getLatencyAggregation(latencyAggregationType, field),
+ failures: {
+ filter: {
+ term: {
+ [EVENT_OUTCOME]: EventOutcome.failure,
+ },
+ },
+ },
+ };
+
+ const query = {
+ bool: {
+ filter: [
+ { term: { [SERVICE_NAME]: serviceName } },
+ { term: { [TRANSACTION_TYPE]: transactionType } },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ...(isComparisonSearch && serviceNodeIds
+ ? [{ terms: { [SERVICE_NODE_NAME]: serviceNodeIds } }]
+ : []),
+ ],
+ },
+ };
+
+ const aggs = {
+ [SERVICE_NODE_NAME]: {
+ terms: {
+ field: SERVICE_NODE_NAME,
+ missing: SERVICE_NODE_NAME_MISSING,
+ ...(size ? { size } : {}),
+ ...(isComparisonSearch ? { include: serviceNodeIds } : {}),
+ },
+ aggs: isComparisonSearch
+ ? {
+ timeseries: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: intervalString,
+ min_doc_count: 0,
+ extended_bounds: { min: start, max: end },
+ },
+ aggs: subAggs,
+ },
+ }
+ : subAggs,
+ },
+ };
+
+ const response = await apmEventClient.search({
+ apm: {
+ events: [
+ getProcessorEventForAggregatedTransactions(
+ searchAggregatedTransactions
+ ),
+ ],
+ },
+ body: { size: 0, query, aggs },
+ });
+
+ const bucketSizeInMinutes = bucketSize / 60;
+
+ return (
+ (response.aggregations?.[SERVICE_NODE_NAME].buckets.map(
+ (serviceNodeBucket) => {
+ const { doc_count: count, key } = serviceNodeBucket;
+ const serviceNodeName = String(key);
+
+ // Timeseries is returned when isComparisonSearch is true
+ if ('timeseries' in serviceNodeBucket) {
+ const { timeseries } = serviceNodeBucket;
+ return {
+ serviceNodeName,
+ errorRate: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.failures.doc_count / dateBucket.doc_count,
+ })),
+ throughput: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: dateBucket.doc_count / bucketSizeInMinutes,
+ })),
+ latency: timeseries.buckets.map((dateBucket) => ({
+ x: dateBucket.key,
+ y: getLatencyValue({
+ aggregation: dateBucket.latency,
+ latencyAggregationType,
+ }),
+ })),
+ };
+ } else {
+ const { failures, latency } = serviceNodeBucket;
+ return {
+ serviceNodeName,
+ errorRate: failures.doc_count / count,
+ latency: getLatencyValue({
+ aggregation: latency,
+ latencyAggregationType,
+ }),
+ throughput: calculateThroughput({ start, end, value: count }),
+ };
+ }
+ }
+ ) as Array>) || []
+ );
+ }
+ );
+}
diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/primary_statistics.ts
similarity index 52%
rename from x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts
rename to x-pack/plugins/apm/server/lib/services/get_service_instances/primary_statistics.ts
index 838753890a8cd..3cd98558eff02 100644
--- a/x-pack/plugins/apm/server/lib/services/get_service_instances/index.ts
+++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/primary_statistics.ts
@@ -9,10 +9,10 @@ import { LatencyAggregationType } from '../../../../common/latency_aggregation_t
import { joinByKey } from '../../../../common/utils/join_by_key';
import { withApmSpan } from '../../../utils/with_apm_span';
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
-import { getServiceInstanceSystemMetricStats } from './get_service_instance_system_metric_stats';
-import { getServiceInstanceTransactionStats } from './get_service_instance_transaction_stats';
+import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics';
+import { getServiceInstancesTransactionStatistics } from './get_service_instances_transaction_statistics';
-export interface ServiceInstanceParams {
+interface ServiceInstancePrimaryStatisticsParams {
environment?: string;
kuery?: string;
latencyAggregationType: LatencyAggregationType;
@@ -21,21 +21,37 @@ export interface ServiceInstanceParams {
transactionType: string;
searchAggregatedTransactions: boolean;
size: number;
- numBuckets: number;
+ start: number;
+ end: number;
}
-export async function getServiceInstances(
- params: Omit
-) {
- return withApmSpan('get_service_instances', async () => {
+export async function getServiceInstancesPrimaryStatistics(
+ params: Omit
+): Promise<
+ Array<{
+ serviceNodeName: string;
+ errorRate?: number;
+ latency?: number;
+ throughput?: number;
+ cpuUsage?: number | null;
+ memoryUsage?: number | null;
+ }>
+> {
+ return withApmSpan('get_service_instances_primary_statistics', async () => {
const paramsForSubQueries = {
...params,
size: 50,
};
const [transactionStats, systemMetricStats] = await Promise.all([
- getServiceInstanceTransactionStats(paramsForSubQueries),
- getServiceInstanceSystemMetricStats(paramsForSubQueries),
+ getServiceInstancesTransactionStatistics({
+ ...paramsForSubQueries,
+ isComparisonSearch: false,
+ }),
+ getServiceInstancesSystemMetricStatistics({
+ ...paramsForSubQueries,
+ isComparisonSearch: false,
+ }),
]);
const stats = joinByKey(
diff --git a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts
index 32f2238b0ddea..3bebcd49ec34a 100644
--- a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts
@@ -35,12 +35,14 @@ export const transactionErrorRateChartPreview = createRoute({
options: { tags: ['access:apm'] },
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- const { _debug, ...alertParams } = context.params.query;
+ const { _inspect, ...alertParams } = context.params.query;
- return getTransactionErrorRateChartPreview({
+ const errorRateChartPreview = await getTransactionErrorRateChartPreview({
setup,
alertParams,
});
+
+ return { errorRateChartPreview };
},
});
@@ -50,11 +52,13 @@ export const transactionErrorCountChartPreview = createRoute({
options: { tags: ['access:apm'] },
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- const { _debug, ...alertParams } = context.params.query;
- return getTransactionErrorCountChartPreview({
+ const { _inspect, ...alertParams } = context.params.query;
+ const errorCountChartPreview = await getTransactionErrorCountChartPreview({
setup,
alertParams,
});
+
+ return { errorCountChartPreview };
},
});
@@ -64,11 +68,13 @@ export const transactionDurationChartPreview = createRoute({
options: { tags: ['access:apm'] },
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
- const { _debug, ...alertParams } = context.params.query;
+ const { _inspect, ...alertParams } = context.params.query;
- return getTransactionDurationChartPreview({
+ const latencyChartPreview = await getTransactionDurationChartPreview({
alertParams,
setup,
});
+
+ return { latencyChartPreview };
},
});
diff --git a/x-pack/plugins/apm/server/routes/create_api/index.test.ts b/x-pack/plugins/apm/server/routes/create_api/index.test.ts
index 01d2797641805..9958b8dec0124 100644
--- a/x-pack/plugins/apm/server/routes/create_api/index.test.ts
+++ b/x-pack/plugins/apm/server/routes/create_api/index.test.ts
@@ -48,6 +48,49 @@ const getCoreMock = () => {
};
};
+const initApi = (params?: RouteParamsRT) => {
+ const { mock, context, createRouter, get, post } = getCoreMock();
+ const handlerMock = jest.fn();
+ createApi()
+ .add(() => ({
+ endpoint: 'GET /foo',
+ params,
+ options: { tags: ['access:apm'] },
+ handler: handlerMock,
+ }))
+ .init(mock, context);
+
+ const routeHandler = get.mock.calls[0][1];
+
+ const responseMock = {
+ ok: jest.fn(),
+ custom: jest.fn(),
+ };
+
+ const simulateRequest = (requestMock: any) => {
+ return routeHandler(
+ {},
+ {
+ // stub default values
+ params: {},
+ query: {},
+ body: null,
+ ...requestMock,
+ },
+ responseMock
+ );
+ };
+
+ return {
+ simulateRequest,
+ handlerMock,
+ createRouter,
+ get,
+ post,
+ responseMock,
+ };
+};
+
describe('createApi', () => {
it('registers a route with the server', () => {
const { mock, context, createRouter, post, get, put } = getCoreMock();
@@ -56,7 +99,7 @@ describe('createApi', () => {
.add(() => ({
endpoint: 'GET /foo',
options: { tags: ['access:apm'] },
- handler: async () => null,
+ handler: async () => ({}),
}))
.add(() => ({
endpoint: 'POST /bar',
@@ -64,21 +107,21 @@ describe('createApi', () => {
body: t.string,
}),
options: { tags: ['access:apm'] },
- handler: async () => null,
+ handler: async () => ({}),
}))
.add(() => ({
endpoint: 'PUT /baz',
options: {
tags: ['access:apm', 'access:apm_write'],
},
- handler: async () => null,
+ handler: async () => ({}),
}))
.add({
endpoint: 'GET /qux',
options: {
tags: ['access:apm', 'access:apm_write'],
},
- handler: async () => null,
+ handler: async () => ({}),
})
.init(mock, context);
@@ -122,102 +165,78 @@ describe('createApi', () => {
});
describe('when validating', () => {
- const initApi = (params?: RouteParamsRT) => {
- const { mock, context, createRouter, get, post } = getCoreMock();
- const handlerMock = jest.fn();
- createApi()
- .add(() => ({
- endpoint: 'GET /foo',
- params,
- options: { tags: ['access:apm'] },
- handler: handlerMock,
- }))
- .init(mock, context);
-
- const routeHandler = get.mock.calls[0][1];
-
- const responseMock = {
- ok: jest.fn(),
- internalError: jest.fn(),
- notFound: jest.fn(),
- forbidden: jest.fn(),
- badRequest: jest.fn(),
- };
-
- const simulate = (requestMock: any) => {
- return routeHandler(
- {},
- {
- // stub default values
- params: {},
- query: {},
- body: null,
- ...requestMock,
- },
- responseMock
- );
- };
-
- return { simulate, handlerMock, createRouter, get, post, responseMock };
- };
-
- it('adds a _debug query parameter by default', async () => {
- const { simulate, handlerMock, responseMock } = initApi();
-
- await simulate({ query: { _debug: 'true' } });
+ describe('_inspect', () => {
+ it('allows _inspect=true', async () => {
+ const { simulateRequest, handlerMock, responseMock } = initApi();
+ await simulateRequest({ query: { _inspect: 'true' } });
+
+ const params = handlerMock.mock.calls[0][0].context.params;
+ expect(params).toEqual({ query: { _inspect: true } });
+ expect(handlerMock).toHaveBeenCalledTimes(1);
+
+ // responds with ok
+ expect(responseMock.custom).not.toHaveBeenCalled();
+ expect(responseMock.ok).toHaveBeenCalledWith({
+ body: { _inspect: [] },
+ });
+ });
- expect(responseMock.badRequest).not.toHaveBeenCalled();
+ it('rejects _inspect=1', async () => {
+ const { simulateRequest, responseMock } = initApi();
+ await simulateRequest({ query: { _inspect: 1 } });
+
+ // responds with error handler
+ expect(responseMock.ok).not.toHaveBeenCalled();
+ expect(responseMock.custom).toHaveBeenCalledWith({
+ body: {
+ attributes: { _inspect: [] },
+ message:
+ 'Invalid value 1 supplied to : strict_keys/query: Partial<{| _inspect: pipe(JSON, boolean) |}>/_inspect: pipe(JSON, boolean)',
+ },
+ statusCode: 400,
+ });
+ });
- expect(handlerMock).toHaveBeenCalledTimes(1);
+ it('allows omitting _inspect', async () => {
+ const { simulateRequest, handlerMock, responseMock } = initApi();
+ await simulateRequest({ query: {} });
- expect(responseMock.ok).toHaveBeenCalled();
+ const params = handlerMock.mock.calls[0][0].context.params;
+ expect(params).toEqual({ query: { _inspect: false } });
+ expect(handlerMock).toHaveBeenCalledTimes(1);
- const params = handlerMock.mock.calls[0][0].context.params;
-
- expect(params).toEqual({
- query: {
- _debug: true,
- },
+ // responds with ok
+ expect(responseMock.custom).not.toHaveBeenCalled();
+ expect(responseMock.ok).toHaveBeenCalledWith({ body: {} });
});
-
- await simulate({
- query: {
- _debug: 1,
- },
- });
-
- expect(responseMock.badRequest).toHaveBeenCalled();
});
- it('throws if any parameters are used but no types are defined', async () => {
- const { simulate, responseMock } = initApi();
+ it('throws if unknown parameters are provided', async () => {
+ const { simulateRequest, responseMock } = initApi();
- await simulate({
- query: {
- _debug: true,
- extra: '',
- },
+ await simulateRequest({
+ query: { _inspect: true, extra: '' },
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(1);
+ expect(responseMock.custom).toHaveBeenCalledTimes(1);
- await simulate({
+ await simulateRequest({
body: { foo: 'bar' },
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(2);
+ expect(responseMock.custom).toHaveBeenCalledTimes(2);
- await simulate({
+ await simulateRequest({
params: {
foo: 'bar',
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(3);
+ expect(responseMock.custom).toHaveBeenCalledTimes(3);
});
it('validates path parameters', async () => {
- const { simulate, handlerMock, responseMock } = initApi(
+ const { simulateRequest, handlerMock, responseMock } = initApi(
t.type({
path: t.type({
foo: t.string,
@@ -225,7 +244,7 @@ describe('createApi', () => {
})
);
- await simulate({
+ await simulateRequest({
params: {
foo: 'bar',
},
@@ -234,7 +253,7 @@ describe('createApi', () => {
expect(handlerMock).toHaveBeenCalledTimes(1);
expect(responseMock.ok).toHaveBeenCalledTimes(1);
- expect(responseMock.badRequest).not.toHaveBeenCalled();
+ expect(responseMock.custom).not.toHaveBeenCalled();
const params = handlerMock.mock.calls[0][0].context.params;
@@ -243,48 +262,48 @@ describe('createApi', () => {
foo: 'bar',
},
query: {
- _debug: false,
+ _inspect: false,
},
});
- await simulate({
+ await simulateRequest({
params: {
bar: 'foo',
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(1);
+ expect(responseMock.custom).toHaveBeenCalledTimes(1);
- await simulate({
+ await simulateRequest({
params: {
foo: 9,
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(2);
+ expect(responseMock.custom).toHaveBeenCalledTimes(2);
- await simulate({
+ await simulateRequest({
params: {
foo: 'bar',
extra: '',
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(3);
+ expect(responseMock.custom).toHaveBeenCalledTimes(3);
});
it('validates body parameters', async () => {
- const { simulate, handlerMock, responseMock } = initApi(
+ const { simulateRequest, handlerMock, responseMock } = initApi(
t.type({
body: t.string,
})
);
- await simulate({
+ await simulateRequest({
body: '',
});
- expect(responseMock.badRequest).not.toHaveBeenCalled();
+ expect(responseMock.custom).not.toHaveBeenCalled();
expect(handlerMock).toHaveBeenCalledTimes(1);
expect(responseMock.ok).toHaveBeenCalledTimes(1);
@@ -293,19 +312,19 @@ describe('createApi', () => {
expect(params).toEqual({
body: '',
query: {
- _debug: false,
+ _inspect: false,
},
});
- await simulate({
+ await simulateRequest({
body: null,
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(1);
+ expect(responseMock.custom).toHaveBeenCalledTimes(1);
});
it('validates query parameters', async () => {
- const { simulate, handlerMock, responseMock } = initApi(
+ const { simulateRequest, handlerMock, responseMock } = initApi(
t.type({
query: t.type({
bar: t.string,
@@ -314,15 +333,15 @@ describe('createApi', () => {
})
);
- await simulate({
+ await simulateRequest({
query: {
bar: '',
- _debug: 'true',
+ _inspect: 'true',
filterNames: JSON.stringify(['hostName', 'agentName']),
},
});
- expect(responseMock.badRequest).not.toHaveBeenCalled();
+ expect(responseMock.custom).not.toHaveBeenCalled();
expect(handlerMock).toHaveBeenCalledTimes(1);
expect(responseMock.ok).toHaveBeenCalledTimes(1);
@@ -331,19 +350,19 @@ describe('createApi', () => {
expect(params).toEqual({
query: {
bar: '',
- _debug: true,
+ _inspect: true,
filterNames: ['hostName', 'agentName'],
},
});
- await simulate({
+ await simulateRequest({
query: {
bar: '',
foo: '',
},
});
- expect(responseMock.badRequest).toHaveBeenCalledTimes(1);
+ expect(responseMock.custom).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts
index 46f2628cc73d5..13e70a2043cf0 100644
--- a/x-pack/plugins/apm/server/routes/create_api/index.ts
+++ b/x-pack/plugins/apm/server/routes/create_api/index.ts
@@ -11,19 +11,20 @@ import { schema } from '@kbn/config-schema';
import * as t from 'io-ts';
import { PathReporter } from 'io-ts/lib/PathReporter';
import { isLeft } from 'fp-ts/lib/Either';
-import { KibanaResponseFactory, RouteRegistrar } from 'src/core/server';
+import { KibanaRequest, RouteRegistrar } from 'src/core/server';
import { RequestAbortedError } from '@elastic/elasticsearch/lib/errors';
import agent from 'elastic-apm-node';
+import { parseMethod } from '../../../common/apm_api/parse_endpoint';
import { merge } from '../../../common/runtime_types/merge';
import { strictKeysRt } from '../../../common/runtime_types/strict_keys_rt';
import { APMConfig } from '../..';
-import { ServerAPI } from '../typings';
+import { InspectResponse, RouteParamsRT, ServerAPI } from '../typings';
import { jsonRt } from '../../../common/runtime_types/json_rt';
import type { ApmPluginRequestHandlerContext } from '../typings';
-const debugRt = t.exact(
+const inspectRt = t.exact(
t.partial({
- query: t.exact(t.partial({ _debug: jsonRt.pipe(t.boolean) })),
+ query: t.exact(t.partial({ _inspect: jsonRt.pipe(t.boolean) })),
})
);
@@ -32,6 +33,11 @@ type RouteOrRouteFactoryFn = Parameters['add']>[0];
const isNotEmpty = (val: any) =>
val !== undefined && val !== null && !(isPlainObject(val) && isEmpty(val));
+export const inspectableEsQueriesMap = new WeakMap<
+ KibanaRequest,
+ InspectResponse
+>();
+
export function createApi() {
const routes: RouteOrRouteFactoryFn[] = [];
const api: ServerAPI<{}> = {
@@ -58,24 +64,10 @@ export function createApi() {
const { params, endpoint, options, handler } = route;
const [method, path] = endpoint.split(' ');
-
- const typedRouterMethod = method.trim().toLowerCase() as
- | 'get'
- | 'post'
- | 'put'
- | 'delete';
-
- if (!['get', 'post', 'put', 'delete'].includes(typedRouterMethod)) {
- throw new Error(
- "Couldn't register route, as endpoint was not prefixed with a valid HTTP method"
- );
- }
+ const typedRouterMethod = parseMethod(method);
// For all runtime types with props, we create an exact
// version that will strip all keys that are unvalidated.
-
- const paramsRt = params ? merge([params, debugRt]) : debugRt;
-
const anyObject = schema.object({}, { unknowns: 'allow' });
(router[typedRouterMethod] as RouteRegistrar<
@@ -102,56 +94,52 @@ export function createApi() {
});
}
- try {
- const paramMap = pickBy(
- {
- path: request.params,
- body: request.body,
- query: {
- _debug: 'false',
- ...request.query,
- },
- },
- isNotEmpty
- );
-
- const result = strictKeysRt(paramsRt).decode(paramMap);
+ // init debug queries
+ inspectableEsQueriesMap.set(request, []);
- if (isLeft(result)) {
- throw Boom.badRequest(PathReporter.report(result)[0]);
- }
+ try {
+ const validParams = validateParams(request, params);
const data = await handler({
request,
context: {
...context,
plugins,
- // Only return values for parameters that have runtime types,
- // but always include query as _debug is always set even if
- // it's not defined in the route.
- params: mergeLodash(
- { query: { _debug: false } },
- pickBy(result.right, isNotEmpty)
- ),
+ params: validParams,
config,
logger,
},
});
- return response.ok({ body: data as any });
+ const body = { ...data };
+ if (validParams.query._inspect) {
+ body._inspect = inspectableEsQueriesMap.get(request);
+ }
+
+ // cleanup
+ inspectableEsQueriesMap.delete(request);
+
+ return response.ok({ body });
} catch (error) {
+ const opts = {
+ statusCode: 500,
+ body: {
+ message: error.message,
+ attributes: {
+ _inspect: inspectableEsQueriesMap.get(request),
+ },
+ },
+ };
+
if (Boom.isBoom(error)) {
- return convertBoomToKibanaResponse(error, response);
+ opts.statusCode = error.output.statusCode;
}
if (error instanceof RequestAbortedError) {
- return response.custom({
- statusCode: 499,
- body: {
- message: 'Client closed request',
- },
- });
+ opts.statusCode = 499;
+ opts.body.message = 'Client closed request';
}
- throw error;
+
+ return response.custom(opts);
}
}
);
@@ -162,22 +150,35 @@ export function createApi() {
return api;
}
-function convertBoomToKibanaResponse(
- error: Boom.Boom,
- response: KibanaResponseFactory
+function validateParams(
+ request: KibanaRequest,
+ params: RouteParamsRT | undefined
) {
- const opts = { body: { message: error.message } };
- switch (error.output.statusCode) {
- case 404:
- return response.notFound(opts);
-
- case 400:
- return response.badRequest(opts);
+ const paramsRt = params ? merge([params, inspectRt]) : inspectRt;
+ const paramMap = pickBy(
+ {
+ path: request.params,
+ body: request.body,
+ query: {
+ _inspect: 'false',
+ // @ts-ignore
+ ...request.query,
+ },
+ },
+ isNotEmpty
+ );
- case 403:
- return response.forbidden(opts);
+ const result = strictKeysRt(paramsRt).decode(paramMap);
- default:
- throw error;
+ if (isLeft(result)) {
+ throw Boom.badRequest(PathReporter.report(result)[0]);
}
+
+ // Only return values for parameters that have runtime types,
+ // but always include query as _inspect is always set even if
+ // it's not defined in the route.
+ return mergeLodash(
+ { query: { _inspect: false } },
+ pickBy(result.right, isNotEmpty)
+ );
}
diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts
index 2bd7e25e848c8..2b5fb0b516ab5 100644
--- a/x-pack/plugins/apm/server/routes/create_apm_api.ts
+++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts
@@ -30,7 +30,8 @@ import {
serviceDependenciesRoute,
serviceMetadataDetailsRoute,
serviceMetadataIconsRoute,
- serviceInstancesRoute,
+ serviceInstancesPrimaryStatisticsRoute,
+ serviceInstancesComparisonStatisticsRoute,
serviceProfilingStatisticsRoute,
serviceProfilingTimelineRoute,
} from './services';
@@ -134,7 +135,8 @@ const createApmApi = () => {
.add(serviceDependenciesRoute)
.add(serviceMetadataDetailsRoute)
.add(serviceMetadataIconsRoute)
- .add(serviceInstancesRoute)
+ .add(serviceInstancesPrimaryStatisticsRoute)
+ .add(serviceInstancesComparisonStatisticsRoute)
.add(serviceErrorGroupsComparisonStatisticsRoute)
.add(serviceProfilingTimelineRoute)
.add(serviceProfilingStatisticsRoute)
diff --git a/x-pack/plugins/apm/server/routes/create_route.ts b/x-pack/plugins/apm/server/routes/create_route.ts
index 4d30e706cdd5c..d74aac0992eb4 100644
--- a/x-pack/plugins/apm/server/routes/create_route.ts
+++ b/x-pack/plugins/apm/server/routes/create_route.ts
@@ -6,20 +6,20 @@
*/
import { CoreSetup } from 'src/core/server';
-import { Route, RouteParamsRT } from './typings';
+import { HandlerReturn, Route, RouteParamsRT } from './typings';
export function createRoute<
TEndpoint extends string,
- TRouteParamsRT extends RouteParamsRT | undefined = undefined,
- TReturn = unknown
+ TReturn extends HandlerReturn,
+ TRouteParamsRT extends RouteParamsRT | undefined = undefined
>(
route: Route
): Route;
export function createRoute<
TEndpoint extends string,
- TRouteParamsRT extends RouteParamsRT | undefined = undefined,
- TReturn = unknown
+ TReturn extends HandlerReturn,
+ TRouteParamsRT extends RouteParamsRT | undefined = undefined
>(
route: (core: CoreSetup) => Route